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;