2013-09-12 11:29:50 -04:00
|
|
|
|
" Vim indent file
|
|
|
|
|
" Language: Clojure
|
|
|
|
|
" Author: Meikel Brandmeyer <mb@kotka.de>
|
|
|
|
|
" URL: http://kotka.de/projects/clojure/vimclojure.html
|
|
|
|
|
"
|
|
|
|
|
" Maintainer: Sung Pae <self@sungpae.com>
|
|
|
|
|
" URL: https://github.com/guns/vim-clojure-static
|
|
|
|
|
" License: Same as Vim
|
|
|
|
|
" Last Change: %%RELEASE_DATE%%
|
|
|
|
|
|
|
|
|
|
" TODO: Indenting after multibyte characters is broken:
|
|
|
|
|
" (let [Δ (if foo
|
|
|
|
|
" bar ; Indent error
|
|
|
|
|
" baz)])
|
|
|
|
|
|
|
|
|
|
if exists("b:did_indent")
|
|
|
|
|
finish
|
|
|
|
|
endif
|
|
|
|
|
let b:did_indent = 1
|
|
|
|
|
|
|
|
|
|
let s:save_cpo = &cpo
|
|
|
|
|
set cpo&vim
|
|
|
|
|
|
|
|
|
|
let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2013-09-12 11:29:50 -04:00
|
|
|
|
function! s:SynIdName()
|
|
|
|
|
return synIDattr(synID(line("."), col("."), 0), "name")
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:CurrentChar()
|
|
|
|
|
return getline('.')[col('.')-1]
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:CurrentWord()
|
|
|
|
|
return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:IsParen()
|
|
|
|
|
return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
|
|
|
|
|
\ s:SynIdName() !~? '\vstring|regex|comment|character'
|
|
|
|
|
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:MatchesOne(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:MatchPairs(open, close, stopat)
|
|
|
|
|
" Stop only on vector and map [ resp. {. Ignore the ones in strings and
|
|
|
|
|
" comments.
|
|
|
|
|
if a:stopat == 0
|
|
|
|
|
let stopat = max([line(".") - g:clojure_maxlines, 0])
|
|
|
|
|
else
|
|
|
|
|
let stopat = a:stopat
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
|
|
|
|
|
return [pos[0], virtcol(pos)]
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:ClojureCheckForStringWorker()
|
|
|
|
|
" 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:SynIdName() !~? "string"
|
|
|
|
|
return -1
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" This will not work for a " in the first column...
|
|
|
|
|
if s:CurrentChar() == '"'
|
|
|
|
|
call cursor(0, col("$") - 2)
|
|
|
|
|
if s:SynIdName() !~? "string"
|
|
|
|
|
return -1
|
|
|
|
|
endif
|
|
|
|
|
if s:CurrentChar() != '\\'
|
|
|
|
|
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:CheckForString()
|
|
|
|
|
let pos = getpos('.')
|
|
|
|
|
try
|
|
|
|
|
let val = s:ClojureCheckForStringWorker()
|
|
|
|
|
finally
|
|
|
|
|
call setpos('.', pos)
|
|
|
|
|
endtry
|
|
|
|
|
return val
|
|
|
|
|
endfunction
|
|
|
|
|
|
2014-01-09 05:59:09 -05:00
|
|
|
|
function! s:StripNamespaceAndMacroChars(word)
|
|
|
|
|
return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
|
|
|
|
|
endfunction
|
|
|
|
|
|
2013-09-12 11:29:50 -04:00
|
|
|
|
function! s:ClojureIsMethodSpecialCaseWorker(position)
|
|
|
|
|
" Find the next enclosing form.
|
|
|
|
|
call search('\S', 'Wb')
|
|
|
|
|
|
|
|
|
|
" Special case: we are at a '(('.
|
|
|
|
|
if s:CurrentChar() == '('
|
|
|
|
|
return 0
|
|
|
|
|
endif
|
|
|
|
|
call cursor(a:position)
|
|
|
|
|
|
|
|
|
|
let nextParen = s:MatchPairs('(', ')', 0)
|
|
|
|
|
|
|
|
|
|
" Special case: we are now at toplevel.
|
|
|
|
|
if nextParen == [0, 0]
|
|
|
|
|
return 0
|
|
|
|
|
endif
|
|
|
|
|
call cursor(nextParen)
|
|
|
|
|
|
|
|
|
|
call search('\S', 'W')
|
2014-01-09 05:59:09 -05:00
|
|
|
|
let w = s:StripNamespaceAndMacroChars(s:CurrentWord())
|
|
|
|
|
if g:clojure_special_indent_words =~ '\<' . w . '\>'
|
2013-09-12 11:29:50 -04:00
|
|
|
|
return 1
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! s:IsMethodSpecialCase(position)
|
|
|
|
|
let pos = getpos('.')
|
|
|
|
|
try
|
|
|
|
|
let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
|
|
|
|
|
finally
|
|
|
|
|
call setpos('.', pos)
|
|
|
|
|
endtry
|
|
|
|
|
return val
|
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
function! GetClojureIndent()
|
|
|
|
|
" Get rid of special case.
|
|
|
|
|
if line(".") == 1
|
|
|
|
|
return 0
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" We have to apply some heuristics here to figure out, whether to use
|
|
|
|
|
" normal lisp indenting or not.
|
|
|
|
|
let i = s:CheckForString()
|
|
|
|
|
if i > -1
|
|
|
|
|
return i + !!g:clojure_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:MatchPairs('(', ')', 0)
|
|
|
|
|
let bracket = s:MatchPairs('\[', '\]', paren[0])
|
|
|
|
|
let curly = s:MatchPairs('{', '}', 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[1]
|
|
|
|
|
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[1]
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" There are neither { nor [ nor (, ie. we are at the toplevel.
|
|
|
|
|
if paren == [0, 0]
|
|
|
|
|
return 0
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
if s:IsMethodSpecialCase(paren)
|
|
|
|
|
return paren[1] + &shiftwidth - 1
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" In case we are at the last character, we use the paren position.
|
|
|
|
|
if col("$") - 1 == paren[1]
|
|
|
|
|
return paren[1]
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" In case after the paren is a whitespace, we search for the next word.
|
|
|
|
|
call cursor(0, col('.') + 1)
|
|
|
|
|
if s:CurrentChar() == ' '
|
|
|
|
|
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[1]
|
|
|
|
|
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:CurrentWord()
|
|
|
|
|
if stridx('([{', w[0]) > -1
|
|
|
|
|
return paren[1]
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
" Test words without namespace qualifiers and leading reader macro
|
|
|
|
|
" metacharacters.
|
|
|
|
|
"
|
|
|
|
|
" e.g. clojure.core/defn and #'defn should both indent like defn.
|
2014-01-09 05:59:09 -05:00
|
|
|
|
let ww = s:StripNamespaceAndMacroChars(w)
|
2013-09-12 11:29:50 -04:00
|
|
|
|
|
|
|
|
|
if &lispwords =~ '\V\<' . ww . '\>'
|
|
|
|
|
return paren[1] + &shiftwidth - 1
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
if g:clojure_fuzzy_indent
|
|
|
|
|
\ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
|
|
|
|
|
\ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
|
|
|
|
|
return paren[1] + &shiftwidth - 1
|
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
call search('\v\_s', 'cW')
|
|
|
|
|
call search('\v\S', 'W')
|
|
|
|
|
if paren[0] < line(".")
|
2013-12-25 21:34:22 -05:00
|
|
|
|
return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
|
2013-09-12 11:29:50 -04:00
|
|
|
|
endif
|
|
|
|
|
|
|
|
|
|
call search('\v\S', 'bW')
|
|
|
|
|
return virtcol(".") + 1
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
" Specially indented symbols from clojure.core and clojure.test.
|
|
|
|
|
"
|
|
|
|
|
" Clojure symbols are indented in the defn style when they:
|
|
|
|
|
"
|
|
|
|
|
" * Define vars and anonymous functions
|
|
|
|
|
" * Create new lexical scopes or scopes with altered environments
|
|
|
|
|
" * Create conditional branches from a predicate function or value
|
|
|
|
|
"
|
|
|
|
|
" The arglists for these functions are generally in the form of [x & body];
|
|
|
|
|
" Functions that accept a flat list of forms do not treat the first argument
|
|
|
|
|
" specially and hence are not indented specially.
|
|
|
|
|
|
|
|
|
|
" Definitions
|
|
|
|
|
setlocal lispwords=
|
|
|
|
|
setlocal lispwords+=bound-fn
|
|
|
|
|
setlocal lispwords+=def
|
|
|
|
|
setlocal lispwords+=definline
|
|
|
|
|
setlocal lispwords+=definterface
|
|
|
|
|
setlocal lispwords+=defmacro
|
|
|
|
|
setlocal lispwords+=defmethod
|
|
|
|
|
setlocal lispwords+=defmulti
|
|
|
|
|
setlocal lispwords+=defn
|
|
|
|
|
setlocal lispwords+=defn-
|
|
|
|
|
setlocal lispwords+=defonce
|
|
|
|
|
setlocal lispwords+=defprotocol
|
|
|
|
|
setlocal lispwords+=defrecord
|
|
|
|
|
setlocal lispwords+=defstruct
|
|
|
|
|
setlocal lispwords+=deftest " clojure.test
|
|
|
|
|
setlocal lispwords+=deftest- " clojure.test
|
|
|
|
|
setlocal lispwords+=deftype
|
|
|
|
|
setlocal lispwords+=extend
|
|
|
|
|
setlocal lispwords+=extend-protocol
|
|
|
|
|
setlocal lispwords+=extend-type
|
|
|
|
|
setlocal lispwords+=fn
|
|
|
|
|
setlocal lispwords+=ns
|
|
|
|
|
setlocal lispwords+=proxy
|
|
|
|
|
setlocal lispwords+=reify
|
|
|
|
|
setlocal lispwords+=set-test " clojure.test
|
|
|
|
|
|
|
|
|
|
" Binding forms
|
|
|
|
|
setlocal lispwords+=as->
|
|
|
|
|
setlocal lispwords+=binding
|
|
|
|
|
setlocal lispwords+=doall
|
|
|
|
|
setlocal lispwords+=dorun
|
|
|
|
|
setlocal lispwords+=doseq
|
|
|
|
|
setlocal lispwords+=dotimes
|
|
|
|
|
setlocal lispwords+=doto
|
|
|
|
|
setlocal lispwords+=for
|
|
|
|
|
setlocal lispwords+=if-let
|
|
|
|
|
setlocal lispwords+=let
|
|
|
|
|
setlocal lispwords+=letfn
|
|
|
|
|
setlocal lispwords+=locking
|
|
|
|
|
setlocal lispwords+=loop
|
|
|
|
|
setlocal lispwords+=testing " clojure.test
|
|
|
|
|
setlocal lispwords+=when-first
|
|
|
|
|
setlocal lispwords+=when-let
|
|
|
|
|
setlocal lispwords+=with-bindings
|
|
|
|
|
setlocal lispwords+=with-in-str
|
|
|
|
|
setlocal lispwords+=with-local-vars
|
|
|
|
|
setlocal lispwords+=with-open
|
|
|
|
|
setlocal lispwords+=with-precision
|
|
|
|
|
setlocal lispwords+=with-redefs
|
|
|
|
|
setlocal lispwords+=with-redefs-fn
|
|
|
|
|
setlocal lispwords+=with-test " clojure.test
|
|
|
|
|
|
|
|
|
|
" Conditional branching
|
|
|
|
|
setlocal lispwords+=case
|
|
|
|
|
setlocal lispwords+=cond->
|
|
|
|
|
setlocal lispwords+=cond->>
|
|
|
|
|
setlocal lispwords+=condp
|
|
|
|
|
setlocal lispwords+=if
|
|
|
|
|
setlocal lispwords+=if-not
|
|
|
|
|
setlocal lispwords+=when
|
|
|
|
|
setlocal lispwords+=when-not
|
|
|
|
|
setlocal lispwords+=while
|
|
|
|
|
|
|
|
|
|
" Exception handling
|
|
|
|
|
setlocal lispwords+=catch
|
|
|
|
|
|
|
|
|
|
let &cpo = s:save_cpo
|
|
|
|
|
unlet! s:save_cpo
|
|
|
|
|
|
|
|
|
|
" vim:sts=8:sw=8:ts=8:noet
|