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

0001 var katescript = {
0002     "name": "C++/boost Style",
0003     "author": "Alex Turbov <i.zaufi@gmail.com>",
0004     "license": "LGPL",
0005     "revision": 32,
0006     "kate-version": "5.1",
0007     "required-syntax-style": "C++",
0008     "indent-languages": ["C++", "C++/Qt4", "ISO C++"],
0009     "priority": 10
0010 }; // kate-script-header, must be at the start of the file without comments, pure json
0011 
0012 /*
0013     This file is part of the Kate Project.
0014 
0015     SPDX-License-Identifier: LGPL-2.0-or-later
0016 */
0017 
0018 /**
0019  * \warning This indenter designed to be used with my C++ style! It consists
0020  * of mix of boost and STL styles + some my, unfortunately (still)
0021  * undocumented additions I've found useful after ~15 years of C++ coding.
0022  * So \b LOT of things here are \b HARDCODED and I \b DON'T care about other
0023  * styles!!!
0024  *
0025  * Ok, you've been warned :-)
0026  *
0027  * More info available here: http://zaufi.github.io/programming/2013/11/29/kate-cppstyle-indenter/
0028  *
0029  * Some settings it assumes being in effect:
0030  * - indent-width 4;
0031  * - space-indent true;
0032  * - auto-brackets true; <-- TODO REALLY?
0033  * - replace-tabs true;
0034  * - replace-tabs-save true;
0035  *
0036  * \todo Better to check (assert) some of that modelines...
0037  */
0038 
0039 // required katepart js libraries
0040 require ("range.js");
0041 require ("string.js");
0042 require ("utils.js")
0043 
0044 // specifies the characters which should trigger indent, beside the default '\n'
0045 // ':' is for `case'/`default' and class access specifiers: public, protected, private
0046 // '/' is for single line comments
0047 // ',' for parameter list
0048 // '<' and '>' is for templates
0049 // '#' is for preprocessor directives
0050 // ')' is for align dangling close bracket
0051 // ';' is for align `for' parts
0052 // ' ' is to add a '()' after `if', `while', `for', ...
0053 // TBD <others>
0054 triggerCharacters = "{}()[]<>/:;,#\\?!|&/%.@ '\"=*^";
0055 
0056 var debugMode = false;
0057 
0058 //BEGIN global variables and functions
0059 var gIndentWidth = 4;
0060 var gSameLineCommentStartAt = 60;                           ///< Position for same-line-comments (inline comments)
0061 var gBraceMap = {
0062     '(': ')', ')': '('
0063   , '<': '>', '>': '<'
0064   , '{': '}', '}': '{'
0065   , '[': ']', ']': '['
0066   };
0067 //END global variables and functions
0068 
0069 /**
0070  * Try to (re)align (to 60th position) inline comment if present
0071  * \return \c true if comment line was moved above
0072  */
0073 function alignInlineComment(line)
0074 {
0075     // Check is there any comment on the current line
0076     var sc = splitByComment(line);
0077     // Did we found smth and if so, make sure it is not a string or comment...
0078     if (sc.hasComment && !isStringOrComment(line, sc.before.length - 1))
0079     {
0080         var rbefore = sc.before.rtrim();
0081         var cursor = view.cursorPosition();
0082         /// \attention Kate has a BUG: even if everything is Ok and no realign
0083         /// required, document gets modified anyway! So condition below
0084         /// designed to prevent document modification w/o real actions won't
0085         /// help anyway :-( Need to fix Kate before!
0086         if (rbefore.length < gSameLineCommentStartAt && sc.before.length != gSameLineCommentStartAt)
0087         {
0088             // Ok, test on the line is shorter than needed.
0089             // But what about current padding?
0090             if (sc.before.length < gSameLineCommentStartAt)
0091                 // Need to add some padding
0092                 document.insertText(
0093                     line
0094                   , sc.before.length
0095                   , String().fill(' ', gSameLineCommentStartAt - sc.before.length)
0096                   );
0097             else
0098                 // Need to remove a redundant padding
0099                 document.removeText(line, gSameLineCommentStartAt, line, sc.before.length);
0100             // Keep cursor at the place we've found it before
0101             view.setCursorPosition(cursor);
0102         }
0103         else if (gSameLineCommentStartAt < rbefore.length)
0104         {
0105             // Move inline comment before the current line
0106             var startPos = document.firstColumn(line);
0107             var currentLineText = String().fill(' ', startPos) + "//" + sc.after.rtrim() + "\n";
0108             document.removeText(line, rbefore.length, line, document.lineLength(line));
0109             document.insertText(line, 0, currentLineText);
0110             // Keep cursor at the place we've found it before
0111             view.setCursorPosition(new Cursor(line + 1, cursor.column));
0112             return true;
0113         }
0114     }
0115     return false;
0116 }
0117 
0118 
0119 function tryIndentRelativePrevNonCommentLine(line)
0120 {
0121     var current_line = line - 1;
0122     while (0 <= current_line && isStringOrComment(current_line, document.firstColumn(current_line)))
0123         --current_line;
0124     if (current_line == -1)
0125         return -2;
0126 
0127     var prevLineFirstChar = document.firstChar(current_line);
0128     var needHalfUnindent = !(
0129         prevLineFirstChar == ','
0130       || prevLineFirstChar == ':'
0131       || prevLineFirstChar == '?'
0132       || prevLineFirstChar == '<'
0133       || prevLineFirstChar == '>'
0134       || prevLineFirstChar == '&'
0135       );
0136     return document.firstColumn(current_line) - (needHalfUnindent ? 2 : 0);
0137 }
0138 
0139 /**
0140  * Try to keep same-line comment.
0141  * I.e. if \c ENTER was hit on a line w/ inline comment and before it,
0142  * try to keep it on a previous line...
0143  */
0144 function tryToKeepInlineComment(line)
0145 {
0146     // Make sure that there is some text still present on a prev line
0147     // i.e. it was just splitted and same-line-comment must be moved back to it
0148     if (document.line(line - 1).trim().length == 0)
0149         return;
0150 
0151     // Check is there any comment on the current (non empty) line
0152     var sc = splitByComment(line);
0153     dbg("sc.hasComment="+sc.hasComment);
0154     dbg("sc.before.rtrim().length="+sc.before.rtrim().length);
0155     if (sc.hasComment)
0156     {
0157         // Ok, here is few cases possible when ENTER pressed in different positions
0158         // |  |smth|was here; |        |// comment
0159         //
0160         // If sc.before has some text, it means that cursor was in the middle of some
0161         // non-commented text, and part of it left on a prev line, so we have to move
0162         // the comment back to that line...
0163         if (sc.before.trim().length > 0)                    // Is there some text before comment?
0164         {
0165             var lastPos = document.lastColumn(line - 1);    // Get last position of non space char @ prev line
0166             // Put the comment text to the prev line w/ padding
0167             document.insertText(
0168                 line - 1
0169               , lastPos + 1
0170               , String().fill(' ', gSameLineCommentStartAt - lastPos - 1)
0171                   + "//"
0172                   + sc.after.rtrim()
0173               );
0174             // Remove it from current line starting from current position
0175             // 'till the line end
0176             document.removeText(line, sc.before.rtrim().length, line, document.lineLength(line));
0177         }
0178         else
0179         {
0180             // No text before comment. Need to remove possible spaces from prev line...
0181             var prevLine = line - 1;
0182             document.removeText(
0183                 prevLine
0184               , document.lastColumn(prevLine) + 1
0185               , prevLine
0186               , document.lineLength(prevLine)
0187               );
0188         }
0189     }
0190 }
0191 
0192 /**
0193  * Return a current preprocessor indentation level
0194  * \note <em>preprocessor indentation</em> means how deep the current line
0195  * inside of \c #if directives.
0196  * \warning Negative result means that smth wrong w/ a source code
0197  */
0198 function getPreprocessorLevelAt(line)
0199 {
0200     // Just follow towards start and count #if/#endif directives
0201     var currentLine = line;
0202     var result = 0;
0203     while (currentLine >= 0)
0204     {
0205         currentLine--;
0206         var currentLineText = document.line(currentLine);
0207         if (currentLineText.search(/^\s*#\s*(if|ifdef|ifndef)\s+.*$/) != -1)
0208             result++;
0209         else if (currentLineText.search(/^\s*#\s*endif.*$/) != -1)
0210             result--;
0211     }
0212     return result;
0213 }
0214 
0215 /**
0216  * Check if \c ENTER was hit between ()/{}/[]/<>
0217  * \todo Match closing brace forward, put content between
0218  * braces on a separate line and align a closing brace.
0219  */
0220 function tryBraceSplit_ch(line)
0221 {
0222     var result = -1;
0223     // Get last char from previous line (opener) and a first from the current (closer)
0224     var firstCharPos = document.lastColumn(line - 1);
0225     var firstChar = document.charAt(line - 1, firstCharPos);
0226     var lastCharPos = document.firstColumn(line);
0227     var lastChar = document.charAt(line, lastCharPos);
0228 
0229     var isCurveBracketsMatched = (firstChar == '{' && lastChar == '}');
0230     var isBracketsMatched = isCurveBracketsMatched
0231         || (firstChar == '[' && lastChar == ']')
0232         || (firstChar == '(' && lastChar == ')')
0233         || (firstChar == '<' && lastChar == '>')
0234         ;
0235     if (isBracketsMatched)
0236     {
0237         var currentIndentation = document.firstVirtualColumn(line - 1);
0238         result = currentIndentation + gIndentWidth;
0239         document.insertText(line, document.firstColumn(line), "\n");
0240         document.indent(new Range(line + 1, 0, line + 1, 1), currentIndentation / gIndentWidth);
0241         // Add half-tab (2 spaces) if matched not a curve bracket or
0242         // open character isn't the only one on the line
0243         var isOpenCharTheOnlyOnLine = (document.firstColumn(line - 1) == firstCharPos);
0244         if (!(isCurveBracketsMatched || isOpenCharTheOnlyOnLine))
0245             document.insertText(line + 1, document.firstColumn(line + 1), "  ");
0246         view.setCursorPosition(line, result);
0247     }
0248     if (result != -1)
0249     {
0250         dbg("tryBraceSplit_ch result="+result);
0251         tryToKeepInlineComment(line);
0252     }
0253     return result;
0254 }
0255 
0256 /**
0257  * Even if counterpart brace not found (\sa \c tryBraceSplit_ch), align the current line
0258  * to one level deeper if last char on a previous line is one of open braces.
0259  * \code
0260  *     foo(|blah);
0261  *     // or
0262  *     {|
0263  *     // or
0264  *     smth<|blah, blah>
0265  *     // or
0266  *     array[|idx] = blah;
0267  * \endcode
0268  */
0269 function tryToAlignAfterOpenBrace_ch(line)
0270 {
0271     var result = -1;
0272     var pos = document.lastColumn(line - 1);
0273     var ch = document.charAt(line - 1, pos);
0274 
0275     if (ch == '(' || ch == '[')
0276     {
0277         result = document.firstColumn(line - 1) + gIndentWidth;
0278     }
0279     else if (ch == '{')
0280     {
0281         if (document.startsWith(line - 1, "namespace", true))
0282             result = 0;
0283         else
0284             result = document.firstColumn(line - 1) + gIndentWidth;
0285     }
0286     else if (ch == '<')
0287     {
0288         // Does it looks like 'operator<<'?
0289         if (document.charAt(line - 1, pos - 1) != '<')
0290             result = document.firstColumn(line - 1) + gIndentWidth;
0291         else
0292             result = document.firstColumn(line - 1) + (gIndentWidth / 2);
0293     }
0294 
0295     if (result != -1)
0296     {
0297         tryToKeepInlineComment(line);
0298         dbg("tryToAlignOpenBrace_ch result="+result);
0299     }
0300     return result;
0301 }
0302 
0303 function tryToAlignBeforeCloseBrace_ch(line)
0304 {
0305     var result = -1;
0306     var pos = document.firstColumn(line);
0307     var ch = document.charAt(line, pos);
0308 
0309     if (ch == '}' || ch == ')' || ch == ']')
0310     {
0311         var openBracePos = document.anchor(line, pos, ch);
0312         dbg("Found open brace @", openBracePos);
0313         if (openBracePos.isValid())
0314             result = document.firstColumn(openBracePos.line) + (ch == '}' ? 0 : (gIndentWidth / 2));
0315     }
0316     else if (ch == '>')
0317     {
0318         // TBD
0319     }
0320 
0321     if (result != -1)
0322     {
0323         tryToKeepInlineComment(line);
0324         dbg("tryToAlignBeforeCloseBrace_ch result="+result);
0325     }
0326     return result;
0327 }
0328 
0329 function tryToAlignBeforeComma_ch(line)
0330 {
0331     var result = -1;
0332     var pos = document.firstColumn(line);
0333     var ch = document.charAt(line, pos);
0334 
0335     if (line > 0 && (ch == ',' || ch == ';'))
0336     {
0337         var openBracePos = document.anchor(line, pos, '(');
0338         if (!openBracePos.isValid())
0339             openBracePos = document.anchor(line, pos, '[');
0340 
0341         if (openBracePos.isValid())
0342             result = document.firstColumn(openBracePos.line) + 2;
0343     }
0344 
0345     if (result != -1)
0346     {
0347         tryToKeepInlineComment(line);
0348         dbg("tryToAlignBeforeComma_ch result="+result);
0349     }
0350     return result;
0351 }
0352 
0353 /// Check if a multiline comment introduced on a previous line
0354 function tryMultilineCommentStart_ch(line)
0355 {
0356     var result = -1;
0357 
0358     // Check if multiline comment was started on the line
0359     // and ENTER wan't pressed right after a /*C-style comment*/
0360     if (document.startsWith(line - 1, "/*", true) && !document.endsWith(line - 1, "*/", true))
0361     {
0362         var filler = String().fill(' ', document.firstVirtualColumn(line - 1) + 1);
0363         var padding = filler + "* ";
0364         // If next line (if present) doesn't looks like a continue of the current comment,
0365         // then append a comment closer also...
0366         if ((line + 1) < document.lines())
0367         {
0368             // Maybe user wants to extend a multiline C-style/Doxygen comment
0369             // by pressing ENTER at start of it?
0370             if (!document.startsWith(line + 1, "*", true))
0371             {
0372                 // ... doesn't looks like a multiline comment
0373                 padding += "\n" + filler;
0374                 // Maybe user just splits a C-style comment?
0375                 if (!document.startsWith(line, "*/", true))
0376                     padding += document.endsWith(line, "*/", true) ? "* " : "*/";
0377                 else
0378                     document.removeText(line, 0, line, document.firstColumn(line))
0379             }                                               // else, no need to append a closing */
0380         }
0381         else                                                // There is no a next line...
0382         {
0383             padding += "\n" + filler;
0384             if (!document.startsWith(line, "*/", true))
0385                 padding += document.endsWith(line, "*/", true) ? "* " : "*/";
0386             else
0387                 document.removeText(line, 0, line, document.firstColumn(line))
0388         }
0389         document.insertText(line, 0, padding);
0390         view.setCursorPosition(line, filler.length + 2);
0391         result = -2;
0392     }
0393     if (result != -1)
0394     {
0395         dbg("tryMultilineCommentStart_ch result="+result);
0396     }
0397     return result;
0398 }
0399 
0400 /// Check if \c ENTER was hit inside or at last line of a multiline comment
0401 function tryMultilineCommentCont_ch(line)
0402 {
0403     var result = -1;
0404     // Check if multiline comment continued on the line:
0405     // 0) it starts w/ a start
0406     // 1) and followed by a space (i.e. it doesn't looks like a dereference) or nothing
0407     var firstCharPos = document.firstColumn(line - 1);
0408     var prevLineFirstChar = document.charAt(line - 1, firstCharPos);
0409     var prevLineSecondChar = document.charAt(line - 1, firstCharPos + 1);
0410     if (prevLineFirstChar == '*' && (prevLineSecondChar == ' ' || prevLineSecondChar == -1))
0411     {
0412         if (document.charAt(line - 1, firstCharPos + 1) == '/')
0413             // ENTER pressed after multiline comment: unindent 1 space!
0414             result = firstCharPos - 1;
0415         else
0416         {
0417             // Ok, ENTER pressed inside of the multiline comment:
0418             // just append one more line...
0419             var filler = String().fill(' ', document.firstColumn(line - 1));
0420             // Try to continue a C-style comment
0421             document.insertText(line, 0, filler + "* ");
0422             result = filler.length;
0423         }
0424     }
0425     if (result != -1)
0426     {
0427         dbg("tryMultilineCommentCont_ch result="+result);
0428     }
0429     return result;
0430 }
0431 
0432 function tryAfterCloseMultilineComment_ch(line)
0433 {
0434     var result = -1;
0435     if (document.startsWith(line - 1, "*/", true))
0436     {
0437         result = document.firstColumn(line - 1) - 1;
0438     }
0439     if (result != -1)
0440     {
0441         dbg("tryAfterCloseMultilineComment_ch result="+result);
0442     }
0443     return result;
0444 }
0445 
0446 /**
0447  * Check if a current line has a text after cursor position
0448  * and a previous one has a comment, then append a <em>"// "</em>
0449  * before cursor and realign if latter was inline comment...
0450  */
0451 function trySplitComment_ch(line)
0452 {
0453     var result = -1;
0454     if (document.lastColumn(line) != -1)
0455     {
0456         // Ok, current line has some text after...
0457         // NOTE There is should be at least one space between
0458         // the text and the comment
0459         var match = /^(.*\s)(\/\/)(.*)$/.exec(document.line(line - 1));
0460         if (match != null && 0 < match[3].trim().length)    // If matched and there is some text in a comment
0461         {
0462             if (0 < match[1].trim().length)                 // Is there some text before the comment?
0463             {
0464                 // Align comment to gSameLineCommentStartAt
0465                 result = gSameLineCommentStartAt;
0466             }
0467             else
0468             {
0469                 result = match[1].length;
0470             }
0471             var leadMatch = /^([^\s]*\s+).*$/.exec(match[3]);
0472             var lead = "";
0473             if (leadMatch != null)
0474                 lead = leadMatch[1];
0475             else
0476                 lead = " ";
0477             document.insertText(line, 0, "//" + lead);
0478         }
0479     }
0480     if (result != -1)
0481     {
0482         dbg("trySplitComment_ch result="+result);
0483     }
0484     return result;
0485 }
0486 
0487 /**
0488  * \brief Indent a next line after some keywords.
0489  *
0490  * Incrase indent after the following keywords:
0491  * - \c if
0492  * - \c else
0493  * - \c for
0494  * - \c while
0495  * - \c do
0496  * - \c case
0497  * - \c default
0498  * - \c return
0499  * - and access modifiers \c public, \c protected and \c private
0500  */
0501 function tryIndentAfterSomeKeywords_ch(line)
0502 {
0503     var result = -1;
0504     // Check if ENTER was pressed after some keywords...
0505     var sr = splitByComment(line - 1);
0506     var prevString = sr.before;
0507     dbg("tryIndentAfterSomeKeywords_ch prevString='"+prevString+"'");
0508     var r = /^(\s*)((if|for|while)\s*\(|\bdo\b|\breturn\b|(((public|protected|private)(\s+(slots|Q_SLOTS))?)|default|case\s+.*)\s*:).*$/
0509       .exec(prevString);
0510     if (r != null)
0511     {
0512         dbg("r=",r);
0513         if (!r[2].startsWith("return") || !prevString.rtrim().endsWith(';'))
0514             result = r[1].length + gIndentWidth;
0515     }
0516     else
0517     {
0518         r = /^\s*\belse\b.*$/.exec(prevString)
0519         if (r != null)
0520         {
0521             var prevPrevString = stripComment(line - 2);
0522             dbg("tryIndentAfterSomeKeywords_ch prevPrevString='"+prevPrevString+"'");
0523             if (prevPrevString.endsWith('}'))
0524                 result = document.firstColumn(line - 2);
0525             else if (prevPrevString.match(/^\s*[\])>]/))
0526                 result = document.firstColumn(line - 2) - gIndentWidth - (gIndentWidth / 2);
0527             else
0528                 result = document.firstColumn(line - 2) - gIndentWidth;
0529             // Realign 'else' statement if needed
0530             var pp = document.firstColumn(line - 1);
0531             if (pp < result)
0532                 document.insertText(line - 1, 0, String().fill(' ', result - pp));
0533             else if (result < pp)
0534                 document.removeText(line - 1, 0, line - 1, pp - result);
0535             result += gIndentWidth;
0536         }
0537     }
0538     if (result != -1)
0539     {
0540         tryToKeepInlineComment(line);
0541         dbg("tryIndentAfterSomeKeywords_ch result="+result);
0542     }
0543     return result;
0544 }
0545 
0546 /**
0547  * Try to indent a line right after a dangling semicolon
0548  * (possible w/ leading close braces and comment after)
0549  * \code
0550  *     foo(
0551  *         blah
0552  *     );|
0553  * \endcode
0554  */
0555 function tryAfterDanglingSemicolon_ch(line)
0556 {
0557     var result = -1;
0558     var prevString = document.line(line - 1);
0559     var r = /^(\s*)(([\)\]}]?\s*)*([\)\]]\s*))?;/.exec(prevString);
0560     if (r != null)
0561     {
0562         result = Math.floor(r[1].length / 4) * 4;
0563     }
0564     else
0565     {
0566         // Does it looks like a template tail?
0567         // i.e. smth like this:
0568         // typedef boost::mpl::blah<
0569         //    params
0570         //  > type;|
0571         r = /^(\s*)([>]+).*;/.exec(prevString);
0572         if (r != null)
0573             result = Math.floor(r[1].length / 4) * 4;
0574     }
0575     if (result != -1)
0576     {
0577         tryToKeepInlineComment(line);
0578         dbg("tryDanglingSemicolon_ch result="+result);
0579     }
0580     return result;
0581 }
0582 
0583 /**
0584  * Check if \c ENTER pressed after equal sign
0585  * \code
0586  *     blah =
0587  *         |blah
0588  * \endcode
0589  */
0590 function tryAfterEqualChar_ch(line)
0591 {
0592     var result = -1;
0593     var pos = document.lastColumn(line - 1);
0594     if (document.charAt(line - 1, pos) == '=')
0595         result = document.firstColumn(line - 1) + gIndentWidth;
0596     if (result != -1)
0597     {
0598         tryToKeepInlineComment(line);
0599         dbg("tryAfterEqualChar_ch result="+result);
0600     }
0601     return result;
0602 }
0603 
0604 /// Check if \c ENTER hits after \c #define w/ a backslash
0605 function tryMacroDefinition_ch(line)
0606 {
0607     var result = -1;
0608     var prevString = document.line(line - 1);
0609     if (prevString.search(/^\s*#\s*define\s+.*\\$/) != -1)
0610         result = gIndentWidth;
0611     if (result != -1)
0612     {
0613         dbg("tryMacroDefinition_ch result="+result);
0614     }
0615     return result;
0616 }
0617 
0618 /**
0619  * Do not incrase indent if ENTER pressed before access
0620  * specifier (i.e. public/private/protected)
0621  */
0622 function tryBeforeAccessSpecifier_ch(line)
0623 {
0624     var result = -1;
0625     if (document.line(line).match(/(public|protected|private):/))
0626     {
0627         var openPos = document.anchor(line, 0, '{');
0628         if (openPos.isValid())
0629             result = document.firstColumn(openPos.line);
0630     }
0631     if (result != -1)
0632     {
0633         tryToKeepInlineComment(line);
0634         dbg("tryBeforeAccessSpecifier_ch result="+result);
0635     }
0636     return result;
0637 }
0638 
0639 /**
0640  * Try to align a line w/ a leading (word) delimiter symbol
0641  * (i.e. not an identifier and a brace)
0642  */
0643 function tryBeforeDanglingDelimiter_ch(line)
0644 {
0645     var result = -1;
0646     var halfTabNeeded =
0647         // current line do not starts w/ a comment
0648         !document.line(line).ltrim().startsWith("//")
0649         // if a previous line starts w/ an identifier
0650       && (document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1)
0651         // but the current one starts w/ a delimiter (which is looks like operator)
0652       && (document.line(line).search(/^\s*[,%&<=:\|\-\?\/\+\*\.]/) != -1)
0653       ;
0654     // check if we r at function call or array index
0655     var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid()
0656       || document.anchor(line, document.firstColumn(line), '[').isValid()
0657       ;
0658     if (halfTabNeeded)
0659         result = document.firstVirtualColumn(line - 1) + (insideBraces ? -2 : 2);
0660     if (result != -1)
0661     {
0662         tryToKeepInlineComment(line);
0663         dbg("tryBeforeDanglingDelimiter_ch result="+result);
0664     }
0665     return result;
0666 }
0667 
0668 function tryPreprocessor_ch(line)
0669 {
0670     var result = -1;
0671     if (document.firstChar(line) == '#')
0672     {
0673         result = 0;
0674         var text = document.line(line);
0675         // Get current depth level
0676         var currentLevel = getPreprocessorLevelAt(line);
0677         if (currentLevel > 0)
0678         {
0679             // How much spaces we have after hash?
0680             var spacesCnt = 0;
0681             var column = document.firstColumn(line) + 1;
0682             var i = column;
0683             for (; i < text.length; i++)
0684             {
0685                 if (text[i] != ' ')
0686                     break;
0687                 spacesCnt++;
0688             }
0689             var wordAfterHash = document.wordAt(line, i);
0690             dbg("wordAfterHash='"+wordAfterHash+"'");
0691             if (wordAfterHash[0] == '#')
0692                 wordAfterHash = wordAfterHash.substring(1, wordAfterHash.length);
0693             if (wordAfterHash == "else" || wordAfterHash == "elif" || wordAfterHash == "endif")
0694                 currentLevel--;
0695             var paddingLen = (currentLevel == 0) ? 0 : (currentLevel - 1) * 2 + 1;
0696             if (spacesCnt < paddingLen)
0697             {
0698                 var padding = String().fill(' ', paddingLen - spacesCnt);
0699                 document.insertText(line, column, padding);
0700             }
0701             else if (paddingLen < spacesCnt)
0702             {
0703                 document.removeText(line, column, line, column + spacesCnt - paddingLen);
0704             }
0705         }
0706     }
0707     if (result != -1)
0708     {
0709         dbg("tryPreprocessor_ch result="+result);
0710     }
0711     return result;
0712 }
0713 
0714 /**
0715  * Check if \c ENTER was pressed on a start of line and
0716  * after a block comment.
0717  */
0718 function tryAfterBlockComment_ch(line)
0719 {
0720     var result = -1;
0721     if (0 < line)
0722     {
0723         var prev_non_empty_line = document.prevNonEmptyLine(line - 1);
0724         if (prev_non_empty_line != -1 && document.line(prev_non_empty_line).trim().startsWith("*/"))
0725         {
0726             var p = document.firstColumn(prev_non_empty_line);
0727             if ((p % gIndentWidth) != 0)
0728                 result = Math.floor(p / gIndentWidth) * gIndentWidth;
0729         }
0730     }
0731     if (result != -1)
0732     {
0733         dbg("tryAfterBlockComment_ch result="+result);
0734     }
0735     return result;
0736 }
0737 
0738 /**
0739  * Check if \c ENTER was pressed after \c break or \c continue statements
0740  * and if so, unindent the current line.
0741  */
0742 function tryAfterBreakContinue_ch(line)
0743 {
0744     var result = -1;
0745     var currentLineText = document.line(line - 1).ltrim();
0746     var should_proceed = currentLineText.startsWith("break;") || currentLineText.startsWith("continue;")
0747     if (should_proceed)
0748     {
0749         result = document.firstColumn(line - 1) - gIndentWidth;
0750     }
0751     if (result != -1)
0752     {
0753         dbg("tryAfterBreakContinue_ch result="+result);
0754     }
0755     return result;
0756 }
0757 
0758 /// \internal
0759 function getStringAligmentAfterSplit(line)
0760 {
0761     var prevLineFirstChar = document.firstChar(line - 1);
0762     var halfIndent = prevLineFirstChar == ','
0763       || prevLineFirstChar == ':'
0764       || prevLineFirstChar == '?'
0765       || prevLineFirstChar == '<'
0766       || prevLineFirstChar == '>'
0767       || prevLineFirstChar == '&'
0768       ;
0769     return document.firstColumn(line - 1) + (
0770         prevLineFirstChar != '"'
0771       ? (halfIndent ? (gIndentWidth / 2) : gIndentWidth)
0772       : 0
0773       );
0774 }
0775 
0776 /**
0777  * Handle the case when \c ENTER has pressed in the middle of a string.
0778  * Find a string begin (a quote char) and analyze if it is a C++11 raw
0779  * string literal. If it is not, add a "closing" quote to a previous line
0780  * and to the current one. Align a 2nd part (the moved down one) of a string
0781  * according a previous line. If latter is a pure string, then give the same
0782  * indentation level, otherwise incrase it to one \c TAB.
0783  *
0784  * Here is few cases possible:
0785  * - \c ENTER has pressed in a line <code>auto some = ""|</code>, so a new
0786  *   line just have an empty string or some text which is doesn't matter now;
0787  * - \c ENTER has pressed in a line <code>auto some = "possible some text here| and here"</code>,
0788  *   then a new line will have <code> and here"</code> text
0789  *
0790  * In both cases attribute at (line-1, lastColumn-1) will be \c String
0791  */
0792 function trySplitString_ch(line)
0793 {
0794     var result = -1;
0795     var column = document.lastColumn(line - 1);
0796 
0797     if (isComment(line - 1, column))
0798         return result;                                      // Do nothing for comments
0799 
0800     // Check if last char on a prev line has string attribute
0801     var lastColumnIsString = isString(line - 1, column);
0802     var firstColumnIsString = isString(line, 0);
0803     var firstChar = (document.charAt(line, 0) == '"');
0804     if (!lastColumnIsString)                                // If it is not,
0805     {
0806         // TODO TBD
0807         if (firstColumnIsString && firstChar == '"')
0808             result = getStringAligmentAfterSplit(line);
0809         return result;                                      // then nothing to do...
0810     }
0811 
0812     var lastChar = (document.charAt(line - 1, column) == '"');
0813     var prevLastColumnIsString = isString(line - 1, column - 1);
0814     var prevLastChar = (document.charAt(line - 1, column - 1) == '"');
0815     dbg("trySplitString_ch: lastColumnIsString="+lastColumnIsString);
0816     dbg("trySplitString_ch: lastChar="+lastChar);
0817     dbg("trySplitString_ch: prevLastColumnIsString="+prevLastColumnIsString);
0818     dbg("trySplitString_ch: prevLastChar="+prevLastChar);
0819     dbg("trySplitString_ch: isString(line,0)="+firstColumnIsString);
0820     dbg("trySplitString_ch: firstChar="+firstChar);
0821     var startOfString = firstColumnIsString && firstChar;
0822     var endOfString = !(firstColumnIsString || firstChar);
0823     var should_proceed = !lastChar && prevLastColumnIsString && (endOfString || !prevLastChar && startOfString)
0824       || lastChar && !prevLastColumnIsString && !prevLastChar && (endOfString || startOfString)
0825       ;
0826     dbg("trySplitString_ch: ------ should_proceed="+should_proceed);
0827     if (should_proceed)
0828     {
0829         // Add closing quote to the previous line
0830         document.insertText(line - 1, document.lineLength(line - 1), '"');
0831         // and open quote to the current one
0832         document.insertText(line, 0, '"');
0833         // NOTE If AutoBrace plugin is used, it won't add a quote
0834         // char, if cursor positioned right before another quote char
0835         // (which was moved from a line above in this case)...
0836         // So, lets force it!
0837         if (startOfString && document.charAt(line, 1) != '"')
0838         {
0839             document.insertText(line, 1, '"');              // Add one more!
0840             view.setCursorPosition(line, 1);                // Step back inside of string
0841         }
0842         result = getStringAligmentAfterSplit(line);
0843     }
0844     if (result != -1)
0845     {
0846         dbg("trySplitString_ch result="+result);
0847         tryToKeepInlineComment(line);
0848     }
0849     return result;
0850 }
0851 
0852 /**
0853  * Here is few cases possible:
0854  * \code
0855  *  // set some var to lambda function
0856  *  auto some = [foo](bar)|
0857  *
0858  *  // lambda as a parameter (possible the first one,
0859  *  // i.e. w/o a leading comma)
0860  *  std::foreach(
0861  *      begin(container)
0862  *    , end(container)
0863  *    , [](const value_type& v)|
0864  *    );
0865  * \endcode
0866  */
0867 function tryAfterLambda_ch(line)
0868 {
0869     var result = -1;
0870     var column = document.lastColumn(line - 1);
0871 
0872     if (isComment(line - 1, column))
0873         return result;                                      // Do nothing for comments
0874 
0875     var sr = splitByComment(line - 1);
0876     if (sr.before.match(/\[[^\]]*\]\([^{]*\)[^{}]*$/))
0877     {
0878         var align = document.firstColumn(line - 1);
0879         var before = sr.before.ltrim();
0880         if (before.startsWith(','))
0881             align += 2;
0882         var padding = String().fill(' ', align);
0883         var tail = before.startsWith('auto ') ? "};" : "}";
0884         document.insertText(
0885             line
0886           , 0
0887           , padding + "{\n" + padding + String().fill(' ', gIndentWidth) + "\n" + padding + tail
0888           );
0889         view.setCursorPosition(line + 1, align + gIndentWidth);
0890         result = -2;
0891     }
0892 
0893     if (result != -1)
0894     {
0895         dbg("tryAfterLambda_ch result="+result);
0896     }
0897     return result;
0898 }
0899 
0900 /// Wrap \c tryToKeepInlineComment as \e caret-handler
0901 function tryToKeepInlineComment_ch(line)
0902 {
0903     tryToKeepInlineComment(line);
0904     return -1;
0905 }
0906 
0907 /**
0908  * \brief Handle \c ENTER key
0909  */
0910 function caretPressed(cursor)
0911 {
0912     var result = -1;
0913     var line = cursor.line;
0914 
0915     // Dunno what to do if previous line isn't available
0916     if (line - 1 < 0)
0917         return result;                                      // Nothing (dunno) to do if no previous line...
0918 
0919     // Register all indent functions
0920     var handlers = [
0921         tryBraceSplit_ch                                    // Handle ENTER between braces
0922       , tryMultilineCommentStart_ch
0923       , tryMultilineCommentCont_ch
0924       , tryAfterCloseMultilineComment_ch
0925       , trySplitComment_ch
0926       , tryToAlignAfterOpenBrace_ch                         // Handle {,[,(,< on a previous line
0927       , tryToAlignBeforeCloseBrace_ch                       // Handle },],),> on a current line before cursor
0928       , tryToAlignBeforeComma_ch                            // Handle ENTER pressed before comma or semicolon
0929       , tryIndentAfterSomeKeywords_ch                       // NOTE It must follow after trySplitComment_ch!
0930       , tryAfterDanglingSemicolon_ch
0931       , tryMacroDefinition_ch
0932       , tryBeforeDanglingDelimiter_ch
0933       , tryBeforeAccessSpecifier_ch
0934       , tryAfterEqualChar_ch
0935       , tryPreprocessor_ch
0936       , tryAfterBlockComment_ch
0937       , tryAfterBreakContinue_ch
0938       , trySplitString_ch                                   // Handle ENTER pressed in the middle of a string
0939       , tryAfterLambda_ch                                   // Handle ENTER after lambda prototype and before body
0940       , tryToKeepInlineComment_ch                           // NOTE This must be a last checker!
0941     ];
0942 
0943     // Apply all functions until result gets changed
0944     for (
0945         var i = 0
0946       ; i < handlers.length && result == -1
0947       ; result = handlers[i++](line)
0948       );
0949 
0950     return result;
0951 }
0952 
0953 /**
0954  * \brief Handle \c '/' key pressed
0955  *
0956  * Check if is it start of a comment. Here is few cases possible:
0957  * \li very first \c '/' -- do nothing
0958  * \li just entered \c '/' is a second in a sequence. If no text before or some present after,
0959  *     do nothing, otherwise align a \e same-line-comment to \c gSameLineCommentStartAt
0960  *     position.
0961  * \li just entered \c '/' is a 3rd in a sequence. If there is some text before and no after,
0962  *     it looks like inlined doxygen comment, so append \c '<' char after. Do nothing otherwise.
0963  * \li if there is a <tt>'// '</tt> string right before just entered \c '/', form a
0964  *     doxygen comment <tt>'///'</tt> or <tt>'///<'</tt> depending on presence of some text
0965  *     on a line before the comment.
0966  *
0967  * \todo Due the BUG #316809 in a current version of Kate, this code doesn't work as expected!
0968  * It always returns a <em>"NormalText"</em>!
0969  * \code
0970  * var cm = document.attributeName(cursor);
0971  * if (cm.indexOf("String") != -1)
0972  *    return;
0973  * \endcode
0974  *
0975  * \bug This code doesn't work properly in the following case:
0976  * \code
0977  *  std::string bug = "some text//
0978  * \endcode
0979  *
0980  */
0981 function trySameLineComment(cursor)
0982 {
0983     var line = cursor.line;
0984     var column = cursor.column;
0985 
0986     // First of all check that we are not withing a string
0987     if (document.isString(line, column))
0988         return;
0989 
0990     var sc = splitByComment(line);
0991     if (sc.hasComment)                                      // Is there any comment on a line?
0992     {
0993         // Make sure we r not in a comment already -- it can be a multiline one...
0994         var fc = document.firstColumn(line);
0995         var text = document.line(line).ltrim();
0996         var nothing_to_do = (fc < (column - 1)) && document.isComment(line, fc);
0997         // Also check that line has smth that needs to be "fixed"...
0998         if (nothing_to_do && text != "//" && text != "// /" && text != "///" && text != "/// /")
0999             return;
1000         // If no text after the comment and it still not aligned
1001         var text_len = sc.before.rtrim().length;
1002         if (text_len != 0 && sc.after.length == 0 && text_len < gSameLineCommentStartAt)
1003         {
1004             // Align it!
1005             document.insertText(
1006                 line
1007               , column - 2
1008               , String().fill(' ', gSameLineCommentStartAt - sc.before.length)
1009               );
1010             document.insertText(line, gSameLineCommentStartAt + 2, ' ');
1011         }
1012         // If text in a comment equals to '/' or ' /' -- it looks like a 3rd '/' pressed
1013         else if (sc.after == " /" || sc.after == "/")
1014         {
1015             // Form a Doxygen comment!
1016             document.removeText(line, column - sc.after.length, line, column);
1017             document.insertText(line, column - sc.after.length, text_len != 0 ? "/< " : "/ ");
1018         }
1019         // If right trimmed text in a comment equals to '/' -- it seems user moves cursor
1020         // one char left (through space) to add one more '/'
1021         else if (sc.after.rtrim() == "/")
1022         {
1023             // Form a Doxygen comment!
1024             document.removeText(line, column, line, column + sc.after.length);
1025             document.insertText(line, column, text_len != 0 ? "< " : " ");
1026         }
1027         else if (sc.after == "/ /")
1028         {
1029             // Looks like user wants to "draw a fence" w/ '/'s
1030             document.removeText(line, column - 2, line, column - 1);
1031         }
1032         else if (text_len == 0 && sc.after.length == 0)
1033         {
1034             document.insertText(line, column, ' ');
1035         }
1036     }
1037 }
1038 
1039 /**
1040  * \brief Maybe '>' needs to be added?
1041  *
1042  * Here is a few cases possible:
1043  * \li user entered <em>"template &gt;</em>
1044  * \li user entered smth like <em>std::map&gt;</em>
1045  * \li user wants to output smth to C++ I/O stream by typing <em>&gt;&gt;</em>
1046  *     possible at the line start, so it must be half indented
1047  * \li shortcut: <tt>some(<|)</tt> transformed into <tt>some()<|</tt>
1048  *
1049  * But, do not add '>' if there some text after cursor.
1050  */
1051 function tryTemplate(cursor)
1052 {
1053     var line = cursor.line;
1054     var column = cursor.column;
1055     var result = -2;
1056 
1057     if (isStringOrComment(line, column))
1058         return result;                                      // Do nothing for comments and strings
1059 
1060     // Check for 'template' keyword at line start
1061     var currentString = document.line(line);
1062     var prevWord = document.wordAt(line, column - 1);
1063     dbg("tryTemplate: prevWord='"+prevWord+"'");
1064     dbg("tryTemplate: prevWord.match="+prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/));
1065     // Add a closing angle bracket if a prev word is not a 'operator'
1066     // and it looks like an identifier or current line starts w/ 'template' keyword
1067     var isCloseAngleBracketNeeded = (prevWord != "operator")
1068       && (currentString.match(/^\s*template\s*<$/) || prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/))
1069       && (column == document.lineLength(line) || document.charAt(cursor).match(/\W/))
1070       ;
1071     if (isCloseAngleBracketNeeded)
1072     {
1073         document.insertText(cursor, ">");
1074         view.setCursorPosition(cursor);
1075     }
1076     else if (justEnteredCharIsFirstOnLine(line, column, '<'))
1077     {
1078         result = tryIndentRelativePrevNonCommentLine(line);
1079     }
1080     // Add a space after 2nd '<' if a word before is not a 'operator'
1081     else if (document.charAt(line, column - 2) == '<')
1082     {
1083         if (document.wordAt(line, column - 3) != "operator")
1084         {
1085             // Looks like case 3...
1086             // 0) try to remove '>' if user typed 'some<' before
1087             // (and closing '>' was added by tryTemplate)
1088             if (column < document.lineLength(line) && document.charAt(line, column) == '>')
1089             {
1090                 document.removeText(line, column, line, column + 1);
1091                 addCharOrJumpOverIt(line, column - 2, ' ');
1092                 view.setCursorPosition(line, column + 1);
1093                 column = column + 1;
1094             }
1095             // add a space after operator<<
1096             document.insertText(line, column, " ");
1097         }
1098         else
1099         {
1100             document.insertText(line, column, "()");
1101             view.setCursorPosition(line, column + 1);
1102         }
1103     }
1104     else
1105     {
1106         cursor = tryJumpOverParenthesis(cursor);            // Try to jump out of parenthesis
1107         tryAddSpaceAfterClosedBracketOrQuote(cursor);
1108     }
1109     return result;
1110 }
1111 
1112 /**
1113  * This function called for some characters and trying to do the following:
1114  * if the cursor (right after a trigger character is entered) is positioned withing
1115  * a parenthesis, move the entered character out of parenthesis.
1116  *
1117  * For example:
1118  * \code
1119  *  auto a = two_params_func(get_first(,|))
1120  *  // ... transformed into
1121  *  auto a = two_params_func(get_first(),|)
1122  * \endcode
1123  *
1124  * because entering comma right after <tt>(</tt> definitely incorrect, but
1125  * we can help the user (programmer) to avoid 3 key presses ;-)
1126  * (RightArrow, ',', space)
1127  *
1128  * except comma here are other "impossible" characters:
1129  * \c ., \c ?, \c :, \c %, \c |, \c /, \c =, \c <, \c >, \c ], \c }
1130  *
1131  * But \c ; will be handled separately to be able to jump over all closing \c ).
1132  *
1133  * \sa \c trySemicolon()
1134  *
1135  * \note This valid if we r not inside a comment or a string literal,
1136  * and the char out of the parenthesis is not the same as just entered ;-)
1137  *
1138  * \param cursor initial cursor position
1139  * \param es edit session instance
1140  * \return new (possible modified) cursor position
1141  */
1142 function tryJumpOverParenthesis(cursor)
1143 {
1144     var line = cursor.line;
1145     var column = cursor.column;
1146 
1147     if (2 < column && isStringOrComment(line, column))
1148         return cursor;                                      // Do nothing for comments of string literals
1149 
1150     // Check that we r inside of parenthesis and some symbol between
1151     var pc = document.charAt(line, column - 2);
1152     var cc = document.charAt(cursor);
1153     if ((pc == '(' && cc == ')') || (pc == '{' && cc == '}'))
1154     {
1155         var c = document.charAt(line, column - 1);
1156         switch (c)
1157         {
1158             case '.':
1159                 if (pc == '(' && cc == ')')
1160                 {
1161                     // Make sure this is not a `catch (...)`
1162                     if (document.startsWith(line, "catch (.)", true))
1163                     {
1164                         document.insertText(line, column, "..");
1165                         view.setCursorPosition(line, column + 3);
1166                         break;
1167                     }
1168                 }
1169             case ',':
1170             case '?':
1171             case ':':
1172             case '%':
1173             case '^':
1174             case '|':
1175             case '/':                                       // TODO ORLY?
1176             case '=':
1177             case '<':
1178             case '>':
1179             case '}':
1180             case ')':
1181             case ']':                                       // NOTE '[' could be a part of lambda
1182             {
1183                 // Ok, move character out of parenthesis
1184                 document.removeText(line, column - 1, line, column);
1185                 // Check is a character after the closing brace the same as just entered one
1186                 addCharOrJumpOverIt(line, column, c);
1187                 return view.cursorPosition();
1188             }
1189             default:
1190                 break;
1191         }
1192     }
1193     return cursor;
1194 }
1195 
1196 /**
1197  * Handle the case when some character was entered after a some closing bracket.
1198  * Here is few close brackets possible:
1199  * \li \c ) -- ordinal function call
1200  * \li \c } -- C++11 constructor call
1201  * \li \c ] -- array access
1202  * \li \c " -- end of a string literal
1203  * \li \c ' -- and of a char literal
1204  *
1205  * This function try to add a space between a closing quote/bracket and operator char.
1206  *
1207  * \note This valid if we r not inside a comment or a string literal.
1208  */
1209 function tryAddSpaceAfterClosedBracketOrQuote(cursor)
1210 {
1211     var line = cursor.line;
1212     var column = cursor.column;
1213 
1214     if (isStringOrComment(line, column - 1))
1215         return cursor;                                      // Do nothing for comments of string literals
1216 
1217     // Check if we have a closing bracket before a last entered char
1218     var b = document.charAt(line, column - 2);
1219     if (!(b == ']' || b == '}' || b == ')' || b == '"' || b == "'"))
1220         return cursor;
1221 
1222     // Ok, lets check what we've got as a last char
1223     var c = document.charAt(line, column - 1);
1224     dbg("tryAddSpaceAfterClosedBracketOrQuote: c='"+c+"', @"+new Cursor(line, column-1));
1225     switch (c)
1226     {
1227         case '*':
1228         case '/':
1229         case '%':
1230         case '&':
1231         case '|':
1232         case '=':
1233         case '^':
1234         case '?':
1235         case ':':
1236         case '<':
1237             document.insertText(line, column - 1, " ");
1238             view.setCursorPosition(line, column + 1);
1239             return view.cursorPosition();
1240         case '>':
1241             // Close angle bracket may be a part of template instantiation
1242             // w/ some function type parameter... Otherwise, add a space after.
1243             if (b != ')')
1244             {
1245                 document.insertText(line, column - 1, " ");
1246                 view.setCursorPosition(line, column + 1);
1247                 return view.cursorPosition();
1248             }
1249             break;
1250         default:
1251             break;
1252     }
1253     return cursor;
1254 }
1255 
1256 /**
1257  * \brief Try to align parameters list
1258  *
1259  * If (just entered) comma is a first symbol on a line,
1260  * just move it on a half-tab left relative to a previous line
1261  * (if latter doesn't starts w/ comma or ':').
1262  * Do nothing otherwise. A space would be added after it anyway.
1263  */
1264 function tryComma(cursor)
1265 {
1266     var result = -2;
1267     var line = cursor.line;
1268     var column = cursor.column;
1269     // Check is comma a very first character on a line...
1270     if (justEnteredCharIsFirstOnLine(line, column, ','))
1271     {
1272         result = tryIndentRelativePrevNonCommentLine(line);
1273     }
1274     else
1275     {
1276         // Try to stick a comma to a previous non-space char
1277         var lastWordPos = document.text(line, 0, line, column - 1).rtrim().length;
1278         if (lastWordPos < column)
1279         {
1280             document.removeText(line, column - 1, line, column);
1281             document.insertText(line, lastWordPos, ",");
1282             cursor = new Cursor(line, lastWordPos + 1);
1283             view.setCursorPosition(cursor);
1284         }
1285     }
1286 
1287     cursor = tryJumpOverParenthesis(cursor);                // Try to jump out of parenthesis
1288     addCharOrJumpOverIt(cursor.line, cursor.column, ' ');
1289     return result;
1290 }
1291 
1292 /**
1293  * \brief Move towards a document start and look for control flow keywords
1294  *
1295  * \note Keyword must be at the line start
1296  *
1297  * \return found line's indent, otherwise \c -2 if nothing found
1298  */
1299 function tryBreakContinue(line, is_break)
1300 {
1301     var result = -2;
1302     // Ok, look backward and find a loop/switch statement
1303     for (; 0 <= line; --line)
1304     {
1305         var text = document.line(line).ltrim();
1306         var is_loop_or_switch = text.startsWith("for ")
1307           || text.startsWith("do ")
1308           || text.startsWith("while ")
1309           || text.startsWith("if ")
1310           || text.startsWith("else if ")
1311           ;
1312         if (is_break)
1313             is_loop_or_switch =  is_loop_or_switch
1314               || text.startsWith("case ")
1315               || text.startsWith("default:")
1316               ;
1317         if (is_loop_or_switch)
1318             break;
1319     }
1320     if (line != -1)                                     // Found smth?
1321         result = document.firstColumn(line) + gIndentWidth;
1322 
1323     return result;
1324 }
1325 
1326 /**
1327  * \brief Handle \c ; character.
1328  *
1329  * Here is few cases possible (handled):
1330  * \li semicolon is a first char on a line -- then, it looks like \c for statement
1331  *      splitted accross the lines
1332  * \li semicolon entered after some keywords: \c break or \c continue, then we
1333  *      need to align this line taking in account a previous one
1334  * \li and finally here is a trick: when auto brackets extension enabled, and user types
1335  *      a function call like this:
1336  * \code
1337  *  auto var = some_call(arg1, arg2|)
1338  * \endcode
1339  * (\c '|' shows a cursor position). Note there is no final semicolon in this expression,
1340  * cuz pressing <tt>'('</tt> leads to the following snippet: <tt>some_call(|)</tt>, so to
1341  * add a semicolon you have to move cursor out of parenthesis. The trick is to allow to press
1342  * <tt>';'</tt> at position shown in the code snippet, so indenter will transform it into this:
1343  * \code
1344  *  auto var = some_call(arg1, arg2);|
1345  * \endcode
1346  * same works even there is no arguments...
1347  *
1348  * All the time, when simicolon is not a first non-space symbol (and not a part of a comment
1349  * or string) it will be stiked to the last non-space character on the line.
1350  */
1351 function trySemicolon(cursor)
1352 {
1353     var result = -2;
1354     var line = cursor.line;
1355     var column = cursor.column;
1356 
1357     if (isStringOrComment(line, column))
1358         return result;                                      // Do nothing for comments and strings
1359 
1360     // If ';' is a first char on a line?
1361     if (justEnteredCharIsFirstOnLine(line, column, ';'))
1362     {
1363         // Check if we are inside a `for' statement
1364         var openBracePos = document.anchor(line, column, '(');
1365         if (openBracePos.isValid())
1366         {
1367             // Add a half-tab relative '('
1368             result = document.firstColumn(openBracePos.line) + 2;
1369             document.insertText(cursor, " ");
1370         }
1371     }
1372     else
1373     {
1374         // Stick ';' to the last "word"
1375         var lcsc = document.prevNonSpaceColumn(line, column - 2);
1376         if (2 < column && lcsc < (column - 2))
1377         {
1378             document.removeText(line, column - 1, line, column);
1379             if (document.charAt(line, lcsc) != ';')
1380             {
1381                 document.insertText(line, lcsc + 1, ";");
1382                 view.setCursorPosition(line, lcsc + 2);
1383             }
1384             else view.setCursorPosition(line, lcsc + 1);
1385             cursor = view.cursorPosition();
1386             column = cursor.column;
1387         }
1388         var text = document.line(line).ltrim();
1389         var is_break = text.startsWith("break;");
1390         var should_proceed = is_break || text.startsWith("continue;")
1391         if (should_proceed)
1392         {
1393             result = tryBreakContinue(line - 1, is_break);
1394             if (result == -2)
1395                 result = -1;
1396         }
1397         // Make sure we r not inside of `for' statement
1398         /// \todo Make sure cursor is really inside of \c for and
1399         /// not smth like this: <tt>for (blah; blah; blah) some(arg,;|)</tt>
1400         else if (!text.startsWith("for "))
1401         {
1402             // Check if next character(s) is ')' and nothing after
1403             should_proceed = true;
1404             var lineLength = document.lineLength(line);
1405             for (var i = column; i < lineLength; ++i)
1406             {
1407                 var c = document.charAt(line, i);
1408                 if (!(c == ')' || c == ']'))
1409                 {
1410                     should_proceed = false;
1411                     break;
1412                 }
1413             }
1414             // Ok, lets move ';' out of "a(b(c(;)))" of any level...
1415             if (should_proceed)
1416             {
1417                 // Remove ';' from column - 1
1418                 document.removeText(line, column - 1, line, column);
1419                 // Append ';' to the end of line
1420                 document.insertText(line, lineLength - 1, ";");
1421                 view.setCursorPosition(line, lineLength);
1422                 cursor = view.cursorPosition();
1423                 column = cursor.column;
1424             }
1425         }
1426         // In C++ there is no need to have more than one semicolon.
1427         // So remove a redundant one!
1428         if (document.charAt(line, column - 2) == ';')
1429         {
1430             // Remove just entered ';'
1431             document.removeText(line, column - 1, line, column);
1432         }
1433     }
1434     return result;
1435 }
1436 
1437 /**
1438  * Handle possible dangling operators (moved from a previous line)
1439  *
1440  * \c ?, \c |, \c ^, \c %, \c .
1441  *
1442  * Add spaces around ternary operator.
1443  */
1444 function tryOperator(cursor, ch)
1445 {
1446     var result = -2;
1447     var line = cursor.line;
1448     var column = cursor.column;
1449 
1450     if (isStringOrComment(line, column))
1451         return result;                                      // Do nothing for comments and strings
1452 
1453     var halfTabNeeded = justEnteredCharIsFirstOnLine(line, column, ch)
1454       && document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1
1455       ;
1456     dbg("tryOperator: halfTabNeeded =", halfTabNeeded);
1457     if (halfTabNeeded)
1458     {
1459         // check if we r at function call or array index
1460         var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid()
1461           || document.anchor(line, document.firstColumn(line), '[').isValid()
1462           || document.anchor(line, document.firstColumn(line), '{').isValid()
1463           ;
1464         dbg("tryOperator: insideBraces =",insideBraces);
1465         result = document.firstColumn(line - 1) + (insideBraces && ch != '.' ? -2 : 2);
1466     }
1467     var prev_pos = cursor;
1468     cursor = tryJumpOverParenthesis(cursor);                // Try to jump out of parenthesis
1469     cursor = tryAddSpaceAfterClosedBracketOrQuote(cursor);
1470 
1471     // Check if a space before '?' still needed
1472     if (prev_pos == cursor && ch == '?' && document.charAt(line, cursor.column - 1) != ' ')
1473         document.insertText(line, cursor.column - 1, " ");  // Add it!
1474 
1475     cursor = view.cursorPosition();                         // Update cursor position
1476     line = cursor.line;
1477     column = cursor.column;
1478 
1479     if (ch == '?')
1480     {
1481         addCharOrJumpOverIt(line, column, ' ');
1482     }
1483     // Handle operator| and/or operator||
1484     else if (ch == '|')
1485     {
1486         /**
1487          * Here is 6+3 cases possible (the last bar is just entered):
1488          * 0) <tt>???</tt> -- add a space before bar and after if needed
1489          * 1) <tt>?? </tt> -- add a space after if needed
1490          * 2) <tt>??|</tt> -- add a space before 1st bar and after the 2nd if needed
1491          * 3) <tt>? |</tt> -- add a space after the 2nd bar if needed
1492          * 4) <tt>?| </tt> -- add a space before 1st bar, remove the mid one, add a space after 2nd bar
1493          * 5) <tt> | </tt> -- remove the mid space, add one after 2nd bar
1494          * and finally,
1495          * 6a) <tt>|| </tt> -- add a space before 1st bar if needed, remove the last bar
1496          * 6b) <tt> ||</tt> -- remove the last bar and add a space after 2nd bar if needed
1497          * 6c) <tt>||</tt> -- add a space after if needed
1498          */
1499         var prev = document.text(line, column - 4, line, column - 1);
1500         dbg("tryOperator: checking @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1501         var space_offset = 0;
1502         if (prev.endsWith(" | "))
1503         {
1504             // case 5: remove the mid space
1505             document.removeText(line, column - 2, line, column - 1);
1506             space_offset = -1;
1507         }
1508         else if (prev.endsWith("|| "))
1509         {
1510             // case 6a: add a space before 1st bar if needed, remove the last bar
1511             document.removeText(line, column - 1, line, column);
1512             var space_has_added = addCharOrJumpOverIt(line, column - 4, ' ');
1513             space_offset = (space_has_added ? 1 : 0) - 2;
1514         }
1515         else if (prev.endsWith(" ||"))
1516         {
1517             // case 6b: remove the last bar
1518             document.removeText(line, column - 1, line, column);
1519             space_offset = -1;
1520         }
1521         else if (prev.endsWith("||"))
1522         {
1523             // case 6a: add a space before and remove the last bar
1524             document.removeText(line, column - 1, line, column);
1525             document.insertText(line, column - 3, " ");
1526         }
1527         else if (prev.endsWith("| "))
1528         {
1529             // case 4: add a space before 1st bar, remove the mid one
1530             document.removeText(line, column - 2, line, column - 1);
1531             document.insertText(line, column - 3, " ");
1532         }
1533         else if (prev.endsWith(" |") || prev.endsWith(" "))
1534         {
1535             // case 3 and 1
1536         }
1537         else
1538         {
1539             // case 2: add a space before 1st bar
1540             if (prev.endsWith('|'))
1541                 space_offset = 1;
1542             // case 0: add a space before bar
1543             document.insertText(line, column - 1 - space_offset, " ");
1544             space_offset = 1;
1545         }
1546         addCharOrJumpOverIt(line, column + space_offset, ' ');
1547     }
1548     // Handle operator% and/or operator^
1549     else if (ch == '%' || ch == '^')
1550     {
1551         var prev = document.text(line, column - 4, line, column - 1);
1552         dbg("tryOperator: checking2 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1553         var patterns = [" % ", "% ", " %", "%", " "];
1554         for (
1555             var i = 0
1556           ; i < patterns.length
1557           ; i++
1558           ) patterns[i] = patterns[i].replace('%', ch);
1559 
1560         var space_offset = 0;
1561         if (prev.endsWith(patterns[0]))
1562         {
1563             // case 0: remove just entered char
1564             document.removeText(line, column - 1, line, column);
1565             space_offset = -2;
1566         }
1567         else if (prev.endsWith(patterns[1]))
1568         {
1569             // case 1: remove just entered char, add a space before
1570             document.removeText(line, column - 1, line, column);
1571             document.insertText(line, column - 3, " ");
1572             space_offset = -1;
1573         }
1574         else if (prev.endsWith(patterns[2]))
1575         {
1576             // case 2: remove just entered char
1577             document.removeText(line, column - 1, line, column);
1578             space_offset = -1;
1579         }
1580         else if (prev.endsWith(patterns[3]))
1581         {
1582             // case 3: add a space before
1583             document.removeText(line, column - 1, line, column);
1584             document.insertText(line, column - 2, " ");
1585             space_offset = 0;
1586         }
1587         else if (prev.endsWith(patterns[4]))
1588         {
1589             // case 4: no space needed before
1590             space_offset = 0;
1591         }
1592         else
1593         {
1594             // case everything else: surround operator w/ spaces
1595             document.insertText(line, column - 1, " ");
1596             space_offset = 1;
1597         }
1598         addCharOrJumpOverIt(line, column + space_offset, ' ');
1599     }
1600     else if (ch == '.')                                     // Replace '..' w/ '...'
1601     {
1602         var prev = document.text(line, column - 3, line, column);
1603         dbg("tryOperator: checking3 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'");
1604         if (prev == "...")                                  // If there is already 3 dots
1605         {
1606             // Remove just entered (redundant) one
1607             document.removeText(line, column - 1, line, column);
1608         }
1609         // Append one more if only two here and we are in 'dsNormal or dsOperator' mode, see https://invent.kde.org/frameworks/syntax-highlighting/-/merge_requests/150
1610         else if (prev[1] == '.' && prev[2] == '.' && (document.defStyleNum(line, column) == 0 || document.defStyleNum(line, column) == 5))
1611         {
1612             addCharOrJumpOverIt(line, column, '.');
1613         }                                                   // Otherwise, do nothing...
1614     }
1615     if (result != -2)
1616     {
1617         dbg("tryOperator result="+result);
1618     }
1619     return result;
1620 }
1621 
1622 /**
1623  * \brief Try to align a given close bracket
1624  */
1625 function tryCloseBracket(cursor, ch)
1626 {
1627     var result = -2;
1628     var line = cursor.line;
1629     var column = cursor.column;
1630 
1631     var braceCursor = Cursor.invalid();
1632     if (ch != '>')
1633     {
1634         // TODO Make sure a given `ch` in the gBraceMap
1635         braceCursor = document.anchor(line, column - 1, gBraceMap[ch]);
1636 
1637         // make sure we are actually on the character we are expecting
1638         // if that's not the case, a brace was probably inserted behind the cursor by auto brackets
1639         realCharacter = document.charAt(line, column - 1);
1640         if (realCharacter != ch) {
1641             braceCursor = document.anchor(line, column, gBraceMap[ch]);
1642         }
1643         // TODO Otherwise, it seems we have a template parameters list...
1644     }
1645 
1646     // unindent when starting new block and previous line has lower indentation level
1647     // prevents over-indentation when inserting condition or loop headers with their opening brace in the next line
1648     var firstPos     = document.firstColumn(line);
1649     var prevFirstPos = document.firstColumn(line - 1);
1650     if (firstPos == column - 1 && ch == '}'  && firstPos > prevFirstPos) {
1651         result = prevFirstPos;
1652     }
1653 
1654     // Check if a given closing brace is a first char on a line
1655     // (i.e. it is 'dangling' brace)...
1656     if (justEnteredCharIsFirstOnLine(line, column, ch) && braceCursor.isValid())
1657     {
1658         // Move to one half-TAB right, if anything but not closing '}', else
1659         // align to the corresponding open char
1660         result = document.firstColumn(braceCursor.line) + (ch != '}' ? 2 : 0);
1661         dbg("tryCloseBracket: setting result="+result);
1662     }
1663 
1664     // Check if ';' required after closing '}', but make sure it was inserted by the user
1665     // and not automatically by auto brackets (behind the cursor)
1666     if (ch == '}' && braceCursor.isValid())
1667     {
1668         var is_check_needed = false;
1669         // Check if corresponding anchor is a class/struct/union/enum,
1670         // (possible keyword located on same or prev line)
1671         // and check for trailing ';'...
1672         var anchoredString = document.line(braceCursor.line);
1673         dbg("tryCloseBracket: anchoredString='"+anchoredString+"'");
1674         var regex = /^(\s*)(class|struct|union|enum).*$/;
1675         var r = regex.exec(anchoredString);
1676         if (r != null)
1677         {
1678             dbg("tryCloseBracket: same line");
1679             is_check_needed = true;
1680         }
1681         else (!is_check_needed && 0 < braceCursor.line)     // Is there any line before?
1682         {
1683             dbg("tryCloseBracket: checking prev line");
1684 
1685             // Ok, lets check it!
1686             anchoredString = document.line(braceCursor.line - 1);
1687             dbg("tryCloseBracket: anchoredString-1='"+anchoredString+"'");
1688             r = regex.exec(anchoredString);
1689             if (r != null)
1690             {
1691                 is_check_needed = true;
1692                 dbg("tryCloseBracket: prev line");
1693             }
1694         }
1695         dbg("tryCloseBracket: is_check_needed="+is_check_needed);
1696         if (is_check_needed)
1697         {
1698             var is_ok = document.line(line)
1699               .substring(column, document.lineLength(line))
1700               .ltrim()
1701               .startsWith(';')
1702               ;
1703             if (!is_ok)
1704             {
1705                 var pos = column;
1706                 var newCursorPos = pos + 1;
1707                 if (document.charAt(line, column - 1) != '}' && document.charAt(line, column) == '}') {
1708                     ++pos;
1709                     --newCursorPos;
1710                 }
1711                 document.insertText(line, pos, ';');
1712                 view.setCursorPosition(line, newCursorPos);
1713             }
1714         }
1715     }
1716     else if (ch == '>')
1717     {
1718         // If user typed 'some' + '<' + '>', jump over the '>'
1719         // (which was added by the tryTemplate)
1720         if (document.charAt(line, column) == '>')
1721         {
1722             document.removeText(line, column, line, column + 1);
1723         }
1724     }
1725 
1726     tryJumpOverParenthesis(view.cursorPosition());
1727 
1728     return result;
1729 }
1730 
1731 /**
1732  * \brief Indent a new scope block
1733  *
1734  * ... try to unindent to be precise... First of all check that open
1735  * \c '{' is a first symbol on a line, and if it doesn't,
1736  * add space (if absent at previous position) after <tt>')'</tt> or \c '='
1737  * or if line stats w/ some keywords: \c enum, \c class, \c struct or \c union.
1738  * Otherwise, look at the previous line for dangling <tt>')'</tt> or
1739  * a line started w/ one of flow control keywords.
1740  *
1741  */
1742 function tryBlock(cursor)
1743 {
1744     var result = -2;
1745     var line = cursor.line;
1746     var column = cursor.column;
1747 
1748     // Make sure we r not in a comment or string
1749     dbg("tryBlock: isStringOrComment(line, column - 2)="+isStringOrComment(line, column - 2))
1750     if (isStringOrComment(line, column - 2))
1751         return result;
1752 
1753     if (justEnteredCharIsFirstOnLine(line, column, '{'))
1754     {
1755         // Check for a dangling close brace on a previous line
1756         // (this may mean that `for' or `if' or `while' w/ looong parameters list on it)
1757         if (document.firstChar(line - 1) == ')')
1758             result = Math.floor(document.firstColumn(line - 1) / gIndentWidth) * gIndentWidth;
1759         else
1760         {
1761             // Otherwise, check for a keyword on the previous line and
1762             // indent the started block to it...
1763             var prevString = document.line(line - 1);
1764             var r = /^(\s*)((catch|if|for|while)\s*\(|do|else|try|(default|case\s+.*)\s*:).*$/.exec(prevString);
1765             if (r != null)
1766                 result = r[1].length;
1767         }
1768     }
1769     else
1770     {
1771         // '{' is not a first char. Check for a previous one...
1772         if (1 < column)
1773         {
1774             var prevChar = document.charAt(line, column - 2);
1775             dbg("tryBlock: prevChar='"+prevChar+"'");
1776             if (prevChar == ')' || prevChar == '=')
1777                 document.insertText(line, column - 1, ' ');
1778             else if (prevChar != ' ')
1779             {
1780                 var currentLine = document.line(line).ltrim();
1781                 var starts_with_keyword = currentLine.startsWith('struct ')
1782                   || currentLine.startsWith('class ')
1783                   || currentLine.startsWith('union ')
1784                   || currentLine.startsWith('enum ')
1785                   ;
1786                 if (starts_with_keyword)
1787                     document.insertText(line, column - 1, ' ');
1788             }
1789         }
1790     }
1791     return result;
1792 }
1793 
1794 /**
1795  * \brief Align preprocessor directives
1796  */
1797 function tryPreprocessor(cursor)
1798 {
1799     var result = -2;
1800     var line = cursor.line;
1801     var column = cursor.column;
1802 
1803     // Check if just entered '#' is a first on a line
1804     if (justEnteredCharIsFirstOnLine(line, column, '#'))
1805     {
1806         // Get current indentation level
1807         var currentLevel = getPreprocessorLevelAt(line);
1808         if (currentLevel > 0)
1809         {
1810             var padding = String().fill(' ', (currentLevel - 1) * 2 + 1);
1811             document.insertText(cursor, padding);
1812         }
1813         result = 0;
1814     }
1815     return result;
1816 }
1817 
1818 /**
1819  * \brief Try to align access modifiers or class initialization list
1820  *
1821  * Here is few cases possible:
1822  * \li \c ':' pressed after a keyword \c public, \c protected or \c private.
1823  *     Then align a current line to corresponding class/struct definition.
1824  *     Check a previous line and if it is not starts w/ \c '{' add a new line before.
1825  * \li \c ':' is a first char on the line, then it looks like a class initialization
1826  *     list or 2nd line of ternary operator.
1827  * \li \c ':' is pressed on a line started w/ \c for statement and after a space
1828  * \li \c ':' after '&gt;' looks like an access to template's member
1829  * \li shortcut: transform <tt>some(:|)</tt> into <tt>some() :|</tt>
1830  *
1831  * \todo Should it be done only for non strings and comments?
1832  */
1833 function tryColon(cursor)
1834 {
1835     var result = -2;
1836     var line = cursor.line;
1837     var column = cursor.column;
1838 
1839     if (isStringOrComment(line, column))
1840         return result;                                      // Do nothing for comments and strings
1841 
1842     // Check if just entered ':' is a first on a line
1843     if (justEnteredCharIsFirstOnLine(line, column, ':'))
1844     {
1845         // Check if there a dangling ')' or '?' (ternary operator) on a previous line
1846         var ch = document.firstChar(line - 1);
1847         if (ch == ')' || ch == '?')
1848             result = document.firstVirtualColumn(line - 1);
1849         else
1850             result = document.firstVirtualColumn(line - 1) + 2;
1851         document.insertText(cursor, " ");
1852     }
1853     else
1854     {
1855         var currentLine = document.line(line);
1856         if (currentLine.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) != -1)
1857         {
1858             var definitionCursor = document.anchor(line, 0, '{');
1859             if (definitionCursor.isValid())
1860             {
1861                 result = document.firstVirtualColumn(definitionCursor.line);
1862                 dbg("tryColon: result="+result);
1863                 if (0 < line)                               // Is there any line before?
1864                 {
1865                     // Check if previous line is not empty and not starts w/ '{'
1866                     var prevLine = document.line(line - 1).trim()
1867                     if (prevLine.length && !prevLine.startsWith("{"))
1868                     {
1869                         // Cuz a new line will be added in place of current, returning
1870                         // result will not affect indentation. So do it manually.
1871                         var firstColumn = document.firstColumn(line);
1872                         var padding = "";
1873                         if (firstColumn < result)
1874                             padding = String().fill(' ', result - firstColumn);
1875                         else if (result < firstColumn)
1876                             document.removeText(line, 0, line, firstColumn - result);
1877                         // Add an empty line before the current
1878                         document.insertText(line, 0, "\n" + padding);
1879                         result = 0;
1880                     }
1881                 }
1882             }
1883         }
1884         else if (document.charAt(line, column - 2) == ' ')
1885         {
1886             // Is it looks like a range based `for' or class/struct/enum?
1887             var add_space = currentLine.ltrim().startsWith("for (")
1888               || currentLine.ltrim().startsWith("class ")
1889               || currentLine.ltrim().startsWith("struct ")
1890               || currentLine.ltrim().startsWith("enum ")
1891               ;
1892             if (add_space)
1893             {
1894                 // Add a space after ':'
1895                 document.insertText(line, column, " ");
1896             }
1897             else if (document.charAt(line, column - 3) == ':')
1898             {
1899                 // Transform ': :' -> '::'
1900                 document.removeText(line, column - 2, line, column - 1);
1901             }
1902         }
1903         else if (document.charAt(line, column - 2) == ':')
1904         {
1905             // A char before is (already) a one more colon.
1906             // Make sure there is no more than two colons...
1907             // NOTE In C++ it is not possible to have more than two of them adjacent!
1908             if (document.charAt(line, column - 3) == ':')
1909             {
1910                 // Remove the current (just entered) one...
1911                 document.removeText(line, column - 1, line, column);
1912             }
1913         }
1914         else
1915         {
1916             // Check that it is not a 'case' and not a magic sequence.
1917             // NOTE "Magic sequence" means support for dynamic expand functions.
1918             // http://zaufi.github.io/programming/2014/02/13/kate-c++-stuff/
1919             var is_magic_sequence = document.charAt(
1920                 line
1921               , document.wordRangeAt(line, column - 1).start.column - 1
1922               ) == ';';
1923             if (!currentLine.ltrim().startsWith("case ") && !is_magic_sequence)
1924             {
1925                 // Add one more ':'
1926                 // Example some<T>: --> some<T>:: or std: --> std::
1927                 document.insertText(line, column, ":");
1928             }
1929             else
1930             {
1931                 // Try to jump out of parenthesis
1932                 cursor = tryJumpOverParenthesis(cursor);
1933                 // Try add a space after close bracket
1934                 tryAddSpaceAfterClosedBracketOrQuote(cursor);
1935             }
1936         }
1937     }
1938     return result;
1939 }
1940 
1941 /**
1942  * \brief Try to add one space after keywords and before an open brace
1943  */
1944 function tryOpenBrace(cursor)
1945 {
1946     var line = cursor.line;
1947     var column = cursor.column;
1948     var wordBefore = document.wordAt(line, column - 1);
1949     dbg("word before: '"+wordBefore+"'");
1950     if (wordBefore.search(/\b(catch|for|if|switch|while|return)\b/) != -1)
1951         document.insertText(line, column - 1, " ");
1952 }
1953 
1954 function getMacroRange(line)
1955 {
1956     function stripLastCharAndRTrim(str)
1957     {
1958         return str.substring(0, str.length - 1).rtrim();
1959     }
1960     var maxLength = 0;
1961     var macroStartLine = -1;
1962     // Look up towards begining of a document
1963     for (var i = line; i >= 0; --i)
1964     {
1965         var currentLineText = document.line(i);
1966         dbg("up: '"+currentLineText+"'");
1967         if (currentLineText.search(/^\s*#\s*define\s+.*\\$/) != -1)
1968         {
1969             macroStartLine = i;
1970             maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1971             break;                                          // Ok, we've found the macro start!
1972         }
1973         else if (currentLineText.search(/\\$/) == -1)
1974             break;                                          // Oops! No backslash found and #define still not reached!
1975         maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1976     }
1977 
1978     if (macroStartLine == -1)
1979         return null;
1980 
1981     // Look down towards end of the document
1982     var macroEndLine = -1;
1983     for (var i = line; i < document.lines(); ++i)
1984     {
1985         var currentLineText = document.line(i);
1986         dbg("dw: '"+currentLineText+"'");
1987         if (currentLineText.search(/\\$/) != -1)            // Make sure the current line have a '\' at the end
1988         {
1989             macroEndLine = i;
1990             maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length);
1991         }
1992         else break;                                         // No backslash at the end --> end of macro!
1993     }
1994 
1995     if (macroEndLine == -1)
1996         return null;
1997 
1998     macroEndLine++;
1999     return {
2000         range: new Range(macroStartLine, 0, macroEndLine, 0)
2001       , max: maxLength
2002       };
2003 }
2004 
2005 /**
2006  * \brief Try to align a backslashes in macro definition
2007  *
2008  * \note It is \b illegal to have smth after a backslash in source code!
2009  */
2010 function tryBackslash(cursor)
2011 {
2012     var line = cursor.line;
2013     var result = getMacroRange(line);                       // Look up and down for macro definition range
2014     if (result != null)
2015     {
2016         dbg("macroRange:",result.range);
2017         dbg("maxLength:",result.max);
2018         // Iterate over macro definition, strip backslash
2019         // and add a padding string up to result.max length + backslash
2020         for (var i = result.range.start.line; i < result.range.end.line; ++i)
2021         {
2022             var currentLineText = document.line(i);
2023             var originalTextLength = currentLineText.length;
2024             currentLineText = currentLineText.substring(0, currentLineText.length - 1).rtrim();
2025             var textLength = currentLineText.length;
2026             document.removeText(i, textLength, i, originalTextLength);
2027             document.insertText(i, textLength, String().fill(' ', result.max - textLength + 1) + "\\");
2028         }
2029     }
2030 }
2031 
2032 /**
2033  * \brief Handle a <tt>@</tt> symbol
2034  *
2035  * Possible user wants to add a Doxygen group
2036  */
2037 function tryDoxygenGrouping(cursor)
2038 {
2039     var line = cursor.line;
2040     var column = cursor.column;
2041     var firstColumn = document.firstColumn(line);
2042     // Check the symbol before the just entered
2043     var looks_like_doxgorup = isStringOrComment(line, column - 2)
2044       && firstColumn == (column - 4)
2045       && document.line(line).ltrim().startsWith("// ")
2046       ;
2047     if (looks_like_doxgorup)
2048     {
2049         document.removeText(line, column - 2, line, column - 1);
2050         var padding = String().fill(' ', firstColumn);
2051         document.insertText(line, column - 1, "{\n" + padding + "\n" + padding + "//@}");
2052         view.setCursorPosition(line + 1, document.lineLength(line + 1));
2053     }
2054 }
2055 
2056 /**
2057  * \brief Handle quote character
2058  *
2059  * Look back for \c 'R' char right before \c '"' and if
2060  * the next one (after \c '"') is not an alphanumeric,
2061  * then add a delimiters.
2062  *
2063  * \attention Effect of AutoBrace extension has already neutralized at this point :)
2064  */
2065 function tryStringLiteral(cursor, ch)
2066 {
2067     var line = cursor.line;
2068     var column = cursor.column;
2069 
2070     // Do nothing for comments
2071     if (isComment(line, column - 2))
2072         return;
2073 
2074     // First of all we have to determinate where we are:
2075     // 0) new string literal just started, or ...
2076     // 1) string literal just ends
2077 
2078     // Check if the '"' is a very first char on a line
2079     var new_string_just_started;
2080     var raw_string;
2081     if (column < 2) {
2082         // Yes, then we have to look to the last char of the previous line
2083         new_string_just_started = !(line != 0 && isString(line - 1, document.lastColumn(line - 1)));
2084     } else {
2085         // Ok, just check attribute of the char right before '"' (special case for R"" raw string literals)
2086         new_string_just_started = !isString(line, column - 2);
2087         if (!new_string_just_started && ch == '"') {
2088             // we need to scan backwards before R", [uUL]R" or u8R"
2089             var stringEnd;
2090             if (column >= 4 && document.charAt(line, column - 2) == 'R' && document.charAt(line, column - 3) == '8' && document.charAt(line, column - 4) == 'u') {
2091                 raw_string = true;
2092                 stringEnd = 5;
2093             } else if (column >= 3 && document.charAt(line, column - 2) == 'R' && (document.charAt(line, column - 3) == 'u' || document.charAt(line, column - 3) == 'U' || document.charAt(line, column - 3) == 'L')) {
2094                 raw_string = true;
2095                 stringEnd = 4;
2096             } else if (column >= 2 && document.charAt(line, column - 2) == 'R') {
2097                 raw_string = true;
2098                 stringEnd = 3;
2099             }
2100             if (raw_string)
2101                 new_string_just_started = (column >= stringEnd) ? !isString(line, column - stringEnd) : !(line != 0 && isString(line - 1, document.lastColumn(line - 1)));
2102         }
2103     }
2104 
2105     // TODO Add a space after possible operator right before just
2106     // started string literal...
2107     if (new_string_just_started)
2108     {
2109         // Is there anything after just entered '"'?
2110         var nc = document.charAt(line, column);
2111         var need_closing_quote = column == document.lineLength(line)
2112           || document.isSpace(nc)
2113           || nc == ','                                      // user tries to add new string param,
2114           || nc == ')'                                      // ... or one more param to the end of some call
2115           || nc == ']'                                      // ... or string literal as subscript index
2116           || nc == ';'                                      // ... or one more string before end of expression
2117           || nc == '<'                                      // ... or `some << "|<<`
2118           ;
2119         if (need_closing_quote)
2120         {
2121             // Check for 'R' right before '"'
2122             if (raw_string)
2123             {
2124                 // Yeah, looks like a raw string literal
2125                 /// \todo Make delimiter configurable... HOW?
2126                 /// It would be nice if indenters can have a configuration page somehow...
2127                 document.insertText(cursor, "~()~\"");
2128                 view.setCursorPosition(line, column + 2);
2129             }
2130             else
2131             {
2132                 document.insertText(cursor, ch);
2133                 view.setCursorPosition(line, column);
2134             }
2135         }
2136     }
2137 }
2138 
2139 /**
2140  * \brief Handle \c '!' char
2141  *
2142  * Exclamation symbol can be a part of \c operator!= or unary operator.
2143  * in both cases, a space required before it! Except few cases:
2144  * - when it is at the line start
2145  * - when a char before it \c '(' -- i.e. argument of a control flow keyword (\c if, \c while)
2146  *   or a function call parameter
2147  * - when a char before it \c '[' (array subscript)
2148  * - when a char before it \c '<' -- here is two cases possible:
2149  *      - it is a first non-type template parameter (w/ type \c bool obviously)
2150  *      - it is a part of less or shift operators.
2151  * To distinct last case, it is enough to check that a word before \c '<' (w/o space)
2152  * is an identifier.
2153  * \note Yep, operators supposed to be separated from around text.
2154  */
2155 function tryExclamation(cursor)
2156 {
2157     var line = cursor.line;
2158     var column = cursor.column;
2159 
2160     if (column == 0)                                        // Do nothing for very first char
2161         return;
2162 
2163     if (isStringOrComment(line, column - 1))                // Do nothing for comments and stings
2164         return;
2165 
2166     if (document.firstColumn(line) == column - 1)           // Make sure '!' is not a first char on a line
2167         return;
2168 
2169     if (column < 2)                                         // Do nothing if there is less than 2 chars before
2170         return;
2171 
2172     var pc = document.charAt(line, column - 2);             // Do nothing if one of 'stop' chars:
2173     if (pc == ' ' || pc == '(' || pc == '[' || pc == '{')
2174         return;
2175 
2176     // And finally make sure it is not a part of 'relation operator'
2177     if (pc == '<' && column >= 3)
2178     {
2179         // Make sure a char before is not a space or another '<'
2180         var ppc = document.charAt(line, column - 3);
2181         if (ppc != ' ' && ppc != '<')
2182             return;
2183     }
2184 
2185     // Ok, if we r here, just insert a space ;)
2186     document.insertText(line, column - 1, " ");
2187 }
2188 
2189 /**
2190  * \brief Handle a space
2191  *
2192  * - add <tt>'()'</tt> pair after some keywords like: \c if, \c while, \c for, \c switch
2193  * - add <tt>';'</tt> if space pressed right after \c return, and no text after it
2194  * - if space pressed inside of angle brackets 'some<|>' transform into 'some < |'
2195  */
2196 function tryKeywordsWithBrackets(cursor)
2197 {
2198     var line = cursor.line;
2199     var column = cursor.column;
2200     var text = document.line(line).ltrim();
2201     var need_brackets = text == "if "
2202       || text == "else if "
2203       || text == "while "
2204       || text == "for "
2205       || text == "switch "
2206       || text == "catch "
2207       ;
2208     if (need_brackets)
2209     {
2210         document.insertText(cursor, "()");
2211         view.setCursorPosition(line, column + 1);
2212     }
2213     else if (text == "return ")
2214     {
2215         document.insertText(cursor, ";");
2216         view.setCursorPosition(line, column);
2217     }
2218     else if (document.charAt(line, column - 2) == '<' && document.charAt(cursor) == '>')
2219     {
2220         document.removeText(line, column, line, column + 1);
2221         document.insertText(line, column - 2, " ");
2222     }
2223 }
2224 
2225 /**
2226  * Try to add space before and after some equal operators.
2227  */
2228 function tryEqualOperator(cursor)
2229 {
2230     var line = cursor.line;
2231     var column = cursor.column;
2232 
2233     // Do nothing for comments or string literals or lines shorter than 2
2234     if (2 < column && isStringOrComment(line, column))
2235         return cursor;
2236 
2237     var c = document.charAt(line, column - 2);
2238     dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 2)+"), c='"+c+"'");
2239 
2240     switch (c)
2241     {
2242         // Two chars operators: !=, ==, ...
2243         case '*':
2244         case '%':
2245         case '/':
2246         case '^':
2247         case '|':
2248         case '&':
2249         case '!':
2250         case '=':
2251             addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2252             // Make sure there is a space before it!
2253             if (column >= 3 && document.charAt(line, column - 3) != ' ')
2254                 document.insertText(line, column - 2, " ");
2255             break;
2256         case '(':                                           // some(=|) --> some() =|
2257             cursor = tryJumpOverParenthesis(cursor);
2258             tryEqualOperator(cursor);                       // Call self again to handle "some()=|"
2259             break;
2260         case ')':                                           // "some()=" or "(expr)=" --> ") =|"
2261         case '}':                                           // It can be a ctor of some proxy object
2262             // Add a space between closing bracket and just entered '='
2263             document.insertText(line, column - 1, " ");
2264             addCharOrJumpOverIt(line, column + 1, ' ');     // Make sure there is a space after it!
2265             break;
2266         case '<':
2267             // Shortcut: transfrom "some<=|>" -> "some <= |"
2268             if (document.charAt(cursor) == '>')
2269                 document.removeText(line, column, line, column + 1);
2270         case '>':
2271             // This could be '<<=', '>>=', '<=', '>='
2272             // Make sure there is a space after it!
2273             addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2274             // Check if this is one of >>= or <<=
2275             if (column >= 3)
2276             {
2277                 if (document.charAt(line, column - 3) == c)
2278                 {
2279                     if (column >= 4 && document.charAt(line, column - 4) != ' ')
2280                         document.insertText(line, column - 3, " ");
2281                 }
2282                 else if (document.charAt(line, column - 3) != ' ')
2283                 {
2284                     // <= or >=
2285                     document.insertText(line, column - 2, " ");
2286                 }
2287             }
2288             break;
2289         case '[':                                           // This could be a part of lambda capture [=]
2290             break;
2291         case ' ':
2292             // Lookup one more character towards left
2293             if (column >= 3)
2294             {
2295                 var pc = document.charAt(line, column - 3);
2296                 dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 3)+"), pc='"+pc+"'");
2297                 switch (pc)
2298                 {
2299                     case '=':                               // Stick the current '=' to the previous char
2300                     case '|':
2301                     case '&':
2302                     case '^':
2303                     case '<':
2304                     case '>':
2305                     case '*':
2306                     case '/':
2307                     case '%':
2308                         document.removeText(line, column - 1, line, column);
2309                         document.insertText(line, column - 2, '=');
2310                         break;
2311                     default:
2312                         break;
2313                 }
2314             }
2315             break;
2316         case '+':
2317         case '-':
2318             addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2319             // Here is few things possible:
2320             // some+=| --> some += |
2321             // some++=| --> some++ = |
2322             // some+++=| --> some++ += |
2323             var space_offset = -1;
2324             if (column >= 3)
2325             {
2326                 if (document.charAt(line, column - 3) == c)
2327                 {
2328                     if (column >= 4)
2329                     {
2330                         if (document.charAt(line, column - 4) == c)
2331                             space_offset = 2;
2332                         else if (document.charAt(line, column - 4) != ' ')
2333                             space_offset = 1;
2334                     }
2335                 }
2336                 else if (document.charAt(line, column - 3) != ' ')
2337                     space_offset = 2;
2338             }
2339             if (space_offset != -1)
2340                 document.insertText(line, column - space_offset, " ");
2341             break;
2342         default:
2343             dbg("tryEqualOperator: default");
2344             // '=' always surrounded by spaces!
2345             addCharOrJumpOverIt(line, column, ' ');         // Make sure there is a space after it!
2346             document.insertText(line, column - 1, " ");     // Make sure there is a space before it!
2347             break;
2348     }
2349 }
2350 
2351 /**
2352  * \brief Process one character
2353  *
2354  * NOTE Cursor positioned right after just entered character and has +1 in column.
2355  *
2356  * \attention This function will roll back the effect of \b AutoBrace extension
2357  * for quote chars. So this indenter can handle that chars withing predictable
2358  * surround...
2359  *
2360  */
2361 function processChar(line, ch)
2362 {
2363     var result = -2;                                        // By default, do nothing...
2364     var cursor = view.cursorPosition();
2365     if (!cursor)
2366         return result;
2367 
2368     // TODO Is there any `assert' in JS?
2369     if (line != cursor.line)
2370     {
2371         dbg("ASSERTION FAILURE: line != cursor.line");
2372         return result;
2373     }
2374 
2375     document.editBegin();
2376     switch (ch)
2377     {
2378         case '\n':
2379             result = caretPressed(cursor);
2380             break;
2381         case '/':
2382             trySameLineComment(cursor);                     // Possible user wants to start a comment
2383             break;
2384         case '<':
2385             result = tryTemplate(cursor);                   // Possible need to add closing '>' after template
2386             break;
2387         case ',':
2388             result = tryComma(cursor);                      // Possible need to align parameters list
2389             break;
2390         case ';':
2391             result = trySemicolon(cursor);                  // Possible `for ()` loop spread over lines
2392             break;
2393         case '?':
2394         case '|':
2395         case '^':
2396         case '%':
2397         case '.':
2398             result = tryOperator(cursor, ch);               // Possible need to align some operator
2399             break;
2400         case '}':
2401         case ')':
2402         case ']':
2403         case '>':
2404             result = tryCloseBracket(cursor, ch);           // Try to align a given close bracket
2405             break;
2406         case '{':
2407             result = tryBlock(cursor);
2408             break;
2409         case '#':
2410             result = tryPreprocessor(cursor);
2411             break;
2412         case ':':
2413             result = tryColon(cursor);
2414             break;
2415         case '(':
2416             tryOpenBrace(cursor);                           // Try to add a space after some keywords
2417             break;
2418         case '\\':
2419             tryBackslash(cursor);
2420             break;
2421         case '@':
2422             tryDoxygenGrouping(cursor);
2423             break;
2424         case '"':
2425         case '\'':
2426             tryStringLiteral(cursor, ch);
2427             break;
2428         case '!':                                           // Almost all the time there should be a space before!
2429             tryExclamation(cursor);
2430             break;
2431         case ' ':
2432             tryKeywordsWithBrackets(cursor);
2433             break;
2434         case '=':
2435             tryEqualOperator(cursor);
2436             break;
2437         case '*':
2438         case '&':
2439             tryAddSpaceAfterClosedBracketOrQuote(cursor);
2440             break;
2441         default:
2442             break;                                          // Nothing to do...
2443     }
2444 
2445     // Make sure it is not a pure comment line
2446     var currentLineText = document.line(cursor.line).ltrim();
2447     if (ch != '\n' && !currentLineText.startsWith("//"))
2448     {
2449         // Ok, try to keep an inline comment aligned (if any)...
2450         // BUG If '=' was inserted (and a space added) in a code line w/ inline comment,
2451         // it seems kate do not update highlighting, so position, where comment was before,
2452         // still counted as a 'Comment' attribute, but actually it should be 'Normal Text'...
2453         // It is why adding '=' will not realign an inline comment...
2454         if (alignInlineComment(cursor.line) && ch == ' ')
2455             document.insertText(view.cursorPosition(), ' ');
2456     }
2457 
2458     document.editEnd();
2459     return result;
2460 }
2461 
2462 function alignPreprocessor(line)
2463 {
2464     if (tryPreprocessor_ch(line) == -1)                     // Is smth happened?
2465         return -2;                                          // No! Signal to upper level to try next aligner...
2466     return 0;                                               // NOTE preprocessor directives always aligned to 0!
2467 }
2468 
2469 /**
2470  * Try to find a next non comment line assuming that a given
2471  * one is a start or middle of a multi-line comment.
2472  *
2473  * \attention This function would ignore anything else than
2474  * a simple comments like this one... I.e. if \b right after
2475  * star+slash starts anything (non comment, or even maybe after
2476  * that another one comment begins), it will be \b IGNORED.
2477  * (Just because this is a damn ugly style!)
2478  *
2479  * \return line number or \c 0 if not found
2480  * \note \c 0 is impossible value, so suitable to indicate an error!
2481  *
2482  * \sa \c alignInsideBraces()
2483  */
2484 function findMultiLineCommentBlockEnd(line)
2485 {
2486     for (; line < document.lines(); line++)
2487     {
2488         var text = document.line(line).rtrim();
2489         if (text.endsWith("*/"))
2490             break;
2491     }
2492     line++;                                                 // Move to *next* line
2493     if (line < document.lines())
2494     {
2495         // Make sure it is not another one comment, and if so,
2496         // going to find it's end as well...
2497         var currentLineText = document.line(line).ltrim();
2498         if (currentLineText.startsWith("//"))
2499             line = findSingleLineCommentBlockEnd(line);
2500         else if (currentLineText.startsWith("/*"))
2501             line = findMultiLineCommentBlockEnd(line);
2502     }
2503     else line = 0;                                          // EOF found
2504     return line;
2505 }
2506 
2507 /**
2508  * Try to find a next non comment line assuming that a given
2509  * one is a single-line one
2510  *
2511  * \return line number or \c 0 if not found
2512  * \note \c 0 is impossible value, so suitable to indicate an error!
2513  *
2514  * \sa \c alignInsideBraces()
2515  */
2516 function findSingleLineCommentBlockEnd(line)
2517 {
2518     while (++line < document.lines())
2519     {
2520         var text = document.line(line).ltrim();
2521         if (text.length == 0) continue;                     // Skip empty lines...
2522         if (!text.startsWith("//")) break;                  // Yeah! Smth was found finally.
2523     }
2524     if (line < document.lines())
2525     {
2526         var currentLineText = document.line(line).ltrim();  // Get text of the found line
2527         while (currentLineText.length == 0)                 // Skip empty lines if any
2528             currentLineText = document.line(++line).ltrim();
2529         // Make sure it is not another one multiline comment, and if so,
2530         // going to find it's end as well...
2531         if (currentLineText.startsWith("/*"))
2532             line = findMultiLineCommentBlockEnd(line);
2533     }
2534     else line = 0;                                          // EOF found
2535     return line;
2536 }
2537 
2538 /**
2539  * Almost anything in a code is placed withing some brackets.
2540  * So the ideas is simple:
2541  * \li find nearest open bracket of any kind
2542  * \li depending on its type and presence of leading delimiters (non identifier characters)
2543  *     add one or half TAB relative a first non-space char of a line w/ found bracket.
2544  *
2545  * But here is some details:
2546  * \li do nothing on empty lines
2547  * \li do nothing if first position is a \e string
2548  * \li align comments according next non-comment and non-preprocessor line
2549  *     (i.e. it's desired indent cuz it maybe still unaligned)
2550  *
2551  * \attention Current Kate version has a BUG: \c anchor() unable to find smth
2552  * in a multiline macro definition (i.e. where every line ends w/ a backslash)!
2553  */
2554 function alignInsideBraces(line)
2555 {
2556     // Make sure there is a text on a line, otherwise nothing to align here...
2557     var thisLineIndent = document.firstColumn(line);
2558     if (thisLineIndent == -1 || document.isString(line, 0))
2559         return 0;
2560 
2561     // Check for comment on the current line
2562     var currentLineText = document.line(line).ltrim();
2563     var nextNonCommentLine = -1;
2564     var middleOfMultilineBlock = false;
2565     var isSingleLineComment = false;
2566     if (currentLineText.startsWith('//'))                   // Is single line comment on this line?
2567     {
2568         dbg("found a single-line comment");
2569         // Yep, go to find a next non-comment line...
2570         nextNonCommentLine = findSingleLineCommentBlockEnd(line);
2571         isSingleLineComment = true;
2572     }
2573     else if (currentLineText.startsWith('/*'))              // Is multiline comment starts on this line?
2574     {
2575         // Yep, go to find a next non-comment line...
2576         dbg("found start of a multiline comment");
2577         nextNonCommentLine = findMultiLineCommentBlockEnd(line);
2578     }
2579     // Are we already inside of a multiline comment?
2580     // NOTE To be sure that we are not inside of #if0/#endif block,
2581     // lets check that current line starts w/ '*' also!
2582     // NOTE Yep, it is expected (hardcoded) that multiline comment has
2583     // all lines started w/ a star symbol!
2584     // TODO BUG Kate has a bug: when multiline code snippet gets inserted into
2585     // a multiline comment block (like Doxygen's @code/@endcode)
2586     // document.isComment() returns true *only& for the first line of it!
2587     // So some other way needs to be found to indent comments properly...
2588     // TODO DAMN... it doesn't work that way also... for snippets longer than 2 lines.
2589     // I suppose kate first insert text, then indent it, and after that highlight it
2590     // So indenters based on a highlighting info will not work! BUT THEY DEFINITELY SHOULD!
2591     else if (currentLineText.startsWith("*") && document.isComment(line, 0))
2592     {
2593         dbg("found middle of a multiline comment");
2594         // Yep, go to find a next non-comment line...
2595         nextNonCommentLine = findMultiLineCommentBlockEnd(line);
2596         middleOfMultilineBlock = true;
2597     }
2598     dbg("line="+line);
2599     dbg("document.isComment(line, 0)="+document.isComment(line, 0));
2600     //dbg("document.defStyleNum(line, 0)="+document.defStyleNum(line-1, 0));
2601     dbg("currentLineText='"+currentLineText+"'");
2602     dbg("middleOfMultilineBlock="+middleOfMultilineBlock);
2603 
2604     if (nextNonCommentLine == 0)                            // End of comment not found?
2605         // ... possible due temporary invalid code...
2606         // anyway, dunno how to align it!
2607         return -2;
2608     // So, are we inside a comment? (and we know where it ends)
2609     if (nextNonCommentLine != -1)
2610     {
2611         // Yep, lets try to get desired indent for next non-comment line
2612         var desiredIndent = indentLine(nextNonCommentLine);
2613         if (desiredIndent < 0)
2614         {
2615             // Have no idea how to indent this comment! So try to align it
2616             // as found line:
2617             desiredIndent = document.firstColumn(nextNonCommentLine);
2618         }
2619         // TODO Make sure that next non-comment line do not starts
2620         // w/ 'special' chars...
2621         return desiredIndent + (middleOfMultilineBlock|0);
2622     }
2623 
2624     var brackets = [
2625         document.anchor(line, document.firstColumn(line), '(')
2626       , document.anchor(line, document.firstColumn(line), '{')
2627       , document.anchor(line, document.firstColumn(line), '[')
2628       ].sort();
2629     dbg("Found open brackets @ "+brackets);
2630 
2631     // Check if we are at some brackets, otherwise do nothing
2632     var nearestBracket = brackets[brackets.length - 1];
2633     if (!nearestBracket.isValid())
2634         return 0;
2635 
2636     // Make sure it is not a `namespace' level
2637     // NOTE '{' brace should be at the same line w/ a 'namespace' keyword
2638     // (yep, according my style... :-)
2639     var bracketChar = document.charAt(nearestBracket);
2640     var parentLineText = document.line(nearestBracket.line).ltrim();
2641     if (bracketChar == '{' && parentLineText.startsWith("namespace"))
2642         return 0;
2643 
2644     // Ok, (re)align it!
2645     var result = -2;
2646     switch (bracketChar)
2647     {
2648         case '{':
2649         case '(':
2650         case '[':
2651             // If current line has some leading delimiter, i.e. non alphanumeric character
2652             // add a half-TAB, otherwise add a one TAB... if needed!
2653             var parentIndent = document.firstColumn(nearestBracket.line);
2654             var openBraceIsFirst = parentIndent == nearestBracket.column;
2655             var firstChar = document.charAt(line, thisLineIndent);
2656             var isCloseBraceFirst = firstChar == ')' || firstChar == ']' || firstChar == '}';
2657             var doNotAddAnything = openBraceIsFirst && isCloseBraceFirst;
2658             var mustAddHalfTab = (!openBraceIsFirst && isCloseBraceFirst)
2659               || firstChar == ','
2660               || firstChar == '?'
2661               || firstChar == ':'
2662               || firstChar == ';'
2663               ;
2664             var desiredIndent = parentIndent + (
2665                 mustAddHalfTab
2666               ? (gIndentWidth / 2)
2667               : (doNotAddAnything ? 0 : gIndentWidth)
2668               );
2669             result = desiredIndent;                         // Reassign a result w/ desired value!
2670             //BEGIN SPAM
2671             dbg("parentIndent="+parentIndent);
2672             dbg("openBraceIsFirst="+openBraceIsFirst);
2673             dbg("firstChar="+firstChar);
2674             dbg("isCloseBraceFirst="+isCloseBraceFirst);
2675             dbg("doNotAddAnything="+doNotAddAnything);
2676             dbg("mustAddHalfTab="+mustAddHalfTab);
2677             dbg("desiredIndent="+desiredIndent);
2678             //END SPAM
2679             break;
2680         default:
2681             dbg("Dunno how to align this line...");
2682             break;
2683     }
2684     return result;
2685 }
2686 
2687 function alignAccessSpecifier(line)
2688 {
2689     var result = -2;
2690     var currentLineText = document.line(line).ltrim();
2691     var match = currentLineText.search(
2692         /^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/
2693       );
2694     if (match != -1)
2695     {
2696         // Ok, lets find an open brace of the `class'/`struct'
2697         var openBracePos = document.anchor(line, document.firstColumn(line), '{');
2698         if (openBracePos.isValid())
2699             result = document.firstColumn(openBracePos.line);
2700     }
2701     return result;
2702 }
2703 
2704 /**
2705  * Try to align \c case statements in a \c switch
2706  */
2707 function alignCase(line)
2708 {
2709     var result = -2;
2710     var currentLineText = document.line(line).ltrim();
2711     if (currentLineText.startsWith("case ") || currentLineText.startsWith("default:"))
2712     {
2713         // Ok, lets find an open brace of the `switch'
2714         var openBracePos = document.anchor(line, document.firstColumn(line), '{');
2715         if (openBracePos.isValid())
2716             result = document.firstColumn(openBracePos.line) + gIndentWidth;
2717     }
2718     return result;
2719 }
2720 
2721 /**
2722  * Try to align \c break or \c continue statements in a loop or \c switch.
2723  *
2724  * Also it take care about the following case:
2725  * \code
2726  *  for (blah-blah)
2727  *  {
2728  *      if (smth)
2729  *          break;
2730  *  }
2731  * \endcode
2732  */
2733 function alignBreakContinue(line)
2734 {
2735     var result = -2;
2736     var currentLineText = document.line(line).ltrim();
2737     var is_break = currentLineText.startsWith("break;");
2738     var should_proceed = is_break || currentLineText.startsWith("continue;")
2739     if (should_proceed)
2740         result = tryBreakContinue(line - 1, is_break);
2741     return result;
2742 }
2743 
2744 /**
2745  * Try to align a given line
2746  * \todo More actions
2747  */
2748 function indentLine(line)
2749 {
2750     dbg(">> Going to indent line "+line);
2751     var result = alignPreprocessor(line);                   // Try to align a preprocessor directive
2752     if (result == -2)                                       // Nothing has changed?
2753         result = alignAccessSpecifier(line);                // Try to align access specifiers in a class
2754     if (result == -2)                                       // Nothing has changed?
2755         result = alignCase(line);                           // Try to align `case' statements in a `switch'
2756     if (result == -2)                                       // Nothing has changed?
2757         result = alignBreakContinue(line);                  // Try to align `break' or `continue' statements
2758     if (result == -2)                                       // Nothing has changed?
2759         result = alignInsideBraces(line);                   // Try to align a generic line
2760     alignInlineComment(line);                               // Always try to align inline comments
2761 
2762     dbg("indentLine result="+result);
2763 
2764     if (result == -2)                                       // Still dunno what to do?
2765         result = -1;                                        // ... just align according a previous non empty line
2766     return result;
2767 }
2768 
2769 /**
2770  * \brief Process a newline or one of \c triggerCharacters character.
2771  *
2772  * This function is called whenever the user hits \c ENTER key.
2773  *
2774  * It gets three arguments: \c line, \c indentwidth in spaces and typed character
2775  *
2776  * Called for each newline (<tt>ch == \n</tt>) and all characters specified in
2777  * the global variable \c triggerCharacters. When calling \e Tools->Align
2778  * the variable \c ch is empty, i.e. <tt>ch == ''</tt>.
2779  */
2780 function indent(line, indentWidth, ch)
2781 {
2782     // NOTE Update some global variables
2783     gIndentWidth = indentWidth;
2784     var crsr = view.cursorPosition();
2785 
2786     dbg("indentWidth: " + indentWidth);
2787     dbg("       Mode: " + document.highlightingModeAt(crsr));
2788     dbg("  Attribute: " + document.attributeName(crsr));
2789     dbg("       line: " + line);
2790     dbg("       char: " + crsr + " -> '" + ch + "'");
2791 
2792     if (ch != "")
2793         return processChar(line, ch);
2794 
2795     return indentLine(line);
2796 }
2797 
2798 /**
2799  * \todo Better to use \c defStyleNum() instead of \c attributeName() and string comparison
2800  *
2801  * \todo Prevent second '//' on a line... ? Fix the current way anyway...
2802  */
2803 
2804 // kate: space-indent on; indent-width 4; replace-tabs on;