1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4#include <assert.h>
5#include <inttypes.h>
6#include <stdbool.h>
7
8#include "nvim/ascii.h"
9#include "nvim/assert.h"
10#include "nvim/change.h"
11#include "nvim/indent.h"
12#include "nvim/eval.h"
13#include "nvim/charset.h"
14#include "nvim/cursor.h"
15#include "nvim/mark.h"
16#include "nvim/memline.h"
17#include "nvim/memory.h"
18#include "nvim/misc1.h"
19#include "nvim/move.h"
20#include "nvim/option.h"
21#include "nvim/regexp.h"
22#include "nvim/screen.h"
23#include "nvim/search.h"
24#include "nvim/strings.h"
25#include "nvim/undo.h"
26#include "nvim/buffer.h"
27
28
29#ifdef INCLUDE_GENERATED_DECLARATIONS
30# include "indent.c.generated.h"
31#endif
32
33// Count the size (in window cells) of the indent in the current line.
34int get_indent(void)
35{
36 return get_indent_str(get_cursor_line_ptr(), (int)curbuf->b_p_ts, false);
37}
38
39
40// Count the size (in window cells) of the indent in line "lnum".
41int get_indent_lnum(linenr_T lnum)
42{
43 return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, false);
44}
45
46
47// Count the size (in window cells) of the indent in line "lnum" of buffer
48// "buf".
49int get_indent_buf(buf_T *buf, linenr_T lnum)
50{
51 return get_indent_str(ml_get_buf(buf, lnum, false), (int)buf->b_p_ts, false);
52}
53
54
55// Count the size (in window cells) of the indent in line "ptr", with
56// 'tabstop' at "ts".
57// If @param list is TRUE, count only screen size for tabs.
58int get_indent_str(char_u *ptr, int ts, int list)
59{
60 int count = 0;
61
62 for (; *ptr; ++ptr) {
63 // Count a tab for what it is worth.
64 if (*ptr == TAB) {
65 if (!list || curwin->w_p_lcs_chars.tab1) {
66 // count a tab for what it is worth
67 count += ts - (count % ts);
68 } else {
69 // In list mode, when tab is not set, count screen char width
70 // for Tab, displays: ^I
71 count += ptr2cells(ptr);
72 }
73 } else if (*ptr == ' ') {
74 // Count a space for one.
75 count++;
76 } else {
77 break;
78 }
79 }
80 return count;
81}
82
83
84// Set the indent of the current line.
85// Leaves the cursor on the first non-blank in the line.
86// Caller must take care of undo.
87// "flags":
88// SIN_CHANGED: call changed_bytes() if the line was changed.
89// SIN_INSERT: insert the indent in front of the line.
90// SIN_UNDO: save line for undo before changing it.
91// @param size measured in spaces
92// Returns true if the line was changed.
93int set_indent(int size, int flags)
94{
95 char_u *p;
96 char_u *newline;
97 char_u *oldline;
98 char_u *s;
99 int todo;
100 int ind_len; // Measured in characters.
101 int line_len;
102 int doit = false;
103 int ind_done = 0; // Measured in spaces.
104 int tab_pad;
105 int retval = false;
106
107 // Number of initial whitespace chars when 'et' and 'pi' are both set.
108 int orig_char_len = -1;
109
110 // First check if there is anything to do and compute the number of
111 // characters needed for the indent.
112 todo = size;
113 ind_len = 0;
114 p = oldline = get_cursor_line_ptr();
115
116 // Calculate the buffer size for the new indent, and check to see if it
117 // isn't already set.
118 // If 'expandtab' isn't set: use TABs; if both 'expandtab' and
119 // 'preserveindent' are set count the number of characters at the
120 // beginning of the line to be copied.
121 if (!curbuf->b_p_et || (!(flags & SIN_INSERT) && curbuf->b_p_pi)) {
122 // If 'preserveindent' is set then reuse as much as possible of
123 // the existing indent structure for the new indent.
124 if (!(flags & SIN_INSERT) && curbuf->b_p_pi) {
125 ind_done = 0;
126
127 // Count as many characters as we can use.
128 while (todo > 0 && ascii_iswhite(*p)) {
129 if (*p == TAB) {
130 tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
131
132 // Stop if this tab will overshoot the target.
133 if (todo < tab_pad) {
134 break;
135 }
136 todo -= tab_pad;
137 ind_len++;
138 ind_done += tab_pad;
139 } else {
140 todo--;
141 ind_len++;
142 ind_done++;
143 }
144 p++;
145 }
146
147 // Set initial number of whitespace chars to copy if we are
148 // preserving indent but expandtab is set.
149 if (curbuf->b_p_et) {
150 orig_char_len = ind_len;
151 }
152
153 // Fill to next tabstop with a tab, if possible.
154 tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
155
156 if ((todo >= tab_pad) && (orig_char_len == -1)) {
157 doit = true;
158 todo -= tab_pad;
159 ind_len++;
160
161 // ind_done += tab_pad;
162 }
163 }
164
165 // Count tabs required for indent.
166 while (todo >= (int)curbuf->b_p_ts) {
167 if (*p != TAB) {
168 doit = true;
169 } else {
170 p++;
171 }
172 todo -= (int)curbuf->b_p_ts;
173 ind_len++;
174
175 // ind_done += (int)curbuf->b_p_ts;
176 }
177 }
178
179 // Count spaces required for indent.
180 while (todo > 0) {
181 if (*p != ' ') {
182 doit = true;
183 } else {
184 p++;
185 }
186 todo--;
187 ind_len++;
188
189 // ind_done++;
190 }
191
192 // Return if the indent is OK already.
193 if (!doit && !ascii_iswhite(*p) && !(flags & SIN_INSERT)) {
194 return false;
195 }
196
197 // Allocate memory for the new line.
198 if (flags & SIN_INSERT) {
199 p = oldline;
200 } else {
201 p = skipwhite(p);
202 }
203 line_len = (int)STRLEN(p) + 1;
204
205 // If 'preserveindent' and 'expandtab' are both set keep the original
206 // characters and allocate accordingly. We will fill the rest with spaces
207 // after the if (!curbuf->b_p_et) below.
208 if (orig_char_len != -1) {
209 int newline_size; // = orig_char_len + size - ind_done + line_len
210 STRICT_ADD(orig_char_len, size, &newline_size, int);
211 STRICT_SUB(newline_size, ind_done, &newline_size, int);
212 STRICT_ADD(newline_size, line_len, &newline_size, int);
213 assert(newline_size >= 0);
214 newline = xmalloc((size_t)newline_size);
215 todo = size - ind_done;
216
217 // Set total length of indent in characters, which may have been
218 // undercounted until now.
219 ind_len = orig_char_len + todo;
220 p = oldline;
221 s = newline;
222
223 while (orig_char_len > 0) {
224 *s++ = *p++;
225 orig_char_len--;
226 }
227
228 // Skip over any additional white space (useful when newindent is less
229 // than old).
230 while (ascii_iswhite(*p)) {
231 p++;
232 }
233 } else {
234 todo = size;
235 assert(ind_len + line_len >= 0);
236 size_t newline_size;
237 STRICT_ADD(ind_len, line_len, &newline_size, size_t);
238 newline = xmalloc(newline_size);
239 s = newline;
240 }
241
242 // Put the characters in the new line.
243 // if 'expandtab' isn't set: use TABs
244 if (!curbuf->b_p_et) {
245 // If 'preserveindent' is set then reuse as much as possible of
246 // the existing indent structure for the new indent.
247 if (!(flags & SIN_INSERT) && curbuf->b_p_pi) {
248 p = oldline;
249 ind_done = 0;
250
251 while (todo > 0 && ascii_iswhite(*p)) {
252 if (*p == TAB) {
253 tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
254
255 // Stop if this tab will overshoot the target.
256 if (todo < tab_pad) {
257 break;
258 }
259 todo -= tab_pad;
260 ind_done += tab_pad;
261 } else {
262 todo--;
263 ind_done++;
264 }
265 *s++ = *p++;
266 }
267
268 // Fill to next tabstop with a tab, if possible.
269 tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
270
271 if (todo >= tab_pad) {
272 *s++ = TAB;
273 todo -= tab_pad;
274 }
275 p = skipwhite(p);
276 }
277
278 while (todo >= (int)curbuf->b_p_ts) {
279 *s++ = TAB;
280 todo -= (int)curbuf->b_p_ts;
281 }
282 }
283
284 while (todo > 0) {
285 *s++ = ' ';
286 todo--;
287 }
288 memmove(s, p, (size_t)line_len);
289
290 // Replace the line (unless undo fails).
291 if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
292 ml_replace(curwin->w_cursor.lnum, newline, false);
293
294 if (flags & SIN_CHANGED) {
295 changed_bytes(curwin->w_cursor.lnum, 0);
296 }
297
298 // Correct saved cursor position if it is in this line.
299 if (saved_cursor.lnum == curwin->w_cursor.lnum) {
300 if (saved_cursor.col >= (colnr_T)(p - oldline)) {
301 // Cursor was after the indent, adjust for the number of
302 // bytes added/removed.
303 saved_cursor.col += ind_len - (colnr_T)(p - oldline);
304
305 } else if (saved_cursor.col >= (colnr_T)(s - newline)) {
306 // Cursor was in the indent, and is now after it, put it back
307 // at the start of the indent (replacing spaces with TAB).
308 saved_cursor.col = (colnr_T)(s - newline);
309 }
310 }
311 retval = true;
312 } else {
313 xfree(newline);
314 }
315 curwin->w_cursor.col = ind_len;
316 return retval;
317}
318
319
320// Return the indent of the current line after a number. Return -1 if no
321// number was found. Used for 'n' in 'formatoptions': numbered list.
322// Since a pattern is used it can actually handle more than numbers.
323int get_number_indent(linenr_T lnum)
324{
325 colnr_T col;
326 pos_T pos;
327 regmatch_T regmatch;
328 int lead_len = 0; // Length of comment leader.
329
330 if (lnum > curbuf->b_ml.ml_line_count) {
331 return -1;
332 }
333 pos.lnum = 0;
334
335 // In format_lines() (i.e. not insert mode), fo+=q is needed too...
336 if ((State & INSERT) || has_format_option(FO_Q_COMS)) {
337 lead_len = get_leader_len(ml_get(lnum), NULL, false, true);
338 }
339 regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC);
340
341 if (regmatch.regprog != NULL) {
342 regmatch.rm_ic = false;
343
344 // vim_regexec() expects a pointer to a line. This lets us
345 // start matching for the flp beyond any comment leader...
346 if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, (colnr_T)0)) {
347 pos.lnum = lnum;
348 pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum));
349 pos.coladd = 0;
350 }
351 vim_regfree(regmatch.regprog);
352 }
353
354 if ((pos.lnum == 0) || (*ml_get_pos(&pos) == NUL)) {
355 return -1;
356 }
357 getvcol(curwin, &pos, &col, NULL, NULL);
358 return (int)col;
359}
360
361/*
362 * Return appropriate space number for breakindent, taking influencing
363 * parameters into account. Window must be specified, since it is not
364 * necessarily always the current one.
365 */
366int get_breakindent_win(win_T *wp, char_u *line)
367 FUNC_ATTR_NONNULL_ARG(1)
368{
369 static int prev_indent = 0; // Cached indent value.
370 static long prev_ts = 0; // Cached tabstop value.
371 static char_u *prev_line = NULL; // cached pointer to line.
372 static varnumber_T prev_tick = 0; // Changedtick of cached value.
373 int bri = 0;
374 // window width minus window margin space, i.e. what rests for text
375 const int eff_wwidth = wp->w_width_inner
376 - ((wp->w_p_nu || wp->w_p_rnu)
377 && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
378 ? number_width(wp) + 1 : 0);
379
380 /* used cached indent, unless pointer or 'tabstop' changed */
381 if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
382 || prev_tick != buf_get_changedtick(wp->w_buffer)) {
383 prev_line = line;
384 prev_ts = wp->w_buffer->b_p_ts;
385 prev_tick = buf_get_changedtick(wp->w_buffer);
386 prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list);
387 }
388 bri = prev_indent + wp->w_p_brishift;
389
390 /* indent minus the length of the showbreak string */
391 if (wp->w_p_brisbr)
392 bri -= vim_strsize(p_sbr);
393
394 /* Add offset for number column, if 'n' is in 'cpoptions' */
395 bri += win_col_off2(wp);
396
397 /* never indent past left window margin */
398 if (bri < 0)
399 bri = 0;
400 /* always leave at least bri_min characters on the left,
401 * if text width is sufficient */
402 else if (bri > eff_wwidth - wp->w_p_brimin)
403 bri = (eff_wwidth - wp->w_p_brimin < 0)
404 ? 0 : eff_wwidth - wp->w_p_brimin;
405
406 return bri;
407}
408
409// When extra == 0: Return true if the cursor is before or on the first
410// non-blank in the line.
411// When extra == 1: Return true if the cursor is before the first non-blank in
412// the line.
413int inindent(int extra)
414{
415 char_u *ptr;
416 colnr_T col;
417
418 for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); ++col) {
419 ptr++;
420 }
421
422 if (col >= curwin->w_cursor.col + extra) {
423 return true;
424 } else {
425 return false;
426 }
427}
428
429
430// Get indent level from 'indentexpr'.
431int get_expr_indent(void)
432{
433 int indent = -1;
434 pos_T save_pos;
435 colnr_T save_curswant;
436 int save_set_curswant;
437 int save_State;
438 int use_sandbox = was_set_insecurely((char_u *)"indentexpr", OPT_LOCAL);
439
440 // Save and restore cursor position and curswant, in case it was changed
441 // * via :normal commands.
442 save_pos = curwin->w_cursor;
443 save_curswant = curwin->w_curswant;
444 save_set_curswant = curwin->w_set_curswant;
445 set_vim_var_nr(VV_LNUM, (varnumber_T) curwin->w_cursor.lnum);
446
447 if (use_sandbox) {
448 sandbox++;
449 }
450 textlock++;
451
452 // Need to make a copy, the 'indentexpr' option could be changed while
453 // evaluating it.
454 char_u *inde_copy = vim_strsave(curbuf->b_p_inde);
455 indent = (int)eval_to_number(inde_copy);
456 xfree(inde_copy);
457
458 if (use_sandbox) {
459 sandbox--;
460 }
461 textlock--;
462
463 // Restore the cursor position so that 'indentexpr' doesn't need to.
464 // Pretend to be in Insert mode, allow cursor past end of line for "o"
465 // command.
466 save_State = State;
467 State = INSERT;
468 curwin->w_cursor = save_pos;
469 curwin->w_curswant = save_curswant;
470 curwin->w_set_curswant = save_set_curswant;
471 check_cursor();
472 State = save_State;
473
474 // If there is an error, just keep the current indent.
475 if (indent < 0) {
476 indent = get_indent();
477 }
478
479 return indent;
480}
481
482
483// When 'p' is present in 'cpoptions, a Vi compatible method is used.
484// The incompatible newer method is quite a bit better at indenting
485// code in lisp-like languages than the traditional one; it's still
486// mostly heuristics however -- Dirk van Deun, dirk@rave.org
487
488// TODO(unknown):
489// Findmatch() should be adapted for lisp, also to make showmatch
490// work correctly: now (v5.3) it seems all C/C++ oriented:
491// - it does not recognize the #\( and #\) notations as character literals
492// - it doesn't know about comments starting with a semicolon
493// - it incorrectly interprets '(' as a character literal
494// All this messes up get_lisp_indent in some rare cases.
495// Update from Sergey Khorev:
496// I tried to fix the first two issues.
497int get_lisp_indent(void)
498{
499 pos_T *pos, realpos, paren;
500 int amount;
501 char_u *that;
502 colnr_T col;
503 colnr_T firsttry;
504 int parencount;
505 int quotecount;
506 int vi_lisp;
507
508 // Set vi_lisp to use the vi-compatible method.
509 vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
510
511 realpos = curwin->w_cursor;
512 curwin->w_cursor.col = 0;
513
514 if ((pos = findmatch(NULL, '(')) == NULL) {
515 pos = findmatch(NULL, '[');
516 } else {
517 paren = *pos;
518 pos = findmatch(NULL, '[');
519
520 if ((pos == NULL) || lt(*pos, paren)) {
521 pos = &paren;
522 }
523 }
524
525 if (pos != NULL) {
526 // Extra trick: Take the indent of the first previous non-white
527 // line that is at the same () level.
528 amount = -1;
529 parencount = 0;
530
531 while (--curwin->w_cursor.lnum >= pos->lnum) {
532 if (linewhite(curwin->w_cursor.lnum)) {
533 continue;
534 }
535
536 for (that = get_cursor_line_ptr(); *that != NUL; ++that) {
537 if (*that == ';') {
538 while (*(that + 1) != NUL) {
539 that++;
540 }
541 continue;
542 }
543
544 if (*that == '\\') {
545 if (*(that + 1) != NUL) {
546 that++;
547 }
548 continue;
549 }
550
551 if ((*that == '"') && (*(that + 1) != NUL)) {
552 while (*++that && *that != '"') {
553 // Skipping escaped characters in the string
554 if (*that == '\\') {
555 if (*++that == NUL) {
556 break;
557 }
558 if (that[1] == NUL) {
559 that++;
560 break;
561 }
562 }
563 }
564 }
565 if ((*that == '(') || (*that == '[')) {
566 parencount++;
567 } else if ((*that == ')') || (*that == ']')) {
568 parencount--;
569 }
570 }
571
572 if (parencount == 0) {
573 amount = get_indent();
574 break;
575 }
576 }
577
578 if (amount == -1) {
579 curwin->w_cursor.lnum = pos->lnum;
580 curwin->w_cursor.col = pos->col;
581 col = pos->col;
582
583 that = get_cursor_line_ptr();
584
585 if (vi_lisp && (get_indent() == 0)) {
586 amount = 2;
587 } else {
588 char_u *line = that;
589
590 amount = 0;
591
592 while (*that && col) {
593 amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount);
594 col--;
595 }
596
597 // Some keywords require "body" indenting rules (the
598 // non-standard-lisp ones are Scheme special forms):
599 // (let ((a 1)) instead (let ((a 1))
600 // (...)) of (...))
601 if (!vi_lisp && ((*that == '(') || (*that == '['))
602 && lisp_match(that + 1)) {
603 amount += 2;
604 } else {
605 that++;
606 amount++;
607 firsttry = amount;
608
609 while (ascii_iswhite(*that)) {
610 amount += lbr_chartabsize(line, that, (colnr_T)amount);
611 that++;
612 }
613
614 if (*that && (*that != ';')) {
615 // Not a comment line.
616 // Test *that != '(' to accommodate first let/do
617 // argument if it is more than one line.
618 if (!vi_lisp && (*that != '(') && (*that != '[')) {
619 firsttry++;
620 }
621
622 parencount = 0;
623 quotecount = 0;
624
625 if (vi_lisp || ((*that != '"') && (*that != '\'')
626 && (*that != '#') && ((*that < '0') || (*that > '9')))) {
627 while (*that
628 && (!ascii_iswhite(*that) || quotecount || parencount)
629 && (!((*that == '(' || *that == '[')
630 && !quotecount && !parencount && vi_lisp))) {
631 if (*that == '"') {
632 quotecount = !quotecount;
633 }
634 if (((*that == '(') || (*that == '[')) && !quotecount) {
635 parencount++;
636 }
637 if (((*that == ')') || (*that == ']')) && !quotecount) {
638 parencount--;
639 }
640 if ((*that == '\\') && (*(that + 1) != NUL)) {
641 amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount);
642 }
643
644 amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount);
645 }
646 }
647
648 while (ascii_iswhite(*that)) {
649 amount += lbr_chartabsize(line, that, (colnr_T)amount);
650 that++;
651 }
652
653 if (!*that || (*that == ';')) {
654 amount = firsttry;
655 }
656 }
657 }
658 }
659 }
660 } else {
661 amount = 0; // No matching '(' or '[' found, use zero indent.
662 }
663 curwin->w_cursor = realpos;
664
665 return amount;
666}
667
668
669static int lisp_match(char_u *p)
670{
671 char_u buf[LSIZE];
672 int len;
673 char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords;
674
675 while (*word != NUL) {
676 (void)copy_option_part(&word, buf, LSIZE, ",");
677 len = (int)STRLEN(buf);
678
679 if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) {
680 return true;
681 }
682 }
683 return false;
684}
685