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;