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;