diff options
-rw-r--r-- | README.md | 3 | ||||
-rwxr-xr-x | build | 1 | ||||
-rwxr-xr-x | build.py | 1 | ||||
-rw-r--r-- | ftdetect/polyglot.vim | 4 | ||||
-rw-r--r-- | ftplugin/fennel.vim | 30 | ||||
-rw-r--r-- | indent/fennel.vim | 330 | ||||
-rw-r--r-- | syntax/fennel.vim | 287 |
7 files changed, 655 insertions, 1 deletions
@@ -10,7 +10,7 @@ A collection of language packs for Vim. > One to rule them all, one to find them, one to bring them all and in the darkness bind them. - It **won't affect your startup time**, as scripts are loaded only on demand\*. -- It **installs and updates 120+ times faster** than the <!--Package Count-->153<!--/Package Count--> packages it consists of. +- It **installs and updates 120+ times faster** than the <!--Package Count-->154<!--/Package Count--> packages it consists of. - Solid syntax and indentation support (other features skipped). Only the best language packs. - All unnecessary files are ignored (like enormous documentation from php support). - No support for esoteric languages, only most popular ones (modern too, like `slim`). @@ -77,6 +77,7 @@ If you need full functionality of any plugin, please use it directly with your p - [emberscript](https://github.com/yalesov/vim-ember-script) (syntax, indent, ftplugin) - [emblem](https://github.com/yalesov/vim-emblem) (syntax, indent, ftplugin) - [erlang](https://github.com/vim-erlang/vim-erlang-runtime) (syntax, indent) +- [fennel](https://github.com/bakpakin/fennel.vim) (syntax, indent, ftplugin) - [ferm](https://github.com/vim-scripts/ferm.vim) (syntax) - [fish](https://github.com/georgewitteman/vim-fish) (syntax, indent, compiler, autoload, ftplugin) - [flatbuffers](https://github.com/dcharbon/vim-flatbuffers) (syntax) @@ -171,6 +171,7 @@ PACKS=" emberscript:yalesov/vim-ember-script emblem:yalesov/vim-emblem erlang:vim-erlang/vim-erlang-runtime + fennel:bakpakin/fennel.vim ferm:vim-scripts/ferm.vim fish:georgewitteman/vim-fish flatbuffers:dcharbon/vim-flatbuffers @@ -254,6 +254,7 @@ language("XDC", extensions=[".xdc"]) language("Zig", extra_extensions=[".zir"]) language("Zir", extensions=[".zir"], polyglot="zig", filetype="zir") language("Jsonnet") +language("Fennel", extensions=[".fnl"]) lines.append('" restore Vi compatibility settings') lines.append('let &cpo = s:cpo_save') diff --git a/ftdetect/polyglot.vim b/ftdetect/polyglot.vim index dfcab62f..028b8eb4 100644 --- a/ftdetect/polyglot.vim +++ b/ftdetect/polyglot.vim @@ -1147,6 +1147,10 @@ if index(g:polyglot_disabled, 'jsonnet') == -1 au BufNewFile,BufRead *.libsonnet set ft=jsonnet endif +if index(g:polyglot_disabled, 'fennel') == -1 + au BufNewFile,BufRead *.fnl set ft=fennel +endif + " restore Vi compatibility settings let &cpo = s:cpo_save unlet s:cpo_save
\ No newline at end of file diff --git a/ftplugin/fennel.vim b/ftplugin/fennel.vim new file mode 100644 index 00000000..1fba74f1 --- /dev/null +++ b/ftplugin/fennel.vim @@ -0,0 +1,30 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'fennel') == -1 + +" Vim filetype plugin file +" Language: FENNEL +" Maintainer: Calvin Rose + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim + +"setlocal iskeyword+=!,_,%,?,-,*,!,+,/,=,<,>,.,:,$,^ +setlocal iskeyword=!,$,%,#,*,+,-,.,/,:,<,=,>,?,_,a-z,A-Z,48-57,128-247,124,126,38,94 + +" There will be false positives, but this is better than missing the whole set +" of user-defined def* definitions. +setlocal define=\\v[(/]def(ault)@!\\S* + +" Remove 't' from 'formatoptions' to avoid auto-wrapping code. +setlocal formatoptions-=t + +setlocal comments=n:; +setlocal commentstring=;\ %s + +let &cpo = s:cpo_save + +endif diff --git a/indent/fennel.vim b/indent/fennel.vim new file mode 100644 index 00000000..c4516135 --- /dev/null +++ b/indent/fennel.vim @@ -0,0 +1,330 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'fennel') == -1 + +" Vim filetype plugin file +" Language: FENNEL +" Maintainer: Calvin Rose +" +" Modified from vim-clojure-static +" https://github.com/guns/vim-clojure-static/blob/master/indent/clojure.vim +" This should eventually be replaced by a more standard and performant +" indenter. + +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +let s:save_cpo = &cpo +set cpo&vim + +setlocal noautoindent nosmartindent +setlocal softtabstop=2 shiftwidth=2 expandtab +setlocal indentkeys=!,o,O + +if exists("*searchpairpos") + + if !exists('g:fennel_maxlines') + let g:fennel_maxlines = 100 + endif + + if !exists('g:fennel_fuzzy_indent') + let g:fennel_fuzzy_indent = 1 + endif + + if !exists('g:fennel_fuzzy_indent_patterns') + let g:fennel_fuzzy_indent_patterns = ['^def', '^let', '^while', '^if', '^fn$', '^var$', '^case$', '^for$', '^each$', '^local$', '^global$', '^match$', '^macro', '^lambda$'] + endif + + if !exists('g:fennel_fuzzy_indent_blacklist') + let g:fennel_fuzzy_indent_blacklist = [] + endif + + if !exists('g:fennel_special_indent_words') + let g:fennel_special_indent_words = '' + endif + + if !exists('g:fennel_align_multiline_strings') + let g:fennel_align_multiline_strings = 0 + endif + + if !exists('g:fennel_align_subforms') + let g:fennel_align_subforms = 0 + endif + + function! s:syn_id_name() + return synIDattr(synID(line("."), col("."), 0), "name") + endfunction + + function! s:ignored_region() + return s:syn_id_name() =~? '\vstring|comment|character' + endfunction + + function! s:current_char() + return getline('.')[col('.')-1] + endfunction + + function! s:current_word() + return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2] + endfunction + + function! s:is_paren() + return s:current_char() =~# '\v[\(\)\[\]\{\}]' && !s:ignored_region() + endfunction + + " Returns 1 if string matches a pattern in 'patterns', which may be a + " list of patterns, or a comma-delimited string of implicitly anchored + " patterns. + function! s:match_one(patterns, string) + let list = type(a:patterns) == type([]) + \ ? a:patterns + \ : map(split(a:patterns, ','), '"^" . v:val . "$"') + for pat in list + if a:string =~# pat | return 1 | endif + endfor + endfunction + + function! s:match_pairs(open, close, stopat) + " Stop only on vector and map [ resp. {. Ignore the ones in strings and + " comments. + if a:stopat == 0 && g:fennel_maxlines > 0 + let stopat = max([line(".") - g:fennel_maxlines, 0]) + else + let stopat = a:stopat + endif + + let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:is_paren()", stopat) + return [pos[0], col(pos)] + endfunction + + function! s:fennel_check_for_string_worker() + " Check whether there is the last character of the previous line is + " highlighted as a string. If so, we check whether it's a ". In this + " case we have to check also the previous character. The " might be the + " closing one. In case the we are still in the string, we search for the + " opening ". If this is not found we take the indent of the line. + let nb = prevnonblank(v:lnum - 1) + + if nb == 0 + return -1 + endif + + call cursor(nb, 0) + call cursor(0, col("$") - 1) + if s:syn_id_name() !~? "string" + return -1 + endif + + " This will not work for a " in the first column... + if s:current_char() == '"' || s:current_char() == '`' + call cursor(0, col("$") - 2) + if s:syn_id_name() !~? "string" + return -1 + endif + if s:current_char() != '\' + return -1 + endif + call cursor(0, col("$") - 1) + endif + + let p = searchpos('\(^\|[^\\]\)\zs"\`', 'bW') + + if p != [0, 0] + return p[1] - 1 + endif + + return indent(".") + endfunction + + function! s:check_for_string() + let pos = getpos('.') + try + let val = s:fennel_check_for_string_worker() + finally + call setpos('.', pos) + endtry + return val + endfunction + + " Returns 1 for opening brackets, -1 for _anything else_. + function! s:bracket_type(char) + return stridx('([{', a:char) > -1 ? 1 : -1 + endfunction + + " Returns: [opening-bracket-lnum, indent] + function! s:fennel_indent_pos() + " Get rid of special case. + if line(".") == 1 + return [0, 0] + endif + + " We have to apply some heuristics here to figure out, whether to use + " normal lisp indenting or not. + let i = s:check_for_string() + if i > -1 + return [0, i + !!g:fennel_align_multiline_strings] + endif + + call cursor(0, 1) + + " Find the next enclosing [ or {. We can limit the second search + " to the line, where the [ was found. If no [ was there this is + " zero and we search for an enclosing {. + let paren = s:match_pairs('(', ')', 0) + let bracket = s:match_pairs('\[', '\]', paren[0]) + let curly = s:match_pairs('{', '}', bracket[0]) + + " In case the curly brace is on a line later then the [ or - in + " case they are on the same line - in a higher column, we take the + " curly indent. + if curly[0] > bracket[0] || curly[1] > bracket[1] + if curly[0] > paren[0] || curly[1] > paren[1] + return curly + endif + endif + + " If the curly was not chosen, we take the bracket indent - if + " there was one. + if bracket[0] > paren[0] || bracket[1] > paren[1] + return bracket + endif + + " There are neither { nor [ nor (, ie. we are at the toplevel. + if paren == [0, 0] + return paren + endif + + " Now we have to reimplement lispindent. This is surprisingly easy, as + " soon as one has access to syntax items. + " + " - Check whether we are in a special position after a word in + " g:fennel_special_indent_words. These are special cases. + " - Get the next keyword after the (. + " - If its first character is also a (, we have another sexp and align + " one column to the right of the unmatched (. + " - In case it is in lispwords, we indent the next line to the column of + " the ( + sw. + " - If not, we check whether it is last word in the line. In that case + " we again use ( + sw for indent. + " - In any other case we use the column of the end of the word + 2. + call cursor(paren) + + " In case we are at the last character, we use the paren position. + if col("$") - 1 == paren[1] + return paren + endif + + " In case after the paren is a whitespace, we search for the next word. + call cursor(0, col('.') + 1) + if s:current_char() == ' ' + call search('\v\S', 'W') + endif + + " If we moved to another line, there is no word after the (. We + " use the ( position for indent. + if line(".") > paren[0] + return paren + endif + + " We still have to check, whether the keyword starts with a (, [ or {. + " In that case we use the ( position for indent. + let w = s:current_word() + if s:bracket_type(w[0]) == 1 + return paren + endif + + let ww = w + + if &lispwords =~# '\V\<' . ww . '\>' + return [paren[0], paren[1] + &shiftwidth - 1] + endif + + if g:fennel_fuzzy_indent + \ && !s:match_one(g:fennel_fuzzy_indent_blacklist, ww) + \ && s:match_one(g:fennel_fuzzy_indent_patterns, ww) + return [paren[0], paren[1] + &shiftwidth - 1] + endif + + call search('\v\_s', 'cW') + call search('\v\S', 'W') + if paren[0] < line(".") + return [paren[0], paren[1] + (g:fennel_align_subforms ? 0 : &shiftwidth - 1)] + endif + + call search('\v\S', 'bW') + return [line('.'), col('.') + 1] + endfunction + + function! GetFennelIndent() + let lnum = line('.') + let orig_lnum = lnum + let orig_col = col('.') + let [opening_lnum, indent] = s:fennel_indent_pos() + + " Account for multibyte characters + if opening_lnum > 0 + let indent -= indent - virtcol([opening_lnum, indent]) + endif + + " Return if there are no previous lines to inherit from + if opening_lnum < 1 || opening_lnum >= lnum - 1 + call cursor(orig_lnum, orig_col) + return indent + endif + + let bracket_count = 0 + + " Take the indent of the first previous non-white line that is + " at the same sexp level. cf. src/misc1.c:get_lisp_indent() + while 1 + let lnum = prevnonblank(lnum - 1) + let col = 1 + + if lnum <= opening_lnum + break + endif + + call cursor(lnum, col) + + " Handle bracket counting edge case + if s:is_paren() + let bracket_count += s:bracket_type(s:current_char()) + endif + + while 1 + if search('\v[(\[{}\])]', '', lnum) < 1 + break + elseif !s:ignored_region() + let bracket_count += s:bracket_type(s:current_char()) + endif + endwhile + + if bracket_count == 0 + " Check if this is part of a multiline string + call cursor(lnum, 1) + if s:syn_id_name() !~? '\vString|Buffer' + call cursor(orig_lnum, orig_col) + return indent(lnum) + endif + endif + endwhile + + call cursor(orig_lnum, orig_col) + return indent + endfunction + + setlocal indentexpr=GetFennelIndent() + +else + + " In case we have searchpairpos not available we fall back to + " normal lisp indenting. + setlocal indentexpr= + setlocal lisp + let b:undo_indent .= '| setlocal lisp<' + +endif + +let &cpo = s:save_cpo +unlet! s:save_cpo + +endif diff --git a/syntax/fennel.vim b/syntax/fennel.vim new file mode 100644 index 00000000..ec6f8084 --- /dev/null +++ b/syntax/fennel.vim @@ -0,0 +1,287 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'fennel') == -1 + +" Vim syntax file +" Language: FENNEL +" Maintainer: Calvin Rose + +if exists("b:current_syntax") + finish +endif + +let s:cpo_sav = &cpo +set cpo&vim + +if has("folding") && exists("g:fennel_fold") && g:fennel_fold > 0 + setlocal foldmethod=syntax +endif + +syntax keyword FennelCommentTodo contained FIXME XXX TODO FIXME: XXX: TODO: + +" FENNEL comments +syn match FennelComment ";.*$" contains=FennelCommentTodo,@Spell + +syntax match FennelStringEscape '\v\\%([abfnrtv'"\\]|x[[0-9a-fA-F]]\{2}|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])' contained +syntax region FennelString matchgroup=FennelStringDelimiter start=/"/ skip=/\\\\\|\\"/ end=/"/ contains=FennelStringEscape,@Spell +syntax region FennelString matchgroup=FennelStringDelimiter start=/'/ skip=/\\\\\|\\'/ end=/'/ contains=FennelStringEscape,@Spell + +syn keyword FennelConstant nil + +syn keyword FennelBoolean true +syn keyword FennelBoolean false + +" Fennel special forms +syn keyword FennelSpecialForm # +syn keyword FennelSpecialForm % +syn keyword FennelSpecialForm * +syn keyword FennelSpecialForm + +syn keyword FennelSpecialForm - +syn keyword FennelSpecialForm -> +syn keyword FennelSpecialForm ->> +syn keyword FennelSpecialForm -?> +syn keyword FennelSpecialForm -?>> +syn keyword FennelSpecialForm . +syn keyword FennelSpecialForm .. +syn keyword FennelSpecialForm / +syn keyword FennelSpecialForm // +syn keyword FennelSpecialForm : +syn keyword FennelSpecialForm < +syn keyword FennelSpecialForm <= +syn keyword FennelSpecialForm = +syn keyword FennelSpecialForm > +syn keyword FennelSpecialForm >= +syn keyword FennelSpecialForm ^ +syn keyword FennelSpecialForm and +syn keyword FennelSpecialForm comment +syn keyword FennelSpecialForm do +syn keyword FennelSpecialForm doc +syn keyword FennelSpecialForm doto +syn keyword FennelSpecialForm each +syn keyword FennelSpecialForm eval-compiler +syn keyword FennelSpecialForm fn +syn keyword FennelSpecialForm for +syn keyword FennelSpecialForm global +syn keyword FennelSpecialForm hashfn +syn keyword FennelSpecialForm if +syn keyword FennelSpecialForm include +syn keyword FennelSpecialForm lambda +syn keyword FennelSpecialForm length +syn keyword FennelSpecialForm let +syn keyword FennelSpecialForm local +syn keyword FennelSpecialForm lua +syn keyword FennelSpecialForm macro +syn keyword FennelSpecialForm macros +syn keyword FennelSpecialForm match +syn keyword FennelSpecialForm not +syn keyword FennelSpecialForm not= +syn keyword FennelSpecialForm or +syn keyword FennelSpecialForm partial +syn keyword FennelSpecialForm quote +syn keyword FennelSpecialForm require-macros +syn keyword FennelSpecialForm set +syn keyword FennelSpecialForm set-forcibly! +syn keyword FennelSpecialForm tset +syn keyword FennelSpecialForm values +syn keyword FennelSpecialForm var +syn keyword FennelSpecialForm when +syn keyword FennelSpecialForm while +syn keyword FennelSpecialForm ~= +syn keyword FennelSpecialForm λ + +" Lua keywords +syntax keyword LuaSpecialValue + \ _G + \ _VERSION + \ assert + \ collectgarbage + \ dofile + \ error + \ getmetatable + \ ipairs + \ load + \ loadfile + \ next + \ pairs + \ pcall + \ print + \ rawequal + \ rawget + \ rawlen + \ rawset + \ require + \ select + \ setmetatable + \ tonumber + \ tostring + \ type + \ xpcall + \ coroutine + \ coroutine.create + \ coroutine.isyieldable + \ coroutine.resume + \ coroutine.running + \ coroutine.status + \ coroutine.wrap + \ coroutine.yield + \ debug + \ debug.debug + \ debug.gethook + \ debug.getinfo + \ debug.getlocal + \ debug.getmetatable + \ debug.getregistry + \ debug.getupvalue + \ debug.getuservalue + \ debug.sethook + \ debug.setlocal + \ debug.setmetatable + \ debug.setupvalue + \ debug.setuservalue + \ debug.traceback + \ debug.upvalueid + \ debug.upvaluejoin + \ io + \ io.close + \ io.flush + \ io.input + \ io.lines + \ io.open + \ io.output + \ io.popen + \ io.read + \ io.stderr + \ io.stdin + \ io.stdout + \ io.tmpfile + \ io.type + \ io.write + \ math + \ math.abs + \ math.acos + \ math.asin + \ math.atan + \ math.ceil + \ math.cos + \ math.deg + \ math.exp + \ math.floor + \ math.fmod + \ math.huge + \ math.log + \ math.max + \ math.maxinteger + \ math.min + \ math.mininteger + \ math.modf + \ math.pi + \ math.rad + \ math.random + \ math.randomseed + \ math.sin + \ math.sqrt + \ math.tan + \ math.tointeger + \ math.type + \ math.ult + \ os + \ os.clock + \ os.date + \ os.difftime + \ os.execute + \ os.exit + \ os.getenv + \ os.remove + \ os.rename + \ os.setlocale + \ os.time + \ os.tmpname + \ package + \ package.config + \ package.cpath + \ package.loaded + \ package.loadlib + \ package.path + \ package.preload + \ package.searchers + \ package.searchpath + \ string + \ string.byte + \ string.char + \ string.dump + \ string.find + \ string.format + \ string.gmatch + \ string.gsub + \ string.len + \ string.lower + \ string.match + \ string.pack + \ string.packsize + \ string.rep + \ string.reverse + \ string.sub + \ string.unpack + \ string.upper + \ table + \ table.concat + \ table.insert + \ table.move + \ table.pack + \ table.remove + \ table.sort + \ table.unpack + \ utf8 + \ utf8.char + \ utf8.charpattern + \ utf8.codepoint + \ utf8.codes + \ utf8.len + \ utf8.offset + +" Fennel Symbols +let s:symcharnodig = '\!\$%\&\#\*\+\-./:<=>?A-Z^_a-z|\x80-\U10FFFF' +let s:symchar = '0-9' . s:symcharnodig +execute 'syn match FennelSymbol "\v<%([' . s:symcharnodig . '])%([' . s:symchar . '])*>"' +execute 'syn match FennelKeyword "\v<:%([' . s:symchar . '])*>"' +unlet! s:symchar s:symcharnodig + +syn match FennelQuote "`" +syn match FennelQuote "@" + +" FENNEL numbers +syntax match FennelNumber "\v\c<[-+]?\d*\.?\d*%([eE][-+]?\d+)?>" +syntax match FennelNumber "\v\c<[-+]?0x[0-9A-F]*\.?[0-9A-F]*>" + +" Grammar root +syntax cluster FennelTop contains=@Spell,FennelComment,FennelConstant,FennelQuote,FennelKeyword,LuaSpecialValue,FennelSymbol,FennelNumber,FennelString,FennelList,FennelArray,FennelTable,FennelSpecialForm,FennelBoolean + +syntax region FennelList matchgroup=FennelParen start="(" end=")" contains=@FennelTop fold +syntax region FennelArray matchgroup=FennelParen start="\[" end="]" contains=@FennelTop fold +syntax region FennelTable matchgroup=FennelParen start="{" end="}" contains=@FennelTop fold + +" Highlight superfluous closing parens, brackets and braces. +syntax match FennelError "]\|}\|)" + +syntax sync fromstart + +" Highlighting +hi def link FennelComment Comment +hi def link FennelSymbol Identifier +hi def link FennelNumber Number +hi def link FennelConstant Constant +hi def link FennelKeyword Keyword +hi def link FennelSpecialForm Special +hi def link LuaSpecialValue Special +hi def link FennelString String +hi def link FennelBuffer String +hi def link FennelStringDelimiter String +hi def link FennelBoolean Boolean + +hi def link FennelQuote SpecialChar +hi def link FennelParen Delimiter + +let b:current_syntax = "fennel" + +let &cpo = s:cpo_sav +unlet! s:cpo_sav + +endif |