~ubuntu-branches/ubuntu/vivid/kate/vivid-updates

« back to all changes in this revision

Viewing changes to addons/ktexteditor/autobrace/autobrace.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2014-12-04 16:49:41 UTC
  • mfrom: (1.6.6)
  • Revision ID: package-import@ubuntu.com-20141204164941-l3qbvsly83hhlw2v
Tags: 4:14.11.97-0ubuntu1
* New upstream release
* Update build-deps and use pkg-kde v3 for Qt 5 build
* kate-data now kate5-data for co-installability

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**
2
 
  * This file is part of the KDE libraries
3
 
  * Copyright (C) 2008 Jakob Petsovits <jpetso@gmx.at>
4
 
  *
5
 
  * This library is free software; you can redistribute it and/or
6
 
  * modify it under the terms of the GNU Library General Public
7
 
  * License version 2 as published by the Free Software Foundation.
8
 
  *
9
 
  * This library is distributed in the hope that it will be useful,
10
 
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 
  * Library General Public License for more details.
13
 
  *
14
 
  * You should have received a copy of the GNU Library General Public License
15
 
  * along with this library; see the file COPYING.LIB.  If not, write to
16
 
  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
 
  * Boston, MA 02110-1301, USA.
18
 
  */
19
 
 
20
 
#include "autobrace.h"
21
 
#include "autobrace_config.h"
22
 
 
23
 
#include <kpluginfactory.h>
24
 
#include <kpluginloader.h>
25
 
 
26
 
#include <ktexteditor/configinterface.h>
27
 
#include <kmessagebox.h>
28
 
#include <klocalizedstring.h>
29
 
#include <iostream>
30
 
#include <kconfiggroup.h>
31
 
 
32
 
AutoBracePlugin *AutoBracePlugin::plugin = 0;
33
 
 
34
 
K_PLUGIN_FACTORY_DEFINITION(AutoBracePluginFactory,
35
 
        registerPlugin<AutoBracePlugin>("ktexteditor_autobrace");
36
 
        registerPlugin<AutoBraceConfig>("ktexteditor_autobrace_config");
37
 
        )
38
 
K_EXPORT_PLUGIN(AutoBracePluginFactory("ktexteditor_autobrace", "ktexteditor_plugins"))
39
 
 
40
 
AutoBracePlugin::AutoBracePlugin(QObject *parent, const QVariantList &args)
41
 
    : KTextEditor::Plugin(parent), m_autoBrackets(true), m_autoQuotations(true)
42
 
{
43
 
    Q_UNUSED(args);
44
 
    plugin = this;
45
 
 
46
 
    readConfig();
47
 
}
48
 
 
49
 
AutoBracePlugin::~AutoBracePlugin()
50
 
{
51
 
    plugin = 0;
52
 
}
53
 
 
54
 
void AutoBracePlugin::addView(KTextEditor::View *view)
55
 
{
56
 
    AutoBracePluginDocument *docplugin;
57
 
 
58
 
    // We're not storing the brace inserter by view but by document,
59
 
    // which makes signal connection and destruction a bit easier.
60
 
    if (m_docplugins.contains(view->document())) {
61
 
        docplugin = m_docplugins.value(view->document());
62
 
    }
63
 
    else {
64
 
        // Create Editor plugin and assign options through reference
65
 
        docplugin = new AutoBracePluginDocument(view->document(),
66
 
                                                m_autoBrackets,
67
 
                                                m_autoQuotations);
68
 
        m_docplugins.insert(view->document(), docplugin);
69
 
    }
70
 
    // Shouldn't be necessary in theory, but for removeView() the document
71
 
    // might already be destroyed and removed. Also used as refcounter.
72
 
    m_documents.insert(view, view->document());
73
 
}
74
 
 
75
 
void AutoBracePlugin::removeView(KTextEditor::View *view)
76
 
{
77
 
    if (m_documents.contains(view))
78
 
    {
79
 
        KTextEditor::Document *document = m_documents.value(view);
80
 
        m_documents.remove(view);
81
 
 
82
 
        // Only detach from the document if it was the last view pointing to that.
83
 
        if (m_documents.keys(document).empty()) {
84
 
            AutoBracePluginDocument *docplugin = m_docplugins.value(document);
85
 
            m_docplugins.remove(document);
86
 
            delete docplugin;
87
 
        }
88
 
    }
89
 
}
90
 
 
91
 
void AutoBracePlugin::readConfig()
92
 
{
93
 
    KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin");
94
 
    m_autoBrackets = cg.readEntry("autobrackets", true);
95
 
    m_autoQuotations = cg.readEntry("autoquotations", false);
96
 
}
97
 
 
98
 
void AutoBracePlugin::writeConfig()
99
 
{
100
 
    KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin");
101
 
    cg.writeEntry("autobrackets", m_autoBrackets);
102
 
    cg.writeEntry("autoquotations", m_autoQuotations);
103
 
}
104
 
 
105
 
/// AutoBracePluginDocument
106
 
 
107
 
AutoBracePluginDocument::AutoBracePluginDocument(KTextEditor::Document* document, const bool& autoBrackets, const bool& autoQuotations)
108
 
  : QObject(document), m_insertionLine(0), m_withSemicolon(false),
109
 
    m_lastRange(KTextEditor::Range::invalid()), m_autoBrackets(autoBrackets), m_autoQuotations(autoQuotations)
110
 
{
111
 
    connect(document, SIGNAL(exclusiveEditStart(KTextEditor::Document*)),
112
 
            this, SLOT(disconnectSlots(KTextEditor::Document*)));
113
 
    connect(document, SIGNAL(exclusiveEditEnd(KTextEditor::Document*)),
114
 
            this, SLOT(connectSlots(KTextEditor::Document*)));
115
 
 
116
 
    connectSlots(document);
117
 
}
118
 
 
119
 
AutoBracePluginDocument::~AutoBracePluginDocument()
120
 
{
121
 
    disconnect(parent() /* == document */, 0, this, 0);
122
 
}
123
 
 
124
 
/**
125
 
 * (Re-)setups slots for AutoBracePluginDocument.
126
 
 * @param document Current document.
127
 
 */
128
 
void AutoBracePluginDocument::connectSlots(KTextEditor::Document *document)
129
 
{
130
 
    connect(document, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
131
 
            this, SLOT(slotTextInserted(KTextEditor::Document*,KTextEditor::Range)));
132
 
    connect(document, SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)),
133
 
            this, SLOT(slotTextRemoved(KTextEditor::Document*,KTextEditor::Range)));
134
 
}
135
 
 
136
 
void AutoBracePluginDocument::disconnectSlots(KTextEditor::Document* document)
137
 
{
138
 
    disconnect(document, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
139
 
               this, SLOT(slotTextInserted(KTextEditor::Document*,KTextEditor::Range)));
140
 
    disconnect(document, SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)),
141
 
               this, SLOT(slotTextRemoved(KTextEditor::Document*,KTextEditor::Range)));
142
 
    disconnect(document, SIGNAL(textChanged(KTextEditor::Document*)),
143
 
               this, SLOT(slotTextChanged(KTextEditor::Document*)));
144
 
}
145
 
 
146
 
/**
147
 
 * Connected to KTextEditor::Document::textChanged() once slotTextInserted()
148
 
 * found a line with an opening brace. This takes care of inserting the new
149
 
 * line with its closing counterpart.
150
 
 */
151
 
void AutoBracePluginDocument::slotTextChanged(KTextEditor::Document *document) {
152
 
    // Disconnect from all signals as we insert stuff by ourselves.
153
 
    // Prevent infinite recursion.
154
 
    disconnectSlots(document);
155
 
 
156
 
    // Make really sure that we want to insert the brace, paste guard and all.
157
 
    if (m_insertionLine != 0
158
 
        && m_insertionLine == document->activeView()->cursorPosition().line()
159
 
        && document->line(m_insertionLine).trimmed().isEmpty())
160
 
    {
161
 
        KTextEditor::View *view = document->activeView();
162
 
        document->startEditing();
163
 
 
164
 
        // If the document's View is a KateView then it's able to indent.
165
 
        // We hereby ignore the indenter and always indent correctly. (Sorry!)
166
 
        if (view->inherits("KateView")) {
167
 
            // Correctly indent the empty line. Magic!
168
 
            KTextEditor::Range lineRange(
169
 
                m_insertionLine, 0,
170
 
                m_insertionLine, document->lineLength(m_insertionLine)
171
 
            );
172
 
            document->replaceText(lineRange, m_indentation);
173
 
 
174
 
            connect(this, SIGNAL(indent()), view, SLOT(indent()));
175
 
            emit indent();
176
 
            disconnect(this, SIGNAL(indent()), view, SLOT(indent()));
177
 
        }
178
 
        // The line with the closing brace. (Inserted via insertLine() in order
179
 
        // to avoid space removal by potential indenters.)
180
 
        document->insertLine(m_insertionLine + 1, m_indentation + '}' + (m_withSemicolon ? ";" : ""));
181
 
 
182
 
        document->endEditing();
183
 
        view->setCursorPosition(document->endOfLine(m_insertionLine));
184
 
    }
185
 
    m_insertionLine = 0;
186
 
 
187
 
    // Re-enable the textInserted() slot again.
188
 
    connectSlots(document);
189
 
}
190
 
 
191
 
/**
192
 
 * Connected to KTextEditor::Documet::textRemoved. Allows to delete
193
 
 * an automatically inserted closing bracket if the opening counterpart
194
 
 * has been removed.
195
 
 */
196
 
void AutoBracePluginDocument::slotTextRemoved(KTextEditor::Document* document, const KTextEditor::Range& range)
197
 
{
198
 
    // If last range equals the deleted text range (last range
199
 
    // is last inserted bracket), we also delete the associated closing bracket.
200
 
    if (m_lastRange == range) {
201
 
        // avoid endless recursion
202
 
        disconnectSlots(document);
203
 
 
204
 
        // Delete the character at the same range because the opening
205
 
        // bracket has already been removed so the closing bracket
206
 
        // should now have been shifted to that same position
207
 
        if (range.isValid()) {
208
 
            document->removeText(range);
209
 
        }
210
 
 
211
 
        connectSlots(document);
212
 
    }
213
 
}
214
 
 
215
 
/**
216
 
 * Connected to KTextEditor::Document::textInserted(), which is emitted on all
217
 
 * insertion changes. Line text and line breaks are emitted separately by
218
 
 * KatePart, and pasted text gets the same treatment as manually entered text.
219
 
 * Because of that, we discard paste operations by only remembering the
220
 
 * insertion status for the last line that was entered.
221
 
 */
222
 
void AutoBracePluginDocument::slotTextInserted(KTextEditor::Document *document,
223
 
                                               const KTextEditor::Range& range)
224
 
{
225
 
    // Fill brackets map matching opening and closing brackets.
226
 
    QMap<QString,QString> brackets;
227
 
    brackets["("] = ")";
228
 
    brackets["["] = "]";
229
 
    
230
 
    // latex wants {, too
231
 
    if (document->mode() == "LaTeX")
232
 
        brackets["{"] = "}";
233
 
    
234
 
    // List of Tokens after which an automatic bracket expanion
235
 
    // is allowed.
236
 
    const static QStringList allowedNextToken = QStringList() << "]" << ")" << ","
237
 
                                                << "." << ";" << "\n" << "\t" << " " << "";
238
 
    const QString text = document->text(range);
239
 
 
240
 
    // An insertion operation cancels any last range removal
241
 
    // operation
242
 
    m_lastRange = KTextEditor::Range::invalid();
243
 
 
244
 
    // Make sure to handle only:
245
 
    // 1.) New lines after { (brace openers)
246
 
    // 2.) Opening braces like '(' and '['
247
 
    // 3.) Quotation marks like " and '
248
 
 
249
 
    // Handle brace openers
250
 
    if (text == "\n") {
251
 
        // Remember this position as insertion candidate.
252
 
        // We don't directly insert this here because of KatePart specifics:
253
 
        // a) Setting the cursor position crashes at this point, and
254
 
        // b) textChanged() only gets called once per edit operation, so we can
255
 
        //    ignore the same braces when they're being inserted via paste.
256
 
        if (isInsertionCandidate(document, range.start().line())) {
257
 
            m_insertionLine = range.end().line();
258
 
            connect(document, SIGNAL(textChanged(KTextEditor::Document*)),
259
 
                    this, SLOT(slotTextChanged(KTextEditor::Document*)));
260
 
        }
261
 
        else {
262
 
            m_insertionLine = 0;
263
 
        }
264
 
    }
265
 
    // Opening brackets (defined in ctor)
266
 
    else if (m_autoBrackets && brackets.contains(text)) {
267
 
        // Only insert auto closing brackets if current text range
268
 
        // is followed by one of the allowed next tokens.
269
 
        if (allowedNextToken.contains(nextToken(document,range))) {
270
 
            insertAutoBracket(document, range, brackets[text]);
271
 
        }
272
 
 
273
 
    }
274
 
    // Check whether closing brackets are allowed.
275
 
    // If a brace is not allowed remove it
276
 
    // and set the cursor to the position after that text range.
277
 
    // Bracket tests bases on this simple idea: A bracket can only be inserted
278
 
    // if it is NOT followed by the same bracket. This results in overwriting closing brackets.
279
 
    else if (m_autoBrackets && brackets.values().contains(text)) {
280
 
        if (nextToken(document,range) == text) {
281
 
            KTextEditor::Cursor saved = range.end();
282
 
            document->removeText(range);
283
 
            document->activeView()->setCursorPosition(saved);
284
 
        }
285
 
    }
286
 
    // Insert auto-quotation marks (if enabled). Same idea as with brackets
287
 
    // applies here: double quotation marks are eaten up and only inserted if not
288
 
    // followed by the same quoation mark. Additionally automatic quotation marks
289
 
    // are inserted only if NOT followed by a back slash (escaping character).
290
 
    else if (m_autoQuotations && (text == "\"" || text == "\'") && previousToken(document, range) != "\\") {
291
 
        const QString next = nextToken(document, range);
292
 
        // Eat it if already there
293
 
        if (next == text) {
294
 
            KTextEditor::Cursor saved = range.end();
295
 
            document->removeText(range);
296
 
            document->activeView()->setCursorPosition(saved);
297
 
        }
298
 
        // Quotation marks only inserted if followed by one of the allowed
299
 
        // next tokens and the number of marks in the insertion line is even
300
 
        // (excluding the already inserted mark)
301
 
        else if (allowedNextToken.contains(next)
302
 
            && (document->line(range.start().line()).count(text) % 2) ) {
303
 
            insertAutoBracket(document, range, text);
304
 
        }
305
 
    }
306
 
}
307
 
 
308
 
/**
309
 
 * Automatically inserts closing bracket. Cursor
310
 
 * is placed in between the brackets.
311
 
 * @param document Current document.
312
 
 * @param range Inserted text range (by text-inserted slot)
313
 
 * @param brace Brace to insert
314
 
 */
315
 
void AutoBracePluginDocument::insertAutoBracket(KTextEditor::Document *document,
316
 
                                                const KTextEditor::Range& range,
317
 
                                                const QString& brace) {
318
 
    // Disconnect Slots to avoid check for redundant closing brackets
319
 
    disconnectSlots(document);
320
 
 
321
 
    // Save range to allow following remove operation to
322
 
    // detect the corresponding closing bracket
323
 
    m_lastRange = range;
324
 
 
325
 
    KTextEditor::Cursor saved = range.end();
326
 
    // Depending on brace, insert corresponding closing brace.
327
 
    document->insertText(range.end(), brace);
328
 
    document->activeView()->setCursorPosition(saved);
329
 
 
330
 
    // Re-Enable insertion slot.
331
 
    connectSlots(document);
332
 
}
333
 
 
334
 
/**
335
 
 * Returns next character after specified text range in document.
336
 
 * @param document Current document.
337
 
 * @param range Inserted text range (by text-inserted slot)
338
 
 * @return Next character after text range
339
 
 */
340
 
const QString AutoBracePluginDocument::nextToken(KTextEditor::Document* document, const KTextEditor::Range& range)
341
 
{
342
 
    // Calculate range after insertion (exactly one character)
343
 
    KTextEditor::Range afterRange(range.end(), range.end().line(), range.end().column()+1);
344
 
 
345
 
    return (afterRange.isValid() ? document->text(afterRange) : "");
346
 
}
347
 
 
348
 
/**
349
 
 * Returns previous character before specified text range in document.
350
 
 * @param document Current document.
351
 
 * @param range Inserted text range (by text-inserted slot)
352
 
 * @return Next character after text range
353
 
 */
354
 
const QString AutoBracePluginDocument::previousToken(KTextEditor::Document* document, const KTextEditor::Range& range)
355
 
{
356
 
    // Calculate range before insertion (exactly one character)
357
 
    KTextEditor::Range beforeRange(range.start().line(), range.start().column()-1, range.start().line(),
358
 
                                   range.start().column());
359
 
 
360
 
    return (beforeRange.isValid() ? document->text(beforeRange) : "");
361
 
}
362
 
 
363
 
bool AutoBracePluginDocument::isInsertionCandidate(KTextEditor::Document *document, int openingBraceLine) {
364
 
    QString line = document->line(openingBraceLine);
365
 
    if (line.isEmpty() || !line.endsWith('{')) {
366
 
        return false;
367
 
    }
368
 
 
369
 
    // Get the indentation prefix.
370
 
    QRegExp rx("^(\\s+)");
371
 
    QString indentation = (rx.indexIn(line) == -1) ? "" : rx.cap(1);
372
 
 
373
 
    // Determine whether to insert a brace or not, depending on the indentation
374
 
    // of the upcoming (non-empty) line.
375
 
    bool isCandidate = true;
376
 
    QString indentationLength = QString::number(indentation.length());
377
 
    QString indentationLengthMinusOne = QString::number(indentation.length() - 1);
378
 
 
379
 
    ///TODO: make configurable
380
 
    // these tokens must not start a line that is used to get the correct indendation width
381
 
    QStringList forbiddenTokenList;
382
 
    if ( line.contains("class") || line.contains("interface") || line.contains("struct") ) {
383
 
        forbiddenTokenList  << "private" << "public" << "protected";
384
 
        if ( document->mode() == "C++" ) {
385
 
            forbiddenTokenList  << "signals" << "Q_SIGNALS";
386
 
        } else {
387
 
            // PHP and potentially others
388
 
            forbiddenTokenList  << "function";
389
 
        }
390
 
    }
391
 
    if ( (document->mode() == "C++" || document->mode() == "C") && line.contains("namespace", Qt::CaseInsensitive) ) {
392
 
        // C++ specific
393
 
        forbiddenTokenList  << "class" << "struct";
394
 
    }
395
 
    const QString forbiddenTokens = forbiddenTokenList.isEmpty() ? QLatin1String("") : QString(QLatin1String("(?!") + forbiddenTokenList.join(QLatin1String("|")) + QLatin1Char(')'));
396
 
 
397
 
    for (int i = openingBraceLine + 1; i < document->lines(); ++i)
398
 
    {
399
 
        line = document->line(i);
400
 
        if (line.trimmed().isEmpty()) {
401
 
            continue; // Empty lines are not a reliable source of information.
402
 
        }
403
 
 
404
 
        if (indentation.length() == 0) {
405
 
            // Inserting a brace is ok if there is a line (not starting with a
406
 
            // brace) without indentation.
407
 
            rx.setPattern("^(?=[^\\}\\s])"
408
 
                // But it's not OK if the line starts with one of our forbidden tokens.
409
 
                + forbiddenTokens
410
 
            );
411
 
        }
412
 
        else {
413
 
            rx.setPattern("^(?:"
414
 
                // Inserting a brace is ok if there is a closing brace with
415
 
                // less indentation than the opener line.
416
 
                "[\\s]{0," + indentationLengthMinusOne + "}\\}"
417
 
                "|"
418
 
                // Inserting a brace is ok if there is a line (not starting with a
419
 
                // brace) with less or similar indentation as the original line.
420
 
                "[\\s]{0," + indentationLength + "}(?=[^\\}\\s])"
421
 
                // But it's not OK if the line starts with one of our forbidden tokens.
422
 
                + forbiddenTokens +
423
 
                ")"
424
 
            );
425
 
        }
426
 
 
427
 
        if (rx.indexIn(line) == -1) {
428
 
            // There is already a brace, or the line is indented more than the
429
 
            // opener line (which means we expect a brace somewhere further down),
430
 
            // or we found a forbidden token.
431
 
            // So don't insert the brace, and just indent the line.
432
 
            isCandidate = false;
433
 
        }
434
 
        // Quit the loop - a non-empty line always leads to a definitive decision.
435
 
        break;
436
 
    }
437
 
 
438
 
    if (isCandidate) {
439
 
        m_indentation = indentation;
440
 
        // in C++ automatically add a semicolon after the closing brace when we create a new class/struct
441
 
        if ( (document->mode() == "C++" || document->mode() == "C")
442
 
                && document->line(openingBraceLine).indexOf(QRegExp("(?:class|struct|enum)\\s+[^\\s]+(\\s*[:,](\\s*((public|protected|private)\\s+)?[^\\s]+))*\\s*\\{\\s*$")) != -1 )
443
 
        {
444
 
            m_withSemicolon = true;
445
 
        } else {
446
 
            m_withSemicolon = false;
447
 
        }
448
 
    }
449
 
    return isCandidate;
450
 
}
451
 
 
452
 
#include "autobrace.moc"