Add fennel support, closes #514

This commit is contained in:
Adam Stankiewicz
2020-08-18 23:09:35 +02:00
parent ee4f51587d
commit e521ba3ae2
7 changed files with 655 additions and 1 deletions

View File

@@ -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)

1
build
View File

@@ -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

View File

@@ -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')

View File

@@ -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

30
ftplugin/fennel.vim Normal file
View File

@@ -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

330
indent/fennel.vim Normal file
View File

@@ -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

287
syntax/fennel.vim Normal file
View File

@@ -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