diff --git a/part/plugins/autobrace/autobrace.cpp b/part/plugins/autobrace/autobrace.cpp index 126a3b8bb..92d34a7b7 100644 --- a/part/plugins/autobrace/autobrace.cpp +++ b/part/plugins/autobrace/autobrace.cpp @@ -1,459 +1,462 @@ /** * This file is part of the KDE libraries * Copyright (C) 2008 Jakob Petsovits * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "autobrace.h" #include "autobrace_config.h" #include #include #include #include #include #include #include AutoBracePlugin *AutoBracePlugin::plugin = 0; K_PLUGIN_FACTORY_DEFINITION(AutoBracePluginFactory, registerPlugin("ktexteditor_autobrace"); registerPlugin("ktexteditor_autobrace_config"); ) K_EXPORT_PLUGIN(AutoBracePluginFactory("ktexteditor_autobrace", "ktexteditor_plugins")) AutoBracePlugin::AutoBracePlugin(QObject *parent, const QVariantList &args) : KTextEditor::Plugin(parent), m_autoBrackets(true), m_autoQuotations(true) { Q_UNUSED(args); plugin = this; readConfig(); } AutoBracePlugin::~AutoBracePlugin() { plugin = 0; } void AutoBracePlugin::addView(KTextEditor::View *view) { if ( KTextEditor::ConfigInterface* confIface = qobject_cast< KTextEditor::ConfigInterface* >(view->document()) ) { QVariant brackets = confIface->configValue("auto-brackets"); if ( brackets.isValid() && brackets.canConvert(QVariant::Bool) && brackets.toBool() ) { confIface->setConfigValue("auto-brackets", false); KMessageBox::information(view, i18n("The autobrace plugin supersedes the Kate-internal \"Auto Brackets\" feature.\n" "The setting was automatically disabled for this document."), i18n("Auto brackets feature disabled"), "AutoBraceSupersedesInformation"); } } AutoBracePluginDocument *docplugin; // We're not storing the brace inserter by view but by document, // which makes signal connection and destruction a bit easier. if (m_docplugins.contains(view->document())) { docplugin = m_docplugins.value(view->document()); } else { // Create Editor plugin and assign options through reference docplugin = new AutoBracePluginDocument(view->document(), m_autoBrackets, m_autoQuotations); m_docplugins.insert(view->document(), docplugin); } // Shouldn't be necessary in theory, but for removeView() the document // might already be destroyed and removed. Also used as refcounter. m_documents.insert(view, view->document()); } void AutoBracePlugin::removeView(KTextEditor::View *view) { if (m_documents.contains(view)) { KTextEditor::Document *document = m_documents.value(view); m_documents.remove(view); // Only detach from the document if it was the last view pointing to that. if (m_documents.keys(document).empty()) { AutoBracePluginDocument *docplugin = m_docplugins.value(document); m_docplugins.remove(document); delete docplugin; } } } void AutoBracePlugin::readConfig() { KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin"); - // Read configuration parameters, make them false by default - // TODO: set to true by default once https://bugs.kde.org/show_bug.cgi?id=234525 got resolved - m_autoBrackets = cg.readEntry("autobrackets", false); + m_autoBrackets = cg.readEntry("autobrackets", true); m_autoQuotations = cg.readEntry("autoquotations", false); } void AutoBracePlugin::writeConfig() { KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin"); cg.writeEntry("autobrackets", m_autoBrackets); cg.writeEntry("autoquotations", m_autoQuotations); } /// AutoBracePluginDocument AutoBracePluginDocument::AutoBracePluginDocument(KTextEditor::Document* document, const bool& autoBrackets, const bool& autoQuotations) : QObject(document), m_insertionLine(0), m_withSemicolon(false), m_lastRange(KTextEditor::Range::invalid()), m_autoBrackets(autoBrackets), m_autoQuotations(autoQuotations) { - // Fill brackets map matching opening and closing brackets. - m_brackets["("] = ")"; - m_brackets["["] = "]"; - connect(document, SIGNAL(exclusiveEditStart(KTextEditor::Document*)), this, SLOT(disconnectSlots(KTextEditor::Document*))); connect(document, SIGNAL(exclusiveEditEnd(KTextEditor::Document*)), this, SLOT(connectSlots(KTextEditor::Document*))); connectSlots(document); } AutoBracePluginDocument::~AutoBracePluginDocument() { disconnect(parent() /* == document */, 0, this, 0); } /** * (Re-)setups slots for AutoBracePluginDocument. * @param document Current document. */ void AutoBracePluginDocument::connectSlots(KTextEditor::Document *document) { connect(document, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(slotTextInserted(KTextEditor::Document*,KTextEditor::Range))); connect(document, SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(slotTextRemoved(KTextEditor::Document*,KTextEditor::Range))); } void AutoBracePluginDocument::disconnectSlots(KTextEditor::Document* document) { disconnect(document, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(slotTextInserted(KTextEditor::Document*,KTextEditor::Range))); disconnect(document, SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(slotTextRemoved(KTextEditor::Document*,KTextEditor::Range))); disconnect(document, SIGNAL(textChanged(KTextEditor::Document*)), this, SLOT(slotTextChanged(KTextEditor::Document*))); } /** * Connected to KTextEditor::Document::textChanged() once slotTextInserted() * found a line with an opening brace. This takes care of inserting the new * line with its closing counterpart. */ void AutoBracePluginDocument::slotTextChanged(KTextEditor::Document *document) { // Disconnect from all signals as we insert stuff by ourselves. // Prevent infinite recursion. disconnectSlots(document); // Make really sure that we want to insert the brace, paste guard and all. if (m_insertionLine != 0 && m_insertionLine == document->activeView()->cursorPosition().line() && document->line(m_insertionLine).trimmed().isEmpty()) { KTextEditor::View *view = document->activeView(); document->startEditing(); // If the document's View is a KateView then it's able to indent. // We hereby ignore the indenter and always indent correctly. (Sorry!) if (view->inherits("KateView")) { // Correctly indent the empty line. Magic! KTextEditor::Range lineRange( m_insertionLine, 0, m_insertionLine, document->lineLength(m_insertionLine) ); document->replaceText(lineRange, m_indentation); connect(this, SIGNAL(indent()), view, SLOT(indent())); emit indent(); disconnect(this, SIGNAL(indent()), view, SLOT(indent())); } // The line with the closing brace. (Inserted via insertLine() in order // to avoid space removal by potential indenters.) document->insertLine(m_insertionLine + 1, m_indentation + '}' + (m_withSemicolon ? ";" : "")); document->endEditing(); view->setCursorPosition(document->endOfLine(m_insertionLine)); } m_insertionLine = 0; // Re-enable the textInserted() slot again. connectSlots(document); } /** * Connected to KTextEditor::Documet::textRemoved. Allows to delete * an automatically inserted closing bracket if the opening counterpart * has been removed. */ void AutoBracePluginDocument::slotTextRemoved(KTextEditor::Document* document, const KTextEditor::Range& range) { // If last range equals the deleted text range (last range // is last inserted bracket), we also delete the associated closing bracket. if (m_lastRange == range) { // avoid endless recursion disconnectSlots(document); // Delete the character at the same range because the opening // bracket has already been removed so the closing bracket // should now have been shifted to that same position if (range.isValid()) { document->removeText(range); } connectSlots(document); } } /** * Connected to KTextEditor::Document::textInserted(), which is emitted on all * insertion changes. Line text and line breaks are emitted separately by * KatePart, and pasted text gets the same treatment as manually entered text. * Because of that, we discard paste operations by only remembering the * insertion status for the last line that was entered. */ void AutoBracePluginDocument::slotTextInserted(KTextEditor::Document *document, const KTextEditor::Range& range) { + // Fill brackets map matching opening and closing brackets. + QMap brackets; + brackets["("] = ")"; + brackets["["] = "]"; + + // latex wants {, too + if (document->mode() == "LaTeX") + brackets["{"] = "}"; + // List of Tokens after which an automatic bracket expanion // is allowed. const static QStringList allowedNextToken = QStringList() << "]" << ")" << "," << "." << ";" << "\n" << "\t" << " " << ""; const QString text = document->text(range); // An insertion operation cancels any last range removal // operation m_lastRange = KTextEditor::Range::invalid(); // Make sure to handle only: // 1.) New lines after { (brace openers) // 2.) Opening braces like '(' and '[' // 3.) Quotation marks like " and ' // Handle brace openers if (text == "\n") { // Remember this position as insertion candidate. // We don't directly insert this here because of KatePart specifics: // a) Setting the cursor position crashes at this point, and // b) textChanged() only gets called once per edit operation, so we can // ignore the same braces when they're being inserted via paste. if (isInsertionCandidate(document, range.start().line())) { m_insertionLine = range.end().line(); connect(document, SIGNAL(textChanged(KTextEditor::Document*)), this, SLOT(slotTextChanged(KTextEditor::Document*))); } else { m_insertionLine = 0; } } // Opening brackets (defined in ctor) - else if (m_autoBrackets && m_brackets.contains(text)) { + else if (m_autoBrackets && brackets.contains(text)) { // Only insert auto closing brackets if current text range // is followed by one of the allowed next tokens. if (allowedNextToken.contains(nextToken(document,range))) { - insertAutoBracket(document, range, m_brackets[text]); + insertAutoBracket(document, range, brackets[text]); } } // Check whether closing brackets are allowed. // If a brace is not allowed remove it // and set the cursor to the position after that text range. // Bracket tests bases on this simple idea: A bracket can only be inserted // if it is NOT followed by the same bracket. This results in overwriting closing brackets. - else if (m_autoBrackets && m_brackets.values().contains(text)) { + else if (m_autoBrackets && brackets.values().contains(text)) { if (nextToken(document,range) == text) { KTextEditor::Cursor saved = range.end(); document->removeText(range); document->activeView()->setCursorPosition(saved); } } // Insert auto-quotation marks (if enabled). Same idea as with brackets // applies here: double quotation marks are eaten up and only inserted if not // followed by the same quoation mark. Additionally automatic quotation marks // are inserted only if NOT followed by a back slash (escaping character). else if (m_autoQuotations && (text == "\"" || text == "\'") && previousToken(document, range) != "\\") { const QString next = nextToken(document, range); // Eat it if already there if (next == text) { KTextEditor::Cursor saved = range.end(); document->removeText(range); document->activeView()->setCursorPosition(saved); } // Quotation marks only inserted if followed by one of the allowed // next tokens and the number of marks in the insertion line is even // (excluding the already inserted mark) else if (allowedNextToken.contains(next) && (document->line(range.start().line()).count(text) % 2) ) { insertAutoBracket(document, range, text); } } } /** * Automatically inserts closing bracket. Cursor * is placed in between the brackets. * @param document Current document. * @param range Inserted text range (by text-inserted slot) * @param brace Brace to insert */ void AutoBracePluginDocument::insertAutoBracket(KTextEditor::Document *document, const KTextEditor::Range& range, const QString& brace) { // Disconnect Slots to avoid check for redundant closing brackets disconnectSlots(document); // Save range to allow following remove operation to // detect the corresponding closing bracket m_lastRange = range; KTextEditor::Cursor saved = range.end(); // Depending on brace, insert corresponding closing brace. document->insertText(range.end(), brace); document->activeView()->setCursorPosition(saved); // Re-Enable insertion slot. connectSlots(document); } /** * Returns next character after specified text range in document. * @param document Current document. * @param range Inserted text range (by text-inserted slot) * @return Next character after text range */ const QString AutoBracePluginDocument::nextToken(KTextEditor::Document* document, const KTextEditor::Range& range) { // Calculate range after insertion (exactly one character) KTextEditor::Range afterRange(range.end(), range.end().line(), range.end().column()+1); return (afterRange.isValid() ? document->text(afterRange) : ""); } /** * Returns previous character before specified text range in document. * @param document Current document. * @param range Inserted text range (by text-inserted slot) * @return Next character after text range */ const QString AutoBracePluginDocument::previousToken(KTextEditor::Document* document, const KTextEditor::Range& range) { // Calculate range before insertion (exactly one character) KTextEditor::Range beforeRange(range.start().line(), range.start().column()-1, range.start().line(), range.start().column()); return (beforeRange.isValid() ? document->text(beforeRange) : ""); } bool AutoBracePluginDocument::isInsertionCandidate(KTextEditor::Document *document, int openingBraceLine) { QString line = document->line(openingBraceLine); if (line.isEmpty() || !line.endsWith('{')) { return false; } // Get the indentation prefix. QRegExp rx("^(\\s+)"); QString indentation = (rx.indexIn(line) == -1) ? "" : rx.cap(1); // Determine whether to insert a brace or not, depending on the indentation // of the upcoming (non-empty) line. bool isCandidate = true; QString indentationLength = QString::number(indentation.length()); QString indentationLengthMinusOne = QString::number(indentation.length() - 1); ///TODO: make configurable // these tokens must not start a line that is used to get the correct indendation width QStringList forbiddenTokenList; if ( line.contains("class") || line.contains("interface") || line.contains("struct") ) { forbiddenTokenList << "private" << "public" << "protected"; if ( document->mode() == "C++" ) { forbiddenTokenList << "signals" << "Q_SIGNALS"; } else { // PHP and potentially others forbiddenTokenList << "function"; } } if ( (document->mode() == "C++" || document->mode() == "C") && line.contains("namespace", Qt::CaseInsensitive) ) { // C++ specific forbiddenTokenList << "class" << "struct"; } const QString forbiddenTokens = forbiddenTokenList.isEmpty() ? QLatin1String("") : QString(QLatin1String("(?!") + forbiddenTokenList.join(QLatin1String("|")) + QLatin1Char(')')); for (int i = openingBraceLine + 1; i < document->lines(); ++i) { line = document->line(i); if (line.trimmed().isEmpty()) { continue; // Empty lines are not a reliable source of information. } if (indentation.length() == 0) { // Inserting a brace is ok if there is a line (not starting with a // brace) without indentation. rx.setPattern("^(?=[^\\}\\s])" // But it's not OK if the line starts with one of our forbidden tokens. + forbiddenTokens ); } else { rx.setPattern("^(?:" // Inserting a brace is ok if there is a closing brace with // less indentation than the opener line. "[\\s]{0," + indentationLengthMinusOne + "}\\}" "|" // Inserting a brace is ok if there is a line (not starting with a // brace) with less or similar indentation as the original line. "[\\s]{0," + indentationLength + "}(?=[^\\}\\s])" // But it's not OK if the line starts with one of our forbidden tokens. + forbiddenTokens + ")" ); } if (rx.indexIn(line) == -1) { // There is already a brace, or the line is indented more than the // opener line (which means we expect a brace somewhere further down), // or we found a forbidden token. // So don't insert the brace, and just indent the line. isCandidate = false; } // Quit the loop - a non-empty line always leads to a definitive decision. break; } if (isCandidate) { m_indentation = indentation; // in C++ automatically add a semicolon after the closing brace when we create a new class/struct if ( (document->mode() == "C++" || document->mode() == "C") && document->line(openingBraceLine).indexOf(QRegExp("(?:class|struct|enum)\\s+[^\\s]+(\\s*[:,](\\s*((public|protected|private)\\s+)?[^\\s]+))*\\s*\\{\\s*$")) != -1 ) { m_withSemicolon = true; } else { m_withSemicolon = false; } } return isCandidate; } #include "autobrace.moc" diff --git a/part/plugins/autobrace/autobrace.h b/part/plugins/autobrace/autobrace.h index 6be71f053..8a17a35f5 100644 --- a/part/plugins/autobrace/autobrace.h +++ b/part/plugins/autobrace/autobrace.h @@ -1,106 +1,105 @@ /** * This file is part of the KDE libraries * Copyright (C) 2008 Jakob Petsovits * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AUTOBRACE_H #define AUTOBRACE_H #include #include #include #include #include #include #include #include class AutoBracePlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit AutoBracePlugin(QObject *parent = 0, const QVariantList &args = QVariantList()); virtual ~AutoBracePlugin(); static AutoBracePlugin *self() { return plugin; } void addView (KTextEditor::View *view); void removeView (KTextEditor::View *view); void readConfig(); void writeConfig(); virtual void readConfig (KConfig *) {} virtual void writeConfig (KConfig *) {} /// Inline Option Get/Setters bool autoBrackets() const { return m_autoBrackets; } void setAutoBrackets(bool y) { m_autoBrackets = y; } bool autoQuotations() const { return m_autoQuotations; } void setAutoQuotations(bool y) { m_autoQuotations = y; } private: static AutoBracePlugin *plugin; QHash m_documents; QHash m_docplugins; bool m_autoBrackets; bool m_autoQuotations; }; class AutoBracePluginDocument : public QObject, public KXMLGUIClient { Q_OBJECT public: explicit AutoBracePluginDocument(KTextEditor::Document *document, const bool& autoBrackets, const bool& autoQuotations); ~AutoBracePluginDocument(); private Q_SLOTS: void slotTextChanged(KTextEditor::Document *document); void slotTextInserted(KTextEditor::Document *document, const KTextEditor::Range& range); void slotTextRemoved(KTextEditor::Document *document, const KTextEditor::Range& range); void connectSlots(KTextEditor::Document* document); void disconnectSlots(KTextEditor::Document* document); private: bool isInsertionCandidate(KTextEditor::Document *document, int openingBraceLine); Q_SIGNALS: void indent(); private: void insertAutoBracket(KTextEditor::Document *document,const KTextEditor::Range& range, const QString& brace); const QString previousToken(KTextEditor::Document *document,const KTextEditor::Range& range); const QString nextToken(KTextEditor::Document *document,const KTextEditor::Range& range); int m_insertionLine; QString m_indentation; bool m_withSemicolon; - QMap m_brackets; KTextEditor::Range m_lastRange; const bool& m_autoBrackets; const bool& m_autoQuotations; }; K_PLUGIN_FACTORY_DECLARATION(AutoBracePluginFactory) #endif // AUTOBRACE_H diff --git a/part/plugins/autobrace/autobrace_config.cpp b/part/plugins/autobrace/autobrace_config.cpp index b44af5149..4e71f2ce9 100644 --- a/part/plugins/autobrace/autobrace_config.cpp +++ b/part/plugins/autobrace/autobrace_config.cpp @@ -1,106 +1,106 @@ /** * This file is part of the KDE libraries * Copyright (C) 2010 André Stein * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "autobrace_config.h" #include "autobrace.h" #include #include #include #include #include #include #include AutoBraceConfig::AutoBraceConfig(QWidget *parent, const QVariantList &args) : KCModule(AutoBracePluginFactory::componentData(), parent, args) { QVBoxLayout *layout = new QVBoxLayout(this); - m_autoBrackets = new QCheckBox(i18n("Automatically add closing brackets ) and ]"), this); + m_autoBrackets = new QCheckBox(i18n("Automatically add closing brackets ) and ] (and } for e.g. LaTeX)"), this); m_autoQuotations = new QCheckBox(i18n("Automatically add closing quotation marks"), this); layout->addWidget(m_autoBrackets); layout->addWidget(m_autoQuotations); setLayout(layout); load(); // Changed slots connect(m_autoBrackets, SIGNAL(toggled(bool)), this, SLOT(slotChanged(bool))); connect(m_autoQuotations, SIGNAL(toggled(bool)), this, SLOT(slotChanged(bool))); } AutoBraceConfig::~AutoBraceConfig() { } void AutoBraceConfig::save() { if (AutoBracePlugin::self()) { AutoBracePlugin::self()->setAutoBrackets(m_autoBrackets->isChecked()); AutoBracePlugin::self()->setAutoQuotations(m_autoQuotations->isChecked()); AutoBracePlugin::self()->writeConfig(); } else { KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin"); cg.writeEntry("autobrackets", m_autoBrackets->isChecked()); cg.writeEntry("autoquotations", m_autoQuotations->isChecked()); } emit changed(false); } void AutoBraceConfig::load() { if (AutoBracePlugin::self()) { AutoBracePlugin::self()->readConfig(); m_autoBrackets->setChecked(AutoBracePlugin::self()->autoBrackets()); m_autoQuotations->setChecked(AutoBracePlugin::self()->autoQuotations()); } else { KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin" ); m_autoBrackets->setChecked(cg.readEntry("autobrackets", QVariant(true)).toBool()); m_autoQuotations->setChecked(cg.readEntry("autoquotations", QVariant(true)).toBool()); } emit changed(false); } void AutoBraceConfig::defaults() { m_autoBrackets->setChecked(true); m_autoQuotations->setChecked(true); emit changed(true); } void AutoBraceConfig::slotChanged(bool) { emit changed(true); } #include "autobrace_config.moc"