diff --git a/README.md b/README.md index c6f537b42..783893f91 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ On top of all language packs from [vim repository](https://github.com/vim/vim/tr - [erlang](https://github.com/vim-erlang/vim-erlang-runtime) (Erlang syntax highlighting for erl, app.src, es, escript, hrl, xrl, yrl, app and yaws files) - [fennel](https://github.com/bakpakin/fennel.vim) (Syntax highlighting for fnl files) - [ferm](https://github.com/vim-scripts/ferm.vim) (Syntax highlighting for ferm files) -- [fish](https://github.com/georgewitteman/vim-fish) (fish syntax highlighting for fish files) +- [fish](https://github.com/blankname/vim-fish) (fish syntax highlighting for fish files) - [flatbuffers](https://github.com/dcharbon/vim-flatbuffers) (Syntax highlighting for fbs files) - [fsharp](https://github.com/ionide/Ionide-vim) (F# syntax highlighting for fs, fsi and fsx files) - [gdscript](https://github.com/calviken/vim-gdscript3) (GDScript syntax highlighting for gd files) diff --git a/autoload/fish.vim b/autoload/fish.vim index 0ea2e4745..92d9f642d 100644 --- a/autoload/fish.vim +++ b/autoload/fish.vim @@ -2,44 +2,112 @@ if has_key(g:polyglot_is_disabled, 'fish') finish endif +function! s:IsString(lnum, col) + " Returns "true" if syntax item at the given position is part of fishString. + let l:stack = map(synstack(a:lnum, a:col), 'synIDattr(v:val, "name")') + return len(filter(l:stack, 'v:val ==# "fishString"')) +endfunction + +function! s:IsContinuedLine(lnum) + " Returns "true" if the given line is a continued line. + return getline(a:lnum - 1) =~ '\v\\$' +endfunction + +function! s:FindPrevLnum(lnum) + " Starting on the given line, search backwards for a line that is not + " empty, not part of a string and not a continued line. + if a:lnum < 1 || a:lnum > line('$') + " First line or wrong value, follow prevnonblank() behaviour and + " return zero. + return 0 + endif + let l:lnum = prevnonblank(a:lnum) + while l:lnum > 0 && ( s:IsContinuedLine(l:lnum) || s:IsString(l:lnum, 1) ) + let l:lnum = prevnonblank(l:lnum - 1) + endwhile + return l:lnum +endfunction + +function! s:IsSwitch(lnum) + " Returns "true" if the given line is part of a switch block. + let l:lnum = a:lnum + let l:line = getline(l:lnum) + let l:in_block = 0 + let l:stop_pat = '\v^\s*%(if|else|while|for|begin)>' + let l:block_start_pat = '\v^\s*%(if|while|for|switch|begin)>' + while l:lnum > 0 + let l:lnum = prevnonblank(l:lnum - 1) + let l:line = getline(l:lnum) + if l:line =~# '\v^\s*end>' + let l:in_block += 1 + elseif l:in_block && l:line =~# l:block_start_pat + let l:in_block -= 1 + elseif !l:in_block && l:line =~# l:stop_pat + return 0 + elseif !l:in_block && l:line =~# '\v^\s*switch>' + return 1 + endif + endwhile + return 0 +endfunction + function! fish#Indent() - let l:prevlnum = prevnonblank(v:lnum - 1) - if l:prevlnum ==# 0 + let l:line = getline(v:lnum) + if s:IsString(v:lnum, 1) + return indent(v:lnum) + endif + " shiftwidth can be misleading in recent versions, use shiftwidth() if + " it is available. + if exists('*shiftwidth') + let l:shiftwidth = shiftwidth() + else + let l:shiftwidth = &shiftwidth + endif + let l:prevlnum = s:FindPrevLnum(v:lnum - 1) + if l:prevlnum == 0 return 0 endif + let l:shift = 0 let l:prevline = getline(l:prevlnum) - let l:line = getline(v:lnum) - let l:shiftwidth = shiftwidth() let l:previndent = indent(l:prevlnum) - let l:indent = l:previndent - if l:prevline =~# '\v^\s*%(begin|if|else|while|for|function|switch|case)>' - let l:indent += l:shiftwidth - endif - if l:line =~# '\v^\s*end>' - let l:indent -= l:shiftwidth - " If we're inside a case, dedent twice because it ends the switch. - if l:prevline =~# '\v^\s*case>' - " Previous line starts the case. - let l:indent -= l:shiftwidth + if s:IsContinuedLine(v:lnum) + " It is customary to increment indentation of continued lines by three + " or a custom value defined by the user if available. + let l:previndent = indent(v:lnum - 1) + if s:IsContinuedLine(v:lnum - 1) + return l:previndent + elseif exists('g:fish_indent_cont') + return l:previndent + g:fish_indent_cont + elseif exists('g:indent_cont') + return l:previndent + g:indent_cont else - " Scan back to a dedented line to find whether we're in a case. - let l:i = l:prevlnum - while l:i >= 1 && indent(l:i) >= l:previndent - let l:i = prevnonblank(l:i - 1) - endwhile - if indent(l:i) < l:previndent && getline(l:i) =~# '\v^\s*case>' - let l:indent -= l:shiftwidth - endif + return l:previndent + 3 endif - elseif l:line =~# '\v^\s*else>' - let l:indent -= l:shiftwidth - elseif l:prevline !~# '\v^\s*switch>' && l:line =~# '\v^\s*case>' - let l:indent -= l:shiftwidth endif - if l:indent < 0 - return 0 + if l:prevline =~# '\v^\s*%(begin|if|else|while|for|function|case|switch)>' + " First line inside a block, increase by one. + let l:shift += 1 + endif + if l:line =~# '\v^\s*%(end|case|else)>' + " "end", "case" or "else", decrease by one. + let l:shift -= 1 + endif + if l:line =~# '\v^\s*' && l:prevline =~# '\v' + " "case" following "switch", increase by one. + let l:shift += 1 + endif + if l:line =~# '\v\s*end>' && s:IsSwitch(v:lnum) + " "end" ends switch block, decrease by one more so it matches + " the indentation of "switch". + let l:shift -= 1 + endif + if l:prevline =~# '\v^\s*%(if|while|for|else|switch|end)>.*' + " "begin" after start of block, increase by one. + let l:shift += 1 endif - return l:indent + let l:indent = l:previndent + l:shift * l:shiftwidth + " Only return zero or positive numbers. + return l:indent < 0 ? 0 : l:indent endfunction function! fish#Format() @@ -49,6 +117,8 @@ function! fish#Format() let l:command = v:lnum.','.(v:lnum+v:count-1).'!fish_indent' echo l:command execute l:command + " Fix indentation and replace tabs with spaces if necessary. + normal! '[='] endif endfunction diff --git a/ftplugin/fish.vim b/ftplugin/fish.vim index d08222dbb..19fa5c38d 100644 --- a/ftplugin/fish.vim +++ b/ftplugin/fish.vim @@ -2,6 +2,14 @@ if has_key(g:polyglot_is_disabled, 'fish') finish endif +if exists('b:did_ftplugin') + finish +end +let b:did_ftplugin = 1 + +let s:save_cpo = &cpo +set cpo&vim + setlocal comments=:# setlocal commentstring=#%s setlocal define=\\v^\\s*function> @@ -9,6 +17,7 @@ setlocal foldexpr=fish#Fold() setlocal formatoptions+=ron1 setlocal formatoptions-=t setlocal include=\\v^\\s*\\.> +setlocal iskeyword=@,48-57,-,_,. setlocal suffixesadd^=.fish " Use the 'j' format option when available. @@ -34,9 +43,27 @@ endif " argument to fish instead of man. execute 'setlocal keywordprg=fish\ '.fnameescape(expand(':p:h:h').'/bin/man.fish') -let b:match_words = - \ escape('<%(begin|function|if|switch|while|for)>:', '<>%|)') +let b:match_ignorecase = 0 +if has('patch-7.3.1037') + let s:if = '%(else\s\+)\@15:::' + \, '<>%|)') let b:endwise_addition = 'end' let b:endwise_words = 'begin,function,if,switch,while,for' let b:endwise_syngroups = 'fishKeyword,fishConditional,fishRepeat' + +let b:undo_ftplugin = " + \ setlocal comments< commentstring< define< foldexpr< formatoptions< + \|setlocal include< iskeyword< suffixesadd< + \|setlocal formatexpr< omnifunc< path< keywordprg< + \|unlet! b:match_words b:endwise_addition b:endwise_words b:endwise_syngroups + \" + +let &cpo = s:save_cpo +unlet s:save_cpo diff --git a/indent/fish.vim b/indent/fish.vim index 656109627..2fa1c1a7f 100644 --- a/indent/fish.vim +++ b/indent/fish.vim @@ -3,4 +3,5 @@ if has_key(g:polyglot_is_disabled, 'fish') endif setlocal indentexpr=fish#Indent() +setlocal indentkeys=!^F,o,O setlocal indentkeys+==end,=else,=case diff --git a/packages.yaml b/packages.yaml index a1bf7d401..5efef051c 100644 --- a/packages.yaml +++ b/packages.yaml @@ -575,7 +575,7 @@ filetypes: - ferm.conf --- name: fish -remote: georgewitteman/vim-fish +remote: blankname/vim-fish filetypes: - name: fish linguist: fish diff --git a/syntax/fish.vim b/syntax/fish.vim index 22813ccb9..6ea51a827 100644 --- a/syntax/fish.vim +++ b/syntax/fish.vim @@ -7,6 +7,7 @@ if exists('b:current_syntax') endif syntax case match +syntax iskeyword @,48-57,-,_,.,/ syntax keyword fishKeyword begin function end syntax keyword fishConditional if else switch @@ -16,8 +17,8 @@ syntax keyword fishLabel case syntax match fishComment /#.*/ syntax match fishSpecial /\\$/ syntax match fishIdentifier /\$[[:alnum:]_]\+/ -syntax region fishString start=/'/ skip=/\\'/ end=/'/ -syntax region fishString start=/"/ skip=/\\"/ end=/"/ contains=fishIdentifier +syntax region fishString start=/'/ skip=/\v(\\{2})|(\\)'/ end=/'/ +syntax region fishString start=/"/ skip=/\v(\\{2})|(\\)"/ end=/"/ contains=fishIdentifier syntax match fishCharacter /\v\\[abefnrtv *?~%#(){}\[\]<>&;"']|\\[xX][0-9a-f]{1,2}|\\o[0-7]{1,2}|\\u[0-9a-f]{1,4}|\\U[0-9a-f]{1,8}|\\c[a-z]/ syntax match fishStatement /\v;\s*\zs\k+>/ syntax match fishCommandSub /\v\(\s*\zs\k+>/