diff options
| author | Adam Stankiewicz <sheerun@sher.pl> | 2013-09-12 17:17:14 +0200 | 
|---|---|---|
| committer | Adam Stankiewicz <sheerun@sher.pl> | 2013-09-12 17:17:14 +0200 | 
| commit | 35433aa23c6f7753fe96c67ed2ffdbb5291085a0 (patch) | |
| tree | f6265a18bb6c6955ab77313f5cdc6cd8af7ac103 /indent | |
| parent | 0a4fcd12b6227ae9975d64353025e55e2e0caddf (diff) | |
| download | vim-polyglot-35433aa23c6f7753fe96c67ed2ffdbb5291085a0.tar.gz vim-polyglot-35433aa23c6f7753fe96c67ed2ffdbb5291085a0.zip | |
vim-haskell -> hasksyn
Diffstat (limited to '')
| -rw-r--r-- | indent/haskell.vim | 325 | 
1 files changed, 268 insertions, 57 deletions
| 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 <motemen@gmail.com> -" 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+=<Bar> +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 =~ '[!#$%&*+./<=>?@\\^|~-]$\|\<do$' -        return match(line, '\s*where \zs\|\S') + &shiftwidth +  " 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 -    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 !~ '\<else\>' -        let s = match(line, '\<if\>.*\&.*\zs\<then\>') -        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\>') -        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\).*\)\@<!\*\s*$' +    return previ + &sw +  endif + +  " If the previous line ends in a where, indent us a step +  if prevl =~ '\Wwhere\s*$' +    return previ + &sw +  endif + +  " If we see a |, first try to line it up with the pipe on the previous line. +  " Search backward on nearby lines, giving up if we hit a line with a \w at +  " column 0. Otherwise, indent it relative to the previous line +  " +  " Here we can also handle the case of lining up data declarations.  The +  " backwards pipe search will fail for a data declaration (since data is at +  " column 0), so we can have an extra check after the pipe search for +  " data..=. +  if thisl =~ '^\s*|$' +    let nearestPipeIndex = s:BackwardPatternSearch(a:lnum, '\(^\s*\)\@<=|') +    if nearestPipeIndex != -1 +      return nearestPipeIndex +    endif + +    let dataEquals = match(prevl, '\(data.*\)\@<==') +    if dataEquals != -1 +      return dataEquals      endif -    let s = match(line, '\<do\s\+\zs[^{]\|\<where\s\+\zs\w\|\<let\s\+\zs\S\|^\s*\zs|\s') -    if s > 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('\(--.*\|"\([^"]\|\\"\)*\)\@<!' . tok, 'bcnW') +endfunction + +" Should return -1 if the given line is inside of an unclosed block comment. +" This is meant to let us exit early from the indenter if we are in a comment. +" Look for the nearest -} and {- such that they are not between "" or in a +" line comment +" +" Note: we may need to restrict how far back this will search.  On the other +" hand, the native vim 'search' function might be efficient enough to support +" entire buffers. +function! s:IsInBlockComment() +  let openCommPos = s:FindTokenNotInCommentOrString('{-') +  " If there is no open comment, then we don't have to look for a close +  if openCommPos == 0 +    return 0 +  endif + +  " Or if there is a close comment marker that comes after the open marker, we +  " are not in a comment.  Note that we potentially need to check the position +  " in the line if they are both on the same line.  I'll fix it later. +  let closeCommPos = s:FindTokenNotInCommentOrString('-}') +  if closeCommPos >= 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, '\<case\>') -    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 | 
