From cb574b283fd19eea20d95c768b64c6b805d81690 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 26 Jul 2016 14:06:32 +0200 Subject: [PATCH] Add livescript, closes #135 --- README.md | 1 + build | 1 + compiler/ls.vim | 78 ++++++++++++ extras/flow.vim | 46 ++++++++ extras/jsdoc.vim | 43 +++++++ extras/ngdoc.vim | 7 ++ ftdetect/polyglot.vim | 13 ++ ftplugin/ls.vim | 208 ++++++++++++++++++++++++++++++++ indent/ls.vim | 268 ++++++++++++++++++++++++++++++++++++++++++ syntax/ls.vim | 140 ++++++++++++++++++++++ 10 files changed, 805 insertions(+) create mode 100644 compiler/ls.vim create mode 100644 ftplugin/ls.vim create mode 100644 indent/ls.vim create mode 100644 syntax/ls.vim diff --git a/README.md b/README.md index dc32ece..0f0ec43 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Optionally download one of the [releases](https://github.com/sheerun/vim-polyglo - [latex](https://github.com/LaTeX-Box-Team/LaTeX-Box) (syntax, indent, ftplugin) - [less](https://github.com/groenewege/vim-less) (syntax, indent, ftplugin, ftdetect) - [liquid](https://github.com/tpope/vim-liquid) (syntax, indent, ftplugin, ftdetect) +- [livescript](https://github.com/gkz/vim-ls) (syntax, indent, compiler, ftplugin, ftdetect) - [lua](https://github.com/tbastos/vim-lua) (syntax, indent) - [mako](https://github.com/sophacles/vim-bundle-mako) (syntax, indent, ftplugin, ftdetect) - [markdown](https://github.com/plasticboy/vim-markdown) (syntax, ftdetect) diff --git a/build b/build index 5594222..ebcdb09 100755 --- a/build +++ b/build @@ -139,6 +139,7 @@ PACKS=" latex:LaTeX-Box-Team/LaTeX-Box less:groenewege/vim-less liquid:tpope/vim-liquid + livescript:gkz/vim-ls lua:tbastos/vim-lua mako:sophacles/vim-bundle-mako markdown:plasticboy/vim-markdown:_SYNTAX diff --git a/compiler/ls.vim b/compiler/ls.vim new file mode 100644 index 0000000..df79291 --- /dev/null +++ b/compiler/ls.vim @@ -0,0 +1,78 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'livescript') == -1 + +" Language: LiveScript +" Maintainer: George Zahariev +" URL: http://github.com/gkz/vim-ls +" License: WTFPL + +if exists('current_compiler') + finish +endif + +let current_compiler = 'ls' +" Pattern to check if livescript is the compiler +let s:pat = '^' . current_compiler + +" Path to LiveScript compiler +if !exists('livescript_compiler') + let livescript_compiler = 'lsc' +endif + +if !exists('livescript_make_options') + let livescript_make_options = '' +endif + +" Get a `makeprg` for the current filename. This is needed to support filenames +" with spaces and quotes, but also not break generic `make`. +function! s:GetMakePrg() + return g:livescript_compiler . ' -c ' . g:livescript_make_options . ' $* ' + \ . fnameescape(expand('%')) +endfunction + +" Set `makeprg` and return 1 if coffee is still the compiler, else return 0. +function! s:SetMakePrg() + if &l:makeprg =~ s:pat + let &l:makeprg = s:GetMakePrg() + elseif &g:makeprg =~ s:pat + let &g:makeprg = s:GetMakePrg() + else + return 0 + endif + + return 1 +endfunction + +" Set a dummy compiler so we can check whether to set locally or globally. +CompilerSet makeprg=ls +call s:SetMakePrg() + +CompilerSet errorformat=%EFailed\ at:\ %f, + \%ECan't\ find:\ %f, + \%CSyntaxError:\ %m\ on\ line\ %l, + \%CError:\ Parse\ error\ on\ line\ %l:\ %m, + \%C,%C\ %.%# + +" Compile the current file. +command! -bang -bar -nargs=* LiveScriptMake make + +" Set `makeprg` on rename since we embed the filename in the setting. +augroup LiveScriptUpdateMakePrg + autocmd! + + " Update `makeprg` if livescript is still the compiler, else stop running this + " function. + function! s:UpdateMakePrg() + if !s:SetMakePrg() + autocmd! LiveScriptUpdateMakePrg + endif + endfunction + + " Set autocmd locally if compiler was set locally. + if &l:makeprg =~ s:pat + autocmd BufFilePost,BufWritePost call s:UpdateMakePrg() + else + autocmd BufFilePost,BufWritePost call s:UpdateMakePrg() + endif +augroup END + +endif diff --git a/extras/flow.vim b/extras/flow.vim index a18c882..6ce7ed9 100644 --- a/extras/flow.vim +++ b/extras/flow.vim @@ -44,3 +44,49 @@ if version >= 508 || !exists("did_javascript_syn_inits") endif endif +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'javascript') == -1 + +syntax region jsFlowTypeStatement start=/type/ end=/=/ oneline skipwhite skipempty nextgroup=jsFlowTypeObject +syntax region jsFlowDeclareBlock start=/declare/ end=/[;\n]/ oneline contains=jsFlow,jsFlowDeclareKeyword,jsFlowStorageClass +syntax region jsFlow start=/:/ end=/\%(\%([),=;\n]\|{\%(.*}\)\@!\|\%({.*}\)\@<=\s*{\)\@=\|void\)/ contains=@jsFlowCluster oneline skipwhite skipempty nextgroup=jsFuncBlock +syntax region jsFlowReturn contained start=/:/ end=/\%(\S\s*\%({\%(.*}\)\@!\)\@=\|\n\)/ contains=@jsFlowCluster oneline skipwhite skipempty nextgroup=jsFuncBlock keepend +syntax region jsFlowTypeObject contained start=/{/ end=/}/ contains=jsFlowTypeKey skipwhite skipempty nextgroup=jsFunctionBlock extend +syntax match jsFlowTypeKey contained /\<[0-9a-zA-Z_$?]*\>\(\s*:\)\@=/ skipwhite skipempty nextgroup=jsFlowTypeValue +syntax region jsFlowTypeValue contained matchgroup=jsFlowNoise start=/:/ end=/[,}]/ contains=@jsFlowCluster +syntax region jsFlowObject contained matchgroup=jsFlowNoise start=/{/ end=/}/ oneline contains=@jsFlowCluster +syntax region jsFlowArray contained matchgroup=jsFlowNoise start=/\[/ end=/\]/ oneline contains=@jsFlowCluster +syntax region jsFlowArrow contained matchgroup=jsFlowNoise start=/(/ end=/)\s*=>/ oneline contains=@jsFlowCluster +syntax keyword jsFlowDeclareKeyword contained declare +syntax keyword jsFlowType contained boolean number string null void any mixed JSON array function object Array +syntax match jsFlowClassProperty contained /\<[0-9a-zA-Z_$]*\>:\@=/ skipwhite skipempty nextgroup=jsFlow +syntax match jsFlowNoise contained /[:;,<>]/ +syntax cluster jsFlowCluster contains=jsFlowType,jsFlowArray,jsFlowObject,jsFlowNoise,jsFlowArrow +syntax keyword jsFlowStorageClass contained const var let +syntax region jsFlowParenRegion contained start=/:\s*(/ end=/)\%(\s*:\)\@=/ oneline contains=@jsFlowCluster skipwhite skipempty nextgroup=jsObjectValue +syntax region jsFlowClass contained matchgroup=jsFlowNoise start=// oneline contains=@jsFlowCluster skipwhite skipempty nextgroup=jsClassBlock + +if version >= 508 || !exists("did_javascript_syn_inits") + if version < 508 + let did_javascript_syn_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + HiLink jsFlow PreProc + HiLink jsFlowReturn PreProc + HiLink jsFlowArray PreProc + HiLink jsFlowDeclareBlock PreProc + HiLink jsFlowObject PreProc + HiLink jsFlowParenRegion PreProc + HiLink jsFlowClass PreProc + HiLink jsFlowTypeObject PreProc + HiLink jsFlowTypeKey PreProc + HiLink jsFlowTypeValue PreProc + HiLink jsFlowClassProperty jsClassProperty + HiLink jsFlowType Type + HiLink jsFlowDeclareKeyword Type + HiLink jsFlowNoise Noise + delcommand HiLink +endif + +endif diff --git a/extras/jsdoc.vim b/extras/jsdoc.vim index c5d7a57..7ce5f62 100644 --- a/extras/jsdoc.vim +++ b/extras/jsdoc.vim @@ -41,3 +41,46 @@ if version >= 508 || !exists("did_javascript_syn_inits") endif endif +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'javascript') == -1 + +"" syntax coloring for javadoc comments (HTML) +syntax region jsComment matchgroup=jsComment start="/\*\s*" end="\*/" contains=jsDocTags,jsCommentTodo,jsCvsTag,@jsHtml,@Spell fold + +" tags containing a param +syntax match jsDocTags contained "@\(alias\|api\|augments\|borrows\|class\|constructs\|default\|defaultvalue\|emits\|exception\|exports\|extends\|fires\|kind\|link\|listens\|member\|member[oO]f\|mixes\|module\|name\|namespace\|requires\|template\|throws\|var\|variation\|version\)\>" skipwhite nextgroup=jsDocParam +" tags containing type and param +syntax match jsDocTags contained "@\(arg\|argument\|cfg\|param\|property\|prop\)\>" skipwhite nextgroup=jsDocType +" tags containing type but no param +syntax match jsDocTags contained "@\(callback\|define\|enum\|external\|implements\|this\|type\|typedef\|return\|returns\)\>" skipwhite nextgroup=jsDocTypeNoParam +" tags containing references +syntax match jsDocTags contained "@\(lends\|see\|tutorial\)\>" skipwhite nextgroup=jsDocSeeTag +" other tags (no extra syntax) +syntax match jsDocTags contained "@\(abstract\|access\|accessor\|author\|classdesc\|constant\|const\|constructor\|copyright\|deprecated\|desc\|description\|dict\|event\|example\|file\|file[oO]verview\|final\|function\|global\|ignore\|inheritDoc\|inner\|instance\|interface\|license\|localdoc\|method\|mixin\|nosideeffects\|override\|overview\|preserve\|private\|protected\|public\|readonly\|since\|static\|struct\|todo\|summary\|undocumented\|virtual\)\>" + +syntax region jsDocType contained matchgroup=jsDocTypeBrackets start="{" end="}" contains=jsDocTypeRecord oneline skipwhite nextgroup=jsDocParam +syntax match jsDocType contained "\%(#\|\"\|\w\|\.\|:\|\/\)\+" skipwhite nextgroup=jsDocParam +syntax region jsDocTypeRecord contained start=/{/ end=/}/ contains=jsDocTypeRecord extend +syntax region jsDocTypeRecord contained start=/\[/ end=/\]/ contains=jsDocTypeRecord extend +syntax region jsDocTypeNoParam contained start="{" end="}" oneline +syntax match jsDocTypeNoParam contained "\%(#\|\"\|\w\|\.\|:\|\/\)\+" +syntax match jsDocParam contained "\%(#\|\$\|-\|'\|\"\|{.\{-}}\|\w\|\.\|:\|\/\|\[.{-}]\|=\)\+" +syntax region jsDocSeeTag contained matchgroup=jsDocSeeTag start="{" end="}" contains=jsDocTags + +if version >= 508 || !exists("did_javascript_syn_inits") + if version < 508 + let did_javascript_syn_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + HiLink jsDocTags Special + HiLink jsDocSeeTag Function + HiLink jsDocType Type + HiLink jsDocTypeBrackets jsDocType + HiLink jsDocTypeRecord jsDocType + HiLink jsDocTypeNoParam Type + HiLink jsDocParam Label + delcommand HiLink +endif + +endif diff --git a/extras/ngdoc.vim b/extras/ngdoc.vim index be314a8..7065cb8 100644 --- a/extras/ngdoc.vim +++ b/extras/ngdoc.vim @@ -5,3 +5,10 @@ syntax match jsDocType contained "\%(#\|\$\|\w\|\"\|-\|\.\|:\|\/\)\+" n syntax match jsDocParam contained "\%(#\|\$\|\w\|\"\|-\|\.\|:\|{\|}\|\/\|\[\|]\|=\)\+" endif +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'javascript') == -1 + +syntax match jsDocTags contained /@\(link\|method[oO]f\|ngdoc\|ng[iI]nject\|restrict\)/ nextgroup=jsDocParam skipwhite +syntax match jsDocType contained "\%(#\|\$\|\w\|\"\|-\|\.\|:\|\/\)\+" nextgroup=jsDocParam skipwhite +syntax match jsDocParam contained "\%(#\|\$\|\w\|\"\|-\|\.\|:\|{\|}\|\/\|\[\|]\|=\)\+" + +endif diff --git a/ftdetect/polyglot.vim b/ftdetect/polyglot.vim index 6e03252..caa6ad5 100644 --- a/ftdetect/polyglot.vim +++ b/ftdetect/polyglot.vim @@ -475,6 +475,19 @@ au BufNewFile,BufRead */templates/**.liquid,*/layout/**.liquid,*/snippets/**.liq endif +" ftdetect/ls.vim +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'livescript') == -1 + +" Language: LiveScript +" Maintainer: George Zahariev +" URL: http://github.com/gkz/vim-ls +" License: WTFPL +" +autocmd BufNewFile,BufRead *.ls set filetype=ls +autocmd BufNewFile,BufRead *Slakefile set filetype=ls + +endif + " ftdetect/mako.vim if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'mako') == -1 diff --git a/ftplugin/ls.vim b/ftplugin/ls.vim new file mode 100644 index 0000000..6ce6b57 --- /dev/null +++ b/ftplugin/ls.vim @@ -0,0 +1,208 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'livescript') == -1 + +" Language: LiveScript +" Maintainer: George Zahariev +" URL: http://github.com/gkz/vim-ls +" License: WTFPL +" +if exists("b:did_ftplugin") + finish +endif + +let b:did_ftplugin = 1 + +setlocal formatoptions-=t formatoptions+=croql +setlocal comments=:# +setlocal commentstring=#\ %s +setlocal omnifunc=javascriptcomplete#CompleteJS + +" Enable LiveScriptMake if it won't overwrite any settings. +if !len(&l:makeprg) + compiler ls +endif + +" Check here too in case the compiler above isn't loaded. +if !exists('livescript_compiler') + let livescript_compiler = 'lsc' +endif + +" Reset the LiveScriptCompile variables for the current buffer. +function! s:LiveScriptCompileResetVars() + " Compiled output buffer + let b:livescript_compile_buf = -1 + let b:livescript_compile_pos = [] + + " If LiveScriptCompile is watching a buffer + let b:livescript_compile_watch = 0 +endfunction + +" Clean things up in the source buffer. +function! s:LiveScriptCompileClose() + exec bufwinnr(b:livescript_compile_src_buf) 'wincmd w' + silent! autocmd! LiveScriptCompileAuWatch * + call s:LiveScriptCompileResetVars() +endfunction + +" Update the LiveScriptCompile buffer given some input lines. +function! s:LiveScriptCompileUpdate(startline, endline) + let input = join(getline(a:startline, a:endline), "\n") + + " Move to the LiveScriptCompile buffer. + exec bufwinnr(b:livescript_compile_buf) 'wincmd w' + + " LiveScript doesn't like empty input. + if !len(input) + return + endif + + " Compile input. + let output = system(g:livescript_compiler . ' -scb 2>&1', input) + + " Be sure we're in the LiveScriptCompile buffer before overwriting. + if exists('b:livescript_compile_buf') + echoerr 'LiveScriptCompile buffers are messed up' + return + endif + + " Replace buffer contents with new output and delete the last empty line. + setlocal modifiable + exec '% delete _' + put! =output + exec '$ delete _' + setlocal nomodifiable + + " Highlight as JavaScript if there is no compile error. + if v:shell_error + setlocal filetype= + else + setlocal filetype=javascript + endif + + call setpos('.', b:livescript_compile_pos) +endfunction + +" Update the LiveScriptCompile buffer with the whole source buffer. +function! s:LiveScriptCompileWatchUpdate() + call s:LiveScriptCompileUpdate(1, '$') + exec bufwinnr(b:livescript_compile_src_buf) 'wincmd w' +endfunction + +" Peek at compiled LiveScript in a scratch buffer. We handle ranges like this +" to prevent the cursor from being moved (and its position saved) before the +" function is called. +function! s:LiveScriptCompile(startline, endline, args) + if !executable(g:livescript_compiler) + echoerr "Can't find LiveScript compiler `" . g:livescript_compiler . "`" + return + endif + + " If in the LiveScriptCompile buffer, switch back to the source buffer and + " continue. + if !exists('b:livescript_compile_buf') + exec bufwinnr(b:livescript_compile_src_buf) 'wincmd w' + endif + + " Parse arguments. + let watch = a:args =~ '\' + let unwatch = a:args =~ '\' + let size = str2nr(matchstr(a:args, '\<\d\+\>')) + + " Determine default split direction. + if exists('g:livescript_compile_vert') + let vert = 1 + else + let vert = a:args =~ '\' + endif + + " Remove any watch listeners. + silent! autocmd! LiveScriptCompileAuWatch * + + " If just unwatching, don't compile. + if unwatch + let b:livescript_compile_watch = 0 + return + endif + + if watch + let b:livescript_compile_watch = 1 + endif + + " Build the LiveScriptCompile buffer if it doesn't exist. + if bufwinnr(b:livescript_compile_buf) == -1 + let src_buf = bufnr('%') + let src_win = bufwinnr(src_buf) + + " Create the new window and resize it. + if vert + let width = size ? size : winwidth(src_win) / 2 + + belowright vertical new + exec 'vertical resize' width + else + " Try to guess the compiled output's height. + let height = size ? size : min([winheight(src_win) / 2, + \ a:endline - a:startline + 5]) + + belowright new + exec 'resize' height + endif + + " We're now in the scratch buffer, so set it up. + setlocal bufhidden=wipe buftype=nofile + setlocal nobuflisted nomodifiable noswapfile nowrap + + autocmd BufWipeout call s:LiveScriptCompileClose() + " Save the cursor when leaving the LiveScriptCompile buffer. + autocmd BufLeave let b:livescript_compile_pos = getpos('.') + + nnoremap q :hide + + let b:livescript_compile_src_buf = src_buf + let buf = bufnr('%') + + " Go back to the source buffer and set it up. + exec bufwinnr(b:livescript_compile_src_buf) 'wincmd w' + let b:livescript_compile_buf = buf + endif + + if b:livescript_compile_watch + call s:LiveScriptCompileWatchUpdate() + + augroup LiveScriptCompileAuWatch + autocmd InsertLeave call s:LiveScriptCompileWatchUpdate() + autocmd BufWritePost call s:LiveScriptCompileWatchUpdate() + augroup END + else + call s:LiveScriptCompileUpdate(a:startline, a:endline) + endif +endfunction + +" Complete arguments for the LiveScriptCompile command. +function! s:LiveScriptCompileComplete(arg, cmdline, cursor) + let args = ['unwatch', 'vertical', 'watch'] + + if !len(a:arg) + return args + endif + + let match = '^' . a:arg + + for arg in args + if arg =~ match + return [arg] + endif + endfor +endfunction + +" Don't overwrite the CoffeeCompile variables. +if !exists("s:livescript_compile_buf") + call s:LiveScriptCompileResetVars() +endif + +" Peek at compiled LiveScript. +command! -range=% -bar -nargs=* -complete=customlist,s:LiveScriptCompileComplete +\ LiveScriptCompile call s:LiveScriptCompile(, , ) +" Run some LiveScript. +command! -range=% -bar LiveScriptRun ,:w !lsc -sp + +endif diff --git a/indent/ls.vim b/indent/ls.vim new file mode 100644 index 0000000..240eab0 --- /dev/null +++ b/indent/ls.vim @@ -0,0 +1,268 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'livescript') == -1 + +" Language: LiveScript +" Maintainer: George Zahariev +" URL: http://github.com/gkz/vim-ls +" License: WTFPL + +if exists("b:did_indent") + finish +endif + +let b:did_indent = 1 + +setlocal autoindent +setlocal indentexpr=GetLiveScriptIndent(v:lnum) +" Make sure GetLiveScriptIndent is run when these are typed so they can be +" indented or outdented. +setlocal indentkeys+=0],0),0.,=else,=when,=catch,=finally + +" Only define the function once. +if exists("*GetLiveScriptIndent") + finish +endif + +" Keywords to indent after +let s:INDENT_AFTER_KEYWORD = '^\%(if\|unless\|else\|for\|while\|until\|' +\ . 'loop\|case\|default\|try\|catch\|finally\|' +\ . 'class\|do\|new\|let\|with\|function\)\>' + +" Operators to indent after +let s:INDENT_AFTER_OPERATOR = '\%([([{:=]\|[-=]>\)$' + +" Keywords and operators that continue a line +let s:CONTINUATION = '\<\%(is\|isnt\|and\|or\|do\)\>$' +\ . '\|' +\ . '\%(-\@\|\*\|/\@' + +" A compound assignment like `... = if ...` +let s:COMPOUND_ASSIGNMENT = '[:=]\s*\%(if\|unless\|for\|while\|until\|' +\ . 'try\|class\|do\|new\|let\|with\)\>' + +" A postfix condition like `return ... if ...`. +let s:POSTFIX_CONDITION = '\S\s\+\zs\<\%(if\|unless\)\>' + +" A single-line else statement like `else ...` but not `else if ... +let s:SINGLE_LINE_ELSE = '^else\s\+\%(\<\%(if\|unless\)\>\)\@!' + +" Max lines to look back for a match +let s:MAX_LOOKBACK = 50 + +" Get the linked syntax name of a character. +function! s:SyntaxName(linenum, col) + return synIDattr(synIDtrans(synID(a:linenum, a:col, 1)), 'name') +endfunction + +" Check if a character is in a comment. +function! s:IsComment(linenum, col) + return s:SyntaxName(a:linenum, a:col) == 'Comment' +endfunction + +" Check if a character is in a string. +function! s:IsString(linenum, col) + return s:SyntaxName(a:linenum, a:col) == 'Constant' +endfunction + +" Check if a character is in a comment or string. +function! s:IsCommentOrString(linenum, col) + return s:SyntaxName(a:linenum, a:col) =~ 'Comment\|Constant' +endfunction + +" Check if a whole line is a comment. +function! s:IsCommentLine(linenum) + " Check the first non-whitespace character. + return s:IsComment(a:linenum, indent(a:linenum) + 1) +endfunction + +" Repeatedly search a line for a regex until one is found outside a string or +" comment. +function! s:SmartSearch(linenum, regex) + " Start at the first column. + let col = 0 + + " Search until there are no more matches, unless a good match is found. + while 1 + call cursor(a:linenum, col + 1) + let [_, col] = searchpos(a:regex, 'cn', a:linenum) + + " No more matches. + if !col + break + endif + + if !s:IsCommentOrString(a:linenum, col) + return 1 + endif + endwhile + + " No good match found. + return 0 +endfunction + +" Skip a match if it's in a comment or string, is a single-line statement that +" isn't adjacent, or is a postfix condition. +function! s:ShouldSkip(startlinenum, linenum, col) + if s:IsCommentOrString(a:linenum, a:col) + return 1 + endif + + " Check for a single-line statement that isn't adjacent. + if s:SmartSearch(a:linenum, '\') && a:startlinenum - a:linenum > 1 + return 1 + endif + + if s:SmartSearch(a:linenum, s:POSTFIX_CONDITION) && + \ !s:SmartSearch(a:linenum, s:COMPOUND_ASSIGNMENT) + return 1 + endif + + return 0 +endfunction + +" Find the farthest line to look back to, capped to line 1 (zero and negative +" numbers cause bad things). +function! s:MaxLookback(startlinenum) + return max([1, a:startlinenum - s:MAX_LOOKBACK]) +endfunction + +" Get the skip expression for searchpair(). +function! s:SkipExpr(startlinenum) + return "s:ShouldSkip(" . a:startlinenum . ", line('.'), col('.'))" +endfunction + +" Search for pairs of text. +function! s:SearchPair(start, end) + " The cursor must be in the first column for regexes to match. + call cursor(0, 1) + + let startlinenum = line('.') + + " Don't need the W flag since MaxLookback caps the search to line 1. + return searchpair(a:start, '', a:end, 'bcn', + \ s:SkipExpr(startlinenum), + \ s:MaxLookback(startlinenum)) +endfunction + +" Try to find a previous matching line. +function! s:GetMatch(curline) + let firstchar = a:curline[0] + + if firstchar == '}' + return s:SearchPair('{', '}') + elseif firstchar == ')' + return s:SearchPair('(', ')') + elseif firstchar == ']' + return s:SearchPair('\[', '\]') + elseif a:curline =~ '^else\>' + return s:SearchPair('\<\%(if\|unless\|case\|when\)\>', '\') + elseif a:curline =~ '^catch\>' + return s:SearchPair('\', '\') + elseif a:curline =~ '^finally\>' + return s:SearchPair('\', '\') + endif + + return 0 +endfunction + +" Get the nearest previous line that isn't a comment. +function! s:GetPrevNormalLine(startlinenum) + let curlinenum = a:startlinenum + + while curlinenum > 0 + let curlinenum = prevnonblank(curlinenum - 1) + + if !s:IsCommentLine(curlinenum) + return curlinenum + endif + endwhile + + return 0 +endfunction + +" Get the contents of a line without leading or trailing whitespace. +function! s:GetTrimmedLine(linenum) + return substitute(substitute(getline(a:linenum), '^\s\+', '', ''), + \ '\s\+$', '', '') +endfunction + +function! s:GetLiveScriptIndent(curlinenum) + let prevlinenum = s:GetPrevNormalLine(a:curlinenum) + + " Don't do anything if there's no previous line. + if !prevlinenum + return -1 + endif + + let curline = s:GetTrimmedLine(a:curlinenum) + + " Try to find a previous matching statement. This handles outdenting. + let matchlinenum = s:GetMatch(curline) + + if matchlinenum + return indent(matchlinenum) + endif + + let prevline = s:GetTrimmedLine(prevlinenum) + let previndent = indent(prevlinenum) + + " Always indent after these operators. + if prevline =~ s:INDENT_AFTER_OPERATOR + return previndent + &shiftwidth + endif + + " Indent after a continuation if it's the first. + if prevline =~ s:CONTINUATION + let prevprevlinenum = s:GetPrevNormalLine(prevlinenum) + let prevprevline = s:GetTrimmedLine(prevprevlinenum) + + if prevprevline !~ s:CONTINUATION && prevprevline !~ s:CONTINUATION_BLOCK + return previndent + &shiftwidth + endif + endif + + " Indent after these keywords and compound assignments if they aren't a + " single-line statement. + if prevline =~ s:INDENT_AFTER_KEYWORD || prevline =~ s:COMPOUND_ASSIGNMENT + if !s:SmartSearch(prevlinenum, '\') && prevline !~ s:SINGLE_LINE_ELSE + return previndent + &shiftwidth + endif + endif + + " Indent a dot access if it's the first. + if curline =~ s:DOT_ACCESS && prevline !~ s:DOT_ACCESS + return previndent + &shiftwidth + endif + + " Outdent after these keywords if they don't have a postfix condition and + " aren't a single-line statement. + if prevline =~ s:OUTDENT_AFTER + if !s:SmartSearch(prevlinenum, s:POSTFIX_CONDITION) || + \ s:SmartSearch(prevlinenum, '\') + return previndent - &shiftwidth + endif + endif + + " No indenting or outdenting is needed. + return -1 +endfunction + +" Wrap s:GetLiveScriptIndent to keep the cursor position. +function! GetLiveScriptIndent(curlinenum) + let oldcursor = getpos('.') + let indent = s:GetLiveScriptIndent(a:curlinenum) + call setpos('.', oldcursor) + + return indent +endfunction + +endif diff --git a/syntax/ls.vim b/syntax/ls.vim new file mode 100644 index 0000000..c3edf65 --- /dev/null +++ b/syntax/ls.vim @@ -0,0 +1,140 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'livescript') == -1 + +" Language: LiveScript " +" Maintainer: George Zahariev +" URL: http://github.com/gkz/vim-ls +" License: WTFPL + +if exists('b:current_syntax') && b:current_syntax == 'livescript' + finish +endif + +let b:current_syntax = "ls" + +" Highlight long strings. +syntax sync minlines=100 + +setlocal iskeyword=48-57,A-Z,$,a-z,_,- + +syntax match lsIdentifier /[$A-Za-z_]\k*\(-[a-zA-Z]\+\)*/ +highlight default link lsIdentifier Identifier + +" These are 'matches' rather than 'keywords' because vim's highlighting priority +" for keywords (the highest) causes them to be wrongly highlighted when used as +" dot-properties. +syntax match lsStatement /\<\%(return\|break\|continue\|throw\)\>/ +highlight default link lsStatement Statement + +syntax match lsRepeat /\<\%(for\%( own\| ever\)\?\|while\|until\)\>/ +highlight default link lsRepeat Repeat + +syntax match lsConditional /\<\%(if\|else\|unless\|switch\|case\|when\|default\|match\)\>/ +highlight default link lsConditional Conditional + +syntax match lsException /\<\%(try\|catch\|finally\)\>/ +highlight default link lsException Exception + +syntax match lsKeyword /\<\%(new\|in\%(stanceof\)\?\|typeof\|delete\|and\|o[fr]\|not\|xor\|is\|isnt\|imp\%(ort\%( all\)\?\|lements\)\|extends\|loop\|from\|to\|til\|by\|do\|then\|function\|class\|let\|with\|export\|const\|var\|eval\|super\|fallthrough\|debugger\|where\|yield\)\>/ +highlight default link lsKeyword Keyword + +syntax match lsBoolean /\<\%(true\|false\|yes\|no\|on\|off\|null\|void\)\>/ +highlight default link lsBoolean Boolean + +" Matches context variables. +syntax match lsContext /\<\%(this\|arguments\|it\|that\|constructor\|prototype\|superclass\)\>/ +highlight default link lsContext Type + +" Keywords reserved by the language +syntax cluster lsReserved contains=lsStatement,lsRepeat,lsConditional, +\ lsException,lsOperator,lsKeyword,lsBoolean + +" Matches ECMAScript 5 built-in globals. +syntax match lsGlobal /\<\%(Array\|Boolean\|Date\|Function\|JSON\|Math\|Number\|Object\|RegExp\|String\|\%(Syntax\|Type\|URI\)\?Error\|is\%(NaN\|Finite\)\|parse\%(Int\|Float\)\|\%(en\|de\)codeURI\%(Component\)\?\)\>/ +highlight default link lsGlobal Structure + +syntax region lsString start=/"/ skip=/\\\\\|\\"/ end=/"/ contains=@lsInterpString +syntax region lsString start=/'/ skip=/\\\\\|\\'/ end=/'/ contains=@lsSimpleString +highlight default link lsString String + +" Matches decimal/floating-point numbers like 10.42e-8. +syntax match lsFloat +\ /\%(\<-\?\|-\)\zs\d[0-9_]*\%(\.\d[0-9_]*\)\?\%(e[+-]\?\d[0-9_]*\)\?\%([a-zA-Z$][$a-zA-Z0-9_]*\)\?/ +\ contains=lsNumberComment +highlight default link lsFloat Float +syntax match lsNumberComment /\d\+\zs\%(e[+-]\?\d\)\@![a-zA-Z$][$a-zA-Z0-9_]*/ contained +highlight default link lsNumberComment Comment +" Matches hex numbers like 0xfff, 0x000. +syntax match lsNumber /\%(\<-\?\|-\)\zs0x\x\+/ +" Matches N radix numbers like 2@1010. +syntax match lsNumber +\ /\%(\<-\?\|-\)\zs\%(\d*\)\~[0-9A-Za-z][0-9A-Za-z_]*/ +highlight default link lsNumber Number + +" Displays an error for reserved words. +syntax match lsReservedError /\<\%(enum\|interface\|package\|private\|protected\|public\|static\)\>/ +highlight default link lsReservedError Error + +syntax keyword lsTodo TODO FIXME XXX contained +highlight default link lsTodo Todo + +syntax match lsComment /#.*/ contains=@Spell,lsTodo +syntax region lsComment start=/\/\*/ end=/\*\// contains=@Spell,lsTodo +highlight default link lsComment Comment + +syntax region lsInfixFunc start=/`/ end=/`/ +highlight default link lsInfixFunc Identifier + +syntax region lsInterpolation matchgroup=lsInterpDelim +\ start=/\#{/ end=/}/ +\ contained contains=TOP +highlight default link lsInterpDelim Delimiter + +" Matches escape sequences like \000, \x00, \u0000, \n. +syntax match lsEscape /\\\d\d\d\|\\x\x\{2\}\|\\u\x\{4\}\|\\./ contained +highlight default link lsEscape SpecialChar + +syntax match lsVarInterpolation /#[$A-Za-z_]\k*\(-[a-zA-Z]\+\)*/ contained +highlight default link lsVarInterpolation Identifier + +" What is in a non-interpolated string +syntax cluster lsSimpleString contains=@Spell,lsEscape +" What is in an interpolated string +syntax cluster lsInterpString contains=@lsSimpleString, +\ lsInterpolation,lsVarInterpolation + +syntax region lsRegex start=/\%(\%()\|\i\@/ contains=fold +highlight default link lsWords String + +" Reserved words can be used as property names. +syntax match lsProp /[$A-Za-z_]\k*[ \t]*:[:=]\@!/ +highlight default link lsProp Label + +syntax match lsKey +\ /\%(\.\@