| " 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: 30 January 2013 |
| |
| " Only load this indent file when no other was loaded. |
| 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 |
| |
| 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|comment' |
| 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:SavePosition() |
| let [ _b, l, c, _o ] = getpos(".") |
| let b = bufnr("%") |
| return [b, l, c] |
| endfunction |
| |
| function! s:RestorePosition(value) |
| let [b, l, c] = a:value |
| if bufnr("%") != b |
| execute b "buffer!" |
| endif |
| call setpos(".", [0, l, c, 0]) |
| 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 = s:SavePosition() |
| try |
| let val = s:ClojureCheckForStringWorker() |
| finally |
| call s:RestorePosition(pos) |
| endtry |
| return val |
| endfunction |
| |
| 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') |
| if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>' |
| return 1 |
| endif |
| |
| return 0 |
| endfunction |
| |
| function! s:IsMethodSpecialCase(position) |
| let pos = s:SavePosition() |
| try |
| let val = s:ClojureIsMethodSpecialCaseWorker(a:position) |
| finally |
| call s:RestorePosition(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. |
| normal! l |
| if s:CurrentChar() == ' ' |
| normal! 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. |
| let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '') |
| |
| 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 |
| |
| normal! W |
| if paren[0] < line(".") |
| return paren[1] + &shiftwidth - 1 |
| endif |
| |
| normal! ge |
| 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 |
| setlocal lispwords+=try " For aesthetics when enclosing single line |
| |
| let &cpo = s:save_cpo |
| unlet! s:save_cpo |
| |
| " vim:sts=4 sw=4 et: |