File indexing completed on 2024-05-19 04:00:09

0001 var katescript = {
0002     "name": "Haskell",
0003     "author": "Erlend Hamberg <ehamberg@gmail.com>",
0004     "license": "LGPL",
0005     "revision": 4,
0006     "kate-version": "5.1"
0007 }; // kate-script-header, must be at the start of the file without comments, pure json
0008 
0009 // required katepart js libraries
0010 require ("range.js");
0011 
0012 // based on Paul Giannaro's Python indenter
0013 
0014 var debugMode = false;
0015 
0016 // words for which space character-triggered indentation should be done
0017 var re_spaceIndent = /\bwhere\b|\bin\b|\belse\b/
0018 
0019 // ‘|’-triggered indentation should only indent if the line starts with ‘|’
0020 var re_pipeIndent = /^\s*\|/
0021 
0022 // regex for symbols
0023 var re_symbols = /^\s*[!$#%&*+.\/<=>?@\\^|~-]/
0024 
0025 // escapes text w.r.t. regex special chars
0026 function escape(text) {
0027     return text.replace(/(\/|\.|,|\+|\?|\||\*|\(|\)|\[|\]|\{|\}|\\)/g, "\\$1");
0028 }
0029 
0030 String.prototype.startsWith = function(prefix) {
0031     return this.search(RegExp("^"+escape(prefix)+"(\\b|\\s|$)")) != -1;
0032 }
0033 
0034 String.prototype.endsWith = function(suffix) {
0035     return this.search(RegExp("(\\b|\\s|^)"+escape(suffix)+"\\s*$")) != -1;
0036 }
0037 
0038 String.prototype.lastCharacter = function() {
0039     var l = this.length;
0040     if (l == 0)
0041         return '';
0042     else
0043         return this.charAt(l - 1);
0044 }
0045 
0046 String.prototype.stripWhiteSpace = function() {
0047     return this.replace(/^[ \t\n\r]+/, '').replace(/[ \t\n\r]+$/, '');
0048 }
0049 
0050 
0051 function dbg(s) {
0052     // debug to the term in blue so that it's easier to make out amongst all
0053     // of Kate's other debug output.
0054     if (debugMode)
0055         debug("\u001B[34m" + s + "\u001B[0m");
0056 }
0057 
0058 var triggerCharacters = "|} ";
0059 
0060 // General notes:
0061 // indent() returns the amount of characters (in spaces) to be indented.
0062 // Special indent() return values:
0063 //   -2 = no indent
0064 //   -1 = keep last indent
0065 
0066 function indent(line, indentWidth, character) {
0067     dbg(document.attribute.toString());
0068     dbg("indent character: '" + character + "'");
0069     var currentLine = document.line(line);
0070     dbg("current line: " + currentLine);
0071     var lastLine = document.line(line - 1);
0072     dbg("last line: " + lastLine);
0073     var lastCharacter = lastLine.lastCharacter();
0074 
0075     // invocations triggered by a space character should be ignored unless the
0076     // line starts with one of the words in re_spaceIndent
0077     if (character == ' ') {
0078         if (currentLine.stripWhiteSpace().search(re_spaceIndent) != 0 ||
0079                 !document.isCode(line, document.lineLength(line) - 2)) {
0080             dbg("skipping...");
0081             return -2;
0082         }
0083     } else if (character == '|') {
0084         if (currentLine.search(re_pipeIndent) == -1 ||
0085                 !document.isCode(line, document.lineLength(line) - 2)) {
0086             dbg("skipping...");
0087             return -2;
0088         }
0089     }
0090 
0091     // we can't really indent line 0
0092     if (line == 0)
0093         return -2;
0094 
0095     // make sure [some of] the last line is code
0096     if (!document.isCode(line - 1, document.lineLength(line - 1) - 1)
0097             && !document.isCode(line - 1, 0)
0098             && lastCharacter != "\"" && lastCharacter != "'") {
0099         dbg("attributes that we don't want! Returning");
0100         return -1;
0101     }
0102 
0103     // check the line contents ...
0104 
0105     // rules that check the end of the last line.
0106     // first, check that the end of the last line actually is code...
0107     if (document.isCode(line - 1, document.lineLength(line - 1) - 1)) {
0108         // indent lines following a line ending with '='
0109         if (lastLine.endsWith("=")) {
0110             dbg('indenting for =');
0111             return document.firstVirtualColumn(line - 1) + indentWidth;
0112         }
0113 
0114         // indent lines following a line ending with '{'
0115         if (lastLine.endsWith("{")) {
0116             dbg('indenting for {');
0117             return document.firstVirtualColumn(line - 1) + indentWidth;
0118         }
0119 
0120         // indent lines following a line ending with 'do'
0121         if (lastLine.endsWith("do")) {
0122             dbg('indenting for do');
0123             return document.firstVirtualColumn(line - 1) + indentWidth;
0124         }
0125 
0126         // indent line after myFunction = do ...
0127         // foo = do bar <- baz
0128         // >>>>>>>>>qzx <- qqx
0129         if (lastLine.search(/\s=\sdo\s\S/) != -1) {
0130             dbg('indenting line for “... = do ...”');
0131             var virtDoCol = document.firstVirtualColumn(line - 1)
0132                             + lastLine.stripWhiteSpace().search(/do\s/);
0133             return virtDoCol + 3;
0134         }
0135 
0136 
0137         // indent line after 'where' unless it starts with 'module'
0138         // instance Functor Tree where
0139         // >>>>fmap = treeMap
0140         if (lastLine.endsWith('where') && !lastLine.startsWith('module')) {
0141             dbg('indenting line for where (3)');
0142             return document.firstVirtualColumn(line - 1) + indentWidth;
0143         }
0144     }
0145 
0146     // indent line after 'where' 6 characters for alignment:
0147     // ... where foo = 3
0148     //     >>>>>>bar = 4
0149     if (lastLine.search(/\s*where\s+[^-]/) != -1) {
0150         dbg('indenting line for where (0)');
0151         var virtFirst = document.firstVirtualColumn(line - 1);
0152         return virtFirst + 6;
0153     }
0154 
0155     // indent line after 'where' by indentWidth:
0156     // ... where -- comment
0157     //     >>>>foo = 4
0158     if (lastLine.stripWhiteSpace().startsWith('where')) {
0159         dbg('indenting line for where (1)');
0160         return document.firstVirtualColumn(line - 1) + indentWidth;
0161     }
0162 
0163     // indent 'where' to column 0 + indentWidth
0164     // fun x = y
0165     // >>>>where y = x+1
0166     if (currentLine.stripWhiteSpace().startsWith('where')) {
0167         dbg('indenting line for where (2)');
0168 
0169         if (lastLine.startsWith('else')) {
0170             return document.firstVirtualColumn(line - 1) + indentWidth;
0171         } else {
0172             return indentWidth;
0173         }
0174     }
0175 
0176     // indent line after 'let' 4 characters for alignment:
0177     // ... let foo = 3
0178     //     >>>>bar = 4
0179     //
0180     // unless
0181     // * we're in a do block OR
0182     // * the current line starts with 'in'
0183     var letCol = lastLine.search(/\blet\b/);
0184     if (letCol != -1 && !currentLine.stripWhiteSpace().startsWith('in')) {
0185 
0186         var virtFirstInLastLine = document.firstVirtualColumn(line - 1);
0187         // do a basic test of whether we are in a do block or not
0188         l = line - 2;
0189 
0190         // find the last non-empty line with an indentation level different
0191         // from the current line ...
0192         while (l > 0 && (document.firstVirtualColumn(l) == virtFirstInLastLine
0193                          || document.line(l).search(/^\s*$/) != -1)) {
0194             l--;
0195         }
0196 
0197         // ... if that line ends with 'do', don't indent
0198         if (document.line(l).endsWith('do')) {
0199             dbg('not indenting for let; in a do block');
0200             return -1;
0201         }
0202 
0203         dbg('indenting line for let');
0204         var virtLetCol = virtFirstInLastLine
0205                          + lastLine.stripWhiteSpace().search(/\blet\b/);
0206         return virtLetCol + 4;
0207     }
0208 
0209     // deindent line starting with 'in' to the level of its corresponding 'let':
0210     // ... let foo = 3
0211     //         bar = 4
0212     //     in foo+bar
0213     if (currentLine.stripWhiteSpace().startsWith('in')) {
0214         dbg('indenting line for in');
0215         var t = line-1;
0216         var indent = -1;
0217         while (t >= 0 && line-t < 100) {
0218             var letCol = document.line(t).stripWhiteSpace().search(/\blet\b/);
0219             if (letCol != -1) {
0220                 indent = document.firstVirtualColumn(t) + letCol;
0221                 break;
0222             }
0223             t--;
0224         }
0225         return indent;
0226     }
0227 
0228     // deindent line starting with 'else' to the level of 'then':
0229     // ... if foo
0230     //        then do
0231     //           bar baz
0232     //        else return []
0233     if (currentLine.stripWhiteSpace().startsWith('else')) {
0234         dbg('indenting line for else');
0235         var t = line-1;
0236         var indent = -1;
0237         while (t >= 0 && line-t < 100) {
0238             var thenCol = document.line(t).stripWhiteSpace().search(/\bthen\b/);
0239             // \s*\bthen\b
0240             if (thenCol != -1) {
0241                 indent = document.firstVirtualColumn(t) + thenCol;
0242                 break;
0243             }
0244             t--;
0245         }
0246         return indent;
0247     }
0248 
0249     // indent line after a line with just 'in' one indent width:
0250     // ... let foo = 3
0251     //         bar = 4
0252     //     in
0253     //     >>>>foo+bar
0254     if (lastLine.stripWhiteSpace() == 'in') {
0255         dbg('indenting line after in');
0256         return document.firstVirtualColumn(line - 1) + indentWidth;
0257     }
0258 
0259     // indent line after 'case' 5 characters for alignment:
0260     // case xs of
0261     // >>>>>[] -> ...
0262     // >>>>>(y:ys) -> ...
0263     var caseCol = lastLine.stripWhiteSpace().search(/\bcase\b/);
0264     if (caseCol != -1) {
0265         dbg('indenting line for case');
0266         var virtCaseCol = document.firstVirtualColumn(line - 1) + caseCol;
0267         return virtCaseCol + 5;
0268     }
0269 
0270     // indent line after 'if/else' 3 characters for alignment:
0271     // if foo == bar
0272     // >>>then baz
0273     // >>>else vaff
0274     var ifCol = lastLine.stripWhiteSpace().search(/\bif\b/);
0275     var thenCol = lastLine.search(/\bthen\b/);
0276     var elseCol = lastLine.search(/\belse\b/);
0277     if (ifCol != -1 && thenCol == -1 && elseCol == -1) {
0278         dbg('indenting line for if');
0279         var virtIfCol = document.firstVirtualColumn(line - 1) + ifCol;
0280         return virtIfCol + 3;
0281     }
0282 
0283     // indent line starting with "deriving: ":
0284     // data Tree a = Node a (Tree a) (Tree a)
0285     //             | Empty
0286     // >>>>>>>>>>>>>>deriving (Show)
0287     //
0288     // - OR -
0289     //
0290     // data Bool = True | False
0291     // >>>>>deriving (Show)
0292     if (currentLine.stripWhiteSpace().startsWith('deriving')) {
0293         dbg('indenting line for deriving');
0294 
0295         var pipeCol = lastLine.search(/\|/);
0296         if (lastLine.stripWhiteSpace().startsWith('data')) {
0297             return document.firstVirtualColumn(line - 1) + indentWidth;
0298         }
0299         else if (pipeCol != -1) {
0300             var t = 1;
0301             while (lastLine[document.firstVirtualColumn(line - 1)+pipeCol+t] == ' ') {
0302                 t++;
0303             }
0304             return pipeCol + t + 1;
0305         }
0306         else {
0307             return document.firstVirtualColumn(line - 1) + 2;
0308         }
0309     }
0310 
0311     // indent lines starting with '|' (guards or alternate constructors):
0312     // f x
0313     // >>| x == 0 = 1
0314     // >>| x == 0 = x
0315     //
0316     // OR
0317     //
0318     // data Bool = True
0319     // >>>>>>>>>>| False
0320     if (currentLine.stripWhiteSpace().startsWith("|")) {
0321         dbg('indenting line for |');
0322         var equalsCol = lastLine.stripWhiteSpace().search(/=/);
0323         var pipeCol = lastLine.stripWhiteSpace().search(/\|/);
0324         var virtFirstInLastLine = document.firstVirtualColumn(line - 1);
0325         if (equalsCol != -1 && lastLine.stripWhiteSpace().startsWith('data')) {
0326             return virtFirstInLastLine + equalsCol;
0327         }
0328         else if (pipeCol != -1) {
0329             return virtFirstInLastLine + pipeCol;
0330         }
0331         else {
0332             return virtFirstInLastLine + indentWidth;
0333         }
0334     }
0335 
0336     // line starting with !#$%&*+./<=>?@\^|~-
0337     if (document.isCode(line, document.lineLength(line) - 1)
0338             && currentLine.search(re_symbols) != -1
0339             && lastLine.search(re_symbols) == -1) {
0340         dbg('indenting for operator');
0341         return document.firstVirtualColumn(line - 1) + indentWidth;
0342     }
0343 
0344     // the line after aline ending with a comma should be indented
0345     if (document.isCode(line - 1, document.lineLength(lastLine) - 1)
0346             && lastLine.search(',\s*$') != -1) {
0347         dbg('indenting after line ending with comma');
0348         return document.firstVirtualColumn(line - 1) + indentWidth;
0349     }
0350 
0351     // [de]indent line starting wih '}' to match the indentation level of '{':
0352     // data Foo {
0353     //       a :: Int
0354     //     , b :: Double
0355     // }<<<
0356     if (currentLine.stripWhiteSpace().endsWith('}')) {
0357         dbg('indenting line for }');
0358         var t = line-1;
0359         var indent = -1;
0360         while (t >= 0 && line-t < 100) {
0361             var braceCol = document.line(t).search(/{/);
0362             if (braceCol != -1) {
0363                 indent = document.firstVirtualColumn(t);
0364                 break;
0365             }
0366             t--;
0367         }
0368         return indent;
0369     }
0370 
0371     //if (lastLine.search(/^\s*$/) != -1) {
0372     //    dbg('indenting for empty line');
0373     //    return 0;
0374     //}
0375 
0376     dbg('continuing with regular indent');
0377     return -1;
0378 }
0379 
0380 // kate: space-indent on; indent-width 4; replace-tabs on;