File indexing completed on 2024-05-19 04:00:10
0001 var katescript = { 0002 "name": "Ruby", 0003 "author": "Robin Pedersen <robinpeder@gmail.com>", 0004 "license": "LGPL", 0005 "revision": 2, 0006 "kate-version": "5.1", 0007 "required-syntax-style": "ruby", 0008 "indent-languages": ["ruby"] 0009 }; // kate-script-header, must be at the start of the file without comments, pure json 0010 0011 /* 0012 This file is part of the Kate Project. 0013 0014 SPDX-License-Identifier: LGPL-2.0-only 0015 */ 0016 0017 // required katepart js libraries 0018 require ("cursor.js"); 0019 require ("range.js"); 0020 0021 //BEGIN USER CONFIGURATION 0022 //END USER CONFIGURATION 0023 0024 0025 // specifies the characters which should trigger indent, beside the default '\n' 0026 var triggerCharacters = "cdefhilnrsuw}]"; 0027 0028 // Indent after lines that match this regexp 0029 var rxIndent = /^\s*(((public|protected|private)\s+)?def|if|unless|for|while|until|class|module|else|elsif|case|when|begin|rescue|ensure|catch)\b/; 0030 0031 // Unindent lines that match this regexp 0032 var rxUnindent = /^\s*((end|when|else|elsif|rescue|ensure)\b|[\]\}])(.*)$/; 0033 0034 var debugMode = false; 0035 function dbg() { 0036 if (debugMode) { 0037 debug.apply(this, arguments); 0038 } 0039 } 0040 0041 function assert(cond) 0042 { 0043 if (!cond) 0044 throw "assertion failure"; 0045 } 0046 0047 // Return the closest non-empty line, ignoring comments 0048 // (result <= line). Return -1 if the document 0049 function findPrevNonCommentLine(line) 0050 { 0051 line = document.prevNonEmptyLine(line); 0052 while (line >= 0 && document.isComment(line, document.firstColumn(line))) { 0053 line = document.prevNonEmptyLine(line - 1); 0054 } 0055 return line; 0056 } 0057 0058 function isLineContinuing(line) 0059 { 0060 return /\\$/.test(document.line(line)); 0061 } 0062 0063 // Return true if the given column is at least equal to the column that 0064 // contains the last non-whitespace character at the given line, or if 0065 // the rest of the line is a comment. 0066 function isLastCodeColumn(line, column) 0067 { 0068 if (column >= document.lastColumn(line)) 0069 return true; 0070 else if (document.isComment(line, document.nextNonSpaceColumn(line, column+1))) 0071 return true; 0072 else 0073 return false; 0074 } 0075 0076 // Look for a pattern at the end of the statement. 0077 // 0078 // Returns true if the pattern is found, in a position 0079 // that is not inside a string or a comment, and the position + 0080 // the length of the matching part is either the end of the 0081 // statement, or a comment. 0082 // 0083 // The regexp must be global, and the search is continued until 0084 // a match is found, or the end of the string is reached. 0085 function testAtEnd(stmt, rx) 0086 { 0087 assert(rx.global); 0088 var cnt = stmt.content(); 0089 var res; 0090 // Work-around QML bug: 0091 rx.lastIndex = 0; 0092 while ((res = rx.exec(cnt)) !== null) { 0093 var start = res.index; 0094 var end = rx.lastIndex; 0095 if (stmt.isCode(start)) { 0096 if (end == cnt.length) 0097 return true; 0098 if (stmt.isComment(end)) 0099 return true; 0100 } 0101 } 0102 return false; 0103 } 0104 0105 function isStmtContinuing(line) 0106 { 0107 // Is there an open parenthesis? 0108 var anch = lastAnchor(line+1, 0); 0109 if (anch.line >= 0) 0110 return true; 0111 0112 var stmt = new Statement(line, line); 0113 var rx = /((\+|\-|\*|\/|\=|&&|\|\||\band\b|\bor\b|,)\s*)/g; 0114 0115 return testAtEnd(stmt, rx); 0116 } 0117 0118 // Return the first line that is not preceded by a "continuing" line. 0119 // Return currLine if currLine <= 0 0120 function findStmtStart(currLine) 0121 { 0122 var p, l = currLine; 0123 do { 0124 if (l <= 0) return l; 0125 p = l; 0126 l = findPrevNonCommentLine(l - 1); 0127 } while ((l == p-1 && isLineContinuing(l)) || isStmtContinuing(l)); 0128 return p; 0129 } 0130 0131 // Statement class 0132 function Statement(start, end) 0133 { 0134 this.start = start; 0135 this.end = end; 0136 0137 // Convert to string for debugging 0138 this.toString = function() { 0139 return "{" + this.start + "," + this.end + "}"; 0140 } 0141 0142 // Return an object having 'line' and 'column' set to the given offset in a statement 0143 this.offsetToCursor = function(offset) { 0144 // TODO Provide helper function for this when API is converted to using cursors: 0145 var line = this.start; 0146 while (line < this.end && document.lineLength(line) < offset) { 0147 offset -= document.lineLength(line++) + 1; 0148 } 0149 return new Cursor(line, offset); 0150 } 0151 0152 // Return document.attribute at the given offset in a statement 0153 this.attribute = function(offset) { 0154 var cur = this.offsetToCursor(offset); 0155 return document.attribute(cur.line, cur.column); 0156 } 0157 0158 // Return document.isCode at the given offset in a statement 0159 this.isCode = function(offset) { 0160 var cur = this.offsetToCursor(offset); 0161 return document.isCode(cur.line, cur.column); 0162 } 0163 0164 // Return document.isComment at the given offset in a statement 0165 this.isComment = function(offset) { 0166 var cur = this.offsetToCursor(offset); 0167 return document.isComment(cur.line, cur.column); 0168 } 0169 0170 // Return the indent at the beginning of the statement 0171 this.indent = function() { 0172 return document.firstVirtualColumn(this.start); 0173 } 0174 0175 // Return the content of the statement from the document 0176 this.content = function() { 0177 var cnt = ""; 0178 for (var l = this.start; l <= this.end; l++) { 0179 cnt += document.line(l).replace(/\\$/, " "); 0180 if (l < this.end) 0181 cnt += " "; 0182 } 0183 return cnt; 0184 } 0185 } 0186 0187 // Returns a tuple that contains the first and last line of the 0188 // previous statement before line. 0189 function findPrevStmt(line) 0190 { 0191 var stmtEnd = findPrevNonCommentLine(line); 0192 var stmtStart = findStmtStart(stmtEnd); 0193 0194 return new Statement(stmtStart, stmtEnd); 0195 } 0196 0197 function isBlockStart(stmt) 0198 { 0199 var cnt = stmt.content(); 0200 var len = cnt.length; 0201 0202 if (rxIndent.test(cnt) && !isRuby3EndlessMethodDefinition(stmt)) 0203 return true; 0204 0205 var rx = /((\bdo\b|\{)(\s*\|.*\|)?\s*)/g; 0206 0207 return testAtEnd(stmt, rx); 0208 } 0209 0210 function isRuby3EndlessMethodDefinition(stmt) 0211 { 0212 var rx = /^\s*def\s+([^\s(=]+)(\s*\([^)]*\))?\s+=/; 0213 0214 return rx.test(stmt.content()); 0215 } 0216 0217 function isBlockEnd(stmt) 0218 { 0219 var cnt = stmt.content(); 0220 0221 return rxUnindent.test(cnt); 0222 } 0223 0224 function findBlockStart(line) 0225 { 0226 var nested = 0; 0227 var stmt = new Statement(line, line); 0228 while (true) { 0229 if (stmt.start < 0) return stmt; 0230 0231 stmt = findPrevStmt(stmt.start - 1); 0232 if (isBlockEnd(stmt)) { 0233 nested++; 0234 } 0235 if (isBlockStart(stmt)) { 0236 if (nested == 0) 0237 return stmt; 0238 else 0239 nested--; 0240 } 0241 } 0242 } 0243 0244 // check if the trigger characters are in the right context, 0245 // otherwise running the indenter might be annoying to the user 0246 function isValidTrigger(line, ch) 0247 { 0248 if (ch == "" || ch == "\n") 0249 return true; // Explicit align or new line 0250 0251 var res = rxUnindent.exec(document.line(line)); 0252 if (res) { 0253 if (res[3] == "") 0254 return true; // Exact match 0255 } 0256 return false; 0257 } 0258 0259 // Helper function to compare two KTextEditor::Cursor objects. 0260 // Returns 0 if equal, 1 if ca > cb, -1 if ca < cb 0261 function compare(ca, cb) 0262 { 0263 if (ca.line != cb.line) { 0264 return (ca.line > cb.line) ? 1 : -1; 0265 } else if (ca.column != cb.column) { 0266 return (ca.column > cb.column) ? 1 : -1; 0267 } else { 0268 return 0; 0269 } 0270 } 0271 0272 // Find the last open bracket before the current line. 0273 // Result is a KTextEditor::Cursor object, with an extra attribute, 'ch' 0274 // containing the type of bracket. 0275 function lastAnchor(line, column) 0276 { 0277 var anch = document.anchor(line, column, '('); 0278 anch.ch = '('; 0279 var tmp1 = document.anchor(line, column, '{'); 0280 tmp1.ch = '{'; 0281 var tmp2 = document.anchor(line, column, '['); 0282 tmp2.ch = '['; 0283 0284 if (compare(tmp1, anch) == 1) 0285 anch = tmp1; 0286 if (compare(tmp2, anch) == 1) 0287 anch = tmp2; 0288 0289 return anch; 0290 } 0291 0292 // indent gets three arguments: line, indentWidth in spaces, 0293 // typed character indent 0294 function indent(line, indentWidth, ch) 0295 { 0296 if (!isValidTrigger(line, ch)) 0297 return -2; 0298 0299 var prevStmt = findPrevStmt(line - 1); 0300 if (prevStmt.end < 0) 0301 return -2; // Can't indent the first line 0302 0303 var prev = document.prevNonEmptyLine(line); 0304 0305 // HACK Detect here documents 0306 if (document.isAttributeName(prev, document.lineLength(prev)-1, "Ruby:Here Document")) { 0307 return -1; // HERE-DOCUMENT 0308 } 0309 // HACK Detect embedded comments 0310 if (document.isAttributeName(prev, document.lineLength(prev)-1, "Ruby:Blockcomment")) { 0311 return -1; 0312 } 0313 0314 var prevStmtCnt = prevStmt.content(); 0315 var prevStmtInd = prevStmt.indent(); 0316 0317 // Are we inside a parameter list, array or hash? 0318 var anch = lastAnchor(line, 0); 0319 if (anch.line >= 0) { 0320 var shouldIndent = (anch.line == prevStmt.end) || testAtEnd(prevStmt, /,\s*/g); 0321 if (!isLastCodeColumn(anch.line, anch.column) || lastAnchor(anch.line, anch.column).line >= 0) { 0322 // TODO This is alignment, should force using spaces instead of tabs: 0323 if (shouldIndent) { 0324 anch.column += 1; 0325 var nextCol = document.nextNonSpaceColumn(anch.line, anch.column); 0326 if (nextCol > 0 && !document.isComment(anch.line, nextCol)) 0327 anch.column = nextCol; 0328 } 0329 // Keep indent of previous statement, while aligning to the anchor column 0330 return [prevStmtInd, document.toVirtualColumn(anch.line, anch.column)]; 0331 } else { 0332 return document.firstVirtualColumn(anch.line) + (shouldIndent ? indentWidth : 0); 0333 } 0334 } 0335 0336 // Handle indenting of multiline statements. 0337 if ((prevStmt.end == line-1 && isLineContinuing(prevStmt.end)) || isStmtContinuing(prevStmt.end)) { 0338 if (prevStmt.start == prevStmt.end) { 0339 if (ch == '' && document.firstVirtualColumn(line) > document.firstVirtualColumn(prevStmt.end)) { 0340 return -2; // Don't force a specific indent level when aligning manually 0341 } 0342 return prevStmtInd + indentWidth * 2; 0343 } else { 0344 return document.firstVirtualColumn(prevStmt.end); 0345 } 0346 } 0347 0348 if (rxUnindent.test(document.line(line))) { 0349 var startStmt = findBlockStart(line); 0350 if (startStmt.start >= 0) 0351 return startStmt.indent(); 0352 else 0353 return -2; 0354 } 0355 0356 if (isBlockStart(prevStmt)) { 0357 return prevStmtInd + indentWidth; 0358 } else if (prevStmtCnt.search(/[\[\{]\s*$/) != -1) { 0359 return prevStmtInd + indentWidth; 0360 } 0361 0362 // Keep current 0363 return prevStmtInd; 0364 } 0365 0366 // kate: indent-width 2; indent-spaces on;