]> git.r.bdr.sh - rbdr/dotfiles/blame_incremental - vim/indent/coffee.vim
Add new provisioning formulas
[rbdr/dotfiles] / vim / indent / coffee.vim
... / ...
CommitLineData
1" Language: CoffeeScript
2" Maintainer: Mick Koch <kchmck@gmail.com>
3" URL: http://github.com/kchmck/vim-coffee-script
4" License: WTFPL
5
6if exists("b:did_indent")
7 finish
8endif
9
10let b:did_indent = 1
11
12setlocal autoindent
13setlocal indentexpr=GetCoffeeIndent(v:lnum)
14" Make sure GetCoffeeIndent is run when these are typed so they can be
15" indented or outdented.
16setlocal indentkeys+=0],0),0.,=else,=when,=catch,=finally
17
18" Only define the function once.
19if exists("*GetCoffeeIndent")
20 finish
21endif
22
23" Keywords to indent after
24let s:INDENT_AFTER_KEYWORD = '^\%(if\|unless\|else\|for\|while\|until\|'
25\ . 'loop\|switch\|when\|try\|catch\|finally\|'
26\ . 'class\)\>'
27
28" Operators to indent after
29let s:INDENT_AFTER_OPERATOR = '\%([([{:=]\|[-=]>\)$'
30
31" Keywords and operators that continue a line
32let s:CONTINUATION = '\<\%(is\|isnt\|and\|or\)\>$'
33\ . '\|'
34\ . '\%(-\@<!-\|+\@<!+\|<\|[-=]\@<!>\|\*\|/\@<!/\|%\||\|'
35\ . '&\|,\|\.\@<!\.\)$'
36
37" Operators that block continuation indenting
38let s:CONTINUATION_BLOCK = '[([{:=]$'
39
40" A continuation dot access
41let s:DOT_ACCESS = '^\.'
42
43" Keywords to outdent after
44let s:OUTDENT_AFTER = '^\%(return\|break\|continue\|throw\)\>'
45
46" A compound assignment like `... = if ...`
47let s:COMPOUND_ASSIGNMENT = '[:=]\s*\%(if\|unless\|for\|while\|until\|'
48\ . 'switch\|try\|class\)\>'
49
50" A postfix condition like `return ... if ...`.
51let s:POSTFIX_CONDITION = '\S\s\+\zs\<\%(if\|unless\)\>'
52
53" A single-line else statement like `else ...` but not `else if ...
54let s:SINGLE_LINE_ELSE = '^else\s\+\%(\<\%(if\|unless\)\>\)\@!'
55
56" Max lines to look back for a match
57let s:MAX_LOOKBACK = 50
58
59" Syntax names for strings
60let s:SYNTAX_STRING = 'coffee\%(String\|AssignString\|Embed\|Regex\|Heregex\|'
61\ . 'Heredoc\)'
62
63" Syntax names for comments
64let s:SYNTAX_COMMENT = 'coffee\%(Comment\|BlockComment\|HeregexComment\)'
65
66" Syntax names for strings and comments
67let s:SYNTAX_STRING_COMMENT = s:SYNTAX_STRING . '\|' . s:SYNTAX_COMMENT
68
69" Get the linked syntax name of a character.
70function! s:SyntaxName(linenum, col)
71 return synIDattr(synID(a:linenum, a:col, 1), 'name')
72endfunction
73
74" Check if a character is in a comment.
75function! s:IsComment(linenum, col)
76 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_COMMENT
77endfunction
78
79" Check if a character is in a string.
80function! s:IsString(linenum, col)
81 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_STRING
82endfunction
83
84" Check if a character is in a comment or string.
85function! s:IsCommentOrString(linenum, col)
86 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_STRING_COMMENT
87endfunction
88
89" Check if a whole line is a comment.
90function! s:IsCommentLine(linenum)
91 " Check the first non-whitespace character.
92 return s:IsComment(a:linenum, indent(a:linenum) + 1)
93endfunction
94
95" Repeatedly search a line for a regex until one is found outside a string or
96" comment.
97function! s:SmartSearch(linenum, regex)
98 " Start at the first column.
99 let col = 0
100
101 " Search until there are no more matches, unless a good match is found.
102 while 1
103 call cursor(a:linenum, col + 1)
104 let [_, col] = searchpos(a:regex, 'cn', a:linenum)
105
106 " No more matches.
107 if !col
108 break
109 endif
110
111 if !s:IsCommentOrString(a:linenum, col)
112 return 1
113 endif
114 endwhile
115
116 " No good match found.
117 return 0
118endfunction
119
120" Skip a match if it's in a comment or string, is a single-line statement that
121" isn't adjacent, or is a postfix condition.
122function! s:ShouldSkip(startlinenum, linenum, col)
123 if s:IsCommentOrString(a:linenum, a:col)
124 return 1
125 endif
126
127 " Check for a single-line statement that isn't adjacent.
128 if s:SmartSearch(a:linenum, '\<then\>') && a:startlinenum - a:linenum > 1
129 return 1
130 endif
131
132 if s:SmartSearch(a:linenum, s:POSTFIX_CONDITION) &&
133 \ !s:SmartSearch(a:linenum, s:COMPOUND_ASSIGNMENT)
134 return 1
135 endif
136
137 return 0
138endfunction
139
140" Find the farthest line to look back to, capped to line 1 (zero and negative
141" numbers cause bad things).
142function! s:MaxLookback(startlinenum)
143 return max([1, a:startlinenum - s:MAX_LOOKBACK])
144endfunction
145
146" Get the skip expression for searchpair().
147function! s:SkipExpr(startlinenum)
148 return "s:ShouldSkip(" . a:startlinenum . ", line('.'), col('.'))"
149endfunction
150
151" Search for pairs of text.
152function! s:SearchPair(start, end)
153 " The cursor must be in the first column for regexes to match.
154 call cursor(0, 1)
155
156 let startlinenum = line('.')
157
158 " Don't need the W flag since MaxLookback caps the search to line 1.
159 return searchpair(a:start, '', a:end, 'bcn',
160 \ s:SkipExpr(startlinenum),
161 \ s:MaxLookback(startlinenum))
162endfunction
163
164" Try to find a previous matching line.
165function! s:GetMatch(curline)
166 let firstchar = a:curline[0]
167
168 if firstchar == '}'
169 return s:SearchPair('{', '}')
170 elseif firstchar == ')'
171 return s:SearchPair('(', ')')
172 elseif firstchar == ']'
173 return s:SearchPair('\[', '\]')
174 elseif a:curline =~ '^else\>'
175 return s:SearchPair('\<\%(if\|unless\|when\)\>', '\<else\>')
176 elseif a:curline =~ '^catch\>'
177 return s:SearchPair('\<try\>', '\<catch\>')
178 elseif a:curline =~ '^finally\>'
179 return s:SearchPair('\<try\>', '\<finally\>')
180 endif
181
182 return 0
183endfunction
184
185" Get the nearest previous line that isn't a comment.
186function! s:GetPrevNormalLine(startlinenum)
187 let curlinenum = a:startlinenum
188
189 while curlinenum > 0
190 let curlinenum = prevnonblank(curlinenum - 1)
191
192 if !s:IsCommentLine(curlinenum)
193 return curlinenum
194 endif
195 endwhile
196
197 return 0
198endfunction
199
200" Try to find a comment in a line.
201function! s:FindComment(linenum)
202 let col = 0
203
204 while 1
205 call cursor(a:linenum, col + 1)
206 let [_, col] = searchpos('#', 'cn', a:linenum)
207
208 if !col
209 break
210 endif
211
212 if s:IsComment(a:linenum, col)
213 return col
214 endif
215 endwhile
216
217 return 0
218endfunction
219
220" Get a line without comments or surrounding whitespace.
221function! s:GetTrimmedLine(linenum)
222 let comment = s:FindComment(a:linenum)
223 let line = getline(a:linenum)
224
225 if comment
226 " Subtract 1 to get to the column before the comment and another 1 for
227 " zero-based indexing.
228 let line = line[:comment - 2]
229 endif
230
231 return substitute(substitute(line, '^\s\+', '', ''),
232 \ '\s\+$', '', '')
233endfunction
234
235function! s:GetCoffeeIndent(curlinenum)
236 let prevlinenum = s:GetPrevNormalLine(a:curlinenum)
237
238 " Don't do anything if there's no previous line.
239 if !prevlinenum
240 return -1
241 endif
242
243 let curline = s:GetTrimmedLine(a:curlinenum)
244
245 " Try to find a previous matching statement. This handles outdenting.
246 let matchlinenum = s:GetMatch(curline)
247
248 if matchlinenum
249 return indent(matchlinenum)
250 endif
251
252 " Try to find a matching `when`.
253 if curline =~ '^when\>' && !s:SmartSearch(prevlinenum, '\<switch\>')
254 let linenum = a:curlinenum
255
256 while linenum > 0
257 let linenum = s:GetPrevNormalLine(linenum)
258
259 if getline(linenum) =~ '^\s*when\>'
260 return indent(linenum)
261 endif
262 endwhile
263
264 return -1
265 endif
266
267 let prevline = s:GetTrimmedLine(prevlinenum)
268 let previndent = indent(prevlinenum)
269
270 " Always indent after these operators.
271 if prevline =~ s:INDENT_AFTER_OPERATOR
272 return previndent + &shiftwidth
273 endif
274
275 " Indent after a continuation if it's the first.
276 if prevline =~ s:CONTINUATION
277 let prevprevlinenum = s:GetPrevNormalLine(prevlinenum)
278 let prevprevline = s:GetTrimmedLine(prevprevlinenum)
279
280 if prevprevline !~ s:CONTINUATION && prevprevline !~ s:CONTINUATION_BLOCK
281 return previndent + &shiftwidth
282 endif
283
284 return -1
285 endif
286
287 " Indent after these keywords and compound assignments if they aren't a
288 " single-line statement.
289 if prevline =~ s:INDENT_AFTER_KEYWORD || prevline =~ s:COMPOUND_ASSIGNMENT
290 if !s:SmartSearch(prevlinenum, '\<then\>') && prevline !~ s:SINGLE_LINE_ELSE
291 return previndent + &shiftwidth
292 endif
293
294 return -1
295 endif
296
297 " Indent a dot access if it's the first.
298 if curline =~ s:DOT_ACCESS && prevline !~ s:DOT_ACCESS
299 return previndent + &shiftwidth
300 endif
301
302 " Outdent after these keywords if they don't have a postfix condition or are
303 " a single-line statement.
304 if prevline =~ s:OUTDENT_AFTER
305 if !s:SmartSearch(prevlinenum, s:POSTFIX_CONDITION) ||
306 \ s:SmartSearch(prevlinenum, '\<then\>')
307 return previndent - &shiftwidth
308 endif
309 endif
310
311 " No indenting or outdenting is needed.
312 return -1
313endfunction
314
315" Wrap s:GetCoffeeIndent to keep the cursor position.
316function! GetCoffeeIndent(curlinenum)
317 let oldcursor = getpos('.')
318 let indent = s:GetCoffeeIndent(a:curlinenum)
319 call setpos('.', oldcursor)
320
321 return indent
322endfunction