~ubuntu-branches/ubuntu/saucy/kate/saucy

« back to all changes in this revision

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

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell, Rohan Garg, Jonathan Riddell
  • Date: 2013-06-21 00:48:29 UTC
  • mfrom: (1.1.28)
  • Revision ID: package-import@ubuntu.com-20130621004829-y2ui02eg0j47h94y
Tags: 4:4.10.80-0ubuntu1
[ Rohan Garg ]
* New upstream release
  - Update and sort install files
  - Drop kubuntu_pate_find_python.diff, kubuntu_kate_initial_preference.patch,
    kubuntu_find_python.diff from debian/patches , not required

[ Jonathan Riddell ]
* New upstream release

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"