]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/indent/coffee.vim
Turn off the blur
[rbdr/dotfiles] / vim / indent / coffee.vim
1 " Language: CoffeeScript
2 " Maintainer: Mick Koch <kchmck@gmail.com>
3 " URL: http://github.com/kchmck/vim-coffee-script
4 " License: WTFPL
5
6 if exists("b:did_indent")
7 finish
8 endif
9
10 let b:did_indent = 1
11
12 setlocal autoindent
13 setlocal indentexpr=GetCoffeeIndent(v:lnum)
14 " Make sure GetCoffeeIndent is run when these are typed so they can be
15 " indented or outdented.
16 setlocal indentkeys+=0],0),0.,=else,=when,=catch,=finally
17
18 " Only define the function once.
19 if exists("*GetCoffeeIndent")
20 finish
21 endif
22
23 " Keywords to indent after
24 let 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
29 let s:INDENT_AFTER_OPERATOR = '\%([([{:=]\|[-=]>\)$'
30
31 " Keywords and operators that continue a line
32 let s:CONTINUATION = '\<\%(is\|isnt\|and\|or\)\>$'
33 \ . '\|'
34 \ . '\%(-\@<!-\|+\@<!+\|<\|[-=]\@<!>\|\*\|/\@<!/\|%\||\|'
35 \ . '&\|,\|\.\@<!\.\)$'
36
37 " Operators that block continuation indenting
38 let s:CONTINUATION_BLOCK = '[([{:=]$'
39
40 " A continuation dot access
41 let s:DOT_ACCESS = '^\.'
42
43 " Keywords to outdent after
44 let s:OUTDENT_AFTER = '^\%(return\|break\|continue\|throw\)\>'
45
46 " A compound assignment like `... = if ...`
47 let s:COMPOUND_ASSIGNMENT = '[:=]\s*\%(if\|unless\|for\|while\|until\|'
48 \ . 'switch\|try\|class\)\>'
49
50 " A postfix condition like `return ... if ...`.
51 let s:POSTFIX_CONDITION = '\S\s\+\zs\<\%(if\|unless\)\>'
52
53 " A single-line else statement like `else ...` but not `else if ...
54 let s:SINGLE_LINE_ELSE = '^else\s\+\%(\<\%(if\|unless\)\>\)\@!'
55
56 " Max lines to look back for a match
57 let s:MAX_LOOKBACK = 50
58
59 " Syntax names for strings
60 let s:SYNTAX_STRING = 'coffee\%(String\|AssignString\|Embed\|Regex\|Heregex\|'
61 \ . 'Heredoc\)'
62
63 " Syntax names for comments
64 let s:SYNTAX_COMMENT = 'coffee\%(Comment\|BlockComment\|HeregexComment\)'
65
66 " Syntax names for strings and comments
67 let s:SYNTAX_STRING_COMMENT = s:SYNTAX_STRING . '\|' . s:SYNTAX_COMMENT
68
69 " Get the linked syntax name of a character.
70 function! s:SyntaxName(linenum, col)
71 return synIDattr(synID(a:linenum, a:col, 1), 'name')
72 endfunction
73
74 " Check if a character is in a comment.
75 function! s:IsComment(linenum, col)
76 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_COMMENT
77 endfunction
78
79 " Check if a character is in a string.
80 function! s:IsString(linenum, col)
81 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_STRING
82 endfunction
83
84 " Check if a character is in a comment or string.
85 function! s:IsCommentOrString(linenum, col)
86 return s:SyntaxName(a:linenum, a:col) =~ s:SYNTAX_STRING_COMMENT
87 endfunction
88
89 " Check if a whole line is a comment.
90 function! s:IsCommentLine(linenum)
91 " Check the first non-whitespace character.
92 return s:IsComment(a:linenum, indent(a:linenum) + 1)
93 endfunction
94
95 " Repeatedly search a line for a regex until one is found outside a string or
96 " comment.
97 function! 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
118 endfunction
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.
122 function! 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
138 endfunction
139
140 " Find the farthest line to look back to, capped to line 1 (zero and negative
141 " numbers cause bad things).
142 function! s:MaxLookback(startlinenum)
143 return max([1, a:startlinenum - s:MAX_LOOKBACK])
144 endfunction
145
146 " Get the skip expression for searchpair().
147 function! s:SkipExpr(startlinenum)
148 return "s:ShouldSkip(" . a:startlinenum . ", line('.'), col('.'))"
149 endfunction
150
151 " Search for pairs of text.
152 function! 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))
162 endfunction
163
164 " Try to find a previous matching line.
165 function! 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
183 endfunction
184
185 " Get the nearest previous line that isn't a comment.
186 function! 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
198 endfunction
199
200 " Try to find a comment in a line.
201 function! 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
218 endfunction
219
220 " Get a line without comments or surrounding whitespace.
221 function! 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\+$', '', '')
233 endfunction
234
235 function! 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
313 endfunction
314
315 " Wrap s:GetCoffeeIndent to keep the cursor position.
316 function! GetCoffeeIndent(curlinenum)
317 let oldcursor = getpos('.')
318 let indent = s:GetCoffeeIndent(a:curlinenum)
319 call setpos('.', oldcursor)
320
321 return indent
322 endfunction