From 35433aa23c6f7753fe96c67ed2ffdbb5291085a0 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Thu, 12 Sep 2013 17:17:14 +0200 Subject: vim-haskell -> hasksyn --- indent/haskell.vim | 325 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 268 insertions(+), 57 deletions(-) (limited to 'indent/haskell.vim') diff --git a/indent/haskell.vim b/indent/haskell.vim index 4e6a1eff..c584e940 100644 --- a/indent/haskell.vim +++ b/indent/haskell.vim @@ -1,85 +1,296 @@ " Vim indent file -" Language: Haskell -" Author: motemen -" Version: 0.1 -" Last Change: 2007-07-25 -" -" Modify g:haskell_indent_if and g:haskell_indent_case to -" change indentation for `if'(default 3) and `case'(default 5). -" Example (in .vimrc): -" > let g:haskell_indent_if = 2 +" Language: Haskell +" Maintainer: Tristan Ravitch if exists('b:did_indent') - finish + finish endif let b:did_indent = 1 -if !exists('g:haskell_indent_if') - " if bool - " >>>then ... - " >>>else ... - let g:haskell_indent_if = 2 +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 -if !exists('g:haskell_indent_case') - " case xs of - " >>>>>[] -> ... - " >>>>>(y:ys) -> ... - let g:haskell_indent_case = 2 +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 -setlocal indentexpr=GetHaskellIndent() -setlocal indentkeys=!^F,o,O -function! GetHaskellIndent() - let line = substitute(getline(getpos('.')[1] - 1), '\t', repeat(' ', &tabstop), 'g') +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 line =~ '[!#$%&*+./<=>?@\\^|~-]$\|\\|=>\)\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 - if line =~ '{$' - return match(line, '\s*where \zs\|\S') + &shiftwidth + " 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 - if line =~ '^\(instance\|class\).*\&.*where$' - return &shiftwidth + let bmatch = match(prevl, '\({\|\[\)') + if bmatch != -1 + return bmatch endif + endif - if line =~ ')$' - let pos = getpos('.') - normal k$ - let paren_end = getpos('.') - normal % - let paren_begin = getpos('.') - call setpos('.', pos) - if paren_begin[1] != paren_end[1] - return paren_begin[2] - 1 - 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 - if line !~ '\' - let s = match(line, '\.*\&.*\zs\') - if s > 0 - return s - 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 - let s = match(line, '\') - if s > 0 - return s + g:haskell_indent_if - 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\).*\)\@ 0 - return s + return previ + &sw + endif + + " If the previous line has a let, line the cursor up with the start of the + " first binding name. Autoindent handles subsequent cases. + " + " This should come after the 'in' aligner so that 'in' is not treated as + " just something to be aligned to the previous binding. + let lbindStart = match(prevl, '\(\Wlet\s\+\)\@<=\w') + if lbindStart != -1 + return lbindStart + endif + + " If requested, dedent from a bare return (presumably in a do block). + " This comes after the trailing operator case - hopefully that will avoid + " returns on lines by themselves but not really in a do block. This is a + " heuristic. + if g:hasksyn_dedent_after_return && prevl =~ '^\s*return\W' + return previ - &sw + endif + + " Similar to the return dedent - after a catchall case _ -> ..., 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 s = match(line, '\') - if s > 0 - return s + g:haskell_indent_case + 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 - return match(line, '\S') -endfunction \ No newline at end of file -- cgit v1.2.3