|
| 1 | +" Vim indent file |
| 2 | +" Language: Clojure |
| 3 | +" Maintainer: Meikel Brandmeyer <mb@kotka.de> |
| 4 | +" URL: http://kotka.de/projects/clojure/vimclojure.html |
| 5 | + |
| 6 | +" Only load this indent file when no other was loaded. |
| 7 | +if exists("b:did_indent") |
| 8 | + finish |
| 9 | +endif |
| 10 | +let b:did_indent = 1 |
| 11 | + |
| 12 | +let s:save_cpo = &cpo |
| 13 | +set cpo&vim |
| 14 | + |
| 15 | +let b:undo_indent = "setlocal ai< si< lw< et< sts< sw< inde< indk<" |
| 16 | + |
| 17 | +setlocal noautoindent expandtab nosmartindent |
| 18 | + |
| 19 | +setlocal softtabstop=2 |
| 20 | +setlocal shiftwidth=2 |
| 21 | + |
| 22 | +setlocal indentkeys=!,o,O |
| 23 | + |
| 24 | +if exists("*searchpairpos") |
| 25 | + |
| 26 | +function! s:MatchPairs(open, close, stopat) |
| 27 | + " Stop only on vector and map [ resp. {. Ignore the ones in strings and |
| 28 | + " comments. |
| 29 | + if a:stopat == 0 |
| 30 | + let stopat = max([line(".") - g:vimclojure#SearchThreshold, 0]) |
| 31 | + else |
| 32 | + let stopat = a:stopat |
| 33 | + endif |
| 34 | + |
| 35 | + let pos = searchpairpos(a:open, '', a:close, 'bWn', |
| 36 | + \ 'vimclojure#util#SynIdName() !~ "clojureParen\\d"', |
| 37 | + \ stopat) |
| 38 | + return [ pos[0], virtcol(pos) ] |
| 39 | +endfunction |
| 40 | + |
| 41 | +function! ClojureCheckForStringWorker() dict |
| 42 | + " Check whether there is the last character of the previous line is |
| 43 | + " highlighted as a string. If so, we check whether it's a ". In this |
| 44 | + " case we have to check also the previous character. The " might be the |
| 45 | + " closing one. In case the we are still in the string, we search for the |
| 46 | + " opening ". If this is not found we take the indent of the line. |
| 47 | + let nb = prevnonblank(v:lnum - 1) |
| 48 | + |
| 49 | + if nb == 0 |
| 50 | + return -1 |
| 51 | + endif |
| 52 | + |
| 53 | + call cursor(nb, 0) |
| 54 | + call cursor(0, col("$") - 1) |
| 55 | + if vimclojure#util#SynIdName() != "clojureString" |
| 56 | + return -1 |
| 57 | + endif |
| 58 | + |
| 59 | + " This will not work for a " in the first column... |
| 60 | + if vimclojure#util#Yank('l', 'normal! "lyl') == '"' |
| 61 | + call cursor(0, col("$") - 2) |
| 62 | + if vimclojure#util#SynIdName() != "clojureString" |
| 63 | + return -1 |
| 64 | + endif |
| 65 | + if vimclojure#util#Yank('l', 'normal! "lyl') != '\\' |
| 66 | + return -1 |
| 67 | + endif |
| 68 | + call cursor(0, col("$") - 1) |
| 69 | + endif |
| 70 | + |
| 71 | + let p = searchpos('\(^\|[^\\]\)\zs"', 'bW') |
| 72 | + |
| 73 | + if p != [0, 0] |
| 74 | + return p[1] - 1 |
| 75 | + endif |
| 76 | + |
| 77 | + return indent(".") |
| 78 | +endfunction |
| 79 | + |
| 80 | +function! s:CheckForString() |
| 81 | + return vimclojure#util#WithSavedPosition({ |
| 82 | + \ 'f' : function("ClojureCheckForStringWorker") |
| 83 | + \ }) |
| 84 | +endfunction |
| 85 | + |
| 86 | +function! ClojureIsMethodSpecialCaseWorker() dict |
| 87 | + " Find the next enclosing form. |
| 88 | + call vimclojure#util#MoveBackward() |
| 89 | + |
| 90 | + " Special case: we are at a '(('. |
| 91 | + if vimclojure#util#Yank('l', 'normal! "lyl') == '(' |
| 92 | + return 0 |
| 93 | + endif |
| 94 | + call cursor(self.pos) |
| 95 | + |
| 96 | + let nextParen = s:MatchPairs('(', ')', 0) |
| 97 | + |
| 98 | + " Special case: we are now at toplevel. |
| 99 | + if nextParen == [0, 0] |
| 100 | + return 0 |
| 101 | + endif |
| 102 | + call cursor(nextParen) |
| 103 | + |
| 104 | + call vimclojure#util#MoveForward() |
| 105 | + let keyword = vimclojure#util#Yank('l', 'normal! "lye') |
| 106 | + if index([ 'deftype', 'defrecord', 'reify', 'proxy', |
| 107 | + \ 'extend-type', 'extend-protocol', |
| 108 | + \ 'letfn' ], keyword) >= 0 |
| 109 | + return 1 |
| 110 | + endif |
| 111 | + |
| 112 | + return 0 |
| 113 | +endfunction |
| 114 | + |
| 115 | +function! s:IsMethodSpecialCase(position) |
| 116 | + let closure = { |
| 117 | + \ 'pos': a:position, |
| 118 | + \ 'f' : function("ClojureIsMethodSpecialCaseWorker") |
| 119 | + \ } |
| 120 | + |
| 121 | + return vimclojure#util#WithSavedPosition(closure) |
| 122 | +endfunction |
| 123 | + |
| 124 | +function! GetClojureIndent() |
| 125 | + " Get rid of special case. |
| 126 | + if line(".") == 1 |
| 127 | + return 0 |
| 128 | + endif |
| 129 | + |
| 130 | + " We have to apply some heuristics here to figure out, whether to use |
| 131 | + " normal lisp indenting or not. |
| 132 | + let i = s:CheckForString() |
| 133 | + if i > -1 |
| 134 | + return i |
| 135 | + endif |
| 136 | + |
| 137 | + call cursor(0, 1) |
| 138 | + |
| 139 | + " Find the next enclosing [ or {. We can limit the second search |
| 140 | + " to the line, where the [ was found. If no [ was there this is |
| 141 | + " zero and we search for an enclosing {. |
| 142 | + let paren = s:MatchPairs('(', ')', 0) |
| 143 | + let bracket = s:MatchPairs('\[', '\]', paren[0]) |
| 144 | + let curly = s:MatchPairs('{', '}', bracket[0]) |
| 145 | + |
| 146 | + " In case the curly brace is on a line later then the [ or - in |
| 147 | + " case they are on the same line - in a higher column, we take the |
| 148 | + " curly indent. |
| 149 | + if curly[0] > bracket[0] || curly[1] > bracket[1] |
| 150 | + if curly[0] > paren[0] || curly[1] > paren[1] |
| 151 | + return curly[1] |
| 152 | + endif |
| 153 | + endif |
| 154 | + |
| 155 | + " If the curly was not chosen, we take the bracket indent - if |
| 156 | + " there was one. |
| 157 | + if bracket[0] > paren[0] || bracket[1] > paren[1] |
| 158 | + return bracket[1] |
| 159 | + endif |
| 160 | + |
| 161 | + " There are neither { nor [ nor (, ie. we are at the toplevel. |
| 162 | + if paren == [0, 0] |
| 163 | + return 0 |
| 164 | + endif |
| 165 | + |
| 166 | + " Now we have to reimplement lispindent. This is surprisingly easy, as |
| 167 | + " soon as one has access to syntax items. |
| 168 | + " |
| 169 | + " - Check whether we are in a special position after deftype, defrecord, |
| 170 | + " reify, proxy or letfn. These are special cases. |
| 171 | + " - Get the next keyword after the (. |
| 172 | + " - If its first character is also a (, we have another sexp and align |
| 173 | + " one column to the right of the unmatched (. |
| 174 | + " - In case it is in lispwords, we indent the next line to the column of |
| 175 | + " the ( + sw. |
| 176 | + " - If not, we check whether it is last word in the line. In that case |
| 177 | + " we again use ( + sw for indent. |
| 178 | + " - In any other case we use the column of the end of the word + 2. |
| 179 | + call cursor(paren) |
| 180 | + |
| 181 | + if s:IsMethodSpecialCase(paren) |
| 182 | + return paren[1] + &shiftwidth - 1 |
| 183 | + endif |
| 184 | + |
| 185 | + " In case we are at the last character, we use the paren position. |
| 186 | + if col("$") - 1 == paren[1] |
| 187 | + return paren[1] |
| 188 | + endif |
| 189 | + |
| 190 | + " In case after the paren is a whitespace, we search for the next word. |
| 191 | + normal! l |
| 192 | + if vimclojure#util#Yank('l', 'normal! "lyl') == ' ' |
| 193 | + normal! w |
| 194 | + endif |
| 195 | + |
| 196 | + " If we moved to another line, there is no word after the (. We |
| 197 | + " use the ( position for indent. |
| 198 | + if line(".") > paren[0] |
| 199 | + return paren[1] |
| 200 | + endif |
| 201 | + |
| 202 | + " We still have to check, whether the keyword starts with a (, [ or {. |
| 203 | + " In that case we use the ( position for indent. |
| 204 | + let w = vimclojure#util#Yank('l', 'normal! "lye') |
| 205 | + if stridx('([{', w[0]) > 0 |
| 206 | + return paren[1] |
| 207 | + endif |
| 208 | + |
| 209 | + if &lispwords =~ '\<' . w . '\>' |
| 210 | + return paren[1] + &shiftwidth - 1 |
| 211 | + endif |
| 212 | + |
| 213 | + " XXX: Slight glitch here with special cases. However it's only |
| 214 | + " a heureustic. Offline we can't do more. |
| 215 | + if g:vimclojure#FuzzyIndent |
| 216 | + \ && w != 'with-meta' |
| 217 | + \ && w != 'clojure.core/with-meta' |
| 218 | + for pat in split(g:vimclojure#FuzzyIndentPatterns, ",") |
| 219 | + if w =~ '\(^\|/\)' . pat . '$' |
| 220 | + \ && w !~ '\(^\|/\)' . pat . '\*$' |
| 221 | + \ && w !~ '\(^\|/\)' . pat . '-fn$' |
| 222 | + return paren[1] + &shiftwidth - 1 |
| 223 | + endif |
| 224 | + endfor |
| 225 | + endif |
| 226 | + |
| 227 | + normal! w |
| 228 | + if paren[0] < line(".") |
| 229 | + return paren[1] + &shiftwidth - 1 |
| 230 | + endif |
| 231 | + |
| 232 | + normal! ge |
| 233 | + return virtcol(".") + 1 |
| 234 | +endfunction |
| 235 | + |
| 236 | +setlocal indentexpr=GetClojureIndent() |
| 237 | + |
| 238 | +else |
| 239 | + |
| 240 | + " In case we have searchpairpos not available we fall back to |
| 241 | + " normal lisp indenting. |
| 242 | + setlocal indentexpr= |
| 243 | + setlocal lisp |
| 244 | + let b:undo_indent .= " lisp<" |
| 245 | + |
| 246 | +endif |
| 247 | + |
| 248 | +" Defintions: |
| 249 | +setlocal lispwords=def,def-,defn,defn-,defmacro,defmacro-,defmethod,defmulti |
| 250 | +setlocal lispwords+=defonce,defvar,defvar-,defunbound,let,fn,letfn,binding,proxy |
| 251 | +setlocal lispwords+=defnk,definterface,defprotocol,deftype,defrecord,reify |
| 252 | +setlocal lispwords+=extend,extend-protocol,extend-type,bound-fn |
| 253 | + |
| 254 | +" Conditionals and Loops: |
| 255 | +setlocal lispwords+=if,if-not,if-let,when,when-not,when-let,when-first |
| 256 | +setlocal lispwords+=condp,case,loop,dotimes,for,while |
| 257 | + |
| 258 | +" Blocks: |
| 259 | +setlocal lispwords+=do,doto,try,catch,locking,with-in-str,with-out-str,with-open |
| 260 | +setlocal lispwords+=dosync,with-local-vars,doseq,dorun,doall,->,->>,future |
| 261 | +setlocal lispwords+=with-bindings |
| 262 | + |
| 263 | +" Namespaces: |
| 264 | +setlocal lispwords+=ns,clojure.core/ns |
| 265 | + |
| 266 | +" Java Classes: |
| 267 | +setlocal lispwords+=gen-class,gen-interface |
| 268 | + |
| 269 | +let &cpo = s:save_cpo |
0 commit comments