• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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"

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal