var katescript = { "name": "C++/boost Style", "author": "Alex Turbov ", "license": "LGPL", "revision": 32, "kate-version": "5.1", "required-syntax-style": "C++", "indent-languages": ["C++", "C++/Qt4", "ISO C++"], "priority": 10 }; // kate-script-header, must be at the start of the file without comments, pure json /* This file is part of the Kate Project. SPDX-License-Identifier: LGPL-2.0-or-later */ /** * \warning This indenter designed to be used with my C++ style! It consists * of mix of boost and STL styles + some my, unfortunately (still) * undocumented additions I've found useful after ~15 years of C++ coding. * So \b LOT of things here are \b HARDCODED and I \b DON'T care about other * styles!!! * * Ok, you've been warned :-) * * More info available here: http://zaufi.github.io/programming/2013/11/29/kate-cppstyle-indenter/ * * Some settings it assumes being in effect: * - indent-width 4; * - space-indent true; * - auto-brackets true; <-- TODO REALLY? * - replace-tabs true; * - replace-tabs-save true; * * \todo Better to check (assert) some of that modelines... */ // required katepart js libraries require ("range.js"); require ("string.js"); require ("utils.js") // specifies the characters which should trigger indent, beside the default '\n' // ':' is for `case'/`default' and class access specifiers: public, protected, private // '/' is for single line comments // ',' for parameter list // '<' and '>' is for templates // '#' is for preprocessor directives // ')' is for align dangling close bracket // ';' is for align `for' parts // ' ' is to add a '()' after `if', `while', `for', ... // TBD triggerCharacters = "{}()[]<>/:;,#\\?!|&/%.@ '\"=*^"; var debugMode = false; //BEGIN global variables and functions var gIndentWidth = 4; var gSameLineCommentStartAt = 60; ///< Position for same-line-comments (inline comments) var gBraceMap = { '(': ')', ')': '(' , '<': '>', '>': '<' , '{': '}', '}': '{' , '[': ']', ']': '[' }; //END global variables and functions /** * Try to (re)align (to 60th position) inline comment if present * \return \c true if comment line was moved above */ function alignInlineComment(line) { // Check is there any comment on the current line var sc = splitByComment(line); // Did we found smth and if so, make sure it is not a string or comment... if (sc.hasComment && !isStringOrComment(line, sc.before.length - 1)) { var rbefore = sc.before.rtrim(); var cursor = view.cursorPosition(); /// \attention Kate has a BUG: even if everything is Ok and no realign /// required, document gets modified anyway! So condition below /// designed to prevent document modification w/o real actions won't /// help anyway :-( Need to fix Kate before! if (rbefore.length < gSameLineCommentStartAt && sc.before.length != gSameLineCommentStartAt) { // Ok, test on the line is shorter than needed. // But what about current padding? if (sc.before.length < gSameLineCommentStartAt) // Need to add some padding document.insertText( line , sc.before.length , String().fill(' ', gSameLineCommentStartAt - sc.before.length) ); else // Need to remove a redundant padding document.removeText(line, gSameLineCommentStartAt, line, sc.before.length); // Keep cursor at the place we've found it before view.setCursorPosition(cursor); } else if (gSameLineCommentStartAt < rbefore.length) { // Move inline comment before the current line var startPos = document.firstColumn(line); var currentLineText = String().fill(' ', startPos) + "//" + sc.after.rtrim() + "\n"; document.removeText(line, rbefore.length, line, document.lineLength(line)); document.insertText(line, 0, currentLineText); // Keep cursor at the place we've found it before view.setCursorPosition(new Cursor(line + 1, cursor.column)); return true; } } return false; } function tryIndentRelativePrevNonCommentLine(line) { var current_line = line - 1; while (0 <= current_line && isStringOrComment(current_line, document.firstColumn(current_line))) --current_line; if (current_line == -1) return -2; var prevLineFirstChar = document.firstChar(current_line); var needHalfUnindent = !( prevLineFirstChar == ',' || prevLineFirstChar == ':' || prevLineFirstChar == '?' || prevLineFirstChar == '<' || prevLineFirstChar == '>' || prevLineFirstChar == '&' ); return document.firstColumn(current_line) - (needHalfUnindent ? 2 : 0); } /** * Try to keep same-line comment. * I.e. if \c ENTER was hit on a line w/ inline comment and before it, * try to keep it on a previous line... */ function tryToKeepInlineComment(line) { // Make sure that there is some text still present on a prev line // i.e. it was just splitted and same-line-comment must be moved back to it if (document.line(line - 1).trim().length == 0) return; // Check is there any comment on the current (non empty) line var sc = splitByComment(line); dbg("sc.hasComment="+sc.hasComment); dbg("sc.before.rtrim().length="+sc.before.rtrim().length); if (sc.hasComment) { // Ok, here is few cases possible when ENTER pressed in different positions // | |smth|was here; | |// comment // // If sc.before has some text, it means that cursor was in the middle of some // non-commented text, and part of it left on a prev line, so we have to move // the comment back to that line... if (sc.before.trim().length > 0) // Is there some text before comment? { var lastPos = document.lastColumn(line - 1); // Get last position of non space char @ prev line // Put the comment text to the prev line w/ padding document.insertText( line - 1 , lastPos + 1 , String().fill(' ', gSameLineCommentStartAt - lastPos - 1) + "//" + sc.after.rtrim() ); // Remove it from current line starting from current position // 'till the line end document.removeText(line, sc.before.rtrim().length, line, document.lineLength(line)); } else { // No text before comment. Need to remove possible spaces from prev line... var prevLine = line - 1; document.removeText( prevLine , document.lastColumn(prevLine) + 1 , prevLine , document.lineLength(prevLine) ); } } } /** * Return a current preprocessor indentation level * \note preprocessor indentation means how deep the current line * inside of \c #if directives. * \warning Negative result means that smth wrong w/ a source code */ function getPreprocessorLevelAt(line) { // Just follow towards start and count #if/#endif directives var currentLine = line; var result = 0; while (currentLine >= 0) { currentLine--; var currentLineText = document.line(currentLine); if (currentLineText.search(/^\s*#\s*(if|ifdef|ifndef)\s+.*$/) != -1) result++; else if (currentLineText.search(/^\s*#\s*endif.*$/) != -1) result--; } return result; } /** * Check if \c ENTER was hit between ()/{}/[]/<> * \todo Match closing brace forward, put content between * braces on a separate line and align a closing brace. */ function tryBraceSplit_ch(line) { var result = -1; // Get last char from previous line (opener) and a first from the current (closer) var firstCharPos = document.lastColumn(line - 1); var firstChar = document.charAt(line - 1, firstCharPos); var lastCharPos = document.firstColumn(line); var lastChar = document.charAt(line, lastCharPos); var isCurveBracketsMatched = (firstChar == '{' && lastChar == '}'); var isBracketsMatched = isCurveBracketsMatched || (firstChar == '[' && lastChar == ']') || (firstChar == '(' && lastChar == ')') || (firstChar == '<' && lastChar == '>') ; if (isBracketsMatched) { var currentIndentation = document.firstVirtualColumn(line - 1); result = currentIndentation + gIndentWidth; document.insertText(line, document.firstColumn(line), "\n"); document.indent(new Range(line + 1, 0, line + 1, 1), currentIndentation / gIndentWidth); // Add half-tab (2 spaces) if matched not a curve bracket or // open character isn't the only one on the line var isOpenCharTheOnlyOnLine = (document.firstColumn(line - 1) == firstCharPos); if (!(isCurveBracketsMatched || isOpenCharTheOnlyOnLine)) document.insertText(line + 1, document.firstColumn(line + 1), " "); view.setCursorPosition(line, result); } if (result != -1) { dbg("tryBraceSplit_ch result="+result); tryToKeepInlineComment(line); } return result; } /** * Even if counterpart brace not found (\sa \c tryBraceSplit_ch), align the current line * to one level deeper if last char on a previous line is one of open braces. * \code * foo(|blah); * // or * {| * // or * smth<|blah, blah> * // or * array[|idx] = blah; * \endcode */ function tryToAlignAfterOpenBrace_ch(line) { var result = -1; var pos = document.lastColumn(line - 1); var ch = document.charAt(line - 1, pos); if (ch == '(' || ch == '[') { result = document.firstColumn(line - 1) + gIndentWidth; } else if (ch == '{') { if (document.startsWith(line - 1, "namespace", true)) result = 0; else result = document.firstColumn(line - 1) + gIndentWidth; } else if (ch == '<') { // Does it looks like 'operator<<'? if (document.charAt(line - 1, pos - 1) != '<') result = document.firstColumn(line - 1) + gIndentWidth; else result = document.firstColumn(line - 1) + (gIndentWidth / 2); } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignOpenBrace_ch result="+result); } return result; } function tryToAlignBeforeCloseBrace_ch(line) { var result = -1; var pos = document.firstColumn(line); var ch = document.charAt(line, pos); if (ch == '}' || ch == ')' || ch == ']') { var openBracePos = document.anchor(line, pos, ch); dbg("Found open brace @", openBracePos); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + (ch == '}' ? 0 : (gIndentWidth / 2)); } else if (ch == '>') { // TBD } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignBeforeCloseBrace_ch result="+result); } return result; } function tryToAlignBeforeComma_ch(line) { var result = -1; var pos = document.firstColumn(line); var ch = document.charAt(line, pos); if (line > 0 && (ch == ',' || ch == ';')) { var openBracePos = document.anchor(line, pos, '('); if (!openBracePos.isValid()) openBracePos = document.anchor(line, pos, '['); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + 2; } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignBeforeComma_ch result="+result); } return result; } /// Check if a multiline comment introduced on a previous line function tryMultilineCommentStart_ch(line) { var result = -1; // Check if multiline comment was started on the line // and ENTER wan't pressed right after a /*C-style comment*/ if (document.startsWith(line - 1, "/*", true) && !document.endsWith(line - 1, "*/", true)) { var filler = String().fill(' ', document.firstVirtualColumn(line - 1) + 1); var padding = filler + "* "; // If next line (if present) doesn't looks like a continue of the current comment, // then append a comment closer also... if ((line + 1) < document.lines()) { // Maybe user wants to extend a multiline C-style/Doxygen comment // by pressing ENTER at start of it? if (!document.startsWith(line + 1, "*", true)) { // ... doesn't looks like a multiline comment padding += "\n" + filler; // Maybe user just splits a C-style comment? if (!document.startsWith(line, "*/", true)) padding += document.endsWith(line, "*/", true) ? "* " : "*/"; else document.removeText(line, 0, line, document.firstColumn(line)) } // else, no need to append a closing */ } else // There is no a next line... { padding += "\n" + filler; if (!document.startsWith(line, "*/", true)) padding += document.endsWith(line, "*/", true) ? "* " : "*/"; else document.removeText(line, 0, line, document.firstColumn(line)) } document.insertText(line, 0, padding); view.setCursorPosition(line, filler.length + 2); result = -2; } if (result != -1) { dbg("tryMultilineCommentStart_ch result="+result); } return result; } /// Check if \c ENTER was hit inside or at last line of a multiline comment function tryMultilineCommentCont_ch(line) { var result = -1; // Check if multiline comment continued on the line: // 0) it starts w/ a start // 1) and followed by a space (i.e. it doesn't looks like a dereference) or nothing var firstCharPos = document.firstColumn(line - 1); var prevLineFirstChar = document.charAt(line - 1, firstCharPos); var prevLineSecondChar = document.charAt(line - 1, firstCharPos + 1); if (prevLineFirstChar == '*' && (prevLineSecondChar == ' ' || prevLineSecondChar == -1)) { if (document.charAt(line - 1, firstCharPos + 1) == '/') // ENTER pressed after multiline comment: unindent 1 space! result = firstCharPos - 1; else { // Ok, ENTER pressed inside of the multiline comment: // just append one more line... var filler = String().fill(' ', document.firstColumn(line - 1)); // Try to continue a C-style comment document.insertText(line, 0, filler + "* "); result = filler.length; } } if (result != -1) { dbg("tryMultilineCommentCont_ch result="+result); } return result; } function tryAfterCloseMultilineComment_ch(line) { var result = -1; if (document.startsWith(line - 1, "*/", true)) { result = document.firstColumn(line - 1) - 1; } if (result != -1) { dbg("tryAfterCloseMultilineComment_ch result="+result); } return result; } /** * Check if a current line has a text after cursor position * and a previous one has a comment, then append a "// " * before cursor and realign if latter was inline comment... */ function trySplitComment_ch(line) { var result = -1; if (document.lastColumn(line) != -1) { // Ok, current line has some text after... // NOTE There is should be at least one space between // the text and the comment var match = /^(.*\s)(\/\/)(.*)$/.exec(document.line(line - 1)); if (match != null && 0 < match[3].trim().length) // If matched and there is some text in a comment { if (0 < match[1].trim().length) // Is there some text before the comment? { // Align comment to gSameLineCommentStartAt result = gSameLineCommentStartAt; } else { result = match[1].length; } var leadMatch = /^([^\s]*\s+).*$/.exec(match[3]); var lead = ""; if (leadMatch != null) lead = leadMatch[1]; else lead = " "; document.insertText(line, 0, "//" + lead); } } if (result != -1) { dbg("trySplitComment_ch result="+result); } return result; } /** * \brief Indent a next line after some keywords. * * Incrase indent after the following keywords: * - \c if * - \c else * - \c for * - \c while * - \c do * - \c case * - \c default * - \c return * - and access modifiers \c public, \c protected and \c private */ function tryIndentAfterSomeKeywords_ch(line) { var result = -1; // Check if ENTER was pressed after some keywords... var sr = splitByComment(line - 1); var prevString = sr.before; dbg("tryIndentAfterSomeKeywords_ch prevString='"+prevString+"'"); var r = /^(\s*)((if|for|while)\s*\(|\bdo\b|\breturn\b|(((public|protected|private)(\s+(slots|Q_SLOTS))?)|default|case\s+.*)\s*:).*$/ .exec(prevString); if (r != null) { dbg("r=",r); if (!r[2].startsWith("return") || !prevString.rtrim().endsWith(';')) result = r[1].length + gIndentWidth; } else { r = /^\s*\belse\b.*$/.exec(prevString) if (r != null) { var prevPrevString = stripComment(line - 2); dbg("tryIndentAfterSomeKeywords_ch prevPrevString='"+prevPrevString+"'"); if (prevPrevString.endsWith('}')) result = document.firstColumn(line - 2); else if (prevPrevString.match(/^\s*[\])>]/)) result = document.firstColumn(line - 2) - gIndentWidth - (gIndentWidth / 2); else result = document.firstColumn(line - 2) - gIndentWidth; // Realign 'else' statement if needed var pp = document.firstColumn(line - 1); if (pp < result) document.insertText(line - 1, 0, String().fill(' ', result - pp)); else if (result < pp) document.removeText(line - 1, 0, line - 1, pp - result); result += gIndentWidth; } } if (result != -1) { tryToKeepInlineComment(line); dbg("tryIndentAfterSomeKeywords_ch result="+result); } return result; } /** * Try to indent a line right after a dangling semicolon * (possible w/ leading close braces and comment after) * \code * foo( * blah * );| * \endcode */ function tryAfterDanglingSemicolon_ch(line) { var result = -1; var prevString = document.line(line - 1); var r = /^(\s*)(([\)\]}]?\s*)*([\)\]]\s*))?;/.exec(prevString); if (r != null) { result = Math.floor(r[1].length / 4) * 4; } else { // Does it looks like a template tail? // i.e. smth like this: // typedef boost::mpl::blah< // params // > type;| r = /^(\s*)([>]+).*;/.exec(prevString); if (r != null) result = Math.floor(r[1].length / 4) * 4; } if (result != -1) { tryToKeepInlineComment(line); dbg("tryDanglingSemicolon_ch result="+result); } return result; } /** * Check if \c ENTER pressed after equal sign * \code * blah = * |blah * \endcode */ function tryAfterEqualChar_ch(line) { var result = -1; var pos = document.lastColumn(line - 1); if (document.charAt(line - 1, pos) == '=') result = document.firstColumn(line - 1) + gIndentWidth; if (result != -1) { tryToKeepInlineComment(line); dbg("tryAfterEqualChar_ch result="+result); } return result; } /// Check if \c ENTER hits after \c #define w/ a backslash function tryMacroDefinition_ch(line) { var result = -1; var prevString = document.line(line - 1); if (prevString.search(/^\s*#\s*define\s+.*\\$/) != -1) result = gIndentWidth; if (result != -1) { dbg("tryMacroDefinition_ch result="+result); } return result; } /** * Do not incrase indent if ENTER pressed before access * specifier (i.e. public/private/protected) */ function tryBeforeAccessSpecifier_ch(line) { var result = -1; if (document.line(line).match(/(public|protected|private):/)) { var openPos = document.anchor(line, 0, '{'); if (openPos.isValid()) result = document.firstColumn(openPos.line); } if (result != -1) { tryToKeepInlineComment(line); dbg("tryBeforeAccessSpecifier_ch result="+result); } return result; } /** * Try to align a line w/ a leading (word) delimiter symbol * (i.e. not an identifier and a brace) */ function tryBeforeDanglingDelimiter_ch(line) { var result = -1; var halfTabNeeded = // current line do not starts w/ a comment !document.line(line).ltrim().startsWith("//") // if a previous line starts w/ an identifier && (document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1) // but the current one starts w/ a delimiter (which is looks like operator) && (document.line(line).search(/^\s*[,%&<=:\|\-\?\/\+\*\.]/) != -1) ; // check if we r at function call or array index var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid() || document.anchor(line, document.firstColumn(line), '[').isValid() ; if (halfTabNeeded) result = document.firstVirtualColumn(line - 1) + (insideBraces ? -2 : 2); if (result != -1) { tryToKeepInlineComment(line); dbg("tryBeforeDanglingDelimiter_ch result="+result); } return result; } function tryPreprocessor_ch(line) { var result = -1; if (document.firstChar(line) == '#') { result = 0; var text = document.line(line); // Get current depth level var currentLevel = getPreprocessorLevelAt(line); if (currentLevel > 0) { // How much spaces we have after hash? var spacesCnt = 0; var column = document.firstColumn(line) + 1; var i = column; for (; i < text.length; i++) { if (text[i] != ' ') break; spacesCnt++; } var wordAfterHash = document.wordAt(line, i); dbg("wordAfterHash='"+wordAfterHash+"'"); if (wordAfterHash[0] == '#') wordAfterHash = wordAfterHash.substring(1, wordAfterHash.length); if (wordAfterHash == "else" || wordAfterHash == "elif" || wordAfterHash == "endif") currentLevel--; var paddingLen = (currentLevel == 0) ? 0 : (currentLevel - 1) * 2 + 1; if (spacesCnt < paddingLen) { var padding = String().fill(' ', paddingLen - spacesCnt); document.insertText(line, column, padding); } else if (paddingLen < spacesCnt) { document.removeText(line, column, line, column + spacesCnt - paddingLen); } } } if (result != -1) { dbg("tryPreprocessor_ch result="+result); } return result; } /** * Check if \c ENTER was pressed on a start of line and * after a block comment. */ function tryAfterBlockComment_ch(line) { var result = -1; if (0 < line) { var prev_non_empty_line = document.prevNonEmptyLine(line - 1); if (prev_non_empty_line != -1 && document.line(prev_non_empty_line).trim().startsWith("*/")) { var p = document.firstColumn(prev_non_empty_line); if ((p % gIndentWidth) != 0) result = Math.floor(p / gIndentWidth) * gIndentWidth; } } if (result != -1) { dbg("tryAfterBlockComment_ch result="+result); } return result; } /** * Check if \c ENTER was pressed after \c break or \c continue statements * and if so, unindent the current line. */ function tryAfterBreakContinue_ch(line) { var result = -1; var currentLineText = document.line(line - 1).ltrim(); var should_proceed = currentLineText.startsWith("break;") || currentLineText.startsWith("continue;") if (should_proceed) { result = document.firstColumn(line - 1) - gIndentWidth; } if (result != -1) { dbg("tryAfterBreakContinue_ch result="+result); } return result; } /// \internal function getStringAligmentAfterSplit(line) { var prevLineFirstChar = document.firstChar(line - 1); var halfIndent = prevLineFirstChar == ',' || prevLineFirstChar == ':' || prevLineFirstChar == '?' || prevLineFirstChar == '<' || prevLineFirstChar == '>' || prevLineFirstChar == '&' ; return document.firstColumn(line - 1) + ( prevLineFirstChar != '"' ? (halfIndent ? (gIndentWidth / 2) : gIndentWidth) : 0 ); } /** * Handle the case when \c ENTER has pressed in the middle of a string. * Find a string begin (a quote char) and analyze if it is a C++11 raw * string literal. If it is not, add a "closing" quote to a previous line * and to the current one. Align a 2nd part (the moved down one) of a string * according a previous line. If latter is a pure string, then give the same * indentation level, otherwise incrase it to one \c TAB. * * Here is few cases possible: * - \c ENTER has pressed in a line auto some = ""|, so a new * line just have an empty string or some text which is doesn't matter now; * - \c ENTER has pressed in a line auto some = "possible some text here| and here", * then a new line will have and here" text * * In both cases attribute at (line-1, lastColumn-1) will be \c String */ function trySplitString_ch(line) { var result = -1; var column = document.lastColumn(line - 1); if (isComment(line - 1, column)) return result; // Do nothing for comments // Check if last char on a prev line has string attribute var lastColumnIsString = isString(line - 1, column); var firstColumnIsString = isString(line, 0); var firstChar = (document.charAt(line, 0) == '"'); if (!lastColumnIsString) // If it is not, { // TODO TBD if (firstColumnIsString && firstChar == '"') result = getStringAligmentAfterSplit(line); return result; // then nothing to do... } var lastChar = (document.charAt(line - 1, column) == '"'); var prevLastColumnIsString = isString(line - 1, column - 1); var prevLastChar = (document.charAt(line - 1, column - 1) == '"'); dbg("trySplitString_ch: lastColumnIsString="+lastColumnIsString); dbg("trySplitString_ch: lastChar="+lastChar); dbg("trySplitString_ch: prevLastColumnIsString="+prevLastColumnIsString); dbg("trySplitString_ch: prevLastChar="+prevLastChar); dbg("trySplitString_ch: isString(line,0)="+firstColumnIsString); dbg("trySplitString_ch: firstChar="+firstChar); var startOfString = firstColumnIsString && firstChar; var endOfString = !(firstColumnIsString || firstChar); var should_proceed = !lastChar && prevLastColumnIsString && (endOfString || !prevLastChar && startOfString) || lastChar && !prevLastColumnIsString && !prevLastChar && (endOfString || startOfString) ; dbg("trySplitString_ch: ------ should_proceed="+should_proceed); if (should_proceed) { // Add closing quote to the previous line document.insertText(line - 1, document.lineLength(line - 1), '"'); // and open quote to the current one document.insertText(line, 0, '"'); // NOTE If AutoBrace plugin is used, it won't add a quote // char, if cursor positioned right before another quote char // (which was moved from a line above in this case)... // So, lets force it! if (startOfString && document.charAt(line, 1) != '"') { document.insertText(line, 1, '"'); // Add one more! view.setCursorPosition(line, 1); // Step back inside of string } result = getStringAligmentAfterSplit(line); } if (result != -1) { dbg("trySplitString_ch result="+result); tryToKeepInlineComment(line); } return result; } /** * Here is few cases possible: * \code * // set some var to lambda function * auto some = [foo](bar)| * * // lambda as a parameter (possible the first one, * // i.e. w/o a leading comma) * std::foreach( * begin(container) * , end(container) * , [](const value_type& v)| * ); * \endcode */ function tryAfterLambda_ch(line) { var result = -1; var column = document.lastColumn(line - 1); if (isComment(line - 1, column)) return result; // Do nothing for comments var sr = splitByComment(line - 1); if (sr.before.match(/\[[^\]]*\]\([^{]*\)[^{}]*$/)) { var align = document.firstColumn(line - 1); var before = sr.before.ltrim(); if (before.startsWith(',')) align += 2; var padding = String().fill(' ', align); var tail = before.startsWith('auto ') ? "};" : "}"; document.insertText( line , 0 , padding + "{\n" + padding + String().fill(' ', gIndentWidth) + "\n" + padding + tail ); view.setCursorPosition(line + 1, align + gIndentWidth); result = -2; } if (result != -1) { dbg("tryAfterLambda_ch result="+result); } return result; } /// Wrap \c tryToKeepInlineComment as \e caret-handler function tryToKeepInlineComment_ch(line) { tryToKeepInlineComment(line); return -1; } /** * \brief Handle \c ENTER key */ function caretPressed(cursor) { var result = -1; var line = cursor.line; // Dunno what to do if previous line isn't available if (line - 1 < 0) return result; // Nothing (dunno) to do if no previous line... // Register all indent functions var handlers = [ tryBraceSplit_ch // Handle ENTER between braces , tryMultilineCommentStart_ch , tryMultilineCommentCont_ch , tryAfterCloseMultilineComment_ch , trySplitComment_ch , tryToAlignAfterOpenBrace_ch // Handle {,[,(,< on a previous line , tryToAlignBeforeCloseBrace_ch // Handle },],),> on a current line before cursor , tryToAlignBeforeComma_ch // Handle ENTER pressed before comma or semicolon , tryIndentAfterSomeKeywords_ch // NOTE It must follow after trySplitComment_ch! , tryAfterDanglingSemicolon_ch , tryMacroDefinition_ch , tryBeforeDanglingDelimiter_ch , tryBeforeAccessSpecifier_ch , tryAfterEqualChar_ch , tryPreprocessor_ch , tryAfterBlockComment_ch , tryAfterBreakContinue_ch , trySplitString_ch // Handle ENTER pressed in the middle of a string , tryAfterLambda_ch // Handle ENTER after lambda prototype and before body , tryToKeepInlineComment_ch // NOTE This must be a last checker! ]; // Apply all functions until result gets changed for ( var i = 0 ; i < handlers.length && result == -1 ; result = handlers[i++](line) ); return result; } /** * \brief Handle \c '/' key pressed * * Check if is it start of a comment. Here is few cases possible: * \li very first \c '/' -- do nothing * \li just entered \c '/' is a second in a sequence. If no text before or some present after, * do nothing, otherwise align a \e same-line-comment to \c gSameLineCommentStartAt * position. * \li just entered \c '/' is a 3rd in a sequence. If there is some text before and no after, * it looks like inlined doxygen comment, so append \c '<' char after. Do nothing otherwise. * \li if there is a '// ' string right before just entered \c '/', form a * doxygen comment '///' or '///<' depending on presence of some text * on a line before the comment. * * \todo Due the BUG #316809 in a current version of Kate, this code doesn't work as expected! * It always returns a "NormalText"! * \code * var cm = document.attributeName(cursor); * if (cm.indexOf("String") != -1) * return; * \endcode * * \bug This code doesn't work properly in the following case: * \code * std::string bug = "some text// * \endcode * */ function trySameLineComment(cursor) { var line = cursor.line; var column = cursor.column; // First of all check that we are not withing a string if (document.isString(line, column)) return; var sc = splitByComment(line); if (sc.hasComment) // Is there any comment on a line? { // Make sure we r not in a comment already -- it can be a multiline one... var fc = document.firstColumn(line); var text = document.line(line).ltrim(); var nothing_to_do = (fc < (column - 1)) && document.isComment(line, fc); // Also check that line has smth that needs to be "fixed"... if (nothing_to_do && text != "//" && text != "// /" && text != "///" && text != "/// /") return; // If no text after the comment and it still not aligned var text_len = sc.before.rtrim().length; if (text_len != 0 && sc.after.length == 0 && text_len < gSameLineCommentStartAt) { // Align it! document.insertText( line , column - 2 , String().fill(' ', gSameLineCommentStartAt - sc.before.length) ); document.insertText(line, gSameLineCommentStartAt + 2, ' '); } // If text in a comment equals to '/' or ' /' -- it looks like a 3rd '/' pressed else if (sc.after == " /" || sc.after == "/") { // Form a Doxygen comment! document.removeText(line, column - sc.after.length, line, column); document.insertText(line, column - sc.after.length, text_len != 0 ? "/< " : "/ "); } // If right trimmed text in a comment equals to '/' -- it seems user moves cursor // one char left (through space) to add one more '/' else if (sc.after.rtrim() == "/") { // Form a Doxygen comment! document.removeText(line, column, line, column + sc.after.length); document.insertText(line, column, text_len != 0 ? "< " : " "); } else if (sc.after == "/ /") { // Looks like user wants to "draw a fence" w/ '/'s document.removeText(line, column - 2, line, column - 1); } else if (text_len == 0 && sc.after.length == 0) { document.insertText(line, column, ' '); } } } /** * \brief Maybe '>' needs to be added? * * Here is a few cases possible: * \li user entered "template > * \li user entered smth like std::map> * \li user wants to output smth to C++ I/O stream by typing >> * possible at the line start, so it must be half indented * \li shortcut: some(<|) transformed into some()<| * * But, do not add '>' if there some text after cursor. */ function tryTemplate(cursor) { var line = cursor.line; var column = cursor.column; var result = -2; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // Check for 'template' keyword at line start var currentString = document.line(line); var prevWord = document.wordAt(line, column - 1); dbg("tryTemplate: prevWord='"+prevWord+"'"); dbg("tryTemplate: prevWord.match="+prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/)); // Add a closing angle bracket if a prev word is not a 'operator' // and it looks like an identifier or current line starts w/ 'template' keyword var isCloseAngleBracketNeeded = (prevWord != "operator") && (currentString.match(/^\s*template\s*<$/) || prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/)) && (column == document.lineLength(line) || document.charAt(cursor).match(/\W/)) ; if (isCloseAngleBracketNeeded) { document.insertText(cursor, ">"); view.setCursorPosition(cursor); } else if (justEnteredCharIsFirstOnLine(line, column, '<')) { result = tryIndentRelativePrevNonCommentLine(line); } // Add a space after 2nd '<' if a word before is not a 'operator' else if (document.charAt(line, column - 2) == '<') { if (document.wordAt(line, column - 3) != "operator") { // Looks like case 3... // 0) try to remove '>' if user typed 'some<' before // (and closing '>' was added by tryTemplate) if (column < document.lineLength(line) && document.charAt(line, column) == '>') { document.removeText(line, column, line, column + 1); addCharOrJumpOverIt(line, column - 2, ' '); view.setCursorPosition(line, column + 1); column = column + 1; } // add a space after operator<< document.insertText(line, column, " "); } else { document.insertText(line, column, "()"); view.setCursorPosition(line, column + 1); } } else { cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis tryAddSpaceAfterClosedBracketOrQuote(cursor); } return result; } /** * This function called for some characters and trying to do the following: * if the cursor (right after a trigger character is entered) is positioned withing * a parenthesis, move the entered character out of parenthesis. * * For example: * \code * auto a = two_params_func(get_first(,|)) * // ... transformed into * auto a = two_params_func(get_first(),|) * \endcode * * because entering comma right after ( definitely incorrect, but * we can help the user (programmer) to avoid 3 key presses ;-) * (RightArrow, ',', space) * * except comma here are other "impossible" characters: * \c ., \c ?, \c :, \c %, \c |, \c /, \c =, \c <, \c >, \c ], \c } * * But \c ; will be handled separately to be able to jump over all closing \c ). * * \sa \c trySemicolon() * * \note This valid if we r not inside a comment or a string literal, * and the char out of the parenthesis is not the same as just entered ;-) * * \param cursor initial cursor position * \param es edit session instance * \return new (possible modified) cursor position */ function tryJumpOverParenthesis(cursor) { var line = cursor.line; var column = cursor.column; if (2 < column && isStringOrComment(line, column)) return cursor; // Do nothing for comments of string literals // Check that we r inside of parenthesis and some symbol between var pc = document.charAt(line, column - 2); var cc = document.charAt(cursor); if ((pc == '(' && cc == ')') || (pc == '{' && cc == '}')) { var c = document.charAt(line, column - 1); switch (c) { case '.': if (pc == '(' && cc == ')') { // Make sure this is not a `catch (...)` if (document.startsWith(line, "catch (.)", true)) { document.insertText(line, column, ".."); view.setCursorPosition(line, column + 3); break; } } case ',': case '?': case ':': case '%': case '^': case '|': case '/': // TODO ORLY? case '=': case '<': case '>': case '}': case ')': case ']': // NOTE '[' could be a part of lambda { // Ok, move character out of parenthesis document.removeText(line, column - 1, line, column); // Check is a character after the closing brace the same as just entered one addCharOrJumpOverIt(line, column, c); return view.cursorPosition(); } default: break; } } return cursor; } /** * Handle the case when some character was entered after a some closing bracket. * Here is few close brackets possible: * \li \c ) -- ordinal function call * \li \c } -- C++11 constructor call * \li \c ] -- array access * \li \c " -- end of a string literal * \li \c ' -- and of a char literal * * This function try to add a space between a closing quote/bracket and operator char. * * \note This valid if we r not inside a comment or a string literal. */ function tryAddSpaceAfterClosedBracketOrQuote(cursor) { var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column - 1)) return cursor; // Do nothing for comments of string literals // Check if we have a closing bracket before a last entered char var b = document.charAt(line, column - 2); if (!(b == ']' || b == '}' || b == ')' || b == '"' || b == "'")) return cursor; // Ok, lets check what we've got as a last char var c = document.charAt(line, column - 1); dbg("tryAddSpaceAfterClosedBracketOrQuote: c='"+c+"', @"+new Cursor(line, column-1)); switch (c) { case '*': case '/': case '%': case '&': case '|': case '=': case '^': case '?': case ':': case '<': document.insertText(line, column - 1, " "); view.setCursorPosition(line, column + 1); return view.cursorPosition(); case '>': // Close angle bracket may be a part of template instantiation // w/ some function type parameter... Otherwise, add a space after. if (b != ')') { document.insertText(line, column - 1, " "); view.setCursorPosition(line, column + 1); return view.cursorPosition(); } break; default: break; } return cursor; } /** * \brief Try to align parameters list * * If (just entered) comma is a first symbol on a line, * just move it on a half-tab left relative to a previous line * (if latter doesn't starts w/ comma or ':'). * Do nothing otherwise. A space would be added after it anyway. */ function tryComma(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Check is comma a very first character on a line... if (justEnteredCharIsFirstOnLine(line, column, ',')) { result = tryIndentRelativePrevNonCommentLine(line); } else { // Try to stick a comma to a previous non-space char var lastWordPos = document.text(line, 0, line, column - 1).rtrim().length; if (lastWordPos < column) { document.removeText(line, column - 1, line, column); document.insertText(line, lastWordPos, ","); cursor = new Cursor(line, lastWordPos + 1); view.setCursorPosition(cursor); } } cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis addCharOrJumpOverIt(cursor.line, cursor.column, ' '); return result; } /** * \brief Move towards a document start and look for control flow keywords * * \note Keyword must be at the line start * * \return found line's indent, otherwise \c -2 if nothing found */ function tryBreakContinue(line, is_break) { var result = -2; // Ok, look backward and find a loop/switch statement for (; 0 <= line; --line) { var text = document.line(line).ltrim(); var is_loop_or_switch = text.startsWith("for ") || text.startsWith("do ") || text.startsWith("while ") || text.startsWith("if ") || text.startsWith("else if ") ; if (is_break) is_loop_or_switch = is_loop_or_switch || text.startsWith("case ") || text.startsWith("default:") ; if (is_loop_or_switch) break; } if (line != -1) // Found smth? result = document.firstColumn(line) + gIndentWidth; return result; } /** * \brief Handle \c ; character. * * Here is few cases possible (handled): * \li semicolon is a first char on a line -- then, it looks like \c for statement * splitted accross the lines * \li semicolon entered after some keywords: \c break or \c continue, then we * need to align this line taking in account a previous one * \li and finally here is a trick: when auto brackets extension enabled, and user types * a function call like this: * \code * auto var = some_call(arg1, arg2|) * \endcode * (\c '|' shows a cursor position). Note there is no final semicolon in this expression, * cuz pressing '(' leads to the following snippet: some_call(|), so to * add a semicolon you have to move cursor out of parenthesis. The trick is to allow to press * ';' at position shown in the code snippet, so indenter will transform it into this: * \code * auto var = some_call(arg1, arg2);| * \endcode * same works even there is no arguments... * * All the time, when simicolon is not a first non-space symbol (and not a part of a comment * or string) it will be stiked to the last non-space character on the line. */ function trySemicolon(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // If ';' is a first char on a line? if (justEnteredCharIsFirstOnLine(line, column, ';')) { // Check if we are inside a `for' statement var openBracePos = document.anchor(line, column, '('); if (openBracePos.isValid()) { // Add a half-tab relative '(' result = document.firstColumn(openBracePos.line) + 2; document.insertText(cursor, " "); } } else { // Stick ';' to the last "word" var lcsc = document.prevNonSpaceColumn(line, column - 2); if (2 < column && lcsc < (column - 2)) { document.removeText(line, column - 1, line, column); if (document.charAt(line, lcsc) != ';') { document.insertText(line, lcsc + 1, ";"); view.setCursorPosition(line, lcsc + 2); } else view.setCursorPosition(line, lcsc + 1); cursor = view.cursorPosition(); column = cursor.column; } var text = document.line(line).ltrim(); var is_break = text.startsWith("break;"); var should_proceed = is_break || text.startsWith("continue;") if (should_proceed) { result = tryBreakContinue(line - 1, is_break); if (result == -2) result = -1; } // Make sure we r not inside of `for' statement /// \todo Make sure cursor is really inside of \c for and /// not smth like this: for (blah; blah; blah) some(arg,;|) else if (!text.startsWith("for ")) { // Check if next character(s) is ')' and nothing after should_proceed = true; var lineLength = document.lineLength(line); for (var i = column; i < lineLength; ++i) { var c = document.charAt(line, i); if (!(c == ')' || c == ']')) { should_proceed = false; break; } } // Ok, lets move ';' out of "a(b(c(;)))" of any level... if (should_proceed) { // Remove ';' from column - 1 document.removeText(line, column - 1, line, column); // Append ';' to the end of line document.insertText(line, lineLength - 1, ";"); view.setCursorPosition(line, lineLength); cursor = view.cursorPosition(); column = cursor.column; } } // In C++ there is no need to have more than one semicolon. // So remove a redundant one! if (document.charAt(line, column - 2) == ';') { // Remove just entered ';' document.removeText(line, column - 1, line, column); } } return result; } /** * Handle possible dangling operators (moved from a previous line) * * \c ?, \c |, \c ^, \c %, \c . * * Add spaces around ternary operator. */ function tryOperator(cursor, ch) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings var halfTabNeeded = justEnteredCharIsFirstOnLine(line, column, ch) && document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1 ; dbg("tryOperator: halfTabNeeded =", halfTabNeeded); if (halfTabNeeded) { // check if we r at function call or array index var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid() || document.anchor(line, document.firstColumn(line), '[').isValid() || document.anchor(line, document.firstColumn(line), '{').isValid() ; dbg("tryOperator: insideBraces =",insideBraces); result = document.firstColumn(line - 1) + (insideBraces && ch != '.' ? -2 : 2); } var prev_pos = cursor; cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis cursor = tryAddSpaceAfterClosedBracketOrQuote(cursor); // Check if a space before '?' still needed if (prev_pos == cursor && ch == '?' && document.charAt(line, cursor.column - 1) != ' ') document.insertText(line, cursor.column - 1, " "); // Add it! cursor = view.cursorPosition(); // Update cursor position line = cursor.line; column = cursor.column; if (ch == '?') { addCharOrJumpOverIt(line, column, ' '); } // Handle operator| and/or operator|| else if (ch == '|') { /** * Here is 6+3 cases possible (the last bar is just entered): * 0) ??? -- add a space before bar and after if needed * 1) ?? -- add a space after if needed * 2) ??| -- add a space before 1st bar and after the 2nd if needed * 3) ? | -- add a space after the 2nd bar if needed * 4) ?| -- add a space before 1st bar, remove the mid one, add a space after 2nd bar * 5) | -- remove the mid space, add one after 2nd bar * and finally, * 6a) || -- add a space before 1st bar if needed, remove the last bar * 6b) || -- remove the last bar and add a space after 2nd bar if needed * 6c) || -- add a space after if needed */ var prev = document.text(line, column - 4, line, column - 1); dbg("tryOperator: checking @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); var space_offset = 0; if (prev.endsWith(" | ")) { // case 5: remove the mid space document.removeText(line, column - 2, line, column - 1); space_offset = -1; } else if (prev.endsWith("|| ")) { // case 6a: add a space before 1st bar if needed, remove the last bar document.removeText(line, column - 1, line, column); var space_has_added = addCharOrJumpOverIt(line, column - 4, ' '); space_offset = (space_has_added ? 1 : 0) - 2; } else if (prev.endsWith(" ||")) { // case 6b: remove the last bar document.removeText(line, column - 1, line, column); space_offset = -1; } else if (prev.endsWith("||")) { // case 6a: add a space before and remove the last bar document.removeText(line, column - 1, line, column); document.insertText(line, column - 3, " "); } else if (prev.endsWith("| ")) { // case 4: add a space before 1st bar, remove the mid one document.removeText(line, column - 2, line, column - 1); document.insertText(line, column - 3, " "); } else if (prev.endsWith(" |") || prev.endsWith(" ")) { // case 3 and 1 } else { // case 2: add a space before 1st bar if (prev.endsWith('|')) space_offset = 1; // case 0: add a space before bar document.insertText(line, column - 1 - space_offset, " "); space_offset = 1; } addCharOrJumpOverIt(line, column + space_offset, ' '); } // Handle operator% and/or operator^ else if (ch == '%' || ch == '^') { var prev = document.text(line, column - 4, line, column - 1); dbg("tryOperator: checking2 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); var patterns = [" % ", "% ", " %", "%", " "]; for ( var i = 0 ; i < patterns.length ; i++ ) patterns[i] = patterns[i].replace('%', ch); var space_offset = 0; if (prev.endsWith(patterns[0])) { // case 0: remove just entered char document.removeText(line, column - 1, line, column); space_offset = -2; } else if (prev.endsWith(patterns[1])) { // case 1: remove just entered char, add a space before document.removeText(line, column - 1, line, column); document.insertText(line, column - 3, " "); space_offset = -1; } else if (prev.endsWith(patterns[2])) { // case 2: remove just entered char document.removeText(line, column - 1, line, column); space_offset = -1; } else if (prev.endsWith(patterns[3])) { // case 3: add a space before document.removeText(line, column - 1, line, column); document.insertText(line, column - 2, " "); space_offset = 0; } else if (prev.endsWith(patterns[4])) { // case 4: no space needed before space_offset = 0; } else { // case everything else: surround operator w/ spaces document.insertText(line, column - 1, " "); space_offset = 1; } addCharOrJumpOverIt(line, column + space_offset, ' '); } else if (ch == '.') // Replace '..' w/ '...' { var prev = document.text(line, column - 3, line, column); dbg("tryOperator: checking3 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); if (prev == "...") // If there is already 3 dots { // Remove just entered (redundant) one document.removeText(line, column - 1, line, column); } // 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 else if (prev[1] == '.' && prev[2] == '.' && (document.defStyleNum(line, column) == 0 || document.defStyleNum(line, column) == 5)) { addCharOrJumpOverIt(line, column, '.'); } // Otherwise, do nothing... } if (result != -2) { dbg("tryOperator result="+result); } return result; } /** * \brief Try to align a given close bracket */ function tryCloseBracket(cursor, ch) { var result = -2; var line = cursor.line; var column = cursor.column; var braceCursor = Cursor.invalid(); if (ch != '>') { // TODO Make sure a given `ch` in the gBraceMap braceCursor = document.anchor(line, column - 1, gBraceMap[ch]); // make sure we are actually on the character we are expecting // if that's not the case, a brace was probably inserted behind the cursor by auto brackets realCharacter = document.charAt(line, column - 1); if (realCharacter != ch) { braceCursor = document.anchor(line, column, gBraceMap[ch]); } // TODO Otherwise, it seems we have a template parameters list... } // unindent when starting new block and previous line has lower indentation level // prevents over-indentation when inserting condition or loop headers with their opening brace in the next line var firstPos = document.firstColumn(line); var prevFirstPos = document.firstColumn(line - 1); if (firstPos == column - 1 && ch == '}' && firstPos > prevFirstPos) { result = prevFirstPos; } // Check if a given closing brace is a first char on a line // (i.e. it is 'dangling' brace)... if (justEnteredCharIsFirstOnLine(line, column, ch) && braceCursor.isValid()) { // Move to one half-TAB right, if anything but not closing '}', else // align to the corresponding open char result = document.firstColumn(braceCursor.line) + (ch != '}' ? 2 : 0); dbg("tryCloseBracket: setting result="+result); } // Check if ';' required after closing '}', but make sure it was inserted by the user // and not automatically by auto brackets (behind the cursor) if (ch == '}' && braceCursor.isValid()) { var is_check_needed = false; // Check if corresponding anchor is a class/struct/union/enum, // (possible keyword located on same or prev line) // and check for trailing ';'... var anchoredString = document.line(braceCursor.line); dbg("tryCloseBracket: anchoredString='"+anchoredString+"'"); var regex = /^(\s*)(class|struct|union|enum).*$/; var r = regex.exec(anchoredString); if (r != null) { dbg("tryCloseBracket: same line"); is_check_needed = true; } else (!is_check_needed && 0 < braceCursor.line) // Is there any line before? { dbg("tryCloseBracket: checking prev line"); // Ok, lets check it! anchoredString = document.line(braceCursor.line - 1); dbg("tryCloseBracket: anchoredString-1='"+anchoredString+"'"); r = regex.exec(anchoredString); if (r != null) { is_check_needed = true; dbg("tryCloseBracket: prev line"); } } dbg("tryCloseBracket: is_check_needed="+is_check_needed); if (is_check_needed) { var is_ok = document.line(line) .substring(column, document.lineLength(line)) .ltrim() .startsWith(';') ; if (!is_ok) { var pos = column; var newCursorPos = pos + 1; if (document.charAt(line, column - 1) != '}' && document.charAt(line, column) == '}') { ++pos; --newCursorPos; } document.insertText(line, pos, ';'); view.setCursorPosition(line, newCursorPos); } } } else if (ch == '>') { // If user typed 'some' + '<' + '>', jump over the '>' // (which was added by the tryTemplate) if (document.charAt(line, column) == '>') { document.removeText(line, column, line, column + 1); } } tryJumpOverParenthesis(view.cursorPosition()); return result; } /** * \brief Indent a new scope block * * ... try to unindent to be precise... First of all check that open * \c '{' is a first symbol on a line, and if it doesn't, * add space (if absent at previous position) after ')' or \c '=' * or if line stats w/ some keywords: \c enum, \c class, \c struct or \c union. * Otherwise, look at the previous line for dangling ')' or * a line started w/ one of flow control keywords. * */ function tryBlock(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Make sure we r not in a comment or string dbg("tryBlock: isStringOrComment(line, column - 2)="+isStringOrComment(line, column - 2)) if (isStringOrComment(line, column - 2)) return result; if (justEnteredCharIsFirstOnLine(line, column, '{')) { // Check for a dangling close brace on a previous line // (this may mean that `for' or `if' or `while' w/ looong parameters list on it) if (document.firstChar(line - 1) == ')') result = Math.floor(document.firstColumn(line - 1) / gIndentWidth) * gIndentWidth; else { // Otherwise, check for a keyword on the previous line and // indent the started block to it... var prevString = document.line(line - 1); var r = /^(\s*)((catch|if|for|while)\s*\(|do|else|try|(default|case\s+.*)\s*:).*$/.exec(prevString); if (r != null) result = r[1].length; } } else { // '{' is not a first char. Check for a previous one... if (1 < column) { var prevChar = document.charAt(line, column - 2); dbg("tryBlock: prevChar='"+prevChar+"'"); if (prevChar == ')' || prevChar == '=') document.insertText(line, column - 1, ' '); else if (prevChar != ' ') { var currentLine = document.line(line).ltrim(); var starts_with_keyword = currentLine.startsWith('struct ') || currentLine.startsWith('class ') || currentLine.startsWith('union ') || currentLine.startsWith('enum ') ; if (starts_with_keyword) document.insertText(line, column - 1, ' '); } } } return result; } /** * \brief Align preprocessor directives */ function tryPreprocessor(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Check if just entered '#' is a first on a line if (justEnteredCharIsFirstOnLine(line, column, '#')) { // Get current indentation level var currentLevel = getPreprocessorLevelAt(line); if (currentLevel > 0) { var padding = String().fill(' ', (currentLevel - 1) * 2 + 1); document.insertText(cursor, padding); } result = 0; } return result; } /** * \brief Try to align access modifiers or class initialization list * * Here is few cases possible: * \li \c ':' pressed after a keyword \c public, \c protected or \c private. * Then align a current line to corresponding class/struct definition. * Check a previous line and if it is not starts w/ \c '{' add a new line before. * \li \c ':' is a first char on the line, then it looks like a class initialization * list or 2nd line of ternary operator. * \li \c ':' is pressed on a line started w/ \c for statement and after a space * \li \c ':' after '>' looks like an access to template's member * \li shortcut: transform some(:|) into some() :| * * \todo Should it be done only for non strings and comments? */ function tryColon(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // Check if just entered ':' is a first on a line if (justEnteredCharIsFirstOnLine(line, column, ':')) { // Check if there a dangling ')' or '?' (ternary operator) on a previous line var ch = document.firstChar(line - 1); if (ch == ')' || ch == '?') result = document.firstVirtualColumn(line - 1); else result = document.firstVirtualColumn(line - 1) + 2; document.insertText(cursor, " "); } else { var currentLine = document.line(line); if (currentLine.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) != -1) { var definitionCursor = document.anchor(line, 0, '{'); if (definitionCursor.isValid()) { result = document.firstVirtualColumn(definitionCursor.line); dbg("tryColon: result="+result); if (0 < line) // Is there any line before? { // Check if previous line is not empty and not starts w/ '{' var prevLine = document.line(line - 1).trim() if (prevLine.length && !prevLine.startsWith("{")) { // Cuz a new line will be added in place of current, returning // result will not affect indentation. So do it manually. var firstColumn = document.firstColumn(line); var padding = ""; if (firstColumn < result) padding = String().fill(' ', result - firstColumn); else if (result < firstColumn) document.removeText(line, 0, line, firstColumn - result); // Add an empty line before the current document.insertText(line, 0, "\n" + padding); result = 0; } } } } else if (document.charAt(line, column - 2) == ' ') { // Is it looks like a range based `for' or class/struct/enum? var add_space = currentLine.ltrim().startsWith("for (") || currentLine.ltrim().startsWith("class ") || currentLine.ltrim().startsWith("struct ") || currentLine.ltrim().startsWith("enum ") ; if (add_space) { // Add a space after ':' document.insertText(line, column, " "); } else if (document.charAt(line, column - 3) == ':') { // Transform ': :' -> '::' document.removeText(line, column - 2, line, column - 1); } } else if (document.charAt(line, column - 2) == ':') { // A char before is (already) a one more colon. // Make sure there is no more than two colons... // NOTE In C++ it is not possible to have more than two of them adjacent! if (document.charAt(line, column - 3) == ':') { // Remove the current (just entered) one... document.removeText(line, column - 1, line, column); } } else { // Check that it is not a 'case' and not a magic sequence. // NOTE "Magic sequence" means support for dynamic expand functions. // http://zaufi.github.io/programming/2014/02/13/kate-c++-stuff/ var is_magic_sequence = document.charAt( line , document.wordRangeAt(line, column - 1).start.column - 1 ) == ';'; if (!currentLine.ltrim().startsWith("case ") && !is_magic_sequence) { // Add one more ':' // Example some: --> some:: or std: --> std:: document.insertText(line, column, ":"); } else { // Try to jump out of parenthesis cursor = tryJumpOverParenthesis(cursor); // Try add a space after close bracket tryAddSpaceAfterClosedBracketOrQuote(cursor); } } } return result; } /** * \brief Try to add one space after keywords and before an open brace */ function tryOpenBrace(cursor) { var line = cursor.line; var column = cursor.column; var wordBefore = document.wordAt(line, column - 1); dbg("word before: '"+wordBefore+"'"); if (wordBefore.search(/\b(catch|for|if|switch|while|return)\b/) != -1) document.insertText(line, column - 1, " "); } function getMacroRange(line) { function stripLastCharAndRTrim(str) { return str.substring(0, str.length - 1).rtrim(); } var maxLength = 0; var macroStartLine = -1; // Look up towards begining of a document for (var i = line; i >= 0; --i) { var currentLineText = document.line(i); dbg("up: '"+currentLineText+"'"); if (currentLineText.search(/^\s*#\s*define\s+.*\\$/) != -1) { macroStartLine = i; maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); break; // Ok, we've found the macro start! } else if (currentLineText.search(/\\$/) == -1) break; // Oops! No backslash found and #define still not reached! maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); } if (macroStartLine == -1) return null; // Look down towards end of the document var macroEndLine = -1; for (var i = line; i < document.lines(); ++i) { var currentLineText = document.line(i); dbg("dw: '"+currentLineText+"'"); if (currentLineText.search(/\\$/) != -1) // Make sure the current line have a '\' at the end { macroEndLine = i; maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); } else break; // No backslash at the end --> end of macro! } if (macroEndLine == -1) return null; macroEndLine++; return { range: new Range(macroStartLine, 0, macroEndLine, 0) , max: maxLength }; } /** * \brief Try to align a backslashes in macro definition * * \note It is \b illegal to have smth after a backslash in source code! */ function tryBackslash(cursor) { var line = cursor.line; var result = getMacroRange(line); // Look up and down for macro definition range if (result != null) { dbg("macroRange:",result.range); dbg("maxLength:",result.max); // Iterate over macro definition, strip backslash // and add a padding string up to result.max length + backslash for (var i = result.range.start.line; i < result.range.end.line; ++i) { var currentLineText = document.line(i); var originalTextLength = currentLineText.length; currentLineText = currentLineText.substring(0, currentLineText.length - 1).rtrim(); var textLength = currentLineText.length; document.removeText(i, textLength, i, originalTextLength); document.insertText(i, textLength, String().fill(' ', result.max - textLength + 1) + "\\"); } } } /** * \brief Handle a @ symbol * * Possible user wants to add a Doxygen group */ function tryDoxygenGrouping(cursor) { var line = cursor.line; var column = cursor.column; var firstColumn = document.firstColumn(line); // Check the symbol before the just entered var looks_like_doxgorup = isStringOrComment(line, column - 2) && firstColumn == (column - 4) && document.line(line).ltrim().startsWith("// ") ; if (looks_like_doxgorup) { document.removeText(line, column - 2, line, column - 1); var padding = String().fill(' ', firstColumn); document.insertText(line, column - 1, "{\n" + padding + "\n" + padding + "//@}"); view.setCursorPosition(line + 1, document.lineLength(line + 1)); } } /** * \brief Handle quote character * * Look back for \c 'R' char right before \c '"' and if * the next one (after \c '"') is not an alphanumeric, * then add a delimiters. * * \attention Effect of AutoBrace extension has already neutralized at this point :) */ function tryStringLiteral(cursor, ch) { var line = cursor.line; var column = cursor.column; // Do nothing for comments if (isComment(line, column - 2)) return; // First of all we have to determinate where we are: // 0) new string literal just started, or ... // 1) string literal just ends // Check if the '"' is a very first char on a line var new_string_just_started; var raw_string; if (column < 2) { // Yes, then we have to look to the last char of the previous line new_string_just_started = !(line != 0 && isString(line - 1, document.lastColumn(line - 1))); } else { // Ok, just check attribute of the char right before '"' (special case for R"" raw string literals) new_string_just_started = !isString(line, column - 2); if (!new_string_just_started && ch == '"') { // we need to scan backwards before R", [uUL]R" or u8R" var stringEnd; if (column >= 4 && document.charAt(line, column - 2) == 'R' && document.charAt(line, column - 3) == '8' && document.charAt(line, column - 4) == 'u') { raw_string = true; stringEnd = 5; } 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')) { raw_string = true; stringEnd = 4; } else if (column >= 2 && document.charAt(line, column - 2) == 'R') { raw_string = true; stringEnd = 3; } if (raw_string) new_string_just_started = (column >= stringEnd) ? !isString(line, column - stringEnd) : !(line != 0 && isString(line - 1, document.lastColumn(line - 1))); } } // TODO Add a space after possible operator right before just // started string literal... if (new_string_just_started) { // Is there anything after just entered '"'? var nc = document.charAt(line, column); var need_closing_quote = column == document.lineLength(line) || document.isSpace(nc) || nc == ',' // user tries to add new string param, || nc == ')' // ... or one more param to the end of some call || nc == ']' // ... or string literal as subscript index || nc == ';' // ... or one more string before end of expression || nc == '<' // ... or `some << "|<<` ; if (need_closing_quote) { // Check for 'R' right before '"' if (raw_string) { // Yeah, looks like a raw string literal /// \todo Make delimiter configurable... HOW? /// It would be nice if indenters can have a configuration page somehow... document.insertText(cursor, "~()~\""); view.setCursorPosition(line, column + 2); } else { document.insertText(cursor, ch); view.setCursorPosition(line, column); } } } } /** * \brief Handle \c '!' char * * Exclamation symbol can be a part of \c operator!= or unary operator. * in both cases, a space required before it! Except few cases: * - when it is at the line start * - when a char before it \c '(' -- i.e. argument of a control flow keyword (\c if, \c while) * or a function call parameter * - when a char before it \c '[' (array subscript) * - when a char before it \c '<' -- here is two cases possible: * - it is a first non-type template parameter (w/ type \c bool obviously) * - it is a part of less or shift operators. * To distinct last case, it is enough to check that a word before \c '<' (w/o space) * is an identifier. * \note Yep, operators supposed to be separated from around text. */ function tryExclamation(cursor) { var line = cursor.line; var column = cursor.column; if (column == 0) // Do nothing for very first char return; if (isStringOrComment(line, column - 1)) // Do nothing for comments and stings return; if (document.firstColumn(line) == column - 1) // Make sure '!' is not a first char on a line return; if (column < 2) // Do nothing if there is less than 2 chars before return; var pc = document.charAt(line, column - 2); // Do nothing if one of 'stop' chars: if (pc == ' ' || pc == '(' || pc == '[' || pc == '{') return; // And finally make sure it is not a part of 'relation operator' if (pc == '<' && column >= 3) { // Make sure a char before is not a space or another '<' var ppc = document.charAt(line, column - 3); if (ppc != ' ' && ppc != '<') return; } // Ok, if we r here, just insert a space ;) document.insertText(line, column - 1, " "); } /** * \brief Handle a space * * - add '()' pair after some keywords like: \c if, \c while, \c for, \c switch * - add ';' if space pressed right after \c return, and no text after it * - if space pressed inside of angle brackets 'some<|>' transform into 'some < |' */ function tryKeywordsWithBrackets(cursor) { var line = cursor.line; var column = cursor.column; var text = document.line(line).ltrim(); var need_brackets = text == "if " || text == "else if " || text == "while " || text == "for " || text == "switch " || text == "catch " ; if (need_brackets) { document.insertText(cursor, "()"); view.setCursorPosition(line, column + 1); } else if (text == "return ") { document.insertText(cursor, ";"); view.setCursorPosition(line, column); } else if (document.charAt(line, column - 2) == '<' && document.charAt(cursor) == '>') { document.removeText(line, column, line, column + 1); document.insertText(line, column - 2, " "); } } /** * Try to add space before and after some equal operators. */ function tryEqualOperator(cursor) { var line = cursor.line; var column = cursor.column; // Do nothing for comments or string literals or lines shorter than 2 if (2 < column && isStringOrComment(line, column)) return cursor; var c = document.charAt(line, column - 2); dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 2)+"), c='"+c+"'"); switch (c) { // Two chars operators: !=, ==, ... case '*': case '%': case '/': case '^': case '|': case '&': case '!': case '=': addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Make sure there is a space before it! if (column >= 3 && document.charAt(line, column - 3) != ' ') document.insertText(line, column - 2, " "); break; case '(': // some(=|) --> some() =| cursor = tryJumpOverParenthesis(cursor); tryEqualOperator(cursor); // Call self again to handle "some()=|" break; case ')': // "some()=" or "(expr)=" --> ") =|" case '}': // It can be a ctor of some proxy object // Add a space between closing bracket and just entered '=' document.insertText(line, column - 1, " "); addCharOrJumpOverIt(line, column + 1, ' '); // Make sure there is a space after it! break; case '<': // Shortcut: transfrom "some<=|>" -> "some <= |" if (document.charAt(cursor) == '>') document.removeText(line, column, line, column + 1); case '>': // This could be '<<=', '>>=', '<=', '>=' // Make sure there is a space after it! addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Check if this is one of >>= or <<= if (column >= 3) { if (document.charAt(line, column - 3) == c) { if (column >= 4 && document.charAt(line, column - 4) != ' ') document.insertText(line, column - 3, " "); } else if (document.charAt(line, column - 3) != ' ') { // <= or >= document.insertText(line, column - 2, " "); } } break; case '[': // This could be a part of lambda capture [=] break; case ' ': // Lookup one more character towards left if (column >= 3) { var pc = document.charAt(line, column - 3); dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 3)+"), pc='"+pc+"'"); switch (pc) { case '=': // Stick the current '=' to the previous char case '|': case '&': case '^': case '<': case '>': case '*': case '/': case '%': document.removeText(line, column - 1, line, column); document.insertText(line, column - 2, '='); break; default: break; } } break; case '+': case '-': addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Here is few things possible: // some+=| --> some += | // some++=| --> some++ = | // some+++=| --> some++ += | var space_offset = -1; if (column >= 3) { if (document.charAt(line, column - 3) == c) { if (column >= 4) { if (document.charAt(line, column - 4) == c) space_offset = 2; else if (document.charAt(line, column - 4) != ' ') space_offset = 1; } } else if (document.charAt(line, column - 3) != ' ') space_offset = 2; } if (space_offset != -1) document.insertText(line, column - space_offset, " "); break; default: dbg("tryEqualOperator: default"); // '=' always surrounded by spaces! addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! document.insertText(line, column - 1, " "); // Make sure there is a space before it! break; } } /** * \brief Process one character * * NOTE Cursor positioned right after just entered character and has +1 in column. * * \attention This function will roll back the effect of \b AutoBrace extension * for quote chars. So this indenter can handle that chars withing predictable * surround... * */ function processChar(line, ch) { var result = -2; // By default, do nothing... var cursor = view.cursorPosition(); if (!cursor) return result; // TODO Is there any `assert' in JS? if (line != cursor.line) { dbg("ASSERTION FAILURE: line != cursor.line"); return result; } document.editBegin(); switch (ch) { case '\n': result = caretPressed(cursor); break; case '/': trySameLineComment(cursor); // Possible user wants to start a comment break; case '<': result = tryTemplate(cursor); // Possible need to add closing '>' after template break; case ',': result = tryComma(cursor); // Possible need to align parameters list break; case ';': result = trySemicolon(cursor); // Possible `for ()` loop spread over lines break; case '?': case '|': case '^': case '%': case '.': result = tryOperator(cursor, ch); // Possible need to align some operator break; case '}': case ')': case ']': case '>': result = tryCloseBracket(cursor, ch); // Try to align a given close bracket break; case '{': result = tryBlock(cursor); break; case '#': result = tryPreprocessor(cursor); break; case ':': result = tryColon(cursor); break; case '(': tryOpenBrace(cursor); // Try to add a space after some keywords break; case '\\': tryBackslash(cursor); break; case '@': tryDoxygenGrouping(cursor); break; case '"': case '\'': tryStringLiteral(cursor, ch); break; case '!': // Almost all the time there should be a space before! tryExclamation(cursor); break; case ' ': tryKeywordsWithBrackets(cursor); break; case '=': tryEqualOperator(cursor); break; case '*': case '&': tryAddSpaceAfterClosedBracketOrQuote(cursor); break; default: break; // Nothing to do... } // Make sure it is not a pure comment line var currentLineText = document.line(cursor.line).ltrim(); if (ch != '\n' && !currentLineText.startsWith("//")) { // Ok, try to keep an inline comment aligned (if any)... // BUG If '=' was inserted (and a space added) in a code line w/ inline comment, // it seems kate do not update highlighting, so position, where comment was before, // still counted as a 'Comment' attribute, but actually it should be 'Normal Text'... // It is why adding '=' will not realign an inline comment... if (alignInlineComment(cursor.line) && ch == ' ') document.insertText(view.cursorPosition(), ' '); } document.editEnd(); return result; } function alignPreprocessor(line) { if (tryPreprocessor_ch(line) == -1) // Is smth happened? return -2; // No! Signal to upper level to try next aligner... return 0; // NOTE preprocessor directives always aligned to 0! } /** * Try to find a next non comment line assuming that a given * one is a start or middle of a multi-line comment. * * \attention This function would ignore anything else than * a simple comments like this one... I.e. if \b right after * star+slash starts anything (non comment, or even maybe after * that another one comment begins), it will be \b IGNORED. * (Just because this is a damn ugly style!) * * \return line number or \c 0 if not found * \note \c 0 is impossible value, so suitable to indicate an error! * * \sa \c alignInsideBraces() */ function findMultiLineCommentBlockEnd(line) { for (; line < document.lines(); line++) { var text = document.line(line).rtrim(); if (text.endsWith("*/")) break; } line++; // Move to *next* line if (line < document.lines()) { // Make sure it is not another one comment, and if so, // going to find it's end as well... var currentLineText = document.line(line).ltrim(); if (currentLineText.startsWith("//")) line = findSingleLineCommentBlockEnd(line); else if (currentLineText.startsWith("/*")) line = findMultiLineCommentBlockEnd(line); } else line = 0; // EOF found return line; } /** * Try to find a next non comment line assuming that a given * one is a single-line one * * \return line number or \c 0 if not found * \note \c 0 is impossible value, so suitable to indicate an error! * * \sa \c alignInsideBraces() */ function findSingleLineCommentBlockEnd(line) { while (++line < document.lines()) { var text = document.line(line).ltrim(); if (text.length == 0) continue; // Skip empty lines... if (!text.startsWith("//")) break; // Yeah! Smth was found finally. } if (line < document.lines()) { var currentLineText = document.line(line).ltrim(); // Get text of the found line while (currentLineText.length == 0) // Skip empty lines if any currentLineText = document.line(++line).ltrim(); // Make sure it is not another one multiline comment, and if so, // going to find it's end as well... if (currentLineText.startsWith("/*")) line = findMultiLineCommentBlockEnd(line); } else line = 0; // EOF found return line; } /** * Almost anything in a code is placed withing some brackets. * So the ideas is simple: * \li find nearest open bracket of any kind * \li depending on its type and presence of leading delimiters (non identifier characters) * add one or half TAB relative a first non-space char of a line w/ found bracket. * * But here is some details: * \li do nothing on empty lines * \li do nothing if first position is a \e string * \li align comments according next non-comment and non-preprocessor line * (i.e. it's desired indent cuz it maybe still unaligned) * * \attention Current Kate version has a BUG: \c anchor() unable to find smth * in a multiline macro definition (i.e. where every line ends w/ a backslash)! */ function alignInsideBraces(line) { // Make sure there is a text on a line, otherwise nothing to align here... var thisLineIndent = document.firstColumn(line); if (thisLineIndent == -1 || document.isString(line, 0)) return 0; // Check for comment on the current line var currentLineText = document.line(line).ltrim(); var nextNonCommentLine = -1; var middleOfMultilineBlock = false; var isSingleLineComment = false; if (currentLineText.startsWith('//')) // Is single line comment on this line? { dbg("found a single-line comment"); // Yep, go to find a next non-comment line... nextNonCommentLine = findSingleLineCommentBlockEnd(line); isSingleLineComment = true; } else if (currentLineText.startsWith('/*')) // Is multiline comment starts on this line? { // Yep, go to find a next non-comment line... dbg("found start of a multiline comment"); nextNonCommentLine = findMultiLineCommentBlockEnd(line); } // Are we already inside of a multiline comment? // NOTE To be sure that we are not inside of #if0/#endif block, // lets check that current line starts w/ '*' also! // NOTE Yep, it is expected (hardcoded) that multiline comment has // all lines started w/ a star symbol! // TODO BUG Kate has a bug: when multiline code snippet gets inserted into // a multiline comment block (like Doxygen's @code/@endcode) // document.isComment() returns true *only& for the first line of it! // So some other way needs to be found to indent comments properly... // TODO DAMN... it doesn't work that way also... for snippets longer than 2 lines. // I suppose kate first insert text, then indent it, and after that highlight it // So indenters based on a highlighting info will not work! BUT THEY DEFINITELY SHOULD! else if (currentLineText.startsWith("*") && document.isComment(line, 0)) { dbg("found middle of a multiline comment"); // Yep, go to find a next non-comment line... nextNonCommentLine = findMultiLineCommentBlockEnd(line); middleOfMultilineBlock = true; } dbg("line="+line); dbg("document.isComment(line, 0)="+document.isComment(line, 0)); //dbg("document.defStyleNum(line, 0)="+document.defStyleNum(line-1, 0)); dbg("currentLineText='"+currentLineText+"'"); dbg("middleOfMultilineBlock="+middleOfMultilineBlock); if (nextNonCommentLine == 0) // End of comment not found? // ... possible due temporary invalid code... // anyway, dunno how to align it! return -2; // So, are we inside a comment? (and we know where it ends) if (nextNonCommentLine != -1) { // Yep, lets try to get desired indent for next non-comment line var desiredIndent = indentLine(nextNonCommentLine); if (desiredIndent < 0) { // Have no idea how to indent this comment! So try to align it // as found line: desiredIndent = document.firstColumn(nextNonCommentLine); } // TODO Make sure that next non-comment line do not starts // w/ 'special' chars... return desiredIndent + (middleOfMultilineBlock|0); } var brackets = [ document.anchor(line, document.firstColumn(line), '(') , document.anchor(line, document.firstColumn(line), '{') , document.anchor(line, document.firstColumn(line), '[') ].sort(); dbg("Found open brackets @ "+brackets); // Check if we are at some brackets, otherwise do nothing var nearestBracket = brackets[brackets.length - 1]; if (!nearestBracket.isValid()) return 0; // Make sure it is not a `namespace' level // NOTE '{' brace should be at the same line w/ a 'namespace' keyword // (yep, according my style... :-) var bracketChar = document.charAt(nearestBracket); var parentLineText = document.line(nearestBracket.line).ltrim(); if (bracketChar == '{' && parentLineText.startsWith("namespace")) return 0; // Ok, (re)align it! var result = -2; switch (bracketChar) { case '{': case '(': case '[': // If current line has some leading delimiter, i.e. non alphanumeric character // add a half-TAB, otherwise add a one TAB... if needed! var parentIndent = document.firstColumn(nearestBracket.line); var openBraceIsFirst = parentIndent == nearestBracket.column; var firstChar = document.charAt(line, thisLineIndent); var isCloseBraceFirst = firstChar == ')' || firstChar == ']' || firstChar == '}'; var doNotAddAnything = openBraceIsFirst && isCloseBraceFirst; var mustAddHalfTab = (!openBraceIsFirst && isCloseBraceFirst) || firstChar == ',' || firstChar == '?' || firstChar == ':' || firstChar == ';' ; var desiredIndent = parentIndent + ( mustAddHalfTab ? (gIndentWidth / 2) : (doNotAddAnything ? 0 : gIndentWidth) ); result = desiredIndent; // Reassign a result w/ desired value! //BEGIN SPAM dbg("parentIndent="+parentIndent); dbg("openBraceIsFirst="+openBraceIsFirst); dbg("firstChar="+firstChar); dbg("isCloseBraceFirst="+isCloseBraceFirst); dbg("doNotAddAnything="+doNotAddAnything); dbg("mustAddHalfTab="+mustAddHalfTab); dbg("desiredIndent="+desiredIndent); //END SPAM break; default: dbg("Dunno how to align this line..."); break; } return result; } function alignAccessSpecifier(line) { var result = -2; var currentLineText = document.line(line).ltrim(); var match = currentLineText.search( /^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/ ); if (match != -1) { // Ok, lets find an open brace of the `class'/`struct' var openBracePos = document.anchor(line, document.firstColumn(line), '{'); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line); } return result; } /** * Try to align \c case statements in a \c switch */ function alignCase(line) { var result = -2; var currentLineText = document.line(line).ltrim(); if (currentLineText.startsWith("case ") || currentLineText.startsWith("default:")) { // Ok, lets find an open brace of the `switch' var openBracePos = document.anchor(line, document.firstColumn(line), '{'); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + gIndentWidth; } return result; } /** * Try to align \c break or \c continue statements in a loop or \c switch. * * Also it take care about the following case: * \code * for (blah-blah) * { * if (smth) * break; * } * \endcode */ function alignBreakContinue(line) { var result = -2; var currentLineText = document.line(line).ltrim(); var is_break = currentLineText.startsWith("break;"); var should_proceed = is_break || currentLineText.startsWith("continue;") if (should_proceed) result = tryBreakContinue(line - 1, is_break); return result; } /** * Try to align a given line * \todo More actions */ function indentLine(line) { dbg(">> Going to indent line "+line); var result = alignPreprocessor(line); // Try to align a preprocessor directive if (result == -2) // Nothing has changed? result = alignAccessSpecifier(line); // Try to align access specifiers in a class if (result == -2) // Nothing has changed? result = alignCase(line); // Try to align `case' statements in a `switch' if (result == -2) // Nothing has changed? result = alignBreakContinue(line); // Try to align `break' or `continue' statements if (result == -2) // Nothing has changed? result = alignInsideBraces(line); // Try to align a generic line alignInlineComment(line); // Always try to align inline comments dbg("indentLine result="+result); if (result == -2) // Still dunno what to do? result = -1; // ... just align according a previous non empty line return result; } /** * \brief Process a newline or one of \c triggerCharacters character. * * This function is called whenever the user hits \c ENTER key. * * It gets three arguments: \c line, \c indentwidth in spaces and typed character * * Called for each newline (ch == \n) and all characters specified in * the global variable \c triggerCharacters. When calling \e Tools->Align * the variable \c ch is empty, i.e. ch == ''. */ function indent(line, indentWidth, ch) { // NOTE Update some global variables gIndentWidth = indentWidth; var crsr = view.cursorPosition(); dbg("indentWidth: " + indentWidth); dbg(" Mode: " + document.highlightingModeAt(crsr)); dbg(" Attribute: " + document.attributeName(crsr)); dbg(" line: " + line); dbg(" char: " + crsr + " -> '" + ch + "'"); if (ch != "") return processChar(line, ch); return indentLine(line); } /** * \todo Better to use \c defStyleNum() instead of \c attributeName() and string comparison * * \todo Prevent second '//' on a line... ? Fix the current way anyway... */ // kate: space-indent on; indent-width 4; replace-tabs on;