File indexing completed on 2024-05-19 04:00:10
0001 var katescript = { 0002 "name": "R", 0003 "author": "Pierre de Villemerereuil <pierre.de.villemereuil@mailoo.org>", 0004 "license": "MIT", 0005 "revision": 1, 0006 "kate-version": "5.0", 0007 "indent-languages": ["R", "R Script", "Script R"] 0008 }; // kate-script-header, must be at the start of the file without comments 0009 0010 // Some functions modified from Python indentation file 0011 // (credit to Paul Giannaros <paul@giannaros.org>, Gerald Senarclens de Grancy <oss@senarclens.eu>) 0012 0013 // required katepart js libraries 0014 require ("range.js"); 0015 require ("string.js"); 0016 require ("utils.js") 0017 0018 triggerCharacters = '})]'; 0019 0020 openings = ['(', '[','{']; 0021 closings = [')', ']','}']; // requires same order as in openings 0022 operators = ['+', '-', '*', '/', '^', 0023 '&', '|', '==', '>', '<', '<=', '>=', '!=', 0024 '%%', '%*%', '%/%', '%in%', 0025 '%>%', '%T>%', '%$%', '%<>%']; 0026 equalOperatorSigns = ['=', '>', '<', '!']; 0027 0028 var debugMode = false; 0029 0030 function dbg() { 0031 if (debugMode) { 0032 debug.apply(this, arguments); 0033 } 0034 } 0035 0036 // Extension of endswith for an array of tests 0037 function endsWithAny(suffixes, string) { 0038 return suffixes.some(function (suffix) { 0039 return string.endsWith(suffix); 0040 }); 0041 } 0042 0043 // Return the given line without comments and leading or trailing whitespace. 0044 // but keep strings (compared to getCode) 0045 // Eg. 0046 // getCode(x) -> "for i in range(3):" 0047 // if document.line(x) == " for i in range(3):" 0048 // getCode(x) -> "for i in range(3):" 0049 // if document.line(x) == "for i in range(3): " 0050 // getCode(x) -> "for i in range(3):" 0051 // if document.line(x) == "for i in range(3): # grand" 0052 function getCodeWithString(lineNr) { 0053 var line = document.line(lineNr); 0054 var code = ''; 0055 for (var position = 0; position < line.length; position++) { 0056 if (document.isCode(lineNr, position) || document.isString(lineNr, position) || document.isOthers(lineNr, position)) { 0057 code += line[position]; 0058 } 0059 } 0060 return code.trim(); 0061 } 0062 0063 // Return the given line without comments and leading or trailing whitespace. 0064 // Eg. 0065 // getCode(x) -> "for i in range(3):" 0066 // if document.line(x) == " for i in range(3):" 0067 // getCode(x) -> "for i in range(3):" 0068 // if document.line(x) == "for i in range(3): " 0069 // getCode(x) -> "for i in range(3):" 0070 // if document.line(x) == "for i in range(3): # grand" 0071 function getCode(lineNr) { 0072 var line = document.line(lineNr); 0073 var code = ''; 0074 for (var position = 0; position < line.length; position++) { 0075 if (document.isCode(lineNr, position)) { 0076 code += line[position]; 0077 } 0078 } 0079 return code.trim(); 0080 } 0081 0082 // Returns the number of spaces after "pos"" 0083 // on the line number "lineNr" 0084 function countSpaces(lineNr, pos) { 0085 var line = document.line(lineNr); 0086 var add = 0; 0087 var pos = pos + 1; 0088 while (line[pos] == " ") { 0089 add++; 0090 pos++; 0091 } 0092 return add; 0093 } 0094 0095 // Returns the indent if finding a mismatch using brackets, commas and equal signs 0096 // If there are no mismatch, -1 is returned. 0097 // `lineNr`: number of the line for which the indent is calculated 0098 function calcMismatchIndent(lineNr, add) { 0099 // initialising some counters 0100 var countClosing = new Array(); 0101 closings.forEach(function(elem) { 0102 countClosing[elem] = 0; 0103 }); 0104 if (closings.indexOf(add) > -1 && add.length) { 0105 countClosing[add]++; 0106 } 0107 var countComma = 0; 0108 0109 // starting looking for mismatches 0110 for (var i = lineNr; i >= 0; --i) { 0111 var lastOpen = document.line(i).length; 0112 var lineString = document.line(i); 0113 for (var j = lineString.length; j >= 0; --j) { 0114 // Ignore comments and strings 0115 if (document.isComment(i, j) || document.isString(i, j)) 0116 continue; 0117 // Testing for brackets 0118 // If a closing bracket, add 1 to counter 0119 if (closings.indexOf(lineString[j]) > -1) { 0120 countClosing[lineString[j]]++; 0121 } 0122 // If an opening bracket, add 1 to the corresponding closing counter 0123 var index = openings.indexOf(lineString[j]); 0124 if (index > -1) { 0125 countClosing[closings[index]]--; 0126 // If an open-but-not-closed bracket is found 0127 // Return indent corresponding to its position 0128 if (countClosing[closings[index]] == -1) 0129 return {indent : j + 1, line : i, pos: lastOpen, type : "unclosed"}; 0130 // Otherwise just update "lastOpen" to j 0131 lastOpen = j; 0132 } 0133 // If the start of the line is reached and 0134 // no comma was "opened" 0135 if (j == document.firstVirtualColumn(i) && countComma == 0) { 0136 // Test if all brackets are closed 0137 var allclosed = true; 0138 for (var key in countClosing) { 0139 if (countClosing[key] != 0) { 0140 allclosed = false; 0141 } 0142 } // If they are all closed, return the indent of this line 0143 dbg("allclosed: " + allclosed) 0144 if (allclosed) { 0145 // if we didn't move, return -1 (keep indent), else return the indent of line i 0146 if (i == lineNr) { 0147 return {indent : -1, line : i, pos: lastOpen, type : "allclosed"}; 0148 } else { 0149 return {indent : j, line : i, pos: lastOpen, type : "allclosed"}; 0150 } 0151 } 0152 } 0153 // Counting the commas if needed 0154 if (lineString[j] == ',') { 0155 // If not between comma-inducing brackets 0156 if (countClosing[')'] == 0 && countClosing[']'] == 0) 0157 countComma++; 0158 } 0159 0160 // // Handling equal signs 0161 // if (lineString[j] == "=" && equalOperatorSigns.indexOf(lineString[j - 1]) == -1 && lineString[j + 1] != "=") { 0162 // // If not between equal-inducing brackets 0163 // if (countClosing[')'] == 0 && countClosing[']'] == 0) { 0164 // // If no comma is "closing" the equal 0165 // if (countComma == 0) { 0166 // // Return the position of equal to create a new indent 0167 // return {indent : j + 1 + countSpaces(i,j), line : i, type : "equal"}; 0168 // } 0169 // } 0170 // } 0171 } 0172 } 0173 return {indent : -1, line : i, type : "unknown"}; 0174 } 0175 0176 // Find the position of an align operator with in the focus line 0177 // accounting for opening and closing brackets 0178 // `lineNr`: number of the line to search the align operator in 0179 // Returns -1 if nothing was found 0180 function findAlignOperator(lineNr, pos) { 0181 var lineString = document.line(lineNr); 0182 // initialising some counters 0183 var countClosing = new Array(); 0184 closings.forEach(function(elem) { 0185 countClosing[elem] = 0; 0186 }); 0187 0188 for (var j = pos - 1; j >= 0; --j) { 0189 // Ignore comments and strings 0190 if (document.isComment(lineNr, j) || document.isString(lineNr, j)) 0191 continue; 0192 0193 // Testing for brackets 0194 // If a closing bracket, add 1 to counter 0195 if (closings.indexOf(lineString[j]) > -1) { 0196 countClosing[lineString[j]]++; 0197 } 0198 0199 // If an opening bracket, add 1 to the corresponding closing counter 0200 var index = openings.indexOf(lineString[j]); 0201 if (index > -1) { 0202 countClosing[closings[index]]--; 0203 // If an open-but-not-closed bracket is found 0204 // Return -1 because nothing was found 0205 if (countClosing[closings[index]] == -1) 0206 return -1 0207 } 0208 0209 0210 if (lineString.substr(j - 1, 2) == "<-" || 0211 (lineString[j] == "=" && equalOperatorSigns.indexOf(lineString[j - 1]) == -1 && lineString[j + 1] != "=") || 0212 lineString[j] == "~") 0213 { 0214 // Test if all brackets are closed 0215 var allclosed = true; 0216 for (var key in countClosing) { 0217 if (countClosing[key] != 0) { 0218 allclosed = false; 0219 } 0220 } // If they are all closed, return the indent of this line 0221 if (allclosed) { 0222 return j; 0223 } 0224 } 0225 } 0226 0227 return -1 0228 } 0229 0230 // Returns the indent based on operators 0231 // `lineNr`: number of the line for which the indent is calculated 0232 // `indentWidth` : indent width 0233 // `lineLastOp` : does the line on which returns was hit end with an operator 0234 // (note that the line for lineLastOp is not necessarily lineNr) 0235 function calcOperatorIndent(lineNr, indentWidth, pos, lineLastOp) { 0236 var currentIndent = document.firstVirtualColumn(lineNr); 0237 var refLine = lineNr; 0238 // If we haven't indented yet and line ends up with an operator 0239 // then indent 0240 // if (currentIndent == 0 && lineLastOp) { 0241 // return indentWidth; 0242 // } 0243 // If the current line ends up with an operator 0244 if (lineLastOp) { 0245 var previousLine = getCodeWithString(refLine - 1); 0246 while (previousLine == '' && refLine >= 0) { 0247 refLine = refLine - 1; 0248 previousLine = getCodeWithString(refLine - 1); 0249 } 0250 // If the line before the indent line doesn't ends up with an operator 0251 if (!endsWithAny(operators, previousLine)) { 0252 // then look for align operator 0253 locAlign = findAlignOperator(lineNr, pos); 0254 if (locAlign != -1) { 0255 return locAlign + 1 + countSpaces(lineNr, locAlign); 0256 } else { 0257 // if no align operator, simply indent 0258 return currentIndent + indentWidth; 0259 } 0260 } else { 0261 // else don't 0262 return currentIndent; 0263 } 0264 } else { 0265 var previousLine = getCodeWithString(refLine - 1); 0266 while (previousLine == '' && refLine >= 0) { 0267 refLine = refLine - 1; 0268 previousLine = getCodeWithString(refLine - 1); 0269 } 0270 0271 // If the previous line ends with an operator 0272 if (endsWithAny(operators, previousLine)) { 0273 // Looking for the start of the operator indenting 0274 for (i = refLine - 1; i>=0; --i) { 0275 // If commented line, skip 0276 if (getCodeWithString(i) != '') { 0277 // If we indented in the past 0278 if (document.firstVirtualColumn(i) < currentIndent) { 0279 currentIndent = document.firstVirtualColumn(i); 0280 var previousLine = getCodeWithString(i - 1); 0281 // and doesn't end up with an operator 0282 if (!endsWithAny(operators, previousLine)) { 0283 //return this line indent otherwise 0284 return currentIndent; 0285 } 0286 } 0287 } 0288 } 0289 } else { 0290 return currentIndent; 0291 } 0292 0293 // If the current line doesn't ends up with an operator, we might need to unindent 0294 // Let's look above 0295 for (i = refLine; i>=0; --i) { 0296 // If a line has a lower indent 0297 if (document.firstVirtualColumn(i) <= currentIndent) { 0298 currentIndent = document.firstVirtualColumn(i); 0299 var previousLine = getCodeWithString(i - 1); 0300 // and doesn't end up with an operator 0301 if (!endsWithAny(operators, previousLine)) { 0302 //return this line indent 0303 return currentIndent; 0304 } 0305 } 0306 } 0307 } 0308 return -1; 0309 } 0310 0311 // Align when a closing bracket was entered 0312 // `lineNr`: number of the line for which the indent is calculated 0313 // `c` : the bracket that was entered 0314 function alignBrackets(lineNr, lastChar, newChar, indentWidth) { 0315 var charsMatch = ( lastChar == '(' && newChar == ')' ) || 0316 ( lastChar == '{' && newChar == '}' ) || 0317 ( lastChar == '[' && newChar == ']' ); 0318 if (charsMatch) { 0319 indentation = document.firstVirtualColumn(lineNr - 1); 0320 document.insertText(lineNr, document.firstColumn(lineNr), "\n"); 0321 view.setCursorPosition(lineNr, document.line(lineNr).length); 0322 document.indent(new Range(lineNr + 1, 0, lineNr + 1, 1), indentation / indentWidth); 0323 } 0324 return document.firstVirtualColumn(lineNr - 1) + indentWidth; 0325 } 0326 0327 0328 // Return the amount of characters (in spaces) to be indented. 0329 // Special indent() return values: 0330 // -2 = no indent 0331 // -1 = keep last indent 0332 function indent(line, indentWidth, ch) { 0333 if (line == 0) // don't ever act on document's first line 0334 return -2; 0335 var lastLine = getCodeWithString(line - 1); 0336 var lastChar = lastLine.substr(-1); 0337 var newChar = document.line(line).substr(document.firstVirtualColumn(line), 1); 0338 dbg("newChar = '" + newChar + "'"); 0339 dbg("lastChar = '" + lastChar + "'"); 0340 dbg("ch = '" + ch + "'"); 0341 0342 // Align if a closing bracket was entered 0343 // (note that the code above might also be triggered if a bracket is closed right after an opened one) 0344 if (closings.indexOf(ch) > -1 && ch.length) { 0345 dbg("closings"); 0346 //if the entered bracket doesn't start the line, do nothing 0347 if (newChar != ch) { 0348 return document.firstVirtualColumn(line); 0349 } 0350 var matchOpen = openings[closings.indexOf(ch)]; 0351 if (lastChar == matchOpen) { 0352 return alignBrackets(line, lastChar, newChar, indentWidth) 0353 } else { 0354 var mismatch = calcMismatchIndent(line - 1, ch); 0355 return document.firstVirtualColumn(mismatch.line); 0356 } 0357 } 0358 0359 // opening brackets and returns: indent (and unindent following bracket if needed) 0360 if (openings.indexOf(lastChar) > -1 && lastChar.length) { 0361 return alignBrackets(line, lastChar, newChar, indentWidth); 0362 } 0363 0364 // calculate indents based on mismatch of brackets, commas and equal signs 0365 var mismatch = calcMismatchIndent(line - 1, ''); 0366 var indent = mismatch.indent; 0367 0368 dbg("mismatch.line = " + mismatch.line) 0369 dbg("mismatch.pos = " + mismatch.pos) 0370 dbg("mismatch.indent = " + mismatch.indent) 0371 dbg("mismatch.type = " + mismatch.type) 0372 0373 // if indent is based on non-opened brackets, try indent because of operators 0374 // Don't do it if the end is "<-" though (necessary because "-" is an operator...) 0375 if (mismatch.type == "allclosed" && !lastLine.endsWith('<-')) { 0376 // compute indent due to an operator 0377 indent = calcOperatorIndent(mismatch.line, indentWidth, mismatch.pos, endsWithAny(operators, lastLine)); 0378 } 0379 0380 if (mismatch.type == "unclosed" && endsWithAny(operators, lastLine)) { 0381 dbg("unclosed"); 0382 // look whether we should account for an align operator 0383 locAlign = findAlignOperator(mismatch.line, mismatch.pos); 0384 if (locAlign != -1) { 0385 indent = locAlign + 1 + countSpaces(mismatch.line, locAlign); 0386 } 0387 } 0388 0389 // if unclosed bracket is found, check that the line didn't end with this bracket 0390 // if it did, do not change anything to the indent 0391 if (mismatch.type == "unclosed" && mismatch.indent == document.line(mismatch.line).length) { 0392 indent = document.firstVirtualColumn(mismatch.line + 1); 0393 } 0394 0395 // At that point, we might have computed an indent equal to the current one, 0396 // let's keep it simple and set indent to -1 in that case 0397 if (document.firstVirtualColumn(line - 1) == indent) { 0398 indent = -1; 0399 } 0400 0401 // If the next character is a closing bracket, 0402 // let's see if we should indent back to the corresponding indent 0403 if (closings.indexOf(newChar) > -1 && newChar.length) { 0404 dbg("next char is closing bracket"); 0405 mismatch = calcMismatchIndent(line - 1, newChar); 0406 // change indent only if the closing bracket matches a "final" one 0407 if (endsWithAny(openings, getCodeWithString(mismatch.line))) { 0408 return document.firstVirtualColumn(mismatch.line); 0409 } 0410 } 0411 0412 // Assignment is important and particular, so always indent when we do it 0413 if (getCodeWithString(line - 1).endsWith('<-') || getCodeWithString(line - 1).endsWith('=')) { 0414 if (indent > -1) 0415 indent += indentWidth; 0416 else 0417 indent = document.firstVirtualColumn(line - 1) + indentWidth; 0418 } 0419 return indent; 0420 } 0421 0422 // kate: space-indent on; indent-width 4; replace-tabs on;