File indexing completed on 2024-05-19 04:00:09

0001 var katescript = {
0002     "name": "C Style",
0003     "author": "Dominik Haumann <dhdev@gmx.de>, Milian Wolff <mail@milianw.de>",
0004     "license": "LGPL",
0005     "revision": 6,
0006     "kate-version": "5.1"
0007 }; // kate-script-header, must be at the start of the file without comments, pure json
0008 
0009 /*
0010     This file is part of the Kate Project.
0011 
0012     SPDX-License-Identifier: LGPL-2.0-only
0013 */
0014 
0015 // required katepart js libraries
0016 require ("range.js");
0017 require ("string.js");
0018 require ("utils.js");
0019 
0020 //BEGIN USER CONFIGURATION
0021 var cfgIndentCase = true;         // indent 'case' and 'default' in a switch?
0022 var cfgIndentNamespace = true;    // indent after 'namespace'?
0023 var cfgAutoInsertStar = true;    // auto insert '*' in C-comments
0024 var cfgSnapSlash = true;         // snap '/' to '*/' in C-comments
0025 var cfgAutoInsertSlashes = false; // auto insert '//' after C++-comments
0026 var cfgAccessModifiers = 0;       // indent level of access modifiers, relative to the class indent level
0027                                   // set to -1 to disable auto-indendation after access modifiers.
0028 //END USER CONFIGURATION
0029 
0030 // indent gets three arguments: line, indentwidth in spaces, typed character
0031 // indent
0032 
0033 // specifies the characters which should trigger indent, beside the default '\n'
0034 triggerCharacters = "{})/:;#";
0035 
0036 var debugMode = false;
0037 
0038 function dbg() {
0039     if (debugMode) {
0040         debug.apply(this, arguments);
0041     }
0042 }
0043 
0044 const multiLineStringLiteralStartRegex = /R"(.+)?\(/
0045 
0046 //BEGIN global variables and functions
0047 // maximum number of lines we look backwards/forward to find out the indentation
0048 // level (the bigger the number, the longer might be the delay)
0049 var gLineDelimiter = 50;     // number
0050 
0051 var gIndentWidth = 4;
0052 var gMode = "C";
0053 //END global variables and functions
0054 
0055 
0056 /**
0057  * Search for a corresponding '{' and return its indentation level. If not found
0058  * return null; (line/column) are the start of the search.
0059  */
0060 function findLeftBrace(line, column)
0061 {
0062     var cursor = document.anchor(line, column, '{');
0063     if (cursor.isValid()) {
0064         var parenthesisCursor = tryParenthesisBeforeBrace(cursor.line, cursor.column);
0065         if (parenthesisCursor.isValid())
0066             cursor = parenthesisCursor;
0067 
0068         dbg("findLeftBrace: success in line " + cursor.line);
0069         return document.firstVirtualColumn(cursor.line);
0070     }
0071 
0072     return -1;
0073 }
0074 
0075 /**
0076  * Find last non-empty line that is not inside a comment or preprocessor
0077  */
0078 function lastNonEmptyLine(line)
0079 {
0080     while (true) {
0081         line = document.prevNonEmptyLine(line);
0082         if ( line == -1 ) {
0083             return -1;
0084         }
0085         var string = document.line(line).ltrim();
0086         ///TODO: cpp multiline comments
0087         ///TODO: multiline macros
0088         if ( string.startsWith("//") || string.startsWith('#') ) {
0089             --line;
0090             continue;
0091         }
0092         break;
0093     }
0094 
0095     return line;
0096 }
0097 
0098 /**
0099  * Character at (line, column) has to be a '{'.
0100  * Now try to find the right line for indentation for constructs like:
0101  * if (a == b
0102  *     && c == d) { <- check for ')', and find '(', then return its indentation
0103  * Returns null, if no success, otherwise an object {line, column}
0104  */
0105 function tryParenthesisBeforeBrace(line, column)
0106 {
0107     var firstColumn = document.firstColumn(line);
0108     while (column > firstColumn && document.isSpace(line, --column));
0109     if (document.charAt(line, column) == ')')
0110         return document.anchor(line, column, '(');
0111     return Cursor.invalid();
0112 }
0113 
0114 /**
0115  * Check for default and case keywords and assume we are in a switch statement.
0116  * Try to find a previous default, case or switch and return its indentation or
0117  * -1 if not found.
0118  */
0119 function trySwitchStatement(line)
0120 {
0121     var currentString = document.line(line);
0122     if (currentString.search(/^\s*(default\s*|case\b.*):/) == -1)
0123         return -1;
0124 
0125     var indentation = -1;
0126     var lineDelimiter = gLineDelimiter;
0127     var currentLine = line;
0128 
0129     while (currentLine > 0 && lineDelimiter > 0) {
0130         --currentLine;
0131         --lineDelimiter;
0132         if (document.firstColumn(currentLine) == -1)
0133             continue;
0134 
0135         currentString = document.line(currentLine);
0136         if (currentString.search(/^\s*(default\s*|case\b.*):/) != -1) {
0137             indentation = document.firstVirtualColumn(currentLine);
0138             break;
0139         } else if (currentString.search(/^\s*switch\b/) != -1) {
0140             indentation = document.firstVirtualColumn(currentLine);
0141             if (cfgIndentCase)
0142                 indentation += gIndentWidth;
0143             break;
0144         }
0145     }
0146 
0147     if (indentation != -1) dbg("trySwitchStatement: success in line " + currentLine);
0148     return indentation;
0149 }
0150 
0151 /**
0152  * Check for private, protected, public, signals etc... and assume we are in a
0153  * class definition. Try to find a previous private/protected/private... or
0154  * class and return its indentation or null if not found.
0155  */
0156 function tryAccessModifiers(line)
0157 {
0158     if (cfgAccessModifiers < 0)
0159         return -1;
0160 
0161     var currentString = document.line(line);
0162     if (currentString.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) == -1)
0163         return -1;
0164 
0165     var cursor = document.anchor(line, 0, '{');
0166     if (!cursor.isValid())
0167         return -1;
0168 
0169     var indentation = document.firstVirtualColumn(cursor.line);
0170     for ( var i = 0; i < cfgAccessModifiers; ++i ) {
0171         indentation += gIndentWidth;
0172     }
0173 
0174     if (indentation != -1) dbg("tryAccessModifiers: success in line " + cursor.line);
0175     return indentation;
0176 }
0177 
0178 
0179 
0180 /**
0181  * Get the position of the first non-space character
0182  * return: position or -1, if there is no non-space character
0183  */
0184 function firstNonSpace(text)
0185 {
0186     if (text && text.search(/^(\s*)\S/) != -1)
0187         return RegExp.$1.length;
0188 
0189     return -1;
0190 }
0191 
0192 /**
0193  * Get the position of the last non-space character
0194  * return: position or -1, if there is no non-space character
0195  */
0196 function lastNonSpace(text)
0197 {
0198     if (text && text.search(/(.*)\S\s*$/) != -1)
0199         return RegExp.$1.length;
0200 
0201     return -1;
0202 }
0203 
0204 /**
0205  * Don't indent when in a multiline string literal of form "something\
0206 nextline"
0207  * Note: the function does not check for comments
0208  */
0209 function tryString(line)
0210 {
0211     var currentLine = line;
0212     var currentString;
0213 
0214     // go line up as long as the previous line ends with an escape character '\'
0215     while (currentLine >= 0) {
0216         currentString = document.line(currentLine - 1);
0217         if (currentString.charAt(document.lastColumn(currentLine - 1)) != '\\')
0218             break;
0219         --currentLine;
0220     }
0221 
0222     // iterate through all lines and toggle bool insideString for every quote
0223     var insideString = false;
0224     var indentation = -1;
0225     while (currentLine < line) {
0226         currentString = document.line(currentLine);
0227         var char1;
0228         var i;
0229         var lineLength = document.lineLength(currentLine);
0230         for (i = 0; i < lineLength; ++i) {
0231             char1 = currentString.charAt(i);
0232             if (char1 == "\\") {
0233                 // skip escaped character
0234                 ++i;
0235             } else if (char1 == "\"") {
0236                 insideString = !insideString;
0237             }
0238         }
0239         ++currentLine;
0240     }
0241 
0242     return insideString ? 0 : -1;
0243 }
0244 
0245 /**
0246  * C comment checking. If the previous line begins with a "/*" or a "* ", then
0247  * return its leading white spaces + ' *' + the white spaces after the *
0248  * return: filler string or null, if not in a C comment
0249  */
0250 function tryCComment(line)
0251 {
0252     var currentLine = document.prevNonEmptyLine(line - 1);
0253     if (currentLine < 0)
0254         return -1;
0255 
0256     var indentation = -1;
0257 
0258     // we found a */, search the opening /* and return its indentation level
0259     if (document.endsWith(currentLine, "*/", true)) {
0260         var cursor = document.rfind(currentLine, document.lastColumn(currentLine), "/*");
0261         if (cursor.isValid() && cursor.column == document.firstColumn(cursor.line))
0262             indentation = document.firstVirtualColumn(cursor.line);
0263 
0264         if (indentation != -1) dbg("tryCComment: success (1) in line " + cursor.line);
0265         return indentation;
0266     }
0267 
0268     // inbetween was an empty line, so do not copy the "*" character
0269     if (currentLine != line - 1)
0270         return -1;
0271 
0272     var firstPos = document.firstColumn(currentLine);
0273     var lastPos = document.lastColumn(currentLine);
0274     var char1 = document.charAt(currentLine, firstPos);
0275     var char2 = document.charAt(currentLine, firstPos + 1);
0276     var currentString = document.line(currentLine);
0277 
0278     if (char1 == '/' && char2 == '*' && !currentString.contains("*/")) {
0279         indentation = document.firstVirtualColumn(currentLine);
0280         if (cfgAutoInsertStar) {
0281             // only add '*', if there is none yet.
0282             indentation += 1;
0283             if (document.firstChar(line) != '*')
0284                 document.insertText(line, view.cursorPosition().column, '*');
0285             if (!document.isSpace(line, document.firstColumn(line) + 1) && !document.endsWith(line, "*/", true))
0286                 document.insertText(line, document.firstColumn(line) + 1, ' ');
0287         }
0288     } else if (char1 == '*') {
0289         var commentLine = currentLine;
0290         while (commentLine >= 0 && document.firstChar(commentLine) == '*'
0291             && !document.endsWith(commentLine, "*/", true)
0292         ) {
0293             --commentLine;
0294         }
0295         if (commentLine < 0) {
0296             indentation = document.firstVirtualColumn(currentLine);
0297         } else if (document.startsWith(commentLine, "/*", true) && !document.endsWith(commentLine, "*/", true)) {
0298             // found a /*, and all succeeding lines start with a *, so it's a comment block
0299             indentation = document.firstVirtualColumn(currentLine);
0300 
0301             // only add '*', if there is none yet.
0302             if (cfgAutoInsertStar && document.firstChar(line) != '*') {
0303                 document.insertText(line, view.cursorPosition().column, '*');
0304                 if (!document.isSpace(line, document.firstColumn(line) + 1))
0305                     document.insertText(line, document.firstColumn(line) + 1, ' ');
0306             }
0307         }
0308     }
0309 
0310     if (indentation != -1) dbg("tryCComment: success (2) in line " + currentLine);
0311     return indentation;
0312 }
0313 
0314 /**
0315  * C++ comment checking. when we want to insert slashes:
0316  * //, ///, //! ///<, //!< and ////...
0317  * return: filler string or null, if not in a star comment
0318  * NOTE: otherwise comments get skipped generally and we use the last code-line
0319  */
0320 function tryCppComment(line)
0321 {
0322     var currentLine = line - 1;
0323     if (currentLine < 0 || !cfgAutoInsertSlashes)
0324         return -1;
0325 
0326     var indentation = -1;
0327     var comment = document.startsWith(currentLine, "//", true);
0328 
0329     // allowed are: //, ///, //! ///<, //!< and ////...
0330     if (comment) {
0331         var firstPos = document.firstColumn(currentLine);
0332         var currentString = document.line(currentLine);
0333 
0334         var char3 = currentString.charAt(firstPos + 2);
0335         var char4 = currentString.charAt(firstPos + 3);
0336         indentation = document.firstVirtualColumn(currentLine);
0337 
0338         if (cfgAutoInsertSlashes) {
0339             if (char3 == '/' && char4 == '/') {
0340                 // match ////... and replace by only two: //
0341                 currentString.search(/^\s*(\/\/)/);
0342             } else if (char3 == '/' || char3 == '!') {
0343                 // match ///, //!, ///< and //!
0344                 currentString.search(/^\s*(\/\/[\/!][<]?\s*)/);
0345             } else {
0346                 // only //, nothing else
0347                 currentString.search(/^\s*(\/\/\s*)/);
0348             }
0349             document.insertText(line, view.cursorPosition().column, RegExp.$1);
0350         }
0351     }
0352 
0353     if (indentation != -1) dbg("tryCppComment: success in line " + currentLine);
0354     return indentation;
0355 }
0356 
0357 /**
0358  * If the last non-empty line ends with a { or [, take its indentation level (or
0359  * maybe the one found by tryParenthesisBeforeBrace()) and return it increased
0360  * by 1 indetation level (special case: namespaces indentation depends on
0361  * cfgIndentNamespace). If not found, return null.
0362  */
0363 function tryBrace(line)
0364 {
0365     function isNamespace(line, column)
0366     {
0367         if (document.firstColumn(line) == column && line > 0)
0368             --line;
0369         var currentString = document.line(line);
0370         return (currentString.search(/^\s*namespace\b/) != -1);
0371     }
0372 
0373     var currentLine = lastNonEmptyLine(line - 1);
0374     if (currentLine < 0)
0375         return -1;
0376 
0377     var lastPos = document.lastColumn(currentLine);
0378     var indentation = -1;
0379 
0380     var currentString = document.line(currentLine);
0381     var matchColumn = currentString.search(/\{[^\}]*$/);
0382     // line ends with [, e.g., array in js or dart
0383     if (matchColumn == -1 && currentString.endsWith("[")) {
0384         matchColumn = currentString.length - 1;
0385     }
0386 
0387     if (matchColumn != -1 && document.isCode(currentLine, matchColumn)) {
0388         dbg("tryBrace: Closing bracket in line " + currentLine);
0389         var cursor = tryParenthesisBeforeBrace(currentLine, lastPos);
0390         if (cursor.isValid()) {
0391             indentation = document.firstVirtualColumn(cursor.line) + gIndentWidth;
0392         } else {
0393             indentation = document.firstVirtualColumn(currentLine);
0394             if (cfgIndentNamespace || !isNamespace(currentLine, lastPos))
0395                 // take its indentation and add one indentation level
0396                 indentation += gIndentWidth;
0397         }
0398     }
0399 
0400     if (indentation != -1) dbg("tryBrace: success in line " + currentLine);
0401     return indentation;
0402 }
0403 
0404 /**
0405  * Check for if, else, while, do, switch, private, public, protected, signals,
0406  * default, case etc... keywords, as we want to indent then. If   is
0407  * non-null/true, then indentation is not increased.
0408  * Note: The code is written to be called *after* tryCComment and tryCppComment!
0409  */
0410 function tryCKeywords(line, isBrace)
0411 {
0412     var currentLine = lastNonEmptyLine(line - 1);
0413     if (currentLine < 0)
0414         return -1;
0415 
0416     // if line ends with ')', find the '(' and check this line then.
0417     var lastPos = document.lastColumn(currentLine);
0418     var cursor = Cursor.invalid();
0419     if (document.charAt(currentLine, lastPos) == ')')
0420         cursor = document.anchor(currentLine, lastPos, '(');
0421     if (cursor.isValid())
0422         currentLine = cursor.line;
0423 
0424     // found non-empty line
0425     var currentString = document.line(currentLine);
0426     if (currentString.search(/^\s*(if\b|for(each)?|do\b|while|switch|[}]?\s*else(if)?|((private|public|protected|case|default|signals|Q_SIGNALS).*:))/) == -1)
0427         return -1;
0428     dbg("Found first word: " + RegExp.$1);
0429     lastPos = document.lastColumn(currentLine);
0430     var lastChar = currentString.charAt(lastPos);
0431     var indentation = -1;
0432 
0433     // ignore trailing comments see: https://bugs.kde.org/show_bug.cgi?id=189339
0434     var commentPos = currentString.indexOf("//")
0435     if (commentPos != -1) {
0436         currentString = currentString.substring(0, commentPos).rtrim();
0437         lastChar = currentString.charAt(currentString.length - 1);
0438     }
0439 
0440     // try to ignore lines like: if (a) b; or if (a) { b; }
0441     if (lastChar != ';' && lastChar != '}') {
0442         // take its indentation and add one indentation level
0443         indentation = document.firstVirtualColumn(currentLine);
0444         if (!isBrace)
0445             indentation += gIndentWidth;
0446     } else if (lastChar == ';') {
0447         // stuff like:
0448         // for(int b;
0449         //     b < 10;
0450         //     --b)
0451         cursor = document.anchor(currentLine, lastPos, '(');
0452         if (cursor.isValid()) {
0453             // Same line, we know there is a keyword here
0454             if (cursor.line == line) {
0455                 indentation = document.toVirtualColumn(cursor.line, cursor.column + 1);
0456             } else {
0457                 // check that the returned cursor's line contains
0458                 // a keyword and isn't a func call.
0459                 var cursorLine = document.line(cursor.line);
0460                 var s = cursorLine.search(/^\s*(if\b|for(each)?|do\b|while|switch|[}]?\s*else(if)?)/);
0461                 if (s != -1) {
0462                     indentation = document.toVirtualColumn(cursor.line, cursor.column + 1);
0463                 }
0464             }
0465         }
0466     }
0467 
0468     if (indentation != -1) dbg("tryCKeywords: success in line " + currentLine);
0469     return indentation;
0470 }
0471 
0472 /**
0473  * Is this line a single statement of a condition:
0474  * if (true)
0475  *     doWork();
0476  * - Finds the previous non empty line
0477  * - Checks if it has control keyword
0478  * - Checks if it doesn't have brace
0479  */
0480 function isSingleStmtCondition(line)
0481 {
0482     if (line > 1) {
0483         var prev = lastNonEmptyLine(line - 1);
0484         var prevText = document.line(prev);
0485         var hasKeyword = prevText.search(/^\s*(if\b|[}]?\s*else(if)?\b|do\b|while\b|for(each)?\b)/);
0486         if (hasKeyword != -1 && !prevText.contains("{")) {
0487             return true;
0488         }
0489     }
0490     return false;
0491 }
0492 
0493 /**
0494  * Search for if, do, while, for, ... as we want to indent then.
0495  * Return null, if nothing useful found.
0496  * Note: The code is written to be called *after* tryCComment and tryCppComment!
0497  */
0498 function tryCondition(line)
0499 {
0500     var currentLine = lastNonEmptyLine(line - 1);
0501     if (currentLine < 0)
0502         return -1;
0503 
0504     // found non-empty line
0505     var currentString = document.line(currentLine);
0506     var lastPos = document.lastColumn(currentLine);
0507     var lastChar = currentString.charAt(lastPos);
0508     var indentation = -1;
0509 
0510     if (lastChar == ';'
0511         && currentString.search(/^\s*(if\b|[}]?\s*else(if)?\b|do\b|while\b|for(each)?\b)/) == -1)
0512     {
0513         // idea: we had something like:
0514         //   if/while/for (expression)
0515         //       statement();  <-- we catch this trailing ';'
0516         // Now, look for a line that starts with if/for/while, that has one
0517         // indent level less.
0518         var currentIndentation = document.firstVirtualColumn(currentLine);
0519         if (currentIndentation == 0)
0520             return -1;
0521 
0522         // Is this a condition with only one stmt in it
0523         var singleStmt = isSingleStmtCondition(currentLine);
0524         if (singleStmt) {
0525             var leftBrace = findLeftBrace(currentLine, 0);
0526             if (leftBrace != -1) {
0527                 indentation = leftBrace + gIndentWidth;
0528                 dbg("tryCondition(1): success in line " + currentLine);
0529                 return indentation;
0530             }
0531         }
0532 
0533 
0534         var lineDelimiter = 10; // 10 limit search, hope this is a sane value
0535         while (currentLine > 0 && lineDelimiter > 0) {
0536             --currentLine;
0537             --lineDelimiter;
0538             var firstPosVirtual = document.firstVirtualColumn(currentLine);
0539             if (firstPosVirtual == -1)
0540                 continue;
0541 
0542             if (firstPosVirtual < currentIndentation) {
0543                 currentString = document.line(currentLine);
0544                 if (currentString.search(/^\s*(if\b|[}]?\s*else(if)?|do\b|while\b|for(each)?)[^{]*$/) != -1)
0545                     indentation = firstPosVirtual;
0546                 break;
0547             } else if (currentLine == 0 || lineDelimiter == 0) {
0548                 return indentation;
0549             }
0550         }
0551     }
0552 
0553     if (indentation != -1) dbg("tryCondition(2): success in line " + currentLine);
0554     return indentation;
0555 }
0556 
0557 /**
0558  * If the non-empty line ends with ); or ',', then search for '(' and return its
0559  * indentation; also try to ignore trailing comments.
0560  */
0561 function tryStatement(line)
0562 {
0563     var currentLine = lastNonEmptyLine(line - 1);
0564     if (currentLine < 0)
0565         return -1;
0566 
0567     var indentation = -1;
0568     var currentString = document.line(currentLine);
0569     if (currentString.endsWith('(')) {
0570         // increase indent level
0571         indentation = document.firstVirtualColumn(currentLine) + gIndentWidth;
0572         dbg("tryStatement (1): success in line " + currentLine);
0573         return indentation;
0574     }
0575     var alignOnSingleQuote = gMode == "PHP/PHP" || gMode == "JavaScript";
0576     // align on strings "..."\n => below the opening quote
0577     // multi-language support: [\.+] for javascript or php
0578     var result = /^(.*)(,|"|'|\))(;?)\s*[\.+]?\s*(\/\/.*|\/\*.*\*\/\s*)?$/.exec(currentString);
0579     if (result != null && result.index == 0) {
0580         var alignOnAnchor = result[3].length == 0 && result[2] != ')';
0581         // search for opening ", ' or (
0582         var cursor = Cursor.invalid();
0583         if (result[2] == '"' || (alignOnSingleQuote && result[2] == "'")) {
0584 
0585             while(true) {
0586                 var i = result[1].length - 1; // start from matched closing ' or "
0587                 // find string opener
0588                 for ( ; i >= 0; --i ) {
0589                     // make sure it's not commented out
0590                     if (currentString[i] == result[2] && (i == 0 || currentString[i - 1] != '\\')) {
0591                         // also make sure that this is not a line like '#include "..."' <-- we don't want to indent here
0592                         if (currentString.match(/^#include/) || currentString.match(/'use strict'/)) {
0593                             return indentation;
0594                         }
0595                         cursor = new Cursor(currentLine, i);
0596                         break;
0597                     }
0598                 }
0599                 if (!alignOnAnchor && currentLine) {
0600                     // when we finished the statement (;) we need to get the first line and use it's indentation
0601                     // i.e.: $foo = "asdf"; -> align on $
0602                     --i; // skip " or '
0603                     // skip whitespaces and stuff like + or . (for PHP, JavaScript, ...)
0604                     for ( ; i >= 0; --i ) {
0605                         if (currentString[i] == ' ' || currentString[i] == "\t"
0606                             || currentString[i] == '.' || currentString[i] == '+')
0607                         {
0608                             continue;
0609                         } else {
0610                             break;
0611                         }
0612                     }
0613                     if ( i > 0 ) {
0614                         // there's something in this line, use it's indentation
0615                         break;
0616                     } else {
0617                         // go to previous line
0618                         --currentLine;
0619                         currentString = document.line(currentLine);
0620                     }
0621                 } else {
0622                     break;
0623                 }
0624             }
0625         } else if (result[2] == ',' && !currentString.match(/\(/)) {
0626             // assume a function call: check for '(' brace
0627             // - if not found, use previous indentation
0628             // - if found, compare the indentation depth of current line and open brace line
0629             //   - if current indentation depth is smaller, use that
0630             //   - otherwise, use the '(' indentation + following white spaces
0631             var currentIndentation = document.firstVirtualColumn(currentLine);
0632             var braceCursor = document.anchor(currentLine, result[1].length, '(');
0633 
0634             if (!braceCursor.isValid() || currentIndentation < braceCursor.column)
0635                 indentation = currentIndentation;
0636             else {
0637                 indentation = braceCursor.column + 1;
0638                 while (document.isSpace(braceCursor.line, indentation))
0639                     ++indentation;
0640             }
0641         } else if (gMode == "Dart" && result[2] == ',' && currentString.contains("(") && currentString.contains(")")) {
0642             // Dart has a lot of stuff and function calls inside ctors
0643             // and code is not aligned at the opening paren hence:
0644             // do nothing
0645         } else {
0646             cursor = document.anchor(currentLine, result[1].length, '(');
0647         }
0648         if (cursor.isValid()) {
0649             if (alignOnAnchor) {
0650                 currentLine = cursor.line;
0651                 var column = cursor.column;
0652                 var inc = 0;
0653                 if (result[2] != '"' && result[2] != "'") {
0654                     // place one column after the opening parens
0655                     column++;
0656                     inc = 1;
0657                 }
0658                 var lastColumn = document.lastColumn(currentLine);
0659                 while (column < lastColumn && document.isSpace(currentLine, column)) {
0660                     ++column;
0661                     inc = 1;
0662                 }
0663                 if (inc > 0)
0664                     indentation = document.toVirtualColumn(currentLine, column);
0665                 else
0666                     indentation = document.firstVirtualColumn(currentLine);
0667             } else {
0668                 currentLine = cursor.line;
0669                 indentation = document.firstVirtualColumn(currentLine);
0670             }
0671         }
0672     } else if ( currentString.rtrim().endsWith(';') ) {
0673         indentation = document.firstVirtualColumn(currentLine);
0674     }
0675 
0676     if (indentation != -1) dbg("tryStatement (2): success in line " + currentLine);
0677     return indentation;
0678 }
0679 
0680 /**
0681  * find out whether we pressed return in something like {} or () or [] and indent properly:
0682  * {}
0683  * becomes:
0684  * {
0685  *   |
0686  * }
0687  */
0688 function tryMatchedAnchor(line, alignOnly)
0689 {
0690     var char = document.firstChar(line);
0691     if ( char != '}' && char != ')' && char != ']' ) {
0692         return -1;
0693     }
0694     // we pressed enter in e.g. ()
0695     var closingAnchor = document.anchor(line, 0, document.firstChar(line));
0696     if (!closingAnchor.isValid()) {
0697         // nothing found, continue with other cases
0698         return -1;
0699     }
0700     if (alignOnly) {
0701         // when aligning only, don't be too smart and just take the indent level of the open anchor
0702         return document.firstVirtualColumn(closingAnchor.line);
0703     }
0704     var lastChar = document.lastChar(line - 1);
0705     var charsMatch = ( lastChar == '(' && char == ')' ) ||
0706                      ( lastChar == '{' && char == '}' ) ||
0707                      ( lastChar == '[' && char == ']' );
0708     var indentLine = -1;
0709     var indentation = -1;
0710     if ( !charsMatch && char != '}' ) {
0711         // otherwise check whether the last line has the expected
0712         // indentation, if not use it instead and place the closing
0713         // anchor on the level of the openeing anchor
0714         var expectedIndentation = document.firstVirtualColumn(closingAnchor.line) + gIndentWidth;
0715         var actualIndentation = document.firstVirtualColumn(line - 1);
0716         var indentation = -1;
0717         if ( expectedIndentation <= actualIndentation ) {
0718             if ( lastChar == ',' && char != ')' ) {
0719                 // use indentation of last line instead and place closing anchor
0720                 // in same column of the openeing anchor
0721                 document.insertText(line, document.firstColumn(line), "\n");
0722                 view.setCursorPosition(line, actualIndentation);
0723                 // indent closing anchor
0724                 document.indent(new Range(line + 1, 0, line + 1, 1), document.toVirtualColumn(closingAnchor) / gIndentWidth);
0725                 // make sure we add spaces to align perfectly on closing anchor
0726                 var padding = document.toVirtualColumn(closingAnchor) % gIndentWidth;
0727                 if ( padding > 0 ) {
0728                     document.insertText(line + 1, document.column - padding, String().fill(' ', padding));
0729                 }
0730                 indentation = actualIndentation;
0731             } else if ( expectedIndentation == actualIndentation ) {
0732                 // otherwise don't add a new line, just use indentation of closing anchor line
0733                 indentation = document.firstVirtualColumn(closingAnchor.line);
0734             } else {
0735                 // otherwise don't add a new line, just align on closing anchor
0736                 indentation = document.toVirtualColumn(closingAnchor) + 1;
0737             }
0738             dbg("tryMatchedAnchor: success in line " + closingAnchor.line);
0739             return indentation;
0740         } else if ( lastChar == ',') {
0741             dbg("tryMatchedAnchor: success in line " + closingAnchor.line);
0742             return document.toVirtualColumn(closingAnchor) + 1;
0743         }
0744     }
0745 
0746     // otherwise we i.e. pressed enter between (), [] or when we enter before curly brace
0747     // increase indentation and place closing anchor on the next line
0748     indentation = document.firstVirtualColumn(closingAnchor.line);
0749     document.insertText(line, document.firstColumn(line), "\n");
0750     view.setCursorPosition(line, indentation);
0751     // indent closing brace
0752     document.indent(new Range(line + 1, 0, line + 1, 1), indentation / gIndentWidth);
0753     dbg("tryMatchedAnchor: success in line " + closingAnchor.line);
0754     return indentation + gIndentWidth;
0755 }
0756 
0757 function escapeForRegex(string) {
0758     if (string !== null && string != undefined) {
0759         return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
0760     }
0761     return string;
0762 }
0763 
0764 /**
0765  * Don't indent on multiline string literals of form R"[optional delimiter text]()[optional delimiter text]"
0766  */
0767 function tryMultiLineStringLiteral(line) {
0768     // The algorithm in this function is only valid for C++, don't waste time for other languages
0769     if (!gMode.contains("C++")) {
0770         return -1;
0771     }
0772     var currentLine = line;
0773     var currentString;
0774     var delim = "";
0775     var found = false;
0776 
0777     var limit = 0; // ensure we don't search the whole document all the time
0778     // go line up as long as the previous line ends with an escape character '\'
0779     // go line up until we find something of form R"[delim](
0780     while (currentLine >= 0) {
0781         currentString = document.line(currentLine - 1);
0782         var startMatch = currentString.match(multiLineStringLiteralStartRegex);
0783         if (startMatch !== null) {
0784             found = true;
0785             delim = startMatch[1] != undefined ? startMatch[1] : "";
0786             currentLine--;
0787             break;
0788         }
0789         --currentLine;
0790         if (limit >= 25) {
0791             break;
0792         }
0793         limit++;
0794     }
0795 
0796     if (!found) {
0797         return -1;
0798     }
0799 
0800     // iterate through all lines and toggle bool insideString for every quote
0801     var endReached = false;
0802     const multiLineStringLiteralEndRegex = delim.length != 0 ? new RegExp('\\)' + escapeForRegex(delim) + '"') : new RegExp("\)\"");
0803     while (currentLine < line) {
0804         currentString = document.line(currentLine);
0805         var endMatch = currentString.match(multiLineStringLiteralEndRegex);
0806         if (endMatch !== null) {
0807             dbg("End reached: " + currentString);
0808             endReached = true;
0809             break;
0810         }
0811         ++currentLine;
0812     }
0813 
0814     return endReached ? -1 : document.firstVirtualColumn(line);
0815 }
0816 
0817 /**
0818  * Indent line.
0819  * Return filler or null.
0820  */
0821 function indentLine(line, alignOnly)
0822 {
0823     var firstChar = document.firstChar(line);
0824     var lastChar = document.lastChar(line);
0825 
0826     var filler = -1;
0827 
0828     if (filler == -1)
0829         filler = tryMatchedAnchor(line, alignOnly);
0830     if (filler == -1)
0831         filler = tryCComment(line);
0832     if (filler == -1 && !alignOnly)
0833         filler = tryCppComment(line);
0834     if (filler == -1)
0835         filler = tryMultiLineStringLiteral(line);
0836     if (filler == -1)
0837         filler = tryString(line);
0838     if (filler == -1)
0839         filler = trySwitchStatement(line);
0840     if (filler == -1)
0841         filler = tryAccessModifiers(line);
0842     if (filler == -1)
0843         filler = tryBrace(line);
0844     if (filler == -1)
0845         filler = tryCKeywords(line, firstChar == '{');
0846     if (filler == -1)
0847         filler = tryCondition(line);
0848     if (filler == -1)
0849         filler = tryStatement(line);
0850 
0851     return filler;
0852 }
0853 
0854 function processChar(line, c)
0855 {
0856     if (c == ';' || !triggerCharacters.contains(c))
0857         return -2;
0858 
0859     var cursor = view.cursorPosition();
0860     if (!cursor)
0861         return -2;
0862 
0863     var column = cursor.column;
0864     var firstPos = document.firstColumn(line);
0865     var prevFirstPos = document.firstColumn(line - 1);
0866     var lastPos = document.lastColumn(line);
0867 
0868     dbg("firstPos: " + firstPos);
0869     dbg("column..: " + column);
0870     dbg("char    : " + c);
0871 
0872     if (firstPos == column - 1 && c == '{') {
0873         // todo: maybe look for if etc.
0874         var filler = tryBrace(line);
0875         if (filler == -1)
0876             filler = tryCKeywords(line, true);
0877         if (filler == -1)
0878             filler = tryCComment(line); // checks, whether we had a "*/"
0879         if (filler == -1)
0880             filler = tryStatement(line);
0881         if (filler == -1)
0882             filler = -2;
0883 
0884         return filler;
0885     } else if (firstPos == column - 1 && c == '}' && document.charAt(line, column - 1) == '}') {
0886         // unindent after closing brace, but not when brace is auto inserted (i.e., behind cursor)
0887         var indentation = findLeftBrace(line, firstPos);
0888         if (indentation == -1)
0889             indentation = -2;
0890         return indentation;
0891     } else if (firstPos == column - 1 && c == '}'  && firstPos > prevFirstPos) {
0892         // align indentation to previous line when creating new block with auto brackets enabled
0893         // prevents over-indentation for if blocks and loops
0894         return document.toVirtualColumn(line - 1, prevFirstPos);
0895     } else if (cfgSnapSlash && c == '/' && lastPos == column - 1) {
0896         // try to snap the string "* /" to "*/"
0897         var currentString = document.line(line);
0898         if (/^(\s*)\*\s+\/\s*$/.test(currentString)) {
0899             currentString = RegExp.$1 + "*/";
0900             document.editBegin();
0901             document.removeLine(line);
0902             document.insertLine(line, currentString);
0903             view.setCursorPosition(line, currentString.length);
0904             document.editEnd();
0905         }
0906         return -2;
0907     } else if (c == ':') {
0908         // todo: handle case, default, signals, private, public, protected, Q_SIGNALS
0909         var filler = trySwitchStatement(line);
0910         if (filler == -1)
0911             filler = tryAccessModifiers(line);
0912         if (filler == -1)
0913             filler = -2;
0914         return filler;
0915     } else if (c == ')' && firstPos == column - 1) {
0916         // align on start of identifier of function call
0917         var openParen = document.anchor(line, column - 1, '(');
0918         if (openParen.isValid()) {
0919             // get identifier
0920             var callLine = document.line(openParen.line);
0921             // strip starting from opening paren
0922             callLine = callLine.substring(0, openParen.column - 1);
0923             indentation = callLine.search(/\b(\w+)\s*$/);
0924             if (indentation != -1) {
0925                 return document.toVirtualColumn(openParen.line, indentation);
0926             }
0927         }
0928     } else if (firstPos == column - 1 && c == '#' && ( gMode == 'C' || gMode == 'C++' || gMode == 'ISO C++' ) ) {
0929         // always put preprocessor stuff upfront
0930         return 0;
0931     }
0932     return -2;
0933 }
0934 
0935 /**
0936  * Process a newline character.
0937  * This function is called whenever the user hits <return/enter>.
0938  */
0939 function indent(line, indentWidth, ch)
0940 {
0941     gIndentWidth = indentWidth;
0942     gMode = document.highlightingModeAt(new Cursor(line, document.lineLength(line)));
0943     var alignOnly = (ch == "");
0944 
0945     if (ch != '\n' && !alignOnly)
0946         return processChar(line, ch);
0947 
0948     return indentLine(line, alignOnly);
0949 }
0950 
0951 // kate: space-indent on; indent-width 4; replace-tabs on;