vim-polyglot/indent/clojure.vim

408 lines
11 KiB
VimL
Raw Normal View History

if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'clojure') != -1
finish
endif
2013-09-12 11:29:50 -04:00
" Vim indent file
2016-07-19 04:09:54 -04:00
" Language: Clojure
" Author: Meikel Brandmeyer <mb@kotka.de>
" URL: http://kotka.de/projects/clojure/vimclojure.html
2013-09-12 11:29:50 -04:00
"
2016-07-19 04:09:54 -04:00
" Maintainer: Sung Pae <self@sungpae.com>
" URL: https://github.com/guns/vim-clojure-static
" License: Same as Vim
" Last Change: %%RELEASE_DATE%%
2013-09-12 11:29:50 -04:00
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
let s:save_cpo = &cpo
set cpo&vim
2014-02-28 18:15:51 -05:00
let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
2013-09-12 11:29:50 -04:00
setlocal noautoindent nosmartindent
setlocal softtabstop=2 shiftwidth=2 expandtab
setlocal indentkeys=!,o,O
if exists("*searchpairpos")
if !exists('g:clojure_maxlines')
let g:clojure_maxlines = 100
endif
if !exists('g:clojure_fuzzy_indent')
let g:clojure_fuzzy_indent = 1
endif
if !exists('g:clojure_fuzzy_indent_patterns')
let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
endif
if !exists('g:clojure_fuzzy_indent_blacklist')
let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$']
endif
if !exists('g:clojure_special_indent_words')
let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn'
endif
if !exists('g:clojure_align_multiline_strings')
let g:clojure_align_multiline_strings = 0
endif
2013-12-25 21:34:22 -05:00
if !exists('g:clojure_align_subforms')
let g:clojure_align_subforms = 0
endif
2014-04-14 19:05:44 -04:00
function! s:syn_id_name()
2013-09-12 11:29:50 -04:00
return synIDattr(synID(line("."), col("."), 0), "name")
endfunction
2014-04-14 19:05:44 -04:00
function! s:ignored_region()
return s:syn_id_name() =~? '\vstring|regex|comment|character'
endfunction
function! s:current_char()
2013-09-12 11:29:50 -04:00
return getline('.')[col('.')-1]
endfunction
2014-04-14 19:05:44 -04:00
function! s:current_word()
2013-09-12 11:29:50 -04:00
return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
endfunction
2014-04-14 19:05:44 -04:00
function! s:is_paren()
return s:current_char() =~# '\v[\(\)\[\]\{\}]' && !s:ignored_region()
2013-09-12 11:29:50 -04:00
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.
2014-04-14 19:05:44 -04:00
function! s:match_one(patterns, string)
2013-09-12 11:29:50 -04:00
let list = type(a:patterns) == type([])
2016-07-19 04:09:54 -04:00
\ ? a:patterns
\ : map(split(a:patterns, ','), '"^" . v:val . "$"')
2013-09-12 11:29:50 -04:00
for pat in list
2014-02-28 18:15:51 -05:00
if a:string =~# pat | return 1 | endif
2013-09-12 11:29:50 -04:00
endfor
endfunction
2014-04-14 19:05:44 -04:00
function! s:match_pairs(open, close, stopat)
2013-09-12 11:29:50 -04:00
" Stop only on vector and map [ resp. {. Ignore the ones in strings and
" comments.
2016-12-20 14:57:20 -05:00
if a:stopat == 0 && g:clojure_maxlines > 0
2013-09-12 11:29:50 -04:00
let stopat = max([line(".") - g:clojure_maxlines, 0])
else
let stopat = a:stopat
endif
2014-04-14 19:05:44 -04:00
let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:is_paren()", stopat)
2014-06-08 07:18:33 -04:00
return [pos[0], col(pos)]
2013-09-12 11:29:50 -04:00
endfunction
2014-04-14 19:05:44 -04:00
function! s:clojure_check_for_string_worker()
2013-09-12 11:29:50 -04:00
" 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)
2014-04-14 19:05:44 -04:00
if s:syn_id_name() !~? "string"
2013-09-12 11:29:50 -04:00
return -1
endif
" This will not work for a " in the first column...
2014-04-14 19:05:44 -04:00
if s:current_char() == '"'
2013-09-12 11:29:50 -04:00
call cursor(0, col("$") - 2)
2014-04-14 19:05:44 -04:00
if s:syn_id_name() !~? "string"
2013-09-12 11:29:50 -04:00
return -1
endif
2016-12-20 14:57:20 -05:00
if s:current_char() != '\'
2013-09-12 11:29:50 -04:00
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
2014-04-14 19:05:44 -04:00
function! s:check_for_string()
2013-09-12 11:29:50 -04:00
let pos = getpos('.')
try
2014-04-14 19:05:44 -04:00
let val = s:clojure_check_for_string_worker()
2013-09-12 11:29:50 -04:00
finally
call setpos('.', pos)
endtry
return val
endfunction
2014-04-14 19:05:44 -04:00
function! s:strip_namespace_and_macro_chars(word)
2014-01-09 05:59:09 -05:00
return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
endfunction
2014-04-14 19:05:44 -04:00
function! s:clojure_is_method_special_case_worker(position)
2013-09-12 11:29:50 -04:00
" Find the next enclosing form.
call search('\S', 'Wb')
" Special case: we are at a '(('.
2014-04-14 19:05:44 -04:00
if s:current_char() == '('
2013-09-12 11:29:50 -04:00
return 0
endif
call cursor(a:position)
2014-04-14 19:05:44 -04:00
let next_paren = s:match_pairs('(', ')', 0)
2013-09-12 11:29:50 -04:00
" Special case: we are now at toplevel.
2014-04-14 19:05:44 -04:00
if next_paren == [0, 0]
2013-09-12 11:29:50 -04:00
return 0
endif
2014-04-14 19:05:44 -04:00
call cursor(next_paren)
2013-09-12 11:29:50 -04:00
call search('\S', 'W')
2014-04-14 19:05:44 -04:00
let w = s:strip_namespace_and_macro_chars(s:current_word())
2014-02-28 18:15:51 -05:00
if g:clojure_special_indent_words =~# '\V\<' . w . '\>'
2013-09-12 11:29:50 -04:00
return 1
endif
return 0
endfunction
2014-04-14 19:05:44 -04:00
function! s:is_method_special_case(position)
2013-09-12 11:29:50 -04:00
let pos = getpos('.')
try
2014-04-14 19:05:44 -04:00
let val = s:clojure_is_method_special_case_worker(a:position)
2013-09-12 11:29:50 -04:00
finally
call setpos('.', pos)
endtry
return val
endfunction
2016-07-19 04:09:54 -04:00
" Check if form is a reader conditional, that is, it is prefixed by #?
" or @#?
function! s:is_reader_conditional_special_case(position)
2017-11-19 15:26:59 -05:00
return getline(a:position[0])[a:position[1] - 3 : a:position[1] - 2] == "#?"
2016-07-19 04:09:54 -04:00
endfunction
2014-04-14 19:05:44 -04:00
" 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:clojure_indent_pos()
2013-09-12 11:29:50 -04:00
" Get rid of special case.
if line(".") == 1
2014-04-14 19:05:44 -04:00
return [0, 0]
2013-09-12 11:29:50 -04:00
endif
" We have to apply some heuristics here to figure out, whether to use
" normal lisp indenting or not.
2014-04-14 19:05:44 -04:00
let i = s:check_for_string()
2013-09-12 11:29:50 -04:00
if i > -1
2014-04-14 19:05:44 -04:00
return [0, i + !!g:clojure_align_multiline_strings]
2013-09-12 11:29:50 -04:00
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 {.
2014-04-14 19:05:44 -04:00
let paren = s:match_pairs('(', ')', 0)
let bracket = s:match_pairs('\[', '\]', paren[0])
let curly = s:match_pairs('{', '}', bracket[0])
2013-09-12 11:29:50 -04:00
" 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]
2014-04-14 19:05:44 -04:00
return curly
2013-09-12 11:29:50 -04:00
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]
2014-04-14 19:05:44 -04:00
return bracket
2013-09-12 11:29:50 -04:00
endif
" There are neither { nor [ nor (, ie. we are at the toplevel.
if paren == [0, 0]
2014-04-14 19:05:44 -04:00
return paren
2013-09-12 11:29:50 -04:00
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:clojure_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)
2014-04-14 19:05:44 -04:00
if s:is_method_special_case(paren)
return [paren[0], paren[1] + &shiftwidth - 1]
2013-09-12 11:29:50 -04:00
endif
2016-07-19 04:09:54 -04:00
if s:is_reader_conditional_special_case(paren)
return paren
endif
2013-09-12 11:29:50 -04:00
" In case we are at the last character, we use the paren position.
if col("$") - 1 == paren[1]
2014-04-14 19:05:44 -04:00
return paren
2013-09-12 11:29:50 -04:00
endif
" In case after the paren is a whitespace, we search for the next word.
call cursor(0, col('.') + 1)
2014-04-14 19:05:44 -04:00
if s:current_char() == ' '
2013-09-12 11:29:50 -04:00
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]
2014-04-14 19:05:44 -04:00
return paren
2013-09-12 11:29:50 -04:00
endif
" We still have to check, whether the keyword starts with a (, [ or {.
" In that case we use the ( position for indent.
2014-04-14 19:05:44 -04:00
let w = s:current_word()
if s:bracket_type(w[0]) == 1
return paren
2013-09-12 11:29:50 -04:00
endif
2017-11-19 15:26:59 -05:00
" If the keyword begins with #, check if it is an anonymous
" function or set, in which case we indent by the shiftwidth
" (minus one if g:clojure_align_subforms = 1), or if it is
" ignored, in which case we use the ( position for indent.
if w[0] == "#"
" TODO: Handle #=() and other rare reader invocations?
if w[1] == '(' || w[1] == '{'
return [paren[0], paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)]
elseif w[1] == '_'
return paren
endif
endif
2013-09-12 11:29:50 -04:00
" Test words without namespace qualifiers and leading reader macro
" metacharacters.
"
" e.g. clojure.core/defn and #'defn should both indent like defn.
2014-04-14 19:05:44 -04:00
let ww = s:strip_namespace_and_macro_chars(w)
2013-09-12 11:29:50 -04:00
2014-02-28 18:15:51 -05:00
if &lispwords =~# '\V\<' . ww . '\>'
2014-04-14 19:05:44 -04:00
return [paren[0], paren[1] + &shiftwidth - 1]
2013-09-12 11:29:50 -04:00
endif
if g:clojure_fuzzy_indent
2014-04-14 19:05:44 -04:00
\ && !s:match_one(g:clojure_fuzzy_indent_blacklist, ww)
\ && s:match_one(g:clojure_fuzzy_indent_patterns, ww)
return [paren[0], paren[1] + &shiftwidth - 1]
2013-09-12 11:29:50 -04:00
endif
call search('\v\_s', 'cW')
call search('\v\S', 'W')
if paren[0] < line(".")
2014-04-14 19:05:44 -04:00
return [paren[0], paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)]
2013-09-12 11:29:50 -04:00
endif
call search('\v\S', 'bW')
2014-06-08 07:18:33 -04:00
return [line('.'), col('.') + 1]
2014-04-14 19:05:44 -04:00
endfunction
function! GetClojureIndent()
let lnum = line('.')
2014-06-08 07:18:33 -04:00
let orig_lnum = lnum
let orig_col = col('.')
2014-04-14 19:05:44 -04:00
let [opening_lnum, indent] = s:clojure_indent_pos()
2014-06-08 07:18:33 -04:00
" Account for multibyte characters
if opening_lnum > 0
let indent -= indent - virtcol([opening_lnum, indent])
endif
2014-04-14 19:05:44 -04:00
" Return if there are no previous lines to inherit from
if opening_lnum < 1 || opening_lnum >= lnum - 1
2014-06-08 07:18:33 -04:00
call cursor(orig_lnum, orig_col)
2014-04-14 19:05:44 -04:00
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|regex'
2014-06-08 07:18:33 -04:00
call cursor(orig_lnum, orig_col)
2014-04-14 19:05:44 -04:00
return indent(lnum)
endif
endif
endwhile
2014-06-08 07:18:33 -04:00
call cursor(orig_lnum, orig_col)
2014-04-14 19:05:44 -04:00
return indent
2013-09-12 11:29:50 -04:00
endfunction
setlocal indentexpr=GetClojureIndent()
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
" vim:sts=8:sw=8:ts=8:noet