]>
Commit | Line | Data |
---|---|---|
0d23b6e5 BB |
1 | " Vim indent file |
2 | " Language: Javascript | |
3 | " Maintainer: Darrick Wiebe <darrick at innatesoftware.com> | |
4 | " URL: http://github.com/pangloss/vim-javascript | |
5 | " Version: 1.0.0 | |
6 | " Last Change: August 31, 2009 | |
7 | " Acknowledgement: Based off of vim-ruby maintained by Nikolai Weibull http://vim-ruby.rubyforge.org | |
8 | ||
9 | " 0. Initialization {{{1 | |
10 | " ================= | |
11 | ||
12 | " Only load this indent file when no other was loaded. | |
13 | if exists("b:did_indent") | |
14 | finish | |
15 | endif | |
16 | let b:did_indent = 1 | |
17 | ||
18 | setlocal nosmartindent | |
19 | ||
20 | " Now, set up our indentation expression and keys that trigger it. | |
21 | setlocal indentexpr=GetJavascriptIndent() | |
22 | setlocal indentkeys=0{,0},0),0],!^F,o,O,e | |
23 | ||
24 | " Only define the function once. | |
25 | if exists("*GetJavascriptIndent") | |
26 | finish | |
27 | endif | |
28 | ||
29 | let s:cpo_save = &cpo | |
30 | set cpo&vim | |
31 | ||
32 | " 1. Variables {{{1 | |
33 | " ============ | |
34 | ||
35 | " Regex of syntax group names that are or delimit string or are comments. | |
36 | let s:syng_strcom = 'javaScript\%(String\|RegexpString\|CommentTodo\|LineComment\|Comment\|DocComment\)' | |
37 | ||
38 | " Regex of syntax group names that are strings. | |
39 | let s:syng_string = 'javaScript\%(RegexpString\)' | |
40 | ||
41 | " Regex of syntax group names that are strings or documentation. | |
42 | let s:syng_stringdoc = 'javaScriptDocComment\|javaScriptComment' | |
43 | ||
44 | " Expression used to check whether we should skip a match with searchpair(). | |
45 | let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'" | |
46 | ||
47 | let s:line_term = '\s*\%(\%(\/\/\).*\)\=$' | |
48 | ||
49 | " Regex that defines continuation lines, not including (, {, or [. | |
50 | let s:continuation_regex = '\%([\\*+/.:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term | |
51 | ||
52 | " Regex that defines continuation lines. | |
53 | " TODO: this needs to deal with if ...: and so on | |
54 | let s:msl_regex = '\%([\\*+/.:([]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term | |
55 | ||
56 | let s:one_line_scope_regex = '\<\%(if\|else\|for\|while\)\>[^{;]*' . s:line_term | |
57 | ||
58 | " Regex that defines blocks. | |
59 | let s:block_regex = '\%({\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term | |
60 | ||
61 | " 2. Auxiliary Functions {{{1 | |
62 | " ====================== | |
63 | ||
64 | " Check if the character at lnum:col is inside a string, comment, or is ascii. | |
65 | function s:IsInStringOrComment(lnum, col) | |
66 | return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom | |
67 | endfunction | |
68 | ||
69 | " Check if the character at lnum:col is inside a string. | |
70 | function s:IsInString(lnum, col) | |
71 | return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string | |
72 | endfunction | |
73 | ||
74 | " Check if the character at lnum:col is inside a string or documentation. | |
75 | function s:IsInStringOrDocumentation(lnum, col) | |
76 | return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_stringdoc | |
77 | endfunction | |
78 | ||
79 | " Find line above 'lnum' that isn't empty, in a comment, or in a string. | |
80 | function s:PrevNonBlankNonString(lnum) | |
81 | let in_block = 0 | |
82 | let lnum = prevnonblank(a:lnum) | |
83 | while lnum > 0 | |
84 | " Go in and out of blocks comments as necessary. | |
85 | " If the line isn't empty (with opt. comment) or in a string, end search. | |
86 | let line = getline(lnum) | |
87 | if line =~ '/\*' | |
88 | if in_block | |
89 | let in_block = 0 | |
90 | else | |
91 | break | |
92 | endif | |
93 | elseif !in_block && line =~ '\*/' | |
94 | let in_block = 1 | |
95 | elseif !in_block && line !~ '^\s*\%(//\).*$' && !(s:IsInStringOrComment(lnum, 1) && s:IsInStringOrComment(lnum, strlen(line))) | |
96 | break | |
97 | endif | |
98 | let lnum = prevnonblank(lnum - 1) | |
99 | endwhile | |
100 | return lnum | |
101 | endfunction | |
102 | ||
103 | " Find line above 'lnum' that started the continuation 'lnum' may be part of. | |
104 | function s:GetMSL(lnum, in_one_line_scope) | |
105 | " Start on the line we're at and use its indent. | |
106 | let msl = a:lnum | |
107 | let lnum = s:PrevNonBlankNonString(a:lnum - 1) | |
108 | while lnum > 0 | |
109 | " If we have a continuation line, or we're in a string, use line as MSL. | |
110 | " Otherwise, terminate search as we have found our MSL already. | |
111 | let line = getline(lnum) | |
112 | let col = match(line, s:msl_regex) + 1 | |
113 | if (col > 0 && !s:IsInStringOrComment(lnum, col)) || s:IsInString(lnum, strlen(line)) | |
114 | let msl = lnum | |
115 | else | |
116 | " Don't use lines that are part of a one line scope as msl unless the | |
117 | " flag in_one_line_scope is set to 1 | |
118 | " | |
119 | if a:in_one_line_scope | |
120 | break | |
121 | end | |
122 | let msl_one_line = s:Match(lnum, s:one_line_scope_regex) | |
123 | if msl_one_line == 0 | |
124 | break | |
125 | endif | |
126 | endif | |
127 | let lnum = s:PrevNonBlankNonString(lnum - 1) | |
128 | endwhile | |
129 | return msl | |
130 | endfunction | |
131 | ||
132 | " Check if line 'lnum' has more opening brackets than closing ones. | |
133 | function s:LineHasOpeningBrackets(lnum) | |
134 | let open_0 = 0 | |
135 | let open_2 = 0 | |
136 | let open_4 = 0 | |
137 | let line = getline(a:lnum) | |
138 | let pos = match(line, '[][(){}]', 0) | |
139 | while pos != -1 | |
140 | if !s:IsInStringOrComment(a:lnum, pos + 1) | |
141 | let idx = stridx('(){}[]', line[pos]) | |
142 | if idx % 2 == 0 | |
143 | let open_{idx} = open_{idx} + 1 | |
144 | else | |
145 | let open_{idx - 1} = open_{idx - 1} - 1 | |
146 | endif | |
147 | endif | |
148 | let pos = match(line, '[][(){}]', pos + 1) | |
149 | endwhile | |
150 | return (open_0 > 0) . (open_2 > 0) . (open_4 > 0) | |
151 | endfunction | |
152 | ||
153 | function s:Match(lnum, regex) | |
154 | let col = match(getline(a:lnum), a:regex) + 1 | |
155 | return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0 | |
156 | endfunction | |
157 | ||
158 | function s:IndentWithContinuation(lnum, ind, width) | |
159 | " Set up variables to use and search for MSL to the previous line. | |
160 | let p_lnum = a:lnum | |
161 | let lnum = s:GetMSL(a:lnum, 1) | |
162 | let line = getline(line) | |
163 | ||
164 | " If the previous line wasn't a MSL and is continuation return its indent. | |
165 | " TODO: the || s:IsInString() thing worries me a bit. | |
166 | if p_lnum != lnum | |
167 | if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line)) | |
168 | return a:ind | |
169 | endif | |
170 | endif | |
171 | ||
172 | " Set up more variables now that we know we aren't continuation bound. | |
173 | let msl_ind = indent(lnum) | |
174 | ||
175 | " If the previous line ended with [*+/.-=], start a continuation that | |
176 | " indents an extra level. | |
177 | if s:Match(lnum, s:continuation_regex) | |
178 | if lnum == p_lnum | |
179 | return msl_ind + a:width | |
180 | else | |
181 | return msl_ind | |
182 | endif | |
183 | endif | |
184 | ||
185 | return a:ind | |
186 | endfunction | |
187 | ||
188 | function s:InOneLineScope(lnum) | |
189 | let msl = s:GetMSL(a:lnum, 1) | |
190 | if msl > 0 && s:Match(msl, s:one_line_scope_regex) | |
191 | return msl | |
192 | endif | |
193 | return 0 | |
194 | endfunction | |
195 | ||
196 | function s:ExitingOneLineScope(lnum) | |
197 | let msl = s:GetMSL(a:lnum, 1) | |
198 | if msl > 0 | |
199 | " if the current line is in a one line scope .. | |
200 | if s:Match(msl, s:one_line_scope_regex) | |
201 | return 0 | |
202 | else | |
203 | let prev_msl = s:GetMSL(msl - 1, 1) | |
204 | if s:Match(prev_msl, s:one_line_scope_regex) | |
205 | return prev_msl | |
206 | endif | |
207 | endif | |
208 | endif | |
209 | return 0 | |
210 | endfunction | |
211 | ||
212 | " 3. GetJavascriptIndent Function {{{1 | |
213 | " ========================= | |
214 | ||
215 | function GetJavascriptIndent() | |
216 | " 3.1. Setup {{{2 | |
217 | " ---------- | |
218 | ||
219 | " Set up variables for restoring position in file. Could use v:lnum here. | |
220 | let vcol = col('.') | |
221 | ||
222 | " 3.2. Work on the current line {{{2 | |
223 | " ----------------------------- | |
224 | ||
225 | " Get the current line. | |
226 | let line = getline(v:lnum) | |
227 | let ind = -1 | |
228 | ||
229 | ||
230 | " If we got a closing bracket on an empty line, find its match and indent | |
231 | " according to it. For parentheses we indent to its column - 1, for the | |
232 | " others we indent to the containing line's MSL's level. Return -1 if fail. | |
233 | let col = matchend(line, '^\s*[]})]') | |
234 | if col > 0 && !s:IsInStringOrComment(v:lnum, col) | |
235 | call cursor(v:lnum, col) | |
236 | let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2) | |
237 | if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0 | |
238 | if line[col-1]==')' && col('.') != col('$') - 1 | |
239 | let ind = virtcol('.')-1 | |
240 | else | |
241 | let ind = indent(s:GetMSL(line('.'), 0)) | |
242 | endif | |
243 | endif | |
244 | return ind | |
245 | endif | |
246 | ||
247 | " If we have a /* or */ set indent to first column. | |
248 | if match(line, '^\s*\%(/\*\|\*/\)$') != -1 | |
249 | return 0 | |
250 | endif | |
251 | ||
252 | " If we are in a multi-line string or line-comment, don't do anything to it. | |
253 | if s:IsInStringOrDocumentation(v:lnum, matchend(line, '^\s*') + 1) | |
254 | return indent('.') | |
255 | endif | |
256 | ||
257 | " 3.3. Work on the previous line. {{{2 | |
258 | " ------------------------------- | |
259 | ||
260 | " Find a non-blank, non-multi-line string line above the current line. | |
261 | let lnum = s:PrevNonBlankNonString(v:lnum - 1) | |
262 | ||
263 | " If the line is empty and inside a string, use the previous line. | |
264 | if line =~ '^\s*$' && lnum != prevnonblank(v:lnum - 1) | |
265 | return indent(prevnonblank(v:lnum)) | |
266 | endif | |
267 | ||
268 | " At the start of the file use zero indent. | |
269 | if lnum == 0 | |
270 | return 0 | |
271 | endif | |
272 | ||
273 | " Set up variables for current line. | |
274 | let line = getline(lnum) | |
275 | let ind = indent(lnum) | |
276 | ||
277 | " If the previous line ended with a block opening, add a level of indent. | |
278 | if s:Match(lnum, s:block_regex) | |
279 | return indent(s:GetMSL(lnum, 0)) + &sw | |
280 | endif | |
281 | ||
282 | " If the previous line contained an opening bracket, and we are still in it, | |
283 | " add indent depending on the bracket type. | |
284 | if line =~ '[[({]' | |
285 | let counts = s:LineHasOpeningBrackets(lnum) | |
286 | if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0 | |
287 | if col('.') + 1 == col('$') | |
288 | return ind + &sw | |
289 | else | |
290 | return virtcol('.') | |
291 | endif | |
292 | elseif counts[1] == '1' || counts[2] == '1' | |
293 | return ind + &sw | |
294 | else | |
295 | call cursor(v:lnum, vcol) | |
296 | end | |
297 | endif | |
298 | ||
299 | " 3.4. Work on the MSL line. {{{2 | |
300 | " -------------------------- | |
301 | ||
302 | let ind_con = ind | |
303 | let ind = s:IndentWithContinuation(lnum, ind_con, &sw) | |
304 | ||
305 | " }}}2 | |
306 | " | |
307 | " | |
308 | let ols = s:InOneLineScope(lnum) | |
309 | if ols > 0 | |
310 | let ind = ind + &sw | |
311 | else | |
312 | let ols = s:ExitingOneLineScope(lnum) | |
313 | while ols > 0 && ind > 0 | |
314 | let ind = ind - &sw | |
315 | let ols = s:InOneLineScope(ols - 1) | |
316 | endwhile | |
317 | endif | |
318 | ||
319 | return ind | |
320 | endfunction | |
321 | ||
322 | " }}}1 | |
323 | ||
324 | let &cpo = s:cpo_save | |
325 | unlet s:cpo_save | |
326 | ||
327 | " vim:set sw=2 sts=2 ts=8 noet: | |
328 |