var katescript = { "name": "Haskell", "author": "Erlend Hamberg ", "license": "LGPL", "revision": 4, "kate-version": "5.1" }; // kate-script-header, must be at the start of the file without comments, pure json // required katepart js libraries require ("range.js"); // based on Paul Giannaro's Python indenter var debugMode = false; // words for which space character-triggered indentation should be done var re_spaceIndent = /\bwhere\b|\bin\b|\belse\b/ // ‘|’-triggered indentation should only indent if the line starts with ‘|’ var re_pipeIndent = /^\s*\|/ // regex for symbols var re_symbols = /^\s*[!$#%&*+.\/<=>?@\\^|~-]/ // escapes text w.r.t. regex special chars function escape(text) { return text.replace(/(\/|\.|,|\+|\?|\||\*|\(|\)|\[|\]|\{|\}|\\)/g, "\\$1"); } String.prototype.startsWith = function(prefix) { return this.search(RegExp("^"+escape(prefix)+"(\\b|\\s|$)")) != -1; } String.prototype.endsWith = function(suffix) { return this.search(RegExp("(\\b|\\s|^)"+escape(suffix)+"\\s*$")) != -1; } String.prototype.lastCharacter = function() { var l = this.length; if (l == 0) return ''; else return this.charAt(l - 1); } String.prototype.stripWhiteSpace = function() { return this.replace(/^[ \t\n\r]+/, '').replace(/[ \t\n\r]+$/, ''); } function dbg(s) { // debug to the term in blue so that it's easier to make out amongst all // of Kate's other debug output. if (debugMode) debug("\u001B[34m" + s + "\u001B[0m"); } var triggerCharacters = "|} "; // General notes: // indent() returns the amount of characters (in spaces) to be indented. // Special indent() return values: // -2 = no indent // -1 = keep last indent function indent(line, indentWidth, character) { dbg(document.attribute.toString()); dbg("indent character: '" + character + "'"); var currentLine = document.line(line); dbg("current line: " + currentLine); var lastLine = document.line(line - 1); dbg("last line: " + lastLine); var lastCharacter = lastLine.lastCharacter(); // invocations triggered by a space character should be ignored unless the // line starts with one of the words in re_spaceIndent if (character == ' ') { if (currentLine.stripWhiteSpace().search(re_spaceIndent) != 0 || !document.isCode(line, document.lineLength(line) - 2)) { dbg("skipping..."); return -2; } } else if (character == '|') { if (currentLine.search(re_pipeIndent) == -1 || !document.isCode(line, document.lineLength(line) - 2)) { dbg("skipping..."); return -2; } } // we can't really indent line 0 if (line == 0) return -2; // make sure [some of] the last line is code if (!document.isCode(line - 1, document.lineLength(line - 1) - 1) && !document.isCode(line - 1, 0) && lastCharacter != "\"" && lastCharacter != "'") { dbg("attributes that we don't want! Returning"); return -1; } // check the line contents ... // rules that check the end of the last line. // first, check that the end of the last line actually is code... if (document.isCode(line - 1, document.lineLength(line - 1) - 1)) { // indent lines following a line ending with '=' if (lastLine.endsWith("=")) { dbg('indenting for ='); return document.firstVirtualColumn(line - 1) + indentWidth; } // indent lines following a line ending with '{' if (lastLine.endsWith("{")) { dbg('indenting for {'); return document.firstVirtualColumn(line - 1) + indentWidth; } // indent lines following a line ending with 'do' if (lastLine.endsWith("do")) { dbg('indenting for do'); return document.firstVirtualColumn(line - 1) + indentWidth; } // indent line after myFunction = do ... // foo = do bar <- baz // >>>>>>>>>qzx <- qqx if (lastLine.search(/\s=\sdo\s\S/) != -1) { dbg('indenting line for “... = do ...”'); var virtDoCol = document.firstVirtualColumn(line - 1) + lastLine.stripWhiteSpace().search(/do\s/); return virtDoCol + 3; } // indent line after 'where' unless it starts with 'module' // instance Functor Tree where // >>>>fmap = treeMap if (lastLine.endsWith('where') && !lastLine.startsWith('module')) { dbg('indenting line for where (3)'); return document.firstVirtualColumn(line - 1) + indentWidth; } } // indent line after 'where' 6 characters for alignment: // ... where foo = 3 // >>>>>>bar = 4 if (lastLine.search(/\s*where\s+[^-]/) != -1) { dbg('indenting line for where (0)'); var virtFirst = document.firstVirtualColumn(line - 1); return virtFirst + 6; } // indent line after 'where' by indentWidth: // ... where -- comment // >>>>foo = 4 if (lastLine.stripWhiteSpace().startsWith('where')) { dbg('indenting line for where (1)'); return document.firstVirtualColumn(line - 1) + indentWidth; } // indent 'where' to column 0 + indentWidth // fun x = y // >>>>where y = x+1 if (currentLine.stripWhiteSpace().startsWith('where')) { dbg('indenting line for where (2)'); if (lastLine.startsWith('else')) { return document.firstVirtualColumn(line - 1) + indentWidth; } else { return indentWidth; } } // indent line after 'let' 4 characters for alignment: // ... let foo = 3 // >>>>bar = 4 // // unless // * we're in a do block OR // * the current line starts with 'in' var letCol = lastLine.search(/\blet\b/); if (letCol != -1 && !currentLine.stripWhiteSpace().startsWith('in')) { var virtFirstInLastLine = document.firstVirtualColumn(line - 1); // do a basic test of whether we are in a do block or not l = line - 2; // find the last non-empty line with an indentation level different // from the current line ... while (l > 0 && (document.firstVirtualColumn(l) == virtFirstInLastLine || document.line(l).search(/^\s*$/) != -1)) { l--; } // ... if that line ends with 'do', don't indent if (document.line(l).endsWith('do')) { dbg('not indenting for let; in a do block'); return -1; } dbg('indenting line for let'); var virtLetCol = virtFirstInLastLine + lastLine.stripWhiteSpace().search(/\blet\b/); return virtLetCol + 4; } // deindent line starting with 'in' to the level of its corresponding 'let': // ... let foo = 3 // bar = 4 // in foo+bar if (currentLine.stripWhiteSpace().startsWith('in')) { dbg('indenting line for in'); var t = line-1; var indent = -1; while (t >= 0 && line-t < 100) { var letCol = document.line(t).stripWhiteSpace().search(/\blet\b/); if (letCol != -1) { indent = document.firstVirtualColumn(t) + letCol; break; } t--; } return indent; } // deindent line starting with 'else' to the level of 'then': // ... if foo // then do // bar baz // else return [] if (currentLine.stripWhiteSpace().startsWith('else')) { dbg('indenting line for else'); var t = line-1; var indent = -1; while (t >= 0 && line-t < 100) { var thenCol = document.line(t).stripWhiteSpace().search(/\bthen\b/); // \s*\bthen\b if (thenCol != -1) { indent = document.firstVirtualColumn(t) + thenCol; break; } t--; } return indent; } // indent line after a line with just 'in' one indent width: // ... let foo = 3 // bar = 4 // in // >>>>foo+bar if (lastLine.stripWhiteSpace() == 'in') { dbg('indenting line after in'); return document.firstVirtualColumn(line - 1) + indentWidth; } // indent line after 'case' 5 characters for alignment: // case xs of // >>>>>[] -> ... // >>>>>(y:ys) -> ... var caseCol = lastLine.stripWhiteSpace().search(/\bcase\b/); if (caseCol != -1) { dbg('indenting line for case'); var virtCaseCol = document.firstVirtualColumn(line - 1) + caseCol; return virtCaseCol + 5; } // indent line after 'if/else' 3 characters for alignment: // if foo == bar // >>>then baz // >>>else vaff var ifCol = lastLine.stripWhiteSpace().search(/\bif\b/); var thenCol = lastLine.search(/\bthen\b/); var elseCol = lastLine.search(/\belse\b/); if (ifCol != -1 && thenCol == -1 && elseCol == -1) { dbg('indenting line for if'); var virtIfCol = document.firstVirtualColumn(line - 1) + ifCol; return virtIfCol + 3; } // indent line starting with "deriving: ": // data Tree a = Node a (Tree a) (Tree a) // | Empty // >>>>>>>>>>>>>>deriving (Show) // // - OR - // // data Bool = True | False // >>>>>deriving (Show) if (currentLine.stripWhiteSpace().startsWith('deriving')) { dbg('indenting line for deriving'); var pipeCol = lastLine.search(/\|/); if (lastLine.stripWhiteSpace().startsWith('data')) { return document.firstVirtualColumn(line - 1) + indentWidth; } else if (pipeCol != -1) { var t = 1; while (lastLine[document.firstVirtualColumn(line - 1)+pipeCol+t] == ' ') { t++; } return pipeCol + t + 1; } else { return document.firstVirtualColumn(line - 1) + 2; } } // indent lines starting with '|' (guards or alternate constructors): // f x // >>| x == 0 = 1 // >>| x == 0 = x // // OR // // data Bool = True // >>>>>>>>>>| False if (currentLine.stripWhiteSpace().startsWith("|")) { dbg('indenting line for |'); var equalsCol = lastLine.stripWhiteSpace().search(/=/); var pipeCol = lastLine.stripWhiteSpace().search(/\|/); var virtFirstInLastLine = document.firstVirtualColumn(line - 1); if (equalsCol != -1 && lastLine.stripWhiteSpace().startsWith('data')) { return virtFirstInLastLine + equalsCol; } else if (pipeCol != -1) { return virtFirstInLastLine + pipeCol; } else { return virtFirstInLastLine + indentWidth; } } // line starting with !#$%&*+./<=>?@\^|~- if (document.isCode(line, document.lineLength(line) - 1) && currentLine.search(re_symbols) != -1 && lastLine.search(re_symbols) == -1) { dbg('indenting for operator'); return document.firstVirtualColumn(line - 1) + indentWidth; } // the line after aline ending with a comma should be indented if (document.isCode(line - 1, document.lineLength(lastLine) - 1) && lastLine.search(',\s*$') != -1) { dbg('indenting after line ending with comma'); return document.firstVirtualColumn(line - 1) + indentWidth; } // [de]indent line starting wih '}' to match the indentation level of '{': // data Foo { // a :: Int // , b :: Double // }<<< if (currentLine.stripWhiteSpace().endsWith('}')) { dbg('indenting line for }'); var t = line-1; var indent = -1; while (t >= 0 && line-t < 100) { var braceCol = document.line(t).search(/{/); if (braceCol != -1) { indent = document.firstVirtualColumn(t); break; } t--; } return indent; } //if (lastLine.search(/^\s*$/) != -1) { // dbg('indenting for empty line'); // return 0; //} dbg('continuing with regular indent'); return -1; } // kate: space-indent on; indent-width 4; replace-tabs on;