Kate
autobrace.cpp
Go to the documentation of this file.
00001 00020 #include "autobrace.h" 00021 #include "autobrace_config.h" 00022 00023 #include <kpluginfactory.h> 00024 #include <kpluginloader.h> 00025 00026 #include <ktexteditor/configinterface.h> 00027 #include <kmessagebox.h> 00028 #include <klocalizedstring.h> 00029 #include <iostream> 00030 #include <kconfiggroup.h> 00031 00032 AutoBracePlugin *AutoBracePlugin::plugin = 0; 00033 00034 K_PLUGIN_FACTORY_DEFINITION(AutoBracePluginFactory, 00035 registerPlugin<AutoBracePlugin>("ktexteditor_autobrace"); 00036 registerPlugin<AutoBraceConfig>("ktexteditor_autobrace_config"); 00037 ) 00038 K_EXPORT_PLUGIN(AutoBracePluginFactory("ktexteditor_autobrace", "ktexteditor_plugins")) 00039 00040 AutoBracePlugin::AutoBracePlugin(QObject *parent, const QVariantList &args) 00041 : KTextEditor::Plugin(parent), m_autoBrackets(true), m_autoQuotations(true) 00042 { 00043 Q_UNUSED(args); 00044 plugin = this; 00045 00046 readConfig(); 00047 } 00048 00049 AutoBracePlugin::~AutoBracePlugin() 00050 { 00051 plugin = 0; 00052 } 00053 00054 void AutoBracePlugin::addView(KTextEditor::View *view) 00055 { 00056 if ( KTextEditor::ConfigInterface* confIface = qobject_cast< KTextEditor::ConfigInterface* >(view->document()) ) { 00057 QVariant brackets = confIface->configValue("auto-brackets"); 00058 if ( brackets.isValid() && brackets.canConvert(QVariant::Bool) && brackets.toBool() ) { 00059 confIface->setConfigValue("auto-brackets", false); 00060 KMessageBox::information(view, i18n("The autobrace plugin supersedes the Kate-internal \"Auto Brackets\" feature.\n" 00061 "The setting was automatically disabled for this document."), 00062 i18n("Auto brackets feature disabled"), "AutoBraceSupersedesInformation"); 00063 } 00064 } 00065 00066 AutoBracePluginDocument *docplugin; 00067 00068 // We're not storing the brace inserter by view but by document, 00069 // which makes signal connection and destruction a bit easier. 00070 if (m_docplugins.contains(view->document())) { 00071 docplugin = m_docplugins.value(view->document()); 00072 } 00073 else { 00074 // Create Editor plugin and assign options through reference 00075 docplugin = new AutoBracePluginDocument(view->document(), 00076 m_autoBrackets, 00077 m_autoQuotations); 00078 m_docplugins.insert(view->document(), docplugin); 00079 } 00080 // Shouldn't be necessary in theory, but for removeView() the document 00081 // might already be destroyed and removed. Also used as refcounter. 00082 m_documents.insert(view, view->document()); 00083 } 00084 00085 void AutoBracePlugin::removeView(KTextEditor::View *view) 00086 { 00087 if (m_documents.contains(view)) 00088 { 00089 KTextEditor::Document *document = m_documents.value(view); 00090 m_documents.remove(view); 00091 00092 // Only detach from the document if it was the last view pointing to that. 00093 if (m_documents.keys(document).empty()) { 00094 AutoBracePluginDocument *docplugin = m_docplugins.value(document); 00095 m_docplugins.remove(document); 00096 delete docplugin; 00097 } 00098 } 00099 } 00100 00101 void AutoBracePlugin::readConfig() 00102 { 00103 KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin"); 00104 // Read configuration parameters, make them false by default 00105 // TODO: set to true by default once https://bugs.kde.org/show_bug.cgi?id=234525 got resolved 00106 m_autoBrackets = cg.readEntry("autobrackets", false); 00107 m_autoQuotations = cg.readEntry("autoquotations", false); 00108 } 00109 00110 void AutoBracePlugin::writeConfig() 00111 { 00112 KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin"); 00113 cg.writeEntry("autobrackets", m_autoBrackets); 00114 cg.writeEntry("autoquotations", m_autoQuotations); 00115 } 00116 00118 00119 AutoBracePluginDocument::AutoBracePluginDocument(KTextEditor::Document* document, const bool& autoBrackets, const bool& autoQuotations) 00120 : QObject(document), m_insertionLine(0), m_withSemicolon(false), 00121 m_lastRange(KTextEditor::Range::invalid()), m_autoBrackets(autoBrackets), m_autoQuotations(autoQuotations) 00122 { 00123 // Fill brackets map matching opening and closing brackets. 00124 m_brackets["("] = ")"; 00125 m_brackets["["] = "]"; 00126 00127 connect(document, SIGNAL(exclusiveEditStart(KTextEditor::Document *)), 00128 this, SLOT(disconnectSlots(KTextEditor::Document *))); 00129 connect(document, SIGNAL(exclusiveEditEnd(KTextEditor::Document *)), 00130 this, SLOT(connectSlots(KTextEditor::Document *))); 00131 00132 connectSlots(document); 00133 } 00134 00135 AutoBracePluginDocument::~AutoBracePluginDocument() 00136 { 00137 disconnect(parent() /* == document */, 0, this, 0); 00138 } 00139 00144 void AutoBracePluginDocument::connectSlots(KTextEditor::Document *document) 00145 { 00146 connect(document, SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00147 this, SLOT(slotTextInserted(KTextEditor::Document*, KTextEditor::Range))); 00148 connect(document, SIGNAL(textRemoved(KTextEditor::Document*, KTextEditor::Range)), 00149 this, SLOT(slotTextRemoved(KTextEditor::Document*, KTextEditor::Range))); 00150 } 00151 00152 void AutoBracePluginDocument::disconnectSlots(KTextEditor::Document* document) 00153 { 00154 disconnect(document, SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00155 this, SLOT(slotTextInserted(KTextEditor::Document*, KTextEditor::Range))); 00156 disconnect(document, SIGNAL(textRemoved(KTextEditor::Document*, KTextEditor::Range)), 00157 this, SLOT(slotTextRemoved(KTextEditor::Document*, KTextEditor::Range))); 00158 disconnect(document, SIGNAL(textChanged(KTextEditor::Document*)), 00159 this, SLOT(slotTextChanged(KTextEditor::Document*))); 00160 } 00161 00167 void AutoBracePluginDocument::slotTextChanged(KTextEditor::Document *document) { 00168 // Disconnect from all signals as we insert stuff by ourselves. 00169 // Prevent infinite recursion. 00170 disconnectSlots(document); 00171 00172 // Make really sure that we want to insert the brace, paste guard and all. 00173 if (m_insertionLine != 0 00174 && m_insertionLine == document->activeView()->cursorPosition().line() 00175 && document->line(m_insertionLine).trimmed().isEmpty()) 00176 { 00177 KTextEditor::View *view = document->activeView(); 00178 document->startEditing(); 00179 00180 // If the document's View is a KateView then it's able to indent. 00181 // We hereby ignore the indenter and always indent correctly. (Sorry!) 00182 if (view->inherits("KateView")) { 00183 // Correctly indent the empty line. Magic! 00184 KTextEditor::Range lineRange( 00185 m_insertionLine, 0, 00186 m_insertionLine, document->lineLength(m_insertionLine) 00187 ); 00188 document->replaceText(lineRange, m_indentation); 00189 00190 connect(this, SIGNAL(indent()), view, SLOT(indent())); 00191 emit indent(); 00192 disconnect(this, SIGNAL(indent()), view, SLOT(indent())); 00193 } 00194 // The line with the closing brace. (Inserted via insertLine() in order 00195 // to avoid space removal by potential indenters.) 00196 document->insertLine(m_insertionLine + 1, m_indentation + '}' + (m_withSemicolon ? ";" : "")); 00197 00198 document->endEditing(); 00199 view->setCursorPosition(document->endOfLine(m_insertionLine)); 00200 } 00201 m_insertionLine = 0; 00202 00203 // Re-enable the textInserted() slot again. 00204 connectSlots(document); 00205 } 00206 00212 void AutoBracePluginDocument::slotTextRemoved(KTextEditor::Document* document, const KTextEditor::Range& range) 00213 { 00214 // If last range equals the deleted text range (last range 00215 // is last inserted bracket), we also delete the associated closing bracket. 00216 if (m_lastRange == range) { 00217 // avoid endless recursion 00218 disconnectSlots(document); 00219 00220 // Delete the character at the same range because the opening 00221 // bracket has already been removed so the closing bracket 00222 // should now have been shifted to that same position 00223 if (range.isValid()) { 00224 document->removeText(range); 00225 } 00226 00227 connectSlots(document); 00228 } 00229 } 00230 00238 void AutoBracePluginDocument::slotTextInserted(KTextEditor::Document *document, 00239 const KTextEditor::Range& range) 00240 { 00241 // List of Tokens after which an automatic bracket expanion 00242 // is allowed. 00243 const static QStringList allowedNextToken = QStringList() << "]" << ")" << "," 00244 << "." << ";" << "\n" << "\t" << " " << ""; 00245 const QString text = document->text(range); 00246 00247 // An insertion operation cancels any last range removal 00248 // operation 00249 m_lastRange = KTextEditor::Range::invalid(); 00250 00251 // Make sure to handle only: 00252 // 1.) New lines after { (brace openers) 00253 // 2.) Opening braces like '(' and '[' 00254 // 3.) Quotation marks like " and ' 00255 00256 // Handle brace openers 00257 if (text == "\n") { 00258 // Remember this position as insertion candidate. 00259 // We don't directly insert this here because of KatePart specifics: 00260 // a) Setting the cursor position crashes at this point, and 00261 // b) textChanged() only gets called once per edit operation, so we can 00262 // ignore the same braces when they're being inserted via paste. 00263 if (isInsertionCandidate(document, range.start().line())) { 00264 m_insertionLine = range.end().line(); 00265 connect(document, SIGNAL(textChanged(KTextEditor::Document*)), 00266 this, SLOT(slotTextChanged(KTextEditor::Document*))); 00267 } 00268 else { 00269 m_insertionLine = 0; 00270 } 00271 } 00272 // Opening brackets (defined in ctor) 00273 else if (m_autoBrackets && m_brackets.contains(text)) { 00274 // Only insert auto closing brackets if current text range 00275 // is followed by one of the allowed next tokens. 00276 if (allowedNextToken.contains(nextToken(document,range))) { 00277 insertAutoBracket(document, range, m_brackets[text]); 00278 } 00279 00280 } 00281 // Check whether closing brackets are allowed. 00282 // If a brace is not allowed remove it 00283 // and set the cursor to the position after that text range. 00284 // Bracket tests bases on this simple idea: A bracket can only be inserted 00285 // if it is NOT followed by the same bracket. This results in overwriting closing brackets. 00286 else if (m_autoBrackets && m_brackets.values().contains(text)) { 00287 if (nextToken(document,range) == text) { 00288 KTextEditor::Cursor saved = range.end(); 00289 document->removeText(range); 00290 document->activeView()->setCursorPosition(saved); 00291 } 00292 } 00293 // Insert auto-quotation marks (if enabled). Same idea as with brackets 00294 // applies here: double quotation marks are eaten up and only inserted if not 00295 // followed by the same quoation mark. Additionally automatic quotation marks 00296 // are inserted only if NOT followed by a back slash (escaping character). 00297 else if (m_autoQuotations && (text == "\"" || text == "\'") && previousToken(document, range) != "\\") { 00298 const QString next = nextToken(document, range); 00299 // Eat it if already there 00300 if (next == text) { 00301 KTextEditor::Cursor saved = range.end(); 00302 document->removeText(range); 00303 document->activeView()->setCursorPosition(saved); 00304 } 00305 // Quotation marks only inserted if followed by one of the allowed 00306 // next tokens and the number of marks in the insertion line is even 00307 // (excluding the already inserted mark) 00308 else if (allowedNextToken.contains(next) 00309 && (document->line(range.start().line()).count(text) % 2) ) { 00310 insertAutoBracket(document, range, text); 00311 } 00312 } 00313 } 00314 00322 void AutoBracePluginDocument::insertAutoBracket(KTextEditor::Document *document, 00323 const KTextEditor::Range& range, 00324 const QString& brace) { 00325 // Disconnect Slots to avoid check for redundant closing brackets 00326 disconnectSlots(document); 00327 00328 // Save range to allow following remove operation to 00329 // detect the corresponding closing bracket 00330 m_lastRange = range; 00331 00332 KTextEditor::Cursor saved = range.end(); 00333 // Depending on brace, insert corresponding closing brace. 00334 document->insertText(range.end(), brace); 00335 document->activeView()->setCursorPosition(saved); 00336 00337 // Re-Enable insertion slot. 00338 connectSlots(document); 00339 } 00340 00347 const QString AutoBracePluginDocument::nextToken(KTextEditor::Document* document, const KTextEditor::Range& range) 00348 { 00349 // Calculate range after insertion (exactly one character) 00350 KTextEditor::Range afterRange(range.end(), range.end().line(), range.end().column()+1); 00351 00352 return (afterRange.isValid() ? document->text(afterRange) : ""); 00353 } 00354 00361 const QString AutoBracePluginDocument::previousToken(KTextEditor::Document* document, const KTextEditor::Range& range) 00362 { 00363 // Calculate range before insertion (exactly one character) 00364 KTextEditor::Range beforeRange(range.start().line(), range.start().column()-1, range.start().line(), 00365 range.start().column()); 00366 00367 return (beforeRange.isValid() ? document->text(beforeRange) : ""); 00368 } 00369 00370 bool AutoBracePluginDocument::isInsertionCandidate(KTextEditor::Document *document, int openingBraceLine) { 00371 QString line = document->line(openingBraceLine); 00372 if (line.isEmpty() || !line.endsWith('{')) { 00373 return false; 00374 } 00375 00376 // Get the indentation prefix. 00377 QRegExp rx("^(\\s+)"); 00378 QString indentation = (rx.indexIn(line) == -1) ? "" : rx.cap(1); 00379 00380 // Determine whether to insert a brace or not, depending on the indentation 00381 // of the upcoming (non-empty) line. 00382 bool isCandidate = true; 00383 QString indentationLength = QString::number(indentation.length()); 00384 QString indentationLengthMinusOne = QString::number(indentation.length() - 1); 00385 00387 // these tokens must not start a line that is used to get the correct indendation width 00388 QStringList forbiddenTokenList; 00389 if ( line.contains("class") || line.contains("interface") || line.contains("struct") ) { 00390 forbiddenTokenList << "private" << "public" << "protected"; 00391 if ( document->mode() == "C++" ) { 00392 forbiddenTokenList << "signals" << "Q_SIGNALS"; 00393 } else { 00394 // PHP and potentially others 00395 forbiddenTokenList << "function"; 00396 } 00397 } 00398 if ( (document->mode() == "C++" || document->mode() == "C") && line.contains("namespace", Qt::CaseInsensitive) ) { 00399 // C++ specific 00400 forbiddenTokenList << "class" << "struct"; 00401 } 00402 const QString forbiddenTokens = forbiddenTokenList.isEmpty() ? QLatin1String("") : QString(QLatin1String("(?!") + forbiddenTokenList.join(QLatin1String("|")) + QLatin1Char(')')); 00403 00404 for (int i = openingBraceLine + 1; i < document->lines(); ++i) 00405 { 00406 line = document->line(i); 00407 if (line.trimmed().isEmpty()) { 00408 continue; // Empty lines are not a reliable source of information. 00409 } 00410 00411 if (indentation.length() == 0) { 00412 // Inserting a brace is ok if there is a line (not starting with a 00413 // brace) without indentation. 00414 rx.setPattern("^(?=[^\\}\\s])" 00415 // But it's not OK if the line starts with one of our forbidden tokens. 00416 + forbiddenTokens 00417 ); 00418 } 00419 else { 00420 rx.setPattern("^(?:" 00421 // Inserting a brace is ok if there is a closing brace with 00422 // less indentation than the opener line. 00423 "[\\s]{0," + indentationLengthMinusOne + "}\\}" 00424 "|" 00425 // Inserting a brace is ok if there is a line (not starting with a 00426 // brace) with less or similar indentation as the original line. 00427 "[\\s]{0," + indentationLength + "}(?=[^\\}\\s])" 00428 // But it's not OK if the line starts with one of our forbidden tokens. 00429 + forbiddenTokens + 00430 ")" 00431 ); 00432 } 00433 00434 if (rx.indexIn(line) == -1) { 00435 // There is already a brace, or the line is indented more than the 00436 // opener line (which means we expect a brace somewhere further down), 00437 // or we found a forbidden token. 00438 // So don't insert the brace, and just indent the line. 00439 isCandidate = false; 00440 } 00441 // Quit the loop - a non-empty line always leads to a definitive decision. 00442 break; 00443 } 00444 00445 if (isCandidate) { 00446 m_indentation = indentation; 00447 // in C++ automatically add a semicolon after the closing brace when we create a new class/struct 00448 if ( (document->mode() == "C++" || document->mode() == "C") 00449 && document->line(openingBraceLine).indexOf(QRegExp("(?:class|struct|enum)\\s+[^\\s]+(\\s*[:,](\\s*((public|protected|private)\\s+)?[^\\s]+))*\\s*\\{\\s*$")) != -1 ) 00450 { 00451 m_withSemicolon = true; 00452 } else { 00453 m_withSemicolon = false; 00454 } 00455 } 00456 return isCandidate; 00457 } 00458 00459 #include "autobrace.moc"
KDE 4.6 API Reference