Mercurial > vim
annotate runtime/indent/solidity.vim @ 34559:ef55fe2ac8be v9.1.0180
patch 9.1.0180: Cursor pos wrong when double-width chars are concealed
Commit: https://github.com/vim/vim/commit/010e1539d67442cc69a97bef6453efaf849d0db3
Author: zeertzjq <zeertzjq@outlook.com>
Date: Thu Mar 14 18:22:17 2024 +0100
patch 9.1.0180: Cursor pos wrong when double-width chars are concealed
Problem: Cursor pos wrong when double-width chars are concealed.
Solution: Advance one more virtual column for a double-width char.
Run some tests with both 'wrap' and 'nowrap' (zeertzjq).
closes: #14197
Signed-off-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 14 Mar 2024 18:30:05 +0100 |
parents | 32c896b7e001 |
children |
rev | line source |
---|---|
30547 | 1 " Vim indent file |
32956
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
2 " Language: Solidity |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
3 " Maintainer: Cothi (jiungdev@gmail.com) |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
4 " Original Author: tomlion (https://github.com/tomlion/vim-solidity) |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
5 " Last Change: 2022 Sep 27 |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
6 " 2023 Aug 22 Vim Project (undo_indent) |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
7 " |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
8 " Acknowledgement: Based off of vim-javascript |
30547 | 9 " |
10 " 0. Initialization {{{1 | |
11 " ================= | |
12 | |
13 " Only load this indent file when no other was loaded. | |
14 if exists("b:did_indent") | |
15 finish | |
16 endif | |
17 let b:did_indent = 1 | |
18 | |
19 setlocal nosmartindent | |
20 | |
21 " Now, set up our indentation expression and keys that trigger it. | |
22 setlocal indentexpr=GetSolidityIndent() | |
23 setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e | |
24 | |
32956
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
25 let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<" |
32c896b7e001
runtime(solidity): add new ftplugin (#12877)
Christian Brabandt <cb@256bit.org>
parents:
30547
diff
changeset
|
26 |
30547 | 27 " Only define the function once. |
28 if exists("*GetSolidityIndent") | |
29 finish | |
30 endif | |
31 | |
32 let s:cpo_save = &cpo | |
33 set cpo&vim | |
34 | |
35 " 1. Variables {{{1 | |
36 " ============ | |
37 | |
38 let s:js_keywords = '^\s*\(break\|case\|catch\|continue\|debugger\|default\|delete\|do\|else\|finally\|for\|function\|if\|in\|instanceof\|new\|return\|switch\|this\|throw\|try\|typeof\|var\|void\|while\|with\)' | |
39 | |
40 " Regex of syntax group names that are or delimit string or are comments. | |
41 let s:syng_strcom = 'string\|regex\|comment\c' | |
42 | |
43 " Regex of syntax group names that are strings. | |
44 let s:syng_string = 'regex\c' | |
45 | |
46 " Regex of syntax group names that are strings or documentation. | |
47 let s:syng_multiline = 'comment\c' | |
48 | |
49 " Regex of syntax group names that are line comment. | |
50 let s:syng_linecom = 'linecomment\c' | |
51 | |
52 " Expression used to check whether we should skip a match with searchpair(). | |
53 let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'" | |
54 | |
55 let s:line_term = '\s*\%(\%(\/\/\).*\)\=$' | |
56 | |
57 " Regex that defines continuation lines, not including (, {, or [. | |
58 let s:continuation_regex = '\%([\\*+/.:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term | |
59 | |
60 " Regex that defines continuation lines. | |
61 " TODO: this needs to deal with if ...: and so on | |
62 let s:msl_regex = '\%([\\*+/.:([]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)' . s:line_term | |
63 | |
64 let s:one_line_scope_regex = '\<\%(if\|else\|for\|while\)\>[^{;]*' . s:line_term | |
65 | |
66 " Regex that defines blocks. | |
67 let s:block_regex = '\%([{[]\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term | |
68 | |
69 let s:var_stmt = '^\s*var' | |
70 | |
71 let s:comma_first = '^\s*,' | |
72 let s:comma_last = ',\s*$' | |
73 | |
74 let s:ternary = '^\s\+[?|:]' | |
75 let s:ternary_q = '^\s\+?' | |
76 | |
77 " 2. Auxiliary Functions {{{1 | |
78 " ====================== | |
79 | |
80 " Check if the character at lnum:col is inside a string, comment, or is ascii. | |
81 function s:IsInStringOrComment(lnum, col) | |
82 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom | |
83 endfunction | |
84 | |
85 " Check if the character at lnum:col is inside a string. | |
86 function s:IsInString(lnum, col) | |
87 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string | |
88 endfunction | |
89 | |
90 " Check if the character at lnum:col is inside a multi-line comment. | |
91 function s:IsInMultilineComment(lnum, col) | |
92 return !s:IsLineComment(a:lnum, a:col) && synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_multiline | |
93 endfunction | |
94 | |
95 " Check if the character at lnum:col is a line comment. | |
96 function s:IsLineComment(lnum, col) | |
97 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_linecom | |
98 endfunction | |
99 | |
100 " Find line above 'lnum' that isn't empty, in a comment, or in a string. | |
101 function s:PrevNonBlankNonString(lnum) | |
102 let in_block = 0 | |
103 let lnum = prevnonblank(a:lnum) | |
104 while lnum > 0 | |
105 " Go in and out of blocks comments as necessary. | |
106 " If the line isn't empty (with opt. comment) or in a string, end search. | |
107 let line = getline(lnum) | |
108 if line =~ '/\*' | |
109 if in_block | |
110 let in_block = 0 | |
111 else | |
112 break | |
113 endif | |
114 elseif !in_block && line =~ '\*/' | |
115 let in_block = 1 | |
116 elseif !in_block && line !~ '^\s*\%(//\).*$' && !(s:IsInStringOrComment(lnum, 1) && s:IsInStringOrComment(lnum, strlen(line))) | |
117 break | |
118 endif | |
119 let lnum = prevnonblank(lnum - 1) | |
120 endwhile | |
121 return lnum | |
122 endfunction | |
123 | |
124 " Find line above 'lnum' that started the continuation 'lnum' may be part of. | |
125 function s:GetMSL(lnum, in_one_line_scope) | |
126 " Start on the line we're at and use its indent. | |
127 let msl = a:lnum | |
128 let lnum = s:PrevNonBlankNonString(a:lnum - 1) | |
129 while lnum > 0 | |
130 " If we have a continuation line, or we're in a string, use line as MSL. | |
131 " Otherwise, terminate search as we have found our MSL already. | |
132 let line = getline(lnum) | |
133 let col = match(line, s:msl_regex) + 1 | |
134 if (col > 0 && !s:IsInStringOrComment(lnum, col)) || s:IsInString(lnum, strlen(line)) | |
135 let msl = lnum | |
136 else | |
137 " Don't use lines that are part of a one line scope as msl unless the | |
138 " flag in_one_line_scope is set to 1 | |
139 " | |
140 if a:in_one_line_scope | |
141 break | |
142 end | |
143 let msl_one_line = s:Match(lnum, s:one_line_scope_regex) | |
144 if msl_one_line == 0 | |
145 break | |
146 endif | |
147 endif | |
148 let lnum = s:PrevNonBlankNonString(lnum - 1) | |
149 endwhile | |
150 return msl | |
151 endfunction | |
152 | |
153 function s:RemoveTrailingComments(content) | |
154 let single = '\/\/\(.*\)\s*$' | |
155 let multi = '\/\*\(.*\)\*\/\s*$' | |
156 return substitute(substitute(a:content, single, '', ''), multi, '', '') | |
157 endfunction | |
158 | |
159 " Find if the string is inside var statement (but not the first string) | |
160 function s:InMultiVarStatement(lnum) | |
161 let lnum = s:PrevNonBlankNonString(a:lnum - 1) | |
162 | |
163 " let type = synIDattr(synID(lnum, indent(lnum) + 1, 0), 'name') | |
164 | |
165 " loop through previous expressions to find a var statement | |
166 while lnum > 0 | |
167 let line = getline(lnum) | |
168 | |
169 " if the line is a js keyword | |
170 if (line =~ s:js_keywords) | |
171 " check if the line is a var stmt | |
172 " if the line has a comma first or comma last then we can assume that we | |
173 " are in a multiple var statement | |
174 if (line =~ s:var_stmt) | |
175 return lnum | |
176 endif | |
177 | |
178 " other js keywords, not a var | |
179 return 0 | |
180 endif | |
181 | |
182 let lnum = s:PrevNonBlankNonString(lnum - 1) | |
183 endwhile | |
184 | |
185 " beginning of program, not a var | |
186 return 0 | |
187 endfunction | |
188 | |
189 " Find line above with beginning of the var statement or returns 0 if it's not | |
190 " this statement | |
191 function s:GetVarIndent(lnum) | |
192 let lvar = s:InMultiVarStatement(a:lnum) | |
193 let prev_lnum = s:PrevNonBlankNonString(a:lnum - 1) | |
194 | |
195 if lvar | |
196 let line = s:RemoveTrailingComments(getline(prev_lnum)) | |
197 | |
198 " if the previous line doesn't end in a comma, return to regular indent | |
199 if (line !~ s:comma_last) | |
200 return indent(prev_lnum) - &sw | |
201 else | |
202 return indent(lvar) + &sw | |
203 endif | |
204 endif | |
205 | |
206 return -1 | |
207 endfunction | |
208 | |
209 | |
210 " Check if line 'lnum' has more opening brackets than closing ones. | |
211 function s:LineHasOpeningBrackets(lnum) | |
212 let open_0 = 0 | |
213 let open_2 = 0 | |
214 let open_4 = 0 | |
215 let line = getline(a:lnum) | |
216 let pos = match(line, '[][(){}]', 0) | |
217 while pos != -1 | |
218 if !s:IsInStringOrComment(a:lnum, pos + 1) | |
219 let idx = stridx('(){}[]', line[pos]) | |
220 if idx % 2 == 0 | |
221 let open_{idx} = open_{idx} + 1 | |
222 else | |
223 let open_{idx - 1} = open_{idx - 1} - 1 | |
224 endif | |
225 endif | |
226 let pos = match(line, '[][(){}]', pos + 1) | |
227 endwhile | |
228 return (open_0 > 0) . (open_2 > 0) . (open_4 > 0) | |
229 endfunction | |
230 | |
231 function s:Match(lnum, regex) | |
232 let col = match(getline(a:lnum), a:regex) + 1 | |
233 return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0 | |
234 endfunction | |
235 | |
236 function s:IndentWithContinuation(lnum, ind, width) | |
237 " Set up variables to use and search for MSL to the previous line. | |
238 let p_lnum = a:lnum | |
239 let lnum = s:GetMSL(a:lnum, 1) | |
240 let line = getline(lnum) | |
241 | |
242 " If the previous line wasn't a MSL and is continuation return its indent. | |
243 " TODO: the || s:IsInString() thing worries me a bit. | |
244 if p_lnum != lnum | |
245 if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line)) | |
246 return a:ind | |
247 endif | |
248 endif | |
249 | |
250 " Set up more variables now that we know we aren't continuation bound. | |
251 let msl_ind = indent(lnum) | |
252 | |
253 " If the previous line ended with [*+/.-=], start a continuation that | |
254 " indents an extra level. | |
255 if s:Match(lnum, s:continuation_regex) | |
256 if lnum == p_lnum | |
257 return msl_ind + a:width | |
258 else | |
259 return msl_ind | |
260 endif | |
261 endif | |
262 | |
263 return a:ind | |
264 endfunction | |
265 | |
266 function s:InOneLineScope(lnum) | |
267 let msl = s:GetMSL(a:lnum, 1) | |
268 if msl > 0 && s:Match(msl, s:one_line_scope_regex) | |
269 return msl | |
270 endif | |
271 return 0 | |
272 endfunction | |
273 | |
274 function s:ExitingOneLineScope(lnum) | |
275 let msl = s:GetMSL(a:lnum, 1) | |
276 if msl > 0 | |
277 " if the current line is in a one line scope .. | |
278 if s:Match(msl, s:one_line_scope_regex) | |
279 return 0 | |
280 else | |
281 let prev_msl = s:GetMSL(msl - 1, 1) | |
282 if s:Match(prev_msl, s:one_line_scope_regex) | |
283 return prev_msl | |
284 endif | |
285 endif | |
286 endif | |
287 return 0 | |
288 endfunction | |
289 | |
290 " 3. GetSolidityIndent Function {{{1 | |
291 " ========================= | |
292 | |
293 function GetSolidityIndent() | |
294 " 3.1. Setup {{{2 | |
295 " ---------- | |
296 | |
297 " Set up variables for restoring position in file. Could use v:lnum here. | |
298 let vcol = col('.') | |
299 | |
300 " 3.2. Work on the current line {{{2 | |
301 " ----------------------------- | |
302 | |
303 let ind = -1 | |
304 " Get the current line. | |
305 let line = getline(v:lnum) | |
306 " previous nonblank line number | |
307 let prevline = prevnonblank(v:lnum - 1) | |
308 | |
309 " If we got a closing bracket on an empty line, find its match and indent | |
310 " according to it. For parentheses we indent to its column - 1, for the | |
311 " others we indent to the containing line's MSL's level. Return -1 if fail. | |
312 let col = matchend(line, '^\s*[],})]') | |
313 if col > 0 && !s:IsInStringOrComment(v:lnum, col) | |
314 call cursor(v:lnum, col) | |
315 | |
316 let lvar = s:InMultiVarStatement(v:lnum) | |
317 if lvar | |
318 let prevline_contents = s:RemoveTrailingComments(getline(prevline)) | |
319 | |
320 " check for comma first | |
321 if (line[col - 1] =~ ',') | |
322 " if the previous line ends in comma or semicolon don't indent | |
323 if (prevline_contents =~ '[;,]\s*$') | |
324 return indent(s:GetMSL(line('.'), 0)) | |
325 " get previous line indent, if it's comma first return prevline indent | |
326 elseif (prevline_contents =~ s:comma_first) | |
327 return indent(prevline) | |
328 " otherwise we indent 1 level | |
329 else | |
330 return indent(lvar) + &sw | |
331 endif | |
332 endif | |
333 endif | |
334 | |
335 | |
336 let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2) | |
337 if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0 | |
338 if line[col-1]==')' && col('.') != col('$') - 1 | |
339 let ind = virtcol('.')-1 | |
340 else | |
341 let ind = indent(s:GetMSL(line('.'), 0)) | |
342 endif | |
343 endif | |
344 return ind | |
345 endif | |
346 | |
347 " If the line is comma first, dedent 1 level | |
348 if (getline(prevline) =~ s:comma_first) | |
349 return indent(prevline) - &sw | |
350 endif | |
351 | |
352 if (line =~ s:ternary) | |
353 if (getline(prevline) =~ s:ternary_q) | |
354 return indent(prevline) | |
355 else | |
356 return indent(prevline) + &sw | |
357 endif | |
358 endif | |
359 | |
360 " If we are in a multi-line comment, cindent does the right thing. | |
361 if s:IsInMultilineComment(v:lnum, 1) && !s:IsLineComment(v:lnum, 1) | |
362 return cindent(v:lnum) | |
363 endif | |
364 | |
365 " Check for multiple var assignments | |
366 " let var_indent = s:GetVarIndent(v:lnum) | |
367 " if var_indent >= 0 | |
368 " return var_indent | |
369 " endif | |
370 | |
371 " 3.3. Work on the previous line. {{{2 | |
372 " ------------------------------- | |
373 | |
374 " If the line is empty and the previous nonblank line was a multi-line | |
375 " comment, use that comment's indent. Deduct one char to account for the | |
376 " space in ' */'. | |
377 if line =~ '^\s*$' && s:IsInMultilineComment(prevline, 1) | |
378 return indent(prevline) - 1 | |
379 endif | |
380 | |
381 " Find a non-blank, non-multi-line string line above the current line. | |
382 let lnum = s:PrevNonBlankNonString(v:lnum - 1) | |
383 | |
384 " If the line is empty and inside a string, use the previous line. | |
385 if line =~ '^\s*$' && lnum != prevline | |
386 return indent(prevnonblank(v:lnum)) | |
387 endif | |
388 | |
389 " At the start of the file use zero indent. | |
390 if lnum == 0 | |
391 return 0 | |
392 endif | |
393 | |
394 " Set up variables for current line. | |
395 let line = getline(lnum) | |
396 let ind = indent(lnum) | |
397 | |
398 " If the previous line ended with a block opening, add a level of indent. | |
399 if s:Match(lnum, s:block_regex) | |
400 return indent(s:GetMSL(lnum, 0)) + &sw | |
401 endif | |
402 | |
403 " If the previous line contained an opening bracket, and we are still in it, | |
404 " add indent depending on the bracket type. | |
405 if line =~ '[[({]' | |
406 let counts = s:LineHasOpeningBrackets(lnum) | |
407 if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0 | |
408 if col('.') + 1 == col('$') | |
409 return ind + &sw | |
410 else | |
411 return virtcol('.') | |
412 endif | |
413 elseif counts[1] == '1' || counts[2] == '1' | |
414 return ind + &sw | |
415 else | |
416 call cursor(v:lnum, vcol) | |
417 end | |
418 endif | |
419 | |
420 " 3.4. Work on the MSL line. {{{2 | |
421 " -------------------------- | |
422 | |
423 let ind_con = ind | |
424 let ind = s:IndentWithContinuation(lnum, ind_con, &sw) | |
425 | |
426 " }}}2 | |
427 " | |
428 " | |
429 let ols = s:InOneLineScope(lnum) | |
430 if ols > 0 | |
431 let ind = ind + &sw | |
432 else | |
433 let ols = s:ExitingOneLineScope(lnum) | |
434 while ols > 0 && ind > 0 | |
435 let ind = ind - &sw | |
436 let ols = s:InOneLineScope(ols - 1) | |
437 endwhile | |
438 endif | |
439 | |
440 return ind | |
441 endfunction | |
442 | |
443 " }}}1 | |
444 | |
445 let &cpo = s:cpo_save | |
446 unlet s:cpo_save |