diff --git a/autotests/input/indent/cstyle/doxygen11/expected b/autotests/input/indent/cstyle/doxygen11/expected new file mode 100644 index 00000000..e003f6ec --- /dev/null +++ b/autotests/input/indent/cstyle/doxygen11/expected @@ -0,0 +1,5 @@ +/** + * foo + * bar + */ +int foo = 1; diff --git a/autotests/input/indent/cstyle/doxygen11/input.js b/autotests/input/indent/cstyle/doxygen11/input.js new file mode 100644 index 00000000..497c8f88 --- /dev/null +++ b/autotests/input/indent/cstyle/doxygen11/input.js @@ -0,0 +1,7 @@ +v.setCursorPosition(0,0); +v.selectAll(); + +var r = v.selection(); +if (r.isValid()) { + v.align(r); +} diff --git a/autotests/input/indent/cstyle/doxygen11/origin b/autotests/input/indent/cstyle/doxygen11/origin new file mode 100644 index 00000000..66e3b434 --- /dev/null +++ b/autotests/input/indent/cstyle/doxygen11/origin @@ -0,0 +1,5 @@ +/** +* foo +* bar +*/ +int foo = 1; diff --git a/autotests/src/templatehandler_test.cpp b/autotests/src/templatehandler_test.cpp index 72727ea9..e03b1ce8 100644 --- a/autotests/src/templatehandler_test.cpp +++ b/autotests/src/templatehandler_test.cpp @@ -1,372 +1,397 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templatehandler_test.h" #include #include #include #include #include #include #include #include QTEST_MAIN(TemplateHandlerTest) using namespace KTextEditor; TemplateHandlerTest::TemplateHandlerTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void TemplateHandlerTest::testUndo() { const QString snippet = "for (${type=\"int\"} ${index=\"i\"} = ; ${index} < ; ++${index})\n" "{\n" " ${index}\n" "}"; auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); // fixed indentation options doc->config()->setTabWidth(8); doc->config()->setIndentationWidth(4); doc->config()->setReplaceTabsDyn(true); view->insertTemplate(KTextEditor::Cursor(0, 0), snippet); const QString result = "for (int i = ; i < ; ++i)\n" "{\n" " i\n" "}"; QCOMPARE(doc->text(), result); doc->replaceText(Range(0, 9, 0, 10), "j"); const QString result2 = "for (int j = ; j < ; ++j)\n" "{\n" " j\n" "}"; QCOMPARE(doc->text(), result2); doc->undo(); QCOMPARE(doc->text(), result); doc->redo(); QCOMPARE(doc->text(), result2); doc->insertText(Cursor(0, 10), "j"); doc->insertText(Cursor(0, 11), "j"); const QString result3 = "for (int jjj = ; jjj < ; ++jjj)\n" "{\n" " jjj\n" "}"; QCOMPARE(doc->text(), result3); doc->undo(); QCOMPARE(doc->text(), result); doc->redo(); QCOMPARE(doc->text(), result3); doc->undo(); QCOMPARE(doc->text(), result); doc->undo(); QCOMPARE(doc->text(), QString()); } void TemplateHandlerTest::testEscapes() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, QStringLiteral("\\${field} ${bar} \\${foo=3} \\\\${baz=7}")); QCOMPARE(doc->text(), QStringLiteral("${field} bar ${foo=3} \\${baz=7}")); } void TemplateHandlerTest::testSimpleMirror() { QFETCH(QString, text); auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, text); QCOMPARE(doc->text(), QString(text).replace("${foo}", "foo")); doc->insertText({0, 0}, "xx"); QCOMPARE(doc->text(), QString(text).replace("${foo}", "xxfoo")); doc->removeText(KTextEditor::Range({0, 0}, {0, 2})); QCOMPARE(doc->text(), QString(text).replace("${foo}", "foo")); delete doc; } void TemplateHandlerTest::testSimpleMirror_data() { QTest::addColumn("text"); QTest::newRow("one") << "${foo}"; QTest::newRow("several") << "${foo} ${foo} Foo ${foo}"; } +void TemplateHandlerTest::testAlignC() +{ + QFETCH(QString, input); + QFETCH(QString, expected); + + auto doc = new KTextEditor::DocumentPrivate(); + doc->setHighlightingMode("C"); + auto view = static_cast(doc->createView(nullptr)); + view->insertTemplate({0, 0}, input); + + QCOMPARE(doc->text(), expected); + + delete doc; +} + +void TemplateHandlerTest::testAlignC_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("one") << "/* ${foo} */" << "/* foo */"; + QTest::newRow("simple") << "/**\n* ${foo}\n*/" << "/**\n * foo\n */"; + QTest::newRow("complex") << "/**\n* @brief: ${...}\n* \n*/" << "/**\n * @brief: ...\n * \n */"; +} + void TemplateHandlerTest::testAdjacentRanges() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo} ${foo}")); QCOMPARE(doc->text(), S("foo foo")); doc->removeText(KTextEditor::Range({0, 3}, {0, 4})); QCOMPARE(doc->text(), S("foofoo")); doc->insertText({0, 1}, S("x")); QCOMPARE(doc->text(), S("fxoofxoo")); doc->insertText({0, 4}, S("y")); QCOMPARE(doc->text(), S("fxooyfxooy")); doc->removeText(KTextEditor::Range({0, 4}, {0, 5})); QCOMPARE(doc->text(), S("fxoofxoo")); delete doc; } void TemplateHandlerTest::testTab() { QFETCH(QString, tpl); QFETCH(int, cursor); auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, tpl); view->setCursorPosition({0, cursor}); // no idea why the event needs to be posted to the focus proxy QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTEST(view->cursorPosition().column(), "expected_cursor"); QTest::keyClick(view->focusProxy(), Qt::Key_Tab, Qt::ShiftModifier); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTEST(view->cursorPosition().column(), "expected_cursor"); delete doc; } void TemplateHandlerTest::testTab_data() { QTest::addColumn("tpl"); QTest::addColumn("cursor"); QTest::addColumn("expected_cursor"); QTest::newRow("simple_start") << "${foo} ${bar}" << 0 << 4; QTest::newRow("simple_mid") << "${foo} ${bar}" << 2 << 4; QTest::newRow("simple_end") << "${foo} ${bar}" << 3 << 4; QTest::newRow("wrap_start") << "${foo} ${bar}" << 4 << 0; QTest::newRow("wrap_mid") << "${foo} ${bar}" << 5 << 0; QTest::newRow("wrap_end") << "${foo} ${bar}" << 6 << 0; QTest::newRow("non_editable_start") << "${foo} ${foo}" << 0 << 0; QTest::newRow("non_editable_mid") << "${foo} ${foo}" << 2 << 0; QTest::newRow("non_editable_end") << "${foo} ${foo}" << 3 << 0; QTest::newRow("skip_non_editable") << "${foo} ${foo} ${bar}" << 0 << 8; QTest::newRow("skip_non_editable_at_end") << "${foo} ${bar} ${foo}" << 4 << 0; QTest::newRow("jump_to_cursor") << "${foo} ${cursor}" << 0 << 4; QTest::newRow("jump_to_cursor_last") << "${foo} ${cursor} ${bar}" << 0 << 5; QTest::newRow("jump_to_cursor_last2") << "${foo} ${cursor} ${bar}" << 5 << 4; } void TemplateHandlerTest::testExitAtCursor() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, QStringLiteral("${foo} ${bar} ${cursor} ${foo}")); view->setCursorPosition({0, 0}); // check it jumps to the cursor QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->cursorPosition().column(), 4); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->cursorPosition().column(), 8); // insert an a at cursor position QTest::keyClick(view->focusProxy(), Qt::Key_A); // check it was inserted QCOMPARE(doc->text(), QStringLiteral("foo bar a foo")); // required to process the deleteLater() used to exit the template handler QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QApplication::processEvents(); // go to the first field and verify it's not mirrored any more (i.e. the handler exited) view->setCursorPosition({0, 0}); QTest::keyClick(view->focusProxy(), Qt::Key_A); QCOMPARE(doc->text(), QStringLiteral("afoo bar a foo")); delete doc; } void TemplateHandlerTest::testDefaultMirror() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo=uppercase(\"hi\")} ${bar=3} ${foo}"), S("function uppercase(x) { return x.toUpperCase(); }")); QCOMPARE(doc->text(), S("HI 3 HI")); doc->insertText({0, 0}, "xy@"); QCOMPARE(doc->text(), S("xy@HI 3 xy@HI")); delete doc; } void TemplateHandlerTest::testFunctionMirror() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo} hi ${uppercase(foo)}"), S("function uppercase(x) { return x.toUpperCase(); }")); QCOMPARE(doc->text(), S("foo hi FOO")); doc->insertText({0, 0}, "xy@"); QCOMPARE(doc->text(), S("xy@foo hi XY@FOO")); delete doc; } void TemplateHandlerTest::testAutoSelection() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, "${foo} ${bar} ${bar} ${cursor} ${baz}"); QCOMPARE(doc->text(), QStringLiteral("foo bar bar baz")); QCOMPARE(view->selectionText(), QStringLiteral("foo")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("bar")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("baz")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QVERIFY(view->selectionRange().isEmpty()); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("foo")); QTest::keyClick(view->focusProxy(), Qt::Key_A); QCOMPARE(doc->text(), QStringLiteral("a bar bar baz")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QVERIFY(view->selectionRange().isEmpty()); } void TemplateHandlerTest::testNotEditableFields() { QFETCH(QString, input); QFETCH(int, change_offset); auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, input); doc->insertText({0, change_offset}, "xxx"); QTEST(doc->text(), "expected"); } void TemplateHandlerTest::testNotEditableFields_data() { QTest::addColumn("input"); QTest::addColumn("change_offset"); QTest::addColumn("expected"); using S = QString; QTest::newRow("mirror") << S("${foo} ${foo}") << 6 << "foo foxxxo"; } void TemplateHandlerTest::testCanRetrieveSelection() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); view->insertText("hi world"); view->setSelection(KTextEditor::Range(0, 1, 0, 4)); view->insertTemplate({0, 1}, QStringLiteral("xx${foo=sel()}xx"), QStringLiteral("function sel() { return view.selectedText(); }") ); QCOMPARE(doc->text(), QStringLiteral("hxxi wxxorld")); } void TemplateHandlerTest::testDefaults_data() { QTest::addColumn("input"); QTest::addColumn("expected"); QTest::addColumn("function"); using S = QString; QTest::newRow("empty") << S() << S() << S(); QTest::newRow("foo") << S("${foo}") << S("foo") << S(); QTest::newRow("foo=3") << S("${foo=3}") << S("3") << S(); QTest::newRow("${foo=3+5}") << S("${foo=3+5}") << S("8") << S(); QTest::newRow("string") << S("${foo=\"3+5\"}") << S("3+5") << S(); QTest::newRow("string_mirror") << S("${foo=\"Bar\"} ${foo}") << S("Bar Bar") << S(); QTest::newRow("func_simple") << S("${foo=myfunc()}") << S("hi") << S("function myfunc() { return 'hi'; }"); QTest::newRow("func_fixed") << S("${myfunc()}") << S("hi") << S("function myfunc() { return 'hi'; }"); QTest::newRow("func_constant_arg") << S("${foo=uppercase(\"Foo\")}") << S("FOO") << S("function uppercase(x) { return x.toUpperCase(); }"); QTest::newRow("func_constant_arg_mirror") << S("${foo=uppercase(\"hi\")} ${bar=3} ${foo}") << S("HI 3 HI") << S("function uppercase(x) { return x.toUpperCase(); }"); QTest::newRow("cursor") << S("${foo} ${cursor}") << S("foo ") << S(); QTest::newRow("only_cursor") << S("${cursor}") << S("") << S(); QTest::newRow("only_cursor_stuff") << S("fdas ${cursor} asdf") << S("fdas asdf") << S(); } void TemplateHandlerTest::testDefaults() { auto doc = new KTextEditor::DocumentPrivate(); auto view = static_cast(doc->createView(nullptr)); QFETCH(QString, input); QFETCH(QString, function); view->insertTemplate(KTextEditor::Cursor(0, 0), input, function); QTEST(doc->text(), "expected"); view->selectAll(); view->keyDelete(); QCOMPARE(doc->text(), QString()); delete doc; } #include "moc_templatehandler_test.cpp" diff --git a/autotests/src/templatehandler_test.h b/autotests/src/templatehandler_test.h index d396629a..9787a789 100644 --- a/autotests/src/templatehandler_test.h +++ b/autotests/src/templatehandler_test.h @@ -1,63 +1,66 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow Copyright (C) 2014 Sven Brauch This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KATE_UNDOMANAGER_TEST_H #define KATE_UNDOMANAGER_TEST_H #include class TemplateHandlerTest : public QObject { Q_OBJECT public: TemplateHandlerTest(); private Q_SLOTS: void testUndo(); void testSimpleMirror(); void testSimpleMirror_data(); + void testAlignC(); + void testAlignC_data(); + void testDefaults(); void testDefaults_data(); void testDefaultMirror(); void testFunctionMirror(); void testNotEditableFields(); void testNotEditableFields_data(); void testAdjacentRanges(); void testTab(); void testTab_data(); void testExitAtCursor(); void testAutoSelection(); void testCanRetrieveSelection(); void testEscapes(); }; #endif diff --git a/src/script/data/indentation/cstyle.js b/src/script/data/indentation/cstyle.js index 500037f5..eb0982b7 100644 --- a/src/script/data/indentation/cstyle.js +++ b/src/script/data/indentation/cstyle.js @@ -1,849 +1,845 @@ var katescript = { "name": "C Style", "author": "Dominik Haumann , Milian Wolff ", "license": "LGPL", "revision": 6, "kate-version": "5.1" }; // kate-script-header, must be at the start of the file without comments, pure json /** * This file is part of the Kate Project. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // required katepart js libraries require ("range.js"); require ("string.js"); require ("utils.js"); //BEGIN USER CONFIGURATION var cfgIndentCase = true; // indent 'case' and 'default' in a switch? var cfgIndentNamespace = true; // indent after 'namespace'? var cfgAutoInsertStar = true; // auto insert '*' in C-comments var cfgSnapSlash = true; // snap '/' to '*/' in C-comments var cfgAutoInsertSlashes = false; // auto insert '//' after C++-comments var cfgAccessModifiers = 0; // indent level of access modifiers, relative to the class indent level // set to -1 to disable auto-indendation after access modifiers. //END USER CONFIGURATION // indent gets three arguments: line, indentwidth in spaces, typed character // indent // specifies the characters which should trigger indent, beside the default '\n' triggerCharacters = "{})/:;#"; var debugMode = false; function dbg() { if (debugMode) { debug.apply(this, arguments); } } //BEGIN global variables and functions // maximum number of lines we look backwards/forward to find out the indentation // level (the bigger the number, the longer might be the delay) var gLineDelimiter = 50; // number var gIndentWidth = 4; var gMode = "C"; //END global variables and functions /** * Search for a corresponding '{' and return its indentation level. If not found * return null; (line/column) are the start of the search. */ function findLeftBrace(line, column) { var cursor = document.anchor(line, column, '{'); if (cursor.isValid()) { var parenthesisCursor = tryParenthesisBeforeBrace(cursor.line, cursor.column); if (parenthesisCursor.isValid()) cursor = parenthesisCursor; dbg("findLeftBrace: success in line " + cursor.line); return document.firstVirtualColumn(cursor.line); } return -1; } /** * Find last non-empty line that is not inside a comment or preprocessor */ function lastNonEmptyLine(line) { while (true) { line = document.prevNonEmptyLine(line); if ( line == -1 ) { return -1; } var string = document.line(line).ltrim(); ///TODO: cpp multiline comments ///TODO: multiline macros if ( string.startsWith("//") || string.startsWith('#') ) { --line; continue; } break; } return line; } /** * Character at (line, column) has to be a '{'. * Now try to find the right line for indentation for constructs like: * if (a == b * && c == d) { <- check for ')', and find '(', then return its indentation * Returns null, if no success, otherwise an object {line, column} */ function tryParenthesisBeforeBrace(line, column) { var firstColumn = document.firstColumn(line); while (column > firstColumn && document.isSpace(line, --column)); if (document.charAt(line, column) == ')') return document.anchor(line, column, '('); return Cursor.invalid(); } /** * Check for default and case keywords and assume we are in a switch statement. * Try to find a previous default, case or switch and return its indentation or * -1 if not found. */ function trySwitchStatement(line) { var currentString = document.line(line); if (currentString.search(/^\s*(default\s*|case\b.*):/) == -1) return -1; var indentation = -1; var lineDelimiter = gLineDelimiter; var currentLine = line; while (currentLine > 0 && lineDelimiter > 0) { --currentLine; --lineDelimiter; if (document.firstColumn(currentLine) == -1) continue; currentString = document.line(currentLine); if (currentString.search(/^\s*(default\s*|case\b.*):/) != -1) { indentation = document.firstVirtualColumn(currentLine); break; } else if (currentString.search(/^\s*switch\b/) != -1) { indentation = document.firstVirtualColumn(currentLine); if (cfgIndentCase) indentation += gIndentWidth; break; } } if (indentation != -1) dbg("trySwitchStatement: success in line " + currentLine); return indentation; } /** * Check for private, protected, public, signals etc... and assume we are in a * class definition. Try to find a previous private/protected/private... or * class and return its indentation or null if not found. */ function tryAccessModifiers(line) { if (cfgAccessModifiers < 0) return -1; var currentString = document.line(line); if (currentString.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) == -1) return -1; var cursor = document.anchor(line, 0, '{'); if (!cursor.isValid()) return -1; var indentation = document.firstVirtualColumn(cursor.line); for ( var i = 0; i < cfgAccessModifiers; ++i ) { indentation += gIndentWidth; } if (indentation != -1) dbg("tryAccessModifiers: success in line " + cursor.line); return indentation; } /** * Get the position of the first non-space character * return: position or -1, if there is no non-space character */ function firstNonSpace(text) { if (text && text.search(/^(\s*)\S/) != -1) return RegExp.$1.length; return -1; } /** * Get the position of the last non-space character * return: position or -1, if there is no non-space character */ function lastNonSpace(text) { if (text && text.search(/(.*)\S\s*$/) != -1) return RegExp.$1.length; return -1; } // /** // * Check, whether the beginning of line is inside a "..." string context. // * Note: the function does not check for comments // * return: leading whitespaces as string, or null if not in a string // */ // function inString(line) // { // var currentLine = line; // var currentString; // // // go line up as long as the previous line ends with an escape character '\' // while (currentLine >= 0) { // currentString = document.line(currentLine - 1); // if (currentString.charAt(document.lastColumn(currentLine - 1)) != '\\') // break; // --currentLine; // } // // // iterate through all lines and toggle bool insideString for every quote // var insideString = false; // var indentation = null; // while (currentLine < line) { // currentString = document.line(currentLine); // var char1; // var i; // var lineLength = document.lineLength(currentLine); // for (i = 0; i < lineLength; ++i) { // char1 = currentString.charAt(i); // if (char1 == "\\") { // // skip escaped character // ++i; // } else if (char1 == "\"") { // insideString = !insideString; // if (insideString) // indentation = currentString.substring(0, document.firstColumn(currentLine) + 1); // } // } // ++currentLine; // } // // return insideString ? indentation : null; // } /** * C comment checking. If the previous line begins with a "/*" or a "* ", then * return its leading white spaces + ' *' + the white spaces after the * * return: filler string or null, if not in a C comment */ function tryCComment(line) { var currentLine = document.prevNonEmptyLine(line - 1); if (currentLine < 0) return -1; var indentation = -1; // we found a */, search the opening /* and return its indentation level if (document.endsWith(currentLine, "*/", true)) { var cursor = document.rfind(currentLine, document.lastColumn(currentLine), "/*"); if (cursor.isValid() && cursor.column == document.firstColumn(cursor.line)) indentation = document.firstVirtualColumn(cursor.line); if (indentation != -1) dbg("tryCComment: success (1) in line " + cursor.line); return indentation; } // inbetween was an empty line, so do not copy the "*" character if (currentLine != line - 1) return -1; var firstPos = document.firstColumn(currentLine); var lastPos = document.lastColumn(currentLine); var char1 = document.charAt(currentLine, firstPos); var char2 = document.charAt(currentLine, firstPos + 1); var currentString = document.line(currentLine); - // not in comment, no * copying - if (!isComment(currentLine, firstPos)) - return -1; - if (char1 == '/' && char2 == '*' && !currentString.contains("*/")) { indentation = document.firstVirtualColumn(currentLine); if (cfgAutoInsertStar) { // only add '*', if there is none yet. indentation += 1; if (document.firstChar(line) != '*') document.insertText(line, view.cursorPosition().column, '*'); if (!document.isSpace(line, document.firstColumn(line) + 1) && !document.endsWith(line, "*/", true)) document.insertText(line, document.firstColumn(line) + 1, ' '); } } else if (char1 == '*') { var commentLine = currentLine; while (commentLine >= 0 && document.firstChar(commentLine) == '*' && !document.endsWith(commentLine, "*/", true) ) { --commentLine; } if (commentLine < 0) { indentation = document.firstVirtualColumn(currentLine); - } else if (document.startsWith(commentLine, "/*", true)) { + } else if (document.startsWith(commentLine, "/*", true) && !document.endsWith(commentLine, "*/", true)) { // found a /*, and all succeeding lines start with a *, so it's a comment block indentation = document.firstVirtualColumn(currentLine); // only add '*', if there is none yet. if (cfgAutoInsertStar && document.firstChar(line) != '*') { document.insertText(line, view.cursorPosition().column, '*'); if (!document.isSpace(line, document.firstColumn(line) + 1)) document.insertText(line, document.firstColumn(line) + 1, ' '); } } } if (indentation != -1) dbg("tryCComment: success (2) in line " + currentLine); return indentation; } /** * C++ comment checking. when we want to insert slashes: * //, ///, //! ///<, //!< and ////... * return: filler string or null, if not in a star comment * NOTE: otherwise comments get skipped generally and we use the last code-line */ function tryCppComment(line) { var currentLine = line - 1; if (currentLine < 0 || !cfgAutoInsertSlashes) return -1; var indentation = -1; var comment = document.startsWith(currentLine, "//", true); // allowed are: //, ///, //! ///<, //!< and ////... if (comment) { var firstPos = document.firstColumn(currentLine); var currentString = document.line(currentLine); var char3 = currentString.charAt(firstPos + 2); var char4 = currentString.charAt(firstPos + 3); indentation = document.firstVirtualColumn(currentLine); if (cfgAutoInsertSlashes) { if (char3 == '/' && char4 == '/') { // match ////... and replace by only two: // currentString.search(/^\s*(\/\/)/); } else if (char3 == '/' || char3 == '!') { // match ///, //!, ///< and //! currentString.search(/^\s*(\/\/[\/!][<]?\s*)/); } else { // only //, nothing else currentString.search(/^\s*(\/\/\s*)/); } document.insertText(line, view.cursorPosition().column, RegExp.$1); } } if (indentation != -1) dbg("tryCppComment: success in line " + currentLine); return indentation; } /** * If the last non-empty line ends with a {, take its indentation level (or * maybe the one found by tryParenthesisBeforeBrace()) and return it increased * by 1 indetation level (special case: namespaces indentation depends on * cfgIndentNamespace). If not found, return null. */ function tryBrace(line) { function isNamespace(line, column) { if (document.firstColumn(line) == column && line > 0) --line; var currentString = document.line(line); return (currentString.search(/^\s*namespace\b/) != -1); } var currentLine = lastNonEmptyLine(line - 1); if (currentLine < 0) return -1; var lastPos = document.lastColumn(currentLine); var indentation = -1; var currentString = document.line(currentLine); var matchColumn = currentString.search(/\{[^\}]*$/); if (matchColumn != -1 && document.isCode(currentLine, matchColumn)) { dbg("tryBrace: Closing bracket in line " + currentLine); var cursor = tryParenthesisBeforeBrace(currentLine, lastPos); if (cursor.isValid()) { indentation = document.firstVirtualColumn(cursor.line) + gIndentWidth; } else { indentation = document.firstVirtualColumn(currentLine); if (cfgIndentNamespace || !isNamespace(currentLine, lastPos)) // take its indentation and add one indentation level indentation += gIndentWidth; } } if (indentation != -1) dbg("tryBrace: success in line " + currentLine); return indentation; } /** * Check for if, else, while, do, switch, private, public, protected, signals, * default, case etc... keywords, as we want to indent then. If is * non-null/true, then indentation is not increased. * Note: The code is written to be called *after* tryCComment and tryCppComment! */ function tryCKeywords(line, isBrace) { var currentLine = lastNonEmptyLine(line - 1); if (currentLine < 0) return -1; // if line ends with ')', find the '(' and check this line then. var lastPos = document.lastColumn(currentLine); var cursor = Cursor.invalid(); if (document.charAt(currentLine, lastPos) == ')') cursor = document.anchor(currentLine, lastPos, '('); if (cursor.isValid()) currentLine = cursor.line; // found non-empty line var currentString = document.line(currentLine); if (currentString.search(/^\s*(if\b|for|do\b|while|switch|[}]?\s*else|((private|public|protected|case|default|signals|Q_SIGNALS).*:))/) == -1) return -1; dbg("Found first word: " + RegExp.$1); lastPos = document.lastColumn(currentLine); var lastChar = currentString.charAt(lastPos); var indentation = -1; // ignore trailing comments see: https://bugs.kde.org/show_bug.cgi?id=189339 var commentPos = currentString.indexOf("//") if (commentPos != -1) { currentString = currentString.substring(0, commentPos).rtrim(); lastChar = currentString.charAt(currentString.length - 1); } // try to ignore lines like: if (a) b; or if (a) { b; } if (lastChar != ';' && lastChar != '}') { // take its indentation and add one indentation level indentation = document.firstVirtualColumn(currentLine); if (!isBrace) indentation += gIndentWidth; } else if (lastChar == ';') { // stuff like: // for(int b; // b < 10; // --b) cursor = document.anchor(currentLine, lastPos, '('); if (cursor.isValid()) { indentation = document.toVirtualColumn(cursor.line, cursor.column + 1); } } if (indentation != -1) dbg("tryCKeywords: success in line " + currentLine); return indentation; } /** * Search for if, do, while, for, ... as we want to indent then. * Return null, if nothing useful found. * Note: The code is written to be called *after* tryCComment and tryCppComment! */ function tryCondition(line) { var currentLine = lastNonEmptyLine(line - 1); if (currentLine < 0) return -1; // found non-empty line var currentString = document.line(currentLine); var lastPos = document.lastColumn(currentLine); var lastChar = currentString.charAt(lastPos); var indentation = -1; if (lastChar == ';' && currentString.search(/^\s*(if\b|[}]?\s*else|do\b|while\b|for)/) == -1) { // idea: we had something like: // if/while/for (expression) // statement(); <-- we catch this trailing ';' // Now, look for a line that starts with if/for/while, that has one // indent level less. var currentIndentation = document.firstVirtualColumn(currentLine); if (currentIndentation == 0) return -1; var lineDelimiter = 10; // 10 limit search, hope this is a sane value while (currentLine > 0 && lineDelimiter > 0) { --currentLine; --lineDelimiter; var firstPosVirtual = document.firstVirtualColumn(currentLine); if (firstPosVirtual == -1) continue; if (firstPosVirtual < currentIndentation) { currentString = document.line(currentLine); if (currentString.search(/^\s*(if\b|[}]?\s*else|do\b|while\b|for)[^{]*$/) != -1) indentation = firstPosVirtual; break; } else if (currentLine == 0 || lineDelimiter == 0) { return indentation; } } } if (indentation != -1) dbg("tryCondition: success in line " + currentLine); return indentation; } /** * If the non-empty line ends with ); or ',', then search for '(' and return its * indentation; also try to ignore trailing comments. */ function tryStatement(line) { var currentLine = lastNonEmptyLine(line - 1); if (currentLine < 0) return -1; var indentation = -1; var currentString = document.line(currentLine); if (currentString.endsWith('(')) { // increase indent level indentation = document.firstVirtualColumn(currentLine) + gIndentWidth; dbg("tryStatement (1): success in line " + currentLine); return indentation; } var alignOnSingleQuote = gMode == "PHP/PHP" || gMode == "JavaScript"; // align on strings "..."\n => below the opening quote // multi-language support: [\.+] for javascript or php var result = /^(.*)(,|"|'|\))(;?)\s*[\.+]?\s*(\/\/.*|\/\*.*\*\/\s*)?$/.exec(currentString); if (result != null && result.index == 0) { var alignOnAnchor = result[3].length == 0 && result[2] != ')'; // search for opening ", ' or ( var cursor = Cursor.invalid(); if (result[2] == '"' || (alignOnSingleQuote && result[2] == "'")) { while(true) { var i = result[1].length - 1; // start from matched closing ' or " // find string opener for ( ; i >= 0; --i ) { // make sure it's not commented out if (currentString[i] == result[2] && (i == 0 || currentString[i - 1] != '\\')) { // also make sure that this is not a line like '#include "..."' <-- we don't want to indent here if (currentString.match(/^#include/) || currentString.match(/'use strict'/)) { return indentation; } cursor = new Cursor(currentLine, i); break; } } if (!alignOnAnchor && currentLine) { // when we finished the statement (;) we need to get the first line and use it's indentation // i.e.: $foo = "asdf"; -> align on $ --i; // skip " or ' // skip whitespaces and stuff like + or . (for PHP, JavaScript, ...) for ( ; i >= 0; --i ) { if (currentString[i] == ' ' || currentString[i] == "\t" || currentString[i] == '.' || currentString[i] == '+') { continue; } else { break; } } if ( i > 0 ) { // there's something in this line, use it's indentation break; } else { // go to previous line --currentLine; currentString = document.line(currentLine); } } else { break; } } } else if (result[2] == ',' && !currentString.match(/\(/)) { // assume a function call: check for '(' brace // - if not found, use previous indentation // - if found, compare the indentation depth of current line and open brace line // - if current indentation depth is smaller, use that // - otherwise, use the '(' indentation + following white spaces var currentIndentation = document.firstVirtualColumn(currentLine); var braceCursor = document.anchor(currentLine, result[1].length, '('); if (!braceCursor.isValid() || currentIndentation < braceCursor.column) indentation = currentIndentation; else { indentation = braceCursor.column + 1; while (document.isSpace(braceCursor.line, indentation)) ++indentation; } } else { cursor = document.anchor(currentLine, result[1].length, '('); } if (cursor.isValid()) { if (alignOnAnchor) { currentLine = cursor.line; var column = cursor.column; var inc = 0; if (result[2] != '"' && result[2] != "'") { // place one column after the opening parens column++; inc = 1; } var lastColumn = document.lastColumn(currentLine); while (column < lastColumn && document.isSpace(currentLine, column)) { ++column; inc = 1; } if (inc > 0) indentation = document.toVirtualColumn(currentLine, column); else indentation = document.firstVirtualColumn(currentLine); } else { currentLine = cursor.line; indentation = document.firstVirtualColumn(currentLine); } } } else if ( currentString.rtrim().endsWith(';') ) { indentation = document.firstVirtualColumn(currentLine); } if (indentation != -1) dbg("tryStatement (2): success in line " + currentLine); return indentation; } /** * find out whether we pressed return in something like {} or () or [] and indent properly: * {} * becomes: * { * | * } */ function tryMatchedAnchor(line, alignOnly) { var char = document.firstChar(line); if ( char != '}' && char != ')' && char != ']' ) { return -1; } // we pressed enter in e.g. () var closingAnchor = document.anchor(line, 0, document.firstChar(line)); if (!closingAnchor.isValid()) { // nothing found, continue with other cases return -1; } if (alignOnly) { // when aligning only, don't be too smart and just take the indent level of the open anchor return document.firstVirtualColumn(closingAnchor.line); } var lastChar = document.lastChar(line - 1); var charsMatch = ( lastChar == '(' && char == ')' ) || ( lastChar == '{' && char == '}' ) || ( lastChar == '[' && char == ']' ); var indentLine = -1; var indentation = -1; if ( !charsMatch && char != '}' ) { // otherwise check whether the last line has the expected // indentation, if not use it instead and place the closing // anchor on the level of the openeing anchor var expectedIndentation = document.firstVirtualColumn(closingAnchor.line) + gIndentWidth; var actualIndentation = document.firstVirtualColumn(line - 1); var indentation = -1; if ( expectedIndentation <= actualIndentation ) { if ( lastChar == ',' ) { // use indentation of last line instead and place closing anchor // in same column of the openeing anchor document.insertText(line, document.firstColumn(line), "\n"); view.setCursorPosition(line, actualIndentation); // indent closing anchor document.indent(new Range(line + 1, 0, line + 1, 1), document.toVirtualColumn(closingAnchor) / gIndentWidth); // make sure we add spaces to align perfectly on closing anchor var padding = document.toVirtualColumn(closingAnchor) % gIndentWidth; if ( padding > 0 ) { document.insertText(line + 1, document.column - padding, String().fill(' ', padding)); } indentation = actualIndentation; } else if ( expectedIndentation == actualIndentation ) { // otherwise don't add a new line, just use indentation of closing anchor line indentation = document.firstVirtualColumn(closingAnchor.line); } else { // otherwise don't add a new line, just align on closing anchor indentation = document.toVirtualColumn(closingAnchor); } dbg("tryMatchedAnchor: success in line " + closingAnchor.line); return indentation; } } // otherwise we i.e. pressed enter between (), [] or when we enter before curly brace // increase indentation and place closing anchor on the next line indentation = document.firstVirtualColumn(closingAnchor.line); document.insertText(line, document.firstColumn(line), "\n"); view.setCursorPosition(line, indentation); // indent closing brace document.indent(new Range(line + 1, 0, line + 1, 1), indentation / gIndentWidth); dbg("tryMatchedAnchor: success in line " + closingAnchor.line); return indentation + gIndentWidth; } /** * Indent line. * Return filler or null. */ function indentLine(line, alignOnly) { var firstChar = document.firstChar(line); var lastChar = document.lastChar(line); var filler = -1; if (filler == -1) filler = tryMatchedAnchor(line, alignOnly); if (filler == -1) filler = tryCComment(line); if (filler == -1 && !alignOnly) filler = tryCppComment(line); if (filler == -1) filler = trySwitchStatement(line); if (filler == -1) filler = tryAccessModifiers(line); if (filler == -1) filler = tryBrace(line); if (filler == -1) filler = tryCKeywords(line, firstChar == '{'); if (filler == -1) filler = tryCondition(line); if (filler == -1) filler = tryStatement(line); return filler; } function processChar(line, c) { if (c == ';' || !triggerCharacters.contains(c)) return -2; var cursor = view.cursorPosition(); if (!cursor) return -2; var column = cursor.column; var firstPos = document.firstColumn(line); var prevFirstPos = document.firstColumn(line - 1); var lastPos = document.lastColumn(line); dbg("firstPos: " + firstPos); dbg("column..: " + column); dbg("char : " + c); if (firstPos == column - 1 && c == '{') { // todo: maybe look for if etc. var filler = tryBrace(line); if (filler == -1) filler = tryCKeywords(line, true); if (filler == -1) filler = tryCComment(line); // checks, whether we had a "*/" if (filler == -1) filler = tryStatement(line); if (filler == -1) filler = -2; return filler; } else if (firstPos == column - 1 && c == '}' && document.charAt(line, column - 1) == '}') { // unindent after closing brace, but not when brace is auto inserted (i.e., behind cursor) var indentation = findLeftBrace(line, firstPos); if (indentation == -1) indentation = -2; return indentation; } else if (firstPos == column - 1 && c == '}' && firstPos > prevFirstPos) { // align indentation to previous line when creating new block with auto brackets enabled // prevents over-indentation for if blocks and loops return prevFirstPos; } else if (cfgSnapSlash && c == '/' && lastPos == column - 1) { // try to snap the string "* /" to "*/" var currentString = document.line(line); if (/^(\s*)\*\s+\/\s*$/.test(currentString)) { currentString = RegExp.$1 + "*/"; document.editBegin(); document.removeLine(line); document.insertLine(line, currentString); view.setCursorPosition(line, currentString.length); document.editEnd(); } return -2; } else if (c == ':') { // todo: handle case, default, signals, private, public, protected, Q_SIGNALS var filler = trySwitchStatement(line); if (filler == -1) filler = tryAccessModifiers(line); if (filler == -1) filler = -2; return filler; } else if (c == ')' && firstPos == column - 1) { // align on start of identifier of function call var openParen = document.anchor(line, column - 1, '('); if (openParen.isValid()) { // get identifier var callLine = document.line(openParen.line); // strip starting from opening paren callLine = callLine.substring(0, openParen.column - 1); indentation = callLine.search(/\b(\w+)\s*$/); if (indentation != -1) { return document.toVirtualColumn(openParen.line, indentation); } } } else if (firstPos == column - 1 && c == '#' && ( gMode == 'C' || gMode == 'C++' || gMode == 'ISO C++' ) ) { // always put preprocessor stuff upfront return 0; } return -2; } /** * Process a newline character. * This function is called whenever the user hits . */ function indent(line, indentWidth, ch) { gIndentWidth = indentWidth; gMode = document.highlightingModeAt(new Cursor(line, document.lineLength(line))); var alignOnly = (ch == ""); if (ch != '\n' && !alignOnly) return processChar(line, ch); return indentLine(line, alignOnly); } // kate: space-indent on; indent-width 4; replace-tabs on;