" Vim indent file " Language: Haskell " Maintainer: Tristan Ravitch if exists('b:did_indent') finish endif let b:did_indent = 1 if !exists('g:hasksyn_indent_search_backward') let g:hasksyn_indent_search_backward = 100 endif if !exists('g:hasksyn_dedent_after_return') let g:hasksyn_dedent_after_return = 1 endif if !exists('g:hasksyn_dedent_after_catchall_case') let g:hasksyn_dedent_after_catchall_case = 1 endif setlocal noautoindent setlocal indentexpr=HIndent(v:lnum) setlocal indentkeys+=0=where setlocal indentkeys+=0=-> setlocal indentkeys+=0==> setlocal indentkeys+=0=in setlocal indentkeys+=0=class,0=instance,0=import setlocal indentkeys+= setlocal indentkeys+=0\, if exists("*HIndent") finish endif function! HIndent(lnum) " Don't do anything boneheaded if we are inside of a block comment if s:IsInBlockComment() return -1 endif let plnum = s:PrevNonCommentLineNum(a:lnum) if plnum == 0 return 0 endif let prevl = s:GetAndStripTrailingComments(plnum) let thisl = s:GetAndStripTrailingComments(a:lnum) let previ = indent(plnum) " If this is a bare where clause, indent it one step. where as part of an " instance should be unaffected unless you put it in an odd place. " This is the wrong thing if you are deeply indented already and want to put " a where clause on the top-level construct, but there isn't much that can " be done about that case... if thisl =~ '^\s*where\s*$' return previ + &sw endif " If we start a new line for a type signature, see if we can line it up with " the previous line. if thisl =~ '^\s*\(->\|=>\)\s*' let tokPos = s:BackwardPatternSearch(a:lnum, '\(::\|->\|=>\)') if tokPos != -1 return tokPos endif endif if prevl =~ '\Wof\s*$' || prevl =~ '\Wdo\s*$' return previ + &sw endif " Now for commas. Commas will align pretty naturally for simple pattern " guards, so don't worry about that for now. If we see the line is just a " comma, search up for something to align it to. In the easy case, look " for a [ or { (the last in their line). Also consider other commas that " are preceeded only by whitespace. This isn't just a previous line check " necessarily, though that would cover most cases. if thisl =~ '^\s*,' let cmatch = match(prevl, '\(^\s*\)\@<=,') if cmatch != -1 return cmatch endif let bmatch = match(prevl, '\({\|\[\)') if bmatch != -1 return bmatch endif endif " Match an 'in' keyword with the corresponding let. Unfortunately, if the " name of your next binding happens to start with 'in', this will muck with " it. Not sure if there is a workaround because we can't force an " auto-indent after 'in ' as far as I can see. if thisl =~ '\s*in$' let letStart = s:BackwardPatternSearch(a:lnum, '\(\W\)\@<=let\W') if letStart != -1 return letStart endif endif " We don't send data or type to column zero because they can be indented " inside of 'class' definitions for data/type families if thisl =~ '^\s*\(class\|instance\|newtype\|import\)' return 0 endif " FIXME: Only do this if the previous line was not already indented for the " same reason. Also be careful of -> in type signatures. Make sure we have " an earlier rule to line those up properly. if prevl =~ '[=>\$\.\^+\&`(-]\s*$' return previ + &sw endif " We have a special case for dealing with trailing '*' operators. If the * " is the end of a kind signature in a type family/associated type, we don't " want to indent the next line. We do if it is just being a * operator in " an expression, though. if prevl =~ '\(\(type\|data\).*\)\@ ..., we can " almost certainly dedent. Again, it comes after the line continuation " heuristic so we don't dedent while someone is making an obviously " multi-line construct if g:hasksyn_dedent_after_catchall_case && prevl =~ '^\s*_\s*->\W' return previ - &sw endif " On the other hand, if the previous line is a where with some bindings " following it on the same line, accommodate and align with the first non-ws " char after the where if prevl =~ '\Wwhere\s\+\w' let bindStart = match(prevl, '\(\Wwhere\s\+\)\@<=\w') if bindStart != -1 return bindStart endif return previ + &sw endif return previ endfunction " Search backwards for a token from the cursor position function! s:FindTokenNotInCommentOrString(tok) return search('\(--.*\|"\([^"]\|\\"\)*\)\@= openCommPos return 0 endif return 1 endfunction " Get the previous line that is not a comment. Pass in the *current* line " number. Also skips blank lines. function! s:PrevNonCommentLineNum(lnum) if a:lnum <= 1 return 0 endif let lnum = a:lnum - 1 while 1 if lnum == 0 return 0 endif let aline = getline(lnum) if aline =~ '^\s*--' let lnum = lnum - 1 else return lnum endif endwhile endfunction function! s:GetAndStripTrailingComments(lnum) let aline = getline(a:lnum) " We can't just remove the string literal since that leaves us with a " trailing operator (=), so replace it with a fake identifier let noStrings = substitute(aline, '"\([^"]\|\\"\)*"', 's', '') let noLineCom = substitute(noStrings, '--.*$', '', '') " If there are no fancy block comments involved, skip some of this extra " work if noLineCom !~ '\({-\|-}\)' return noLineCom endif " We stripped line comments, now we need to strip out any relevant multiline " comments. This includes comments starting much earlier but ending on this " line or comments starting on this line and continuing to the next. This " is probably easiest in two steps: {- to (-}|$) and then ^ to -}. " Note we are using a non-greedy match here so that only the minimal {- -} " pair is consumed. let noBlock1 = substitute(noLineComm, '{-.\{-}-}', '', '') let noBlock2 = substitute(noBlock1, '{-.\{-}$', '', '') let noBlock3 = substitute(noBlock2, '^.\{-}-}', '', '') return noBlock3 endfunction " Search backwards from lnum for pat, returning the starting index if found " within the search range or -1 if not found. Stops searching at lines " starting at column 0 with an identifier character. function! s:BackwardPatternSearch(lnum, pat) let lnum = s:PrevNonCommentLineNum(a:lnum) while 1 let aline = s:GetAndStripTrailingComments(lnum) if a:lnum - lnum > g:hasksyn_indent_search_backward return -1 endif let theMatch = match(aline, a:pat) if theMatch != -1 return theMatch else " We want to be able to consider lines starting in column 0, but we don't " want to search back past them. if aline =~ '^\w' return -1 endif let lnum = s:PrevNonCommentLineNum(lnum) endif endwhile endfunction