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 ></em> 1044 * \li user entered smth like <em>std::map></em> 1045 * \li user wants to output smth to C++ I/O stream by typing <em>>></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 '>' 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;