2
* This file is part of the KDE libraries
3
* Copyright (C) 2008 Jakob Petsovits <jpetso@gmx.at>
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.
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.
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.
20
#include "autobrace.h"
21
#include "autobrace_config.h"
23
#include <kpluginfactory.h>
24
#include <kpluginloader.h>
26
#include <ktexteditor/configinterface.h>
27
#include <kmessagebox.h>
28
#include <klocalizedstring.h>
30
#include <kconfiggroup.h>
32
AutoBracePlugin *AutoBracePlugin::plugin = 0;
34
K_PLUGIN_FACTORY_DEFINITION(AutoBracePluginFactory,
35
registerPlugin<AutoBracePlugin>("ktexteditor_autobrace");
36
registerPlugin<AutoBraceConfig>("ktexteditor_autobrace_config");
38
K_EXPORT_PLUGIN(AutoBracePluginFactory("ktexteditor_autobrace", "ktexteditor_plugins"))
40
AutoBracePlugin::AutoBracePlugin(QObject *parent, const QVariantList &args)
41
: KTextEditor::Plugin(parent), m_autoBrackets(true), m_autoQuotations(true)
49
AutoBracePlugin::~AutoBracePlugin()
54
void AutoBracePlugin::addView(KTextEditor::View *view)
56
AutoBracePluginDocument *docplugin;
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());
64
// Create Editor plugin and assign options through reference
65
docplugin = new AutoBracePluginDocument(view->document(),
68
m_docplugins.insert(view->document(), docplugin);
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());
75
void AutoBracePlugin::removeView(KTextEditor::View *view)
77
if (m_documents.contains(view))
79
KTextEditor::Document *document = m_documents.value(view);
80
m_documents.remove(view);
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);
91
void AutoBracePlugin::readConfig()
93
KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin");
94
m_autoBrackets = cg.readEntry("autobrackets", true);
95
m_autoQuotations = cg.readEntry("autoquotations", false);
98
void AutoBracePlugin::writeConfig()
100
KConfigGroup cg(KGlobal::config(), "AutoBrace Plugin");
101
cg.writeEntry("autobrackets", m_autoBrackets);
102
cg.writeEntry("autoquotations", m_autoQuotations);
105
/// AutoBracePluginDocument
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)
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*)));
116
connectSlots(document);
119
AutoBracePluginDocument::~AutoBracePluginDocument()
121
disconnect(parent() /* == document */, 0, this, 0);
125
* (Re-)setups slots for AutoBracePluginDocument.
126
* @param document Current document.
128
void AutoBracePluginDocument::connectSlots(KTextEditor::Document *document)
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)));
136
void AutoBracePluginDocument::disconnectSlots(KTextEditor::Document* document)
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*)));
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.
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);
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())
161
KTextEditor::View *view = document->activeView();
162
document->startEditing();
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(
170
m_insertionLine, document->lineLength(m_insertionLine)
172
document->replaceText(lineRange, m_indentation);
174
connect(this, SIGNAL(indent()), view, SLOT(indent()));
176
disconnect(this, SIGNAL(indent()), view, SLOT(indent()));
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 ? ";" : ""));
182
document->endEditing();
183
view->setCursorPosition(document->endOfLine(m_insertionLine));
187
// Re-enable the textInserted() slot again.
188
connectSlots(document);
192
* Connected to KTextEditor::Documet::textRemoved. Allows to delete
193
* an automatically inserted closing bracket if the opening counterpart
196
void AutoBracePluginDocument::slotTextRemoved(KTextEditor::Document* document, const KTextEditor::Range& range)
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);
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);
211
connectSlots(document);
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.
222
void AutoBracePluginDocument::slotTextInserted(KTextEditor::Document *document,
223
const KTextEditor::Range& range)
225
// Fill brackets map matching opening and closing brackets.
226
QMap<QString,QString> brackets;
230
// latex wants {, too
231
if (document->mode() == "LaTeX")
234
// List of Tokens after which an automatic bracket expanion
236
const static QStringList allowedNextToken = QStringList() << "]" << ")" << ","
237
<< "." << ";" << "\n" << "\t" << " " << "";
238
const QString text = document->text(range);
240
// An insertion operation cancels any last range removal
242
m_lastRange = KTextEditor::Range::invalid();
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 '
249
// Handle brace openers
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*)));
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]);
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);
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
294
KTextEditor::Cursor saved = range.end();
295
document->removeText(range);
296
document->activeView()->setCursorPosition(saved);
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);
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
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);
321
// Save range to allow following remove operation to
322
// detect the corresponding closing bracket
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);
330
// Re-Enable insertion slot.
331
connectSlots(document);
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
340
const QString AutoBracePluginDocument::nextToken(KTextEditor::Document* document, const KTextEditor::Range& range)
342
// Calculate range after insertion (exactly one character)
343
KTextEditor::Range afterRange(range.end(), range.end().line(), range.end().column()+1);
345
return (afterRange.isValid() ? document->text(afterRange) : "");
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
354
const QString AutoBracePluginDocument::previousToken(KTextEditor::Document* document, const KTextEditor::Range& range)
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());
360
return (beforeRange.isValid() ? document->text(beforeRange) : "");
363
bool AutoBracePluginDocument::isInsertionCandidate(KTextEditor::Document *document, int openingBraceLine) {
364
QString line = document->line(openingBraceLine);
365
if (line.isEmpty() || !line.endsWith('{')) {
369
// Get the indentation prefix.
370
QRegExp rx("^(\\s+)");
371
QString indentation = (rx.indexIn(line) == -1) ? "" : rx.cap(1);
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);
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";
387
// PHP and potentially others
388
forbiddenTokenList << "function";
391
if ( (document->mode() == "C++" || document->mode() == "C") && line.contains("namespace", Qt::CaseInsensitive) ) {
393
forbiddenTokenList << "class" << "struct";
395
const QString forbiddenTokens = forbiddenTokenList.isEmpty() ? QLatin1String("") : QString(QLatin1String("(?!") + forbiddenTokenList.join(QLatin1String("|")) + QLatin1Char(')'));
397
for (int i = openingBraceLine + 1; i < document->lines(); ++i)
399
line = document->line(i);
400
if (line.trimmed().isEmpty()) {
401
continue; // Empty lines are not a reliable source of information.
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.
414
// Inserting a brace is ok if there is a closing brace with
415
// less indentation than the opener line.
416
"[\\s]{0," + indentationLengthMinusOne + "}\\}"
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.
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.
434
// Quit the loop - a non-empty line always leads to a definitive decision.
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 )
444
m_withSemicolon = true;
446
m_withSemicolon = false;
452
#include "autobrace.moc"