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 | /// change.c: functions related to changing text |
5 | |
6 | #include "nvim/assert.h" |
7 | #include "nvim/buffer.h" |
8 | #include "nvim/buffer_updates.h" |
9 | #include "nvim/change.h" |
10 | #include "nvim/charset.h" |
11 | #include "nvim/cursor.h" |
12 | #include "nvim/diff.h" |
13 | #include "nvim/edit.h" |
14 | #include "nvim/eval.h" |
15 | #include "nvim/fileio.h" |
16 | #include "nvim/fold.h" |
17 | #include "nvim/indent.h" |
18 | #include "nvim/indent_c.h" |
19 | #include "nvim/mark.h" |
20 | #include "nvim/memline.h" |
21 | #include "nvim/misc1.h" |
22 | #include "nvim/move.h" |
23 | #include "nvim/option.h" |
24 | #include "nvim/screen.h" |
25 | #include "nvim/search.h" |
26 | #include "nvim/state.h" |
27 | #include "nvim/ui.h" |
28 | #include "nvim/undo.h" |
29 | |
30 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
31 | # include "change.c.generated.h" |
32 | #endif |
33 | |
34 | /// If the file is readonly, give a warning message with the first change. |
35 | /// Don't do this for autocommands. |
36 | /// Doesn't use emsg(), because it flushes the macro buffer. |
37 | /// If we have undone all changes b_changed will be false, but "b_did_warn" |
38 | /// will be true. |
39 | /// "col" is the column for the message; non-zero when in insert mode and |
40 | /// 'showmode' is on. |
41 | /// Careful: may trigger autocommands that reload the buffer. |
42 | void change_warning(int col) |
43 | { |
44 | static char *w_readonly = N_("W10: Warning: Changing a readonly file" ); |
45 | |
46 | if (curbuf->b_did_warn == false |
47 | && curbufIsChanged() == 0 |
48 | && !autocmd_busy |
49 | && curbuf->b_p_ro) { |
50 | curbuf_lock++; |
51 | apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, false, curbuf); |
52 | curbuf_lock--; |
53 | if (!curbuf->b_p_ro) { |
54 | return; |
55 | } |
56 | // Do what msg() does, but with a column offset if the warning should |
57 | // be after the mode message. |
58 | msg_start(); |
59 | if (msg_row == Rows - 1) { |
60 | msg_col = col; |
61 | } |
62 | msg_source(HL_ATTR(HLF_W)); |
63 | msg_ext_set_kind("wmsg" ); |
64 | MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); |
65 | set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1); |
66 | msg_clr_eos(); |
67 | (void)msg_end(); |
68 | if (msg_silent == 0 && !silent_mode && ui_active()) { |
69 | ui_flush(); |
70 | os_delay(1000L, true); // give the user time to think about it |
71 | } |
72 | curbuf->b_did_warn = true; |
73 | redraw_cmdline = false; // don't redraw and erase the message |
74 | if (msg_row < Rows - 1) { |
75 | showmode(); |
76 | } |
77 | } |
78 | } |
79 | |
80 | /// Call this function when something in the current buffer is changed. |
81 | /// |
82 | /// Most often called through changed_bytes() and changed_lines(), which also |
83 | /// mark the area of the display to be redrawn. |
84 | /// |
85 | /// Careful: may trigger autocommands that reload the buffer. |
86 | void changed(void) |
87 | { |
88 | if (!curbuf->b_changed) { |
89 | int save_msg_scroll = msg_scroll; |
90 | |
91 | // Give a warning about changing a read-only file. This may also |
92 | // check-out the file, thus change "curbuf"! |
93 | change_warning(0); |
94 | |
95 | // Create a swap file if that is wanted. |
96 | // Don't do this for "nofile" and "nowrite" buffer types. |
97 | if (curbuf->b_may_swap |
98 | && !bt_dontwrite(curbuf) |
99 | ) { |
100 | int save_need_wait_return = need_wait_return; |
101 | |
102 | need_wait_return = false; |
103 | ml_open_file(curbuf); |
104 | |
105 | // The ml_open_file() can cause an ATTENTION message. |
106 | // Wait two seconds, to make sure the user reads this unexpected |
107 | // message. Since we could be anywhere, call wait_return() now, |
108 | // and don't let the emsg() set msg_scroll. |
109 | if (need_wait_return && emsg_silent == 0) { |
110 | ui_flush(); |
111 | os_delay(2000L, true); |
112 | wait_return(true); |
113 | msg_scroll = save_msg_scroll; |
114 | } else { |
115 | need_wait_return = save_need_wait_return; |
116 | } |
117 | } |
118 | changed_internal(); |
119 | } |
120 | buf_inc_changedtick(curbuf); |
121 | |
122 | // If a pattern is highlighted, the position may now be invalid. |
123 | highlight_match = false; |
124 | } |
125 | |
126 | /// Internal part of changed(), no user interaction. |
127 | /// Also used for recovery. |
128 | void changed_internal(void) |
129 | { |
130 | curbuf->b_changed = true; |
131 | ml_setflags(curbuf); |
132 | check_status(curbuf); |
133 | redraw_tabline = true; |
134 | need_maketitle = true; // set window title later |
135 | } |
136 | |
137 | /// Common code for when a change was made. |
138 | /// See changed_lines() for the arguments. |
139 | /// Careful: may trigger autocommands that reload the buffer. |
140 | static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, |
141 | long xtra) |
142 | { |
143 | int i; |
144 | int cols; |
145 | pos_T *p; |
146 | int add; |
147 | |
148 | // mark the buffer as modified |
149 | changed(); |
150 | |
151 | if (curwin->w_p_diff && diff_internal()) { |
152 | curtab->tp_diff_update = true; |
153 | } |
154 | |
155 | // set the '. mark |
156 | if (!cmdmod.keepjumps) { |
157 | RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0); |
158 | |
159 | // Create a new entry if a new undo-able change was started or we |
160 | // don't have an entry yet. |
161 | if (curbuf->b_new_change || curbuf->b_changelistlen == 0) { |
162 | if (curbuf->b_changelistlen == 0) { |
163 | add = true; |
164 | } else { |
165 | // Don't create a new entry when the line number is the same |
166 | // as the last one and the column is not too far away. Avoids |
167 | // creating many entries for typing "xxxxx". |
168 | p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; |
169 | if (p->lnum != lnum) { |
170 | add = true; |
171 | } else { |
172 | cols = comp_textwidth(false); |
173 | if (cols == 0) { |
174 | cols = 79; |
175 | } |
176 | add = (p->col + cols < col || col + cols < p->col); |
177 | } |
178 | } |
179 | if (add) { |
180 | // This is the first of a new sequence of undo-able changes |
181 | // and it's at some distance of the last change. Use a new |
182 | // position in the changelist. |
183 | curbuf->b_new_change = false; |
184 | |
185 | if (curbuf->b_changelistlen == JUMPLISTSIZE) { |
186 | // changelist is full: remove oldest entry |
187 | curbuf->b_changelistlen = JUMPLISTSIZE - 1; |
188 | memmove(curbuf->b_changelist, curbuf->b_changelist + 1, |
189 | sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1)); |
190 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
191 | // Correct position in changelist for other windows on |
192 | // this buffer. |
193 | if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) { |
194 | wp->w_changelistidx--; |
195 | } |
196 | } |
197 | } |
198 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
199 | // For other windows, if the position in the changelist is |
200 | // at the end it stays at the end. |
201 | if (wp->w_buffer == curbuf |
202 | && wp->w_changelistidx == curbuf->b_changelistlen) { |
203 | wp->w_changelistidx++; |
204 | } |
205 | } |
206 | curbuf->b_changelistlen++; |
207 | } |
208 | } |
209 | curbuf->b_changelist[curbuf->b_changelistlen - 1] = |
210 | curbuf->b_last_change; |
211 | // The current window is always after the last change, so that "g," |
212 | // takes you back to it. |
213 | curwin->w_changelistidx = curbuf->b_changelistlen; |
214 | } |
215 | |
216 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
217 | if (wp->w_buffer == curbuf) { |
218 | // Mark this window to be redrawn later. |
219 | if (wp->w_redr_type < VALID) { |
220 | wp->w_redr_type = VALID; |
221 | } |
222 | |
223 | // Check if a change in the buffer has invalidated the cached |
224 | // values for the cursor. |
225 | // Update the folds for this window. Can't postpone this, because |
226 | // a following operator might work on the whole fold: ">>dd". |
227 | foldUpdate(wp, lnum, lnume + xtra - 1); |
228 | |
229 | // The change may cause lines above or below the change to become |
230 | // included in a fold. Set lnum/lnume to the first/last line that |
231 | // might be displayed differently. |
232 | // Set w_cline_folded here as an efficient way to update it when |
233 | // inserting lines just above a closed fold. */ |
234 | bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL); |
235 | if (wp->w_cursor.lnum == lnum) { |
236 | wp->w_cline_folded = folded; |
237 | } |
238 | folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL); |
239 | if (wp->w_cursor.lnum == lnume) { |
240 | wp->w_cline_folded = folded; |
241 | } |
242 | |
243 | // If the changed line is in a range of previously folded lines, |
244 | // compare with the first line in that range. |
245 | if (wp->w_cursor.lnum <= lnum) { |
246 | i = find_wl_entry(wp, lnum); |
247 | if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) { |
248 | changed_line_abv_curs_win(wp); |
249 | } |
250 | } |
251 | |
252 | if (wp->w_cursor.lnum > lnum) { |
253 | changed_line_abv_curs_win(wp); |
254 | } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) { |
255 | changed_cline_bef_curs_win(wp); |
256 | } |
257 | if (wp->w_botline >= lnum) { |
258 | // Assume that botline doesn't change (inserted lines make |
259 | // other lines scroll down below botline). |
260 | approximate_botline_win(wp); |
261 | } |
262 | |
263 | // Check if any w_lines[] entries have become invalid. |
264 | // For entries below the change: Correct the lnums for |
265 | // inserted/deleted lines. Makes it possible to stop displaying |
266 | // after the change. |
267 | for (i = 0; i < wp->w_lines_valid; i++) { |
268 | if (wp->w_lines[i].wl_valid) { |
269 | if (wp->w_lines[i].wl_lnum >= lnum) { |
270 | if (wp->w_lines[i].wl_lnum < lnume) { |
271 | // line included in change |
272 | wp->w_lines[i].wl_valid = false; |
273 | } else if (xtra != 0) { |
274 | // line below change |
275 | wp->w_lines[i].wl_lnum += xtra; |
276 | wp->w_lines[i].wl_lastlnum += xtra; |
277 | } |
278 | } else if (wp->w_lines[i].wl_lastlnum >= lnum) { |
279 | // change somewhere inside this range of folded lines, |
280 | // may need to be redrawn |
281 | wp->w_lines[i].wl_valid = false; |
282 | } |
283 | } |
284 | } |
285 | |
286 | // Take care of side effects for setting w_topline when folds have |
287 | // changed. Esp. when the buffer was changed in another window. |
288 | if (hasAnyFolding(wp)) { |
289 | set_topline(wp, wp->w_topline); |
290 | } |
291 | |
292 | // Relative numbering may require updating more. Cursor line |
293 | // highlighting probably needs to be updated if it's below the |
294 | // change. |
295 | if (wp->w_p_rnu |
296 | || (wp->w_p_cul && lnum <= wp->w_last_cursorline)) { |
297 | redraw_win_later(wp, SOME_VALID); |
298 | } |
299 | } |
300 | } |
301 | |
302 | // Call update_screen() later, which checks out what needs to be redrawn, |
303 | // since it notices b_mod_set and then uses b_mod_*. |
304 | if (must_redraw < VALID) { |
305 | must_redraw = VALID; |
306 | } |
307 | |
308 | // when the cursor line is changed always trigger CursorMoved |
309 | if (lnum <= curwin->w_cursor.lnum |
310 | && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) { |
311 | curwin->w_last_cursormoved.lnum = 0; |
312 | } |
313 | } |
314 | |
315 | static void changedOneline(buf_T *buf, linenr_T lnum) |
316 | { |
317 | if (buf->b_mod_set) { |
318 | // find the maximum area that must be redisplayed |
319 | if (lnum < buf->b_mod_top) { |
320 | buf->b_mod_top = lnum; |
321 | } else if (lnum >= buf->b_mod_bot) { |
322 | buf->b_mod_bot = lnum + 1; |
323 | } |
324 | } else { |
325 | // set the area that must be redisplayed to one line |
326 | buf->b_mod_set = true; |
327 | buf->b_mod_top = lnum; |
328 | buf->b_mod_bot = lnum + 1; |
329 | buf->b_mod_xlines = 0; |
330 | } |
331 | } |
332 | |
333 | /// Changed bytes within a single line for the current buffer. |
334 | /// - marks the windows on this buffer to be redisplayed |
335 | /// - marks the buffer changed by calling changed() |
336 | /// - invalidates cached values |
337 | /// Careful: may trigger autocommands that reload the buffer. |
338 | void changed_bytes(linenr_T lnum, colnr_T col) |
339 | { |
340 | changedOneline(curbuf, lnum); |
341 | changed_common(lnum, col, lnum + 1, 0L); |
342 | // notify any channels that are watching |
343 | buf_updates_send_changes(curbuf, lnum, 1, 1, true); |
344 | |
345 | // Diff highlighting in other diff windows may need to be updated too. |
346 | if (curwin->w_p_diff) { |
347 | linenr_T wlnum; |
348 | |
349 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
350 | if (wp->w_p_diff && wp != curwin) { |
351 | redraw_win_later(wp, VALID); |
352 | wlnum = diff_lnum_win(lnum, wp); |
353 | if (wlnum > 0) { |
354 | changedOneline(wp->w_buffer, wlnum); |
355 | } |
356 | } |
357 | } |
358 | } |
359 | } |
360 | |
361 | /// Appended "count" lines below line "lnum" in the current buffer. |
362 | /// Must be called AFTER the change and after mark_adjust(). |
363 | /// Takes care of marking the buffer to be redrawn and sets the changed flag. |
364 | void appended_lines(linenr_T lnum, long count) |
365 | { |
366 | changed_lines(lnum + 1, 0, lnum + 1, count, true); |
367 | } |
368 | |
369 | /// Like appended_lines(), but adjust marks first. |
370 | void appended_lines_mark(linenr_T lnum, long count) |
371 | { |
372 | // Skip mark_adjust when adding a line after the last one, there can't |
373 | // be marks there. But it's still needed in diff mode. |
374 | if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { |
375 | mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); |
376 | } |
377 | changed_lines(lnum + 1, 0, lnum + 1, count, true); |
378 | } |
379 | |
380 | /// Deleted "count" lines at line "lnum" in the current buffer. |
381 | /// Must be called AFTER the change and after mark_adjust(). |
382 | /// Takes care of marking the buffer to be redrawn and sets the changed flag. |
383 | void deleted_lines(linenr_T lnum, long count) |
384 | { |
385 | changed_lines(lnum, 0, lnum + count, -count, true); |
386 | } |
387 | |
388 | /// Like deleted_lines(), but adjust marks first. |
389 | /// Make sure the cursor is on a valid line before calling, a GUI callback may |
390 | /// be triggered to display the cursor. |
391 | void deleted_lines_mark(linenr_T lnum, long count) |
392 | { |
393 | mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); |
394 | changed_lines(lnum, 0, lnum + count, -count, true); |
395 | } |
396 | |
397 | /// Marks the area to be redrawn after a change. |
398 | /// |
399 | /// @param buf the buffer where lines were changed |
400 | /// @param lnum first line with change |
401 | /// @param lnume line below last changed line |
402 | /// @param xtra number of extra lines (negative when deleting) |
403 | void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra) |
404 | { |
405 | if (buf->b_mod_set) { |
406 | // find the maximum area that must be redisplayed |
407 | if (lnum < buf->b_mod_top) { |
408 | buf->b_mod_top = lnum; |
409 | } |
410 | if (lnum < buf->b_mod_bot) { |
411 | // adjust old bot position for xtra lines |
412 | buf->b_mod_bot += xtra; |
413 | if (buf->b_mod_bot < lnum) { |
414 | buf->b_mod_bot = lnum; |
415 | } |
416 | } |
417 | if (lnume + xtra > buf->b_mod_bot) { |
418 | buf->b_mod_bot = lnume + xtra; |
419 | } |
420 | buf->b_mod_xlines += xtra; |
421 | } else { |
422 | // set the area that must be redisplayed |
423 | buf->b_mod_set = true; |
424 | buf->b_mod_top = lnum; |
425 | buf->b_mod_bot = lnume + xtra; |
426 | buf->b_mod_xlines = xtra; |
427 | } |
428 | } |
429 | |
430 | /// Changed lines for the current buffer. |
431 | /// Must be called AFTER the change and after mark_adjust(). |
432 | /// - mark the buffer changed by calling changed() |
433 | /// - mark the windows on this buffer to be redisplayed |
434 | /// - invalidate cached values |
435 | /// "lnum" is the first line that needs displaying, "lnume" the first line |
436 | /// below the changed lines (BEFORE the change). |
437 | /// When only inserting lines, "lnum" and "lnume" are equal. |
438 | /// Takes care of calling changed() and updating b_mod_*. |
439 | /// Careful: may trigger autocommands that reload the buffer. |
440 | void |
441 | changed_lines( |
442 | linenr_T lnum, // first line with change |
443 | colnr_T col, // column in first line with change |
444 | linenr_T lnume, // line below last changed line |
445 | long xtra, // number of extra lines (negative when deleting) |
446 | bool do_buf_event // some callers like undo/redo call changed_lines() |
447 | // and then increment changedtick *again*. This flag |
448 | // allows these callers to send the nvim_buf_lines_event |
449 | // events after they're done modifying changedtick. |
450 | ) |
451 | { |
452 | changed_lines_buf(curbuf, lnum, lnume, xtra); |
453 | |
454 | if (xtra == 0 && curwin->w_p_diff && !diff_internal()) { |
455 | // When the number of lines doesn't change then mark_adjust() isn't |
456 | // called and other diff buffers still need to be marked for |
457 | // displaying. |
458 | linenr_T wlnum; |
459 | |
460 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
461 | if (wp->w_p_diff && wp != curwin) { |
462 | redraw_win_later(wp, VALID); |
463 | wlnum = diff_lnum_win(lnum, wp); |
464 | if (wlnum > 0) { |
465 | changed_lines_buf(wp->w_buffer, wlnum, |
466 | lnume - lnum + wlnum, 0L); |
467 | } |
468 | } |
469 | } |
470 | } |
471 | |
472 | changed_common(lnum, col, lnume, xtra); |
473 | |
474 | if (do_buf_event) { |
475 | int64_t num_added = (int64_t)(lnume + xtra - lnum); |
476 | int64_t num_removed = lnume - lnum; |
477 | buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); |
478 | } |
479 | } |
480 | |
481 | /// Called when the changed flag must be reset for buffer `buf`. |
482 | /// When `ff` is true also reset 'fileformat'. |
483 | /// When `always_inc_changedtick` is true b:changedtick is incremented even |
484 | /// when the changed flag was off. |
485 | void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) |
486 | { |
487 | if (buf->b_changed || (ff && file_ff_differs(buf, false))) { |
488 | buf->b_changed = false; |
489 | ml_setflags(buf); |
490 | if (ff) { |
491 | save_file_ff(buf); |
492 | } |
493 | check_status(buf); |
494 | redraw_tabline = true; |
495 | need_maketitle = true; // set window title later |
496 | buf_inc_changedtick(buf); |
497 | } else if (always_inc_changedtick) { |
498 | buf_inc_changedtick(buf); |
499 | } |
500 | } |
501 | |
502 | /// Insert string "p" at the cursor position. Stops at a NUL byte. |
503 | /// Handles Replace mode and multi-byte characters. |
504 | void ins_bytes(char_u *p) |
505 | { |
506 | ins_bytes_len(p, STRLEN(p)); |
507 | } |
508 | |
509 | /// Insert string "p" with length "len" at the cursor position. |
510 | /// Handles Replace mode and multi-byte characters. |
511 | void ins_bytes_len(char_u *p, size_t len) |
512 | { |
513 | size_t n; |
514 | for (size_t i = 0; i < len; i += n) { |
515 | if (enc_utf8) { |
516 | // avoid reading past p[len] |
517 | n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i)); |
518 | } else { |
519 | n = (size_t)(*mb_ptr2len)(p + i); |
520 | } |
521 | ins_char_bytes(p + i, n); |
522 | } |
523 | } |
524 | |
525 | /// Insert or replace a single character at the cursor position. |
526 | /// When in REPLACE or VREPLACE mode, replace any existing character. |
527 | /// Caller must have prepared for undo. |
528 | /// For multi-byte characters we get the whole character, the caller must |
529 | /// convert bytes to a character. |
530 | void ins_char(int c) |
531 | { |
532 | char_u buf[MB_MAXBYTES + 1]; |
533 | size_t n = (size_t)utf_char2bytes(c, buf); |
534 | |
535 | // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. |
536 | // Happens for CTRL-Vu9900. |
537 | if (buf[0] == 0) { |
538 | buf[0] = '\n'; |
539 | } |
540 | ins_char_bytes(buf, n); |
541 | } |
542 | |
543 | void ins_char_bytes(char_u *buf, size_t charlen) |
544 | { |
545 | // Break tabs if needed. |
546 | if (virtual_active() && curwin->w_cursor.coladd > 0) { |
547 | coladvance_force(getviscol()); |
548 | } |
549 | |
550 | size_t col = (size_t)curwin->w_cursor.col; |
551 | linenr_T lnum = curwin->w_cursor.lnum; |
552 | char_u *oldp = ml_get(lnum); |
553 | size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL |
554 | |
555 | // The lengths default to the values for when not replacing. |
556 | size_t oldlen = 0; // nr of bytes inserted |
557 | size_t newlen = charlen; // nr of bytes deleted (0 when not replacing) |
558 | |
559 | if (State & REPLACE_FLAG) { |
560 | if (State & VREPLACE_FLAG) { |
561 | // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag. |
562 | // Returns the old value of list, so when finished, |
563 | // curwin->w_p_list should be set back to this. |
564 | int old_list = curwin->w_p_list; |
565 | if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { |
566 | curwin->w_p_list = false; |
567 | } |
568 | // In virtual replace mode each character may replace one or more |
569 | // characters (zero if it's a TAB). Count the number of bytes to |
570 | // be deleted to make room for the new character, counting screen |
571 | // cells. May result in adding spaces to fill a gap. |
572 | colnr_T vcol; |
573 | getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL); |
574 | colnr_T new_vcol = vcol + chartabsize(buf, vcol); |
575 | while (oldp[col + oldlen] != NUL && vcol < new_vcol) { |
576 | vcol += chartabsize(oldp + col + oldlen, vcol); |
577 | // Don't need to remove a TAB that takes us to the right |
578 | // position. |
579 | if (vcol > new_vcol && oldp[col + oldlen] == TAB) { |
580 | break; |
581 | } |
582 | oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen); |
583 | // Deleted a bit too much, insert spaces. |
584 | if (vcol > new_vcol) { |
585 | newlen += (size_t)(vcol - new_vcol); |
586 | } |
587 | } |
588 | curwin->w_p_list = old_list; |
589 | } else if (oldp[col] != NUL) { |
590 | // normal replace |
591 | oldlen = (size_t)(*mb_ptr2len)(oldp + col); |
592 | } |
593 | |
594 | |
595 | // Push the replaced bytes onto the replace stack, so that they can be |
596 | // put back when BS is used. The bytes of a multi-byte character are |
597 | // done the other way around, so that the first byte is popped off |
598 | // first (it tells the byte length of the character). |
599 | replace_push(NUL); |
600 | for (size_t i = 0; i < oldlen; i++) { |
601 | i += (size_t)replace_push_mb(oldp + col + i) - 1; |
602 | } |
603 | } |
604 | |
605 | char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); |
606 | |
607 | // Copy bytes before the cursor. |
608 | if (col > 0) { |
609 | memmove(newp, oldp, (size_t)col); |
610 | } |
611 | |
612 | // Copy bytes after the changed character(s). |
613 | char_u *p = newp + col; |
614 | if (linelen > col + oldlen) { |
615 | memmove(p + newlen, oldp + col + oldlen, |
616 | (size_t)(linelen - col - oldlen)); |
617 | } |
618 | |
619 | // Insert or overwrite the new character. |
620 | memmove(p, buf, charlen); |
621 | |
622 | // Fill with spaces when necessary. |
623 | for (size_t i = charlen; i < newlen; i++) { |
624 | p[i] = ' '; |
625 | } |
626 | |
627 | // Replace the line in the buffer. |
628 | ml_replace(lnum, newp, false); |
629 | |
630 | // mark the buffer as changed and prepare for displaying |
631 | changed_bytes(lnum, (colnr_T)col); |
632 | |
633 | // If we're in Insert or Replace mode and 'showmatch' is set, then briefly |
634 | // show the match for right parens and braces. |
635 | if (p_sm && (State & INSERT) |
636 | && msg_silent == 0 |
637 | && !ins_compl_active() |
638 | ) { |
639 | showmatch(utf_ptr2char(buf)); |
640 | } |
641 | |
642 | if (!p_ri || (State & REPLACE_FLAG)) { |
643 | // Normal insert: move cursor right |
644 | curwin->w_cursor.col += (int)charlen; |
645 | } |
646 | // TODO(Bram): should try to update w_row here, to avoid recomputing it later. |
647 | } |
648 | |
649 | /// Insert a string at the cursor position. |
650 | /// Note: Does NOT handle Replace mode. |
651 | /// Caller must have prepared for undo. |
652 | void ins_str(char_u *s) |
653 | { |
654 | char_u *oldp, *newp; |
655 | int newlen = (int)STRLEN(s); |
656 | int oldlen; |
657 | colnr_T col; |
658 | linenr_T lnum = curwin->w_cursor.lnum; |
659 | |
660 | if (virtual_active() && curwin->w_cursor.coladd > 0) { |
661 | coladvance_force(getviscol()); |
662 | } |
663 | |
664 | col = curwin->w_cursor.col; |
665 | oldp = ml_get(lnum); |
666 | oldlen = (int)STRLEN(oldp); |
667 | |
668 | newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); |
669 | if (col > 0) { |
670 | memmove(newp, oldp, (size_t)col); |
671 | } |
672 | memmove(newp + col, s, (size_t)newlen); |
673 | int bytes = oldlen - col + 1; |
674 | assert(bytes >= 0); |
675 | memmove(newp + col + newlen, oldp + col, (size_t)bytes); |
676 | ml_replace(lnum, newp, false); |
677 | changed_bytes(lnum, col); |
678 | curwin->w_cursor.col += newlen; |
679 | } |
680 | |
681 | // Delete one character under the cursor. |
682 | // If "fixpos" is true, don't leave the cursor on the NUL after the line. |
683 | // Caller must have prepared for undo. |
684 | // |
685 | // return FAIL for failure, OK otherwise |
686 | int del_char(bool fixpos) |
687 | { |
688 | // Make sure the cursor is at the start of a character. |
689 | mb_adjust_cursor(); |
690 | if (*get_cursor_pos_ptr() == NUL) { |
691 | return FAIL; |
692 | } |
693 | return del_chars(1L, fixpos); |
694 | } |
695 | |
696 | /// Like del_bytes(), but delete characters instead of bytes. |
697 | int del_chars(long count, int fixpos) |
698 | { |
699 | int bytes = 0; |
700 | long i; |
701 | char_u *p; |
702 | int l; |
703 | |
704 | p = get_cursor_pos_ptr(); |
705 | for (i = 0; i < count && *p != NUL; i++) { |
706 | l = (*mb_ptr2len)(p); |
707 | bytes += l; |
708 | p += l; |
709 | } |
710 | return del_bytes(bytes, fixpos, true); |
711 | } |
712 | |
713 | /// Delete "count" bytes under the cursor. |
714 | /// If "fixpos" is true, don't leave the cursor on the NUL after the line. |
715 | /// Caller must have prepared for undo. |
716 | /// |
717 | /// @param count number of bytes to be deleted |
718 | /// @param fixpos_arg leave the cursor on the NUL after the line |
719 | /// @param use_delcombine 'delcombine' option applies |
720 | /// |
721 | /// @return FAIL for failure, OK otherwise |
722 | int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) |
723 | { |
724 | linenr_T lnum = curwin->w_cursor.lnum; |
725 | colnr_T col = curwin->w_cursor.col; |
726 | bool fixpos = fixpos_arg; |
727 | char_u *oldp = ml_get(lnum); |
728 | colnr_T oldlen = (colnr_T)STRLEN(oldp); |
729 | |
730 | // Can't do anything when the cursor is on the NUL after the line. |
731 | if (col >= oldlen) { |
732 | return FAIL; |
733 | } |
734 | // If "count" is zero there is nothing to do. |
735 | if (count == 0) { |
736 | return OK; |
737 | } |
738 | // If "count" is negative the caller must be doing something wrong. |
739 | if (count < 1) { |
740 | IEMSGN("E950: Invalid count for del_bytes(): %ld" , count); |
741 | return FAIL; |
742 | } |
743 | |
744 | // If 'delcombine' is set and deleting (less than) one character, only |
745 | // delete the last combining character. |
746 | if (p_deco && use_delcombine && enc_utf8 |
747 | && utfc_ptr2len(oldp + col) >= count) { |
748 | int cc[MAX_MCO]; |
749 | int n; |
750 | |
751 | (void)utfc_ptr2char(oldp + col, cc); |
752 | if (cc[0] != NUL) { |
753 | // Find the last composing char, there can be several. |
754 | n = col; |
755 | do { |
756 | col = n; |
757 | count = utf_ptr2len(oldp + n); |
758 | n += count; |
759 | } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n)); |
760 | fixpos = false; |
761 | } |
762 | } |
763 | |
764 | // When count is too big, reduce it. |
765 | int movelen = oldlen - col - count + 1; // includes trailing NUL |
766 | if (movelen <= 1) { |
767 | // If we just took off the last character of a non-blank line, and |
768 | // fixpos is TRUE, we don't want to end up positioned at the NUL, |
769 | // unless "restart_edit" is set or 'virtualedit' contains "onemore". |
770 | if (col > 0 && fixpos && restart_edit == 0 |
771 | && (ve_flags & VE_ONEMORE) == 0 |
772 | ) { |
773 | curwin->w_cursor.col--; |
774 | curwin->w_cursor.coladd = 0; |
775 | curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); |
776 | } |
777 | count = oldlen - col; |
778 | movelen = 1; |
779 | } |
780 | |
781 | // If the old line has been allocated the deletion can be done in the |
782 | // existing line. Otherwise a new line has to be allocated. |
783 | bool was_alloced = ml_line_alloced(); // check if oldp was allocated |
784 | char_u *newp; |
785 | if (was_alloced) { |
786 | ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); |
787 | newp = oldp; // use same allocated memory |
788 | } else { // need to allocate a new line |
789 | newp = xmalloc((size_t)(oldlen + 1 - count)); |
790 | memmove(newp, oldp, (size_t)col); |
791 | } |
792 | memmove(newp + col, oldp + col + count, (size_t)movelen); |
793 | if (!was_alloced) { |
794 | ml_replace(lnum, newp, false); |
795 | } |
796 | |
797 | // mark the buffer as changed and prepare for displaying |
798 | changed_bytes(lnum, curwin->w_cursor.col); |
799 | |
800 | return OK; |
801 | } |
802 | |
803 | /// Copy the indent from ptr to the current line (and fill to size). |
804 | /// Leaves the cursor on the first non-blank in the line. |
805 | /// @return true if the line was changed. |
806 | int copy_indent(int size, char_u *src) |
807 | { |
808 | char_u *p = NULL; |
809 | char_u *line = NULL; |
810 | char_u *s; |
811 | int todo; |
812 | int ind_len; |
813 | int line_len = 0; |
814 | int tab_pad; |
815 | int ind_done; |
816 | int round; |
817 | |
818 | // Round 1: compute the number of characters needed for the indent |
819 | // Round 2: copy the characters. |
820 | for (round = 1; round <= 2; round++) { |
821 | todo = size; |
822 | ind_len = 0; |
823 | ind_done = 0; |
824 | s = src; |
825 | |
826 | // Count/copy the usable portion of the source line. |
827 | while (todo > 0 && ascii_iswhite(*s)) { |
828 | if (*s == TAB) { |
829 | tab_pad = (int)curbuf->b_p_ts |
830 | - (ind_done % (int)curbuf->b_p_ts); |
831 | |
832 | // Stop if this tab will overshoot the target. |
833 | if (todo < tab_pad) { |
834 | break; |
835 | } |
836 | todo -= tab_pad; |
837 | ind_done += tab_pad; |
838 | } else { |
839 | todo--; |
840 | ind_done++; |
841 | } |
842 | ind_len++; |
843 | |
844 | if (p != NULL) { |
845 | *p++ = *s; |
846 | } |
847 | s++; |
848 | } |
849 | |
850 | // Fill to next tabstop with a tab, if possible. |
851 | tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); |
852 | |
853 | if ((todo >= tab_pad) && !curbuf->b_p_et) { |
854 | todo -= tab_pad; |
855 | ind_len++; |
856 | |
857 | if (p != NULL) { |
858 | *p++ = TAB; |
859 | } |
860 | } |
861 | |
862 | // Add tabs required for indent. |
863 | while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) { |
864 | todo -= (int)curbuf->b_p_ts; |
865 | ind_len++; |
866 | |
867 | if (p != NULL) { |
868 | *p++ = TAB; |
869 | } |
870 | } |
871 | |
872 | // Count/add spaces required for indent. |
873 | while (todo > 0) { |
874 | todo--; |
875 | ind_len++; |
876 | |
877 | if (p != NULL) { |
878 | *p++ = ' '; |
879 | } |
880 | } |
881 | |
882 | if (p == NULL) { |
883 | // Allocate memory for the result: the copied indent, new indent |
884 | // and the rest of the line. |
885 | line_len = (int)STRLEN(get_cursor_line_ptr()) + 1; |
886 | assert(ind_len + line_len >= 0); |
887 | size_t line_size; |
888 | STRICT_ADD(ind_len, line_len, &line_size, size_t); |
889 | line = xmalloc(line_size); |
890 | p = line; |
891 | } |
892 | } |
893 | |
894 | // Append the original line |
895 | memmove(p, get_cursor_line_ptr(), (size_t)line_len); |
896 | |
897 | // Replace the line |
898 | ml_replace(curwin->w_cursor.lnum, line, false); |
899 | |
900 | // Put the cursor after the indent. |
901 | curwin->w_cursor.col = ind_len; |
902 | return true; |
903 | } |
904 | |
905 | /// open_line: Add a new line below or above the current line. |
906 | /// |
907 | /// For VREPLACE mode, we only add a new line when we get to the end of the |
908 | /// file, otherwise we just start replacing the next line. |
909 | /// |
910 | /// Caller must take care of undo. Since VREPLACE may affect any number of |
911 | /// lines however, it may call u_save_cursor() again when starting to change a |
912 | /// new line. |
913 | /// "flags": OPENLINE_DELSPACES delete spaces after cursor |
914 | /// OPENLINE_DO_COM format comments |
915 | /// OPENLINE_KEEPTRAIL keep trailing spaces |
916 | /// OPENLINE_MARKFIX adjust mark positions after the line break |
917 | /// OPENLINE_COM_LIST format comments with list or 2nd line indent |
918 | /// |
919 | /// "second_line_indent": indent for after ^^D in Insert mode or if flag |
920 | /// OPENLINE_COM_LIST |
921 | /// |
922 | /// @return true on success, false on failure |
923 | int open_line( |
924 | int dir, // FORWARD or BACKWARD |
925 | int flags, |
926 | int second_line_indent |
927 | ) |
928 | { |
929 | char_u *next_line = NULL; // copy of the next line |
930 | char_u * = NULL; // what goes to next line |
931 | colnr_T less_cols = 0; // less columns for mark in new line |
932 | colnr_T less_cols_off = 0; // columns to skip for mark adjust |
933 | pos_T old_cursor; // old cursor position |
934 | colnr_T newcol = 0; // new cursor column |
935 | int newindent = 0; // auto-indent of the new line |
936 | bool trunc_line = false; // truncate current line afterwards |
937 | bool retval = false; // return value |
938 | int = 0; // length of p_extra string |
939 | int lead_len; // length of comment leader |
940 | char_u *lead_flags; // position in 'comments' for comment leader |
941 | char_u *leader = NULL; // copy of comment leader |
942 | char_u *allocated = NULL; // allocated memory |
943 | char_u *p; |
944 | char_u saved_char = NUL; // init for GCC |
945 | pos_T *pos; |
946 | bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin |
947 | && *curbuf->b_p_inde == NUL); |
948 | bool no_si = false; // reset did_si afterwards |
949 | int first_char = NUL; // init for GCC |
950 | int vreplace_mode; |
951 | bool did_append; // appended a new line |
952 | int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting |
953 | |
954 | // make a copy of the current line so we can mess with it |
955 | char_u *saved_line = vim_strsave(get_cursor_line_ptr()); |
956 | |
957 | if (State & VREPLACE_FLAG) { |
958 | // With VREPLACE we make a copy of the next line, which we will be |
959 | // starting to replace. First make the new line empty and let vim play |
960 | // with the indenting and comment leader to its heart's content. Then |
961 | // we grab what it ended up putting on the new line, put back the |
962 | // original line, and call ins_char() to put each new character onto |
963 | // the line, replacing what was there before and pushing the right |
964 | // stuff onto the replace stack. -- webb. |
965 | if (curwin->w_cursor.lnum < orig_line_count) { |
966 | next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); |
967 | } else { |
968 | next_line = vim_strsave((char_u *)"" ); |
969 | } |
970 | |
971 | // In VREPLACE mode, a NL replaces the rest of the line, and starts |
972 | // replacing the next line, so push all of the characters left on the |
973 | // line onto the replace stack. We'll push any other characters that |
974 | // might be replaced at the start of the next line (due to autoindent |
975 | // etc) a bit later. |
976 | replace_push(NUL); // Call twice because BS over NL expects it |
977 | replace_push(NUL); |
978 | p = saved_line + curwin->w_cursor.col; |
979 | while (*p != NUL) { |
980 | p += replace_push_mb(p); |
981 | } |
982 | saved_line[curwin->w_cursor.col] = NUL; |
983 | } |
984 | |
985 | if ((State & INSERT) |
986 | && !(State & VREPLACE_FLAG) |
987 | ) { |
988 | p_extra = saved_line + curwin->w_cursor.col; |
989 | if (do_si) { // need first char after new line break |
990 | p = skipwhite(p_extra); |
991 | first_char = *p; |
992 | } |
993 | extra_len = (int)STRLEN(p_extra); |
994 | saved_char = *p_extra; |
995 | *p_extra = NUL; |
996 | } |
997 | |
998 | u_clearline(); // cannot do "U" command when adding lines |
999 | did_si = false; |
1000 | ai_col = 0; |
1001 | |
1002 | // If we just did an auto-indent, then we didn't type anything on |
1003 | // the prior line, and it should be truncated. Do this even if 'ai' is not |
1004 | // set because automatically inserting a comment leader also sets did_ai. |
1005 | if (dir == FORWARD && did_ai) { |
1006 | trunc_line = true; |
1007 | } |
1008 | |
1009 | // If 'autoindent' and/or 'smartindent' is set, try to figure out what |
1010 | // indent to use for the new line. |
1011 | if (curbuf->b_p_ai |
1012 | || do_si |
1013 | ) { |
1014 | // count white space on current line |
1015 | newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false); |
1016 | if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) { |
1017 | newindent = second_line_indent; // for ^^D command in insert mode |
1018 | } |
1019 | |
1020 | // Do smart indenting. |
1021 | // In insert/replace mode (only when dir == FORWARD) |
1022 | // we may move some text to the next line. If it starts with '{' |
1023 | // don't add an indent. Fixes inserting a NL before '{' in line |
1024 | // "if (condition) {" |
1025 | if (!trunc_line && do_si && *saved_line != NUL |
1026 | && (p_extra == NULL || first_char != '{')) { |
1027 | char_u *ptr; |
1028 | char_u last_char; |
1029 | |
1030 | old_cursor = curwin->w_cursor; |
1031 | ptr = saved_line; |
1032 | if (flags & OPENLINE_DO_COM) { |
1033 | lead_len = get_leader_len(ptr, NULL, false, true); |
1034 | } else { |
1035 | lead_len = 0; |
1036 | } |
1037 | if (dir == FORWARD) { |
1038 | // Skip preprocessor directives, unless they are |
1039 | // recognised as comments. |
1040 | if (lead_len == 0 && ptr[0] == '#') { |
1041 | while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { |
1042 | ptr = ml_get(--curwin->w_cursor.lnum); |
1043 | } |
1044 | newindent = get_indent(); |
1045 | } |
1046 | if (flags & OPENLINE_DO_COM) { |
1047 | lead_len = get_leader_len(ptr, NULL, false, true); |
1048 | } else { |
1049 | lead_len = 0; |
1050 | } |
1051 | if (lead_len > 0) { |
1052 | // This case gets the following right: |
1053 | // \* |
1054 | // * A comment (read '\' as '/'). |
1055 | // */ |
1056 | // #define IN_THE_WAY |
1057 | // This should line up here; |
1058 | p = skipwhite(ptr); |
1059 | if (p[0] == '/' && p[1] == '*') { |
1060 | p++; |
1061 | } |
1062 | if (p[0] == '*') { |
1063 | for (p++; *p; p++) { |
1064 | if (p[0] == '/' && p[-1] == '*') { |
1065 | // End of C comment, indent should line up |
1066 | // with the line containing the start of |
1067 | // the comment |
1068 | curwin->w_cursor.col = (colnr_T)(p - ptr); |
1069 | if ((pos = findmatch(NULL, NUL)) != NULL) { |
1070 | curwin->w_cursor.lnum = pos->lnum; |
1071 | newindent = get_indent(); |
1072 | } |
1073 | } |
1074 | } |
1075 | } |
1076 | } else { // Not a comment line |
1077 | // Find last non-blank in line |
1078 | p = ptr + STRLEN(ptr) - 1; |
1079 | while (p > ptr && ascii_iswhite(*p)) { |
1080 | p--; |
1081 | } |
1082 | last_char = *p; |
1083 | |
1084 | // find the character just before the '{' or ';' |
1085 | if (last_char == '{' || last_char == ';') { |
1086 | if (p > ptr) { |
1087 | p--; |
1088 | } |
1089 | while (p > ptr && ascii_iswhite(*p)) { |
1090 | p--; |
1091 | } |
1092 | } |
1093 | // Try to catch lines that are split over multiple |
1094 | // lines. eg: |
1095 | // if (condition && |
1096 | // condition) { |
1097 | // Should line up here! |
1098 | // } |
1099 | if (*p == ')') { |
1100 | curwin->w_cursor.col = (colnr_T)(p - ptr); |
1101 | if ((pos = findmatch(NULL, '(')) != NULL) { |
1102 | curwin->w_cursor.lnum = pos->lnum; |
1103 | newindent = get_indent(); |
1104 | ptr = get_cursor_line_ptr(); |
1105 | } |
1106 | } |
1107 | // If last character is '{' do indent, without |
1108 | // checking for "if" and the like. |
1109 | if (last_char == '{') { |
1110 | did_si = true; // do indent |
1111 | no_si = true; // don't delete it when '{' typed |
1112 | // Look for "if" and the like, use 'cinwords'. |
1113 | // Don't do this if the previous line ended in ';' or |
1114 | // '}'. |
1115 | } else if (last_char != ';' && last_char != '}' |
1116 | && cin_is_cinword(ptr)) { |
1117 | did_si = true; |
1118 | } |
1119 | } |
1120 | } else { // dir == BACKWARD |
1121 | // Skip preprocessor directives, unless they are |
1122 | // recognised as comments. |
1123 | if (lead_len == 0 && ptr[0] == '#') { |
1124 | bool was_backslashed = false; |
1125 | |
1126 | while ((ptr[0] == '#' || was_backslashed) |
1127 | && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { |
1128 | if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') { |
1129 | was_backslashed = true; |
1130 | } else { |
1131 | was_backslashed = false; |
1132 | } |
1133 | ptr = ml_get(++curwin->w_cursor.lnum); |
1134 | } |
1135 | if (was_backslashed) { |
1136 | newindent = 0; // Got to end of file |
1137 | } else { |
1138 | newindent = get_indent(); |
1139 | } |
1140 | } |
1141 | p = skipwhite(ptr); |
1142 | if (*p == '}') { // if line starts with '}': do indent |
1143 | did_si = true; |
1144 | } else { // can delete indent when '{' typed |
1145 | can_si_back = true; |
1146 | } |
1147 | } |
1148 | curwin->w_cursor = old_cursor; |
1149 | } |
1150 | if (do_si) { |
1151 | can_si = true; |
1152 | } |
1153 | |
1154 | did_ai = true; |
1155 | } |
1156 | |
1157 | // Find out if the current line starts with a comment leader. |
1158 | // This may then be inserted in front of the new line. |
1159 | end_comment_pending = NUL; |
1160 | if (flags & OPENLINE_DO_COM) { |
1161 | lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); |
1162 | } else { |
1163 | lead_len = 0; |
1164 | } |
1165 | if (lead_len > 0) { |
1166 | char_u *lead_repl = NULL; // replaces comment leader |
1167 | int lead_repl_len = 0; // length of *lead_repl |
1168 | char_u lead_middle[COM_MAX_LEN]; // middle-comment string |
1169 | char_u lead_end[COM_MAX_LEN]; // end-comment string |
1170 | char_u * = NULL; // where lead_end has been found |
1171 | int = false; // append extra space |
1172 | int current_flag; |
1173 | int require_blank = false; // requires blank after middle |
1174 | char_u *p2; |
1175 | |
1176 | // If the comment leader has the start, middle or end flag, it may not |
1177 | // be used or may be replaced with the middle leader. |
1178 | for (p = lead_flags; *p && *p != ':'; p++) { |
1179 | if (*p == COM_BLANK) { |
1180 | require_blank = true; |
1181 | continue; |
1182 | } |
1183 | if (*p == COM_START || *p == COM_MIDDLE) { |
1184 | current_flag = *p; |
1185 | if (*p == COM_START) { |
1186 | // Doing "O" on a start of comment does not insert leader. |
1187 | if (dir == BACKWARD) { |
1188 | lead_len = 0; |
1189 | break; |
1190 | } |
1191 | |
1192 | // find start of middle part |
1193 | (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, "," ); |
1194 | require_blank = false; |
1195 | } |
1196 | |
1197 | // Isolate the strings of the middle and end leader. |
1198 | while (*p && p[-1] != ':') { // find end of middle flags |
1199 | if (*p == COM_BLANK) { |
1200 | require_blank = true; |
1201 | } |
1202 | p++; |
1203 | } |
1204 | (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, "," ); |
1205 | |
1206 | while (*p && p[-1] != ':') { // find end of end flags |
1207 | // Check whether we allow automatic ending of comments |
1208 | if (*p == COM_AUTO_END) { |
1209 | end_comment_pending = -1; // means we want to set it |
1210 | } |
1211 | p++; |
1212 | } |
1213 | size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, "," ); |
1214 | |
1215 | if (end_comment_pending == -1) { // we can set it now |
1216 | end_comment_pending = lead_end[n - 1]; |
1217 | } |
1218 | |
1219 | // If the end of the comment is in the same line, don't use |
1220 | // the comment leader. |
1221 | if (dir == FORWARD) { |
1222 | for (p = saved_line + lead_len; *p; p++) { |
1223 | if (STRNCMP(p, lead_end, n) == 0) { |
1224 | comment_end = p; |
1225 | lead_len = 0; |
1226 | break; |
1227 | } |
1228 | } |
1229 | } |
1230 | |
1231 | // Doing "o" on a start of comment inserts the middle leader. |
1232 | if (lead_len > 0) { |
1233 | if (current_flag == COM_START) { |
1234 | lead_repl = lead_middle; |
1235 | lead_repl_len = (int)STRLEN(lead_middle); |
1236 | } |
1237 | |
1238 | // If we have hit RETURN immediately after the start |
1239 | // comment leader, then put a space after the middle |
1240 | // comment leader on the next line. |
1241 | if (!ascii_iswhite(saved_line[lead_len - 1]) |
1242 | && ((p_extra != NULL |
1243 | && (int)curwin->w_cursor.col == lead_len) |
1244 | || (p_extra == NULL |
1245 | && saved_line[lead_len] == NUL) |
1246 | || require_blank)) { |
1247 | extra_space = true; |
1248 | } |
1249 | } |
1250 | break; |
1251 | } |
1252 | if (*p == COM_END) { |
1253 | // Doing "o" on the end of a comment does not insert leader. |
1254 | // Remember where the end is, might want to use it to find the |
1255 | // start (for C-comments). |
1256 | if (dir == FORWARD) { |
1257 | comment_end = skipwhite(saved_line); |
1258 | lead_len = 0; |
1259 | break; |
1260 | } |
1261 | |
1262 | // Doing "O" on the end of a comment inserts the middle leader. |
1263 | // Find the string for the middle leader, searching backwards. |
1264 | while (p > curbuf->b_p_com && *p != ',') { |
1265 | p--; |
1266 | } |
1267 | for (lead_repl = p; lead_repl > curbuf->b_p_com |
1268 | && lead_repl[-1] != ':'; lead_repl--) { |
1269 | } |
1270 | lead_repl_len = (int)(p - lead_repl); |
1271 | |
1272 | // We can probably always add an extra space when doing "O" on |
1273 | // the comment-end |
1274 | extra_space = true; |
1275 | |
1276 | // Check whether we allow automatic ending of comments |
1277 | for (p2 = p; *p2 && *p2 != ':'; p2++) { |
1278 | if (*p2 == COM_AUTO_END) { |
1279 | end_comment_pending = -1; // means we want to set it |
1280 | } |
1281 | } |
1282 | if (end_comment_pending == -1) { |
1283 | // Find last character in end-comment string |
1284 | while (*p2 && *p2 != ',') { |
1285 | p2++; |
1286 | } |
1287 | end_comment_pending = p2[-1]; |
1288 | } |
1289 | break; |
1290 | } |
1291 | if (*p == COM_FIRST) { |
1292 | // Comment leader for first line only: Don't repeat leader |
1293 | // when using "O", blank out leader when using "o". |
1294 | if (dir == BACKWARD) { |
1295 | lead_len = 0; |
1296 | } else { |
1297 | lead_repl = (char_u *)"" ; |
1298 | lead_repl_len = 0; |
1299 | } |
1300 | break; |
1301 | } |
1302 | } |
1303 | if (lead_len > 0) { |
1304 | // allocate buffer (may concatenate p_extra later) |
1305 | int bytes = lead_len |
1306 | + lead_repl_len |
1307 | + extra_space |
1308 | + extra_len |
1309 | + (second_line_indent > 0 ? second_line_indent : 0) |
1310 | + 1; |
1311 | assert(bytes >= 0); |
1312 | leader = xmalloc((size_t)bytes); |
1313 | allocated = leader; // remember to free it later |
1314 | |
1315 | STRLCPY(leader, saved_line, lead_len + 1); |
1316 | |
1317 | // Replace leader with lead_repl, right or left adjusted |
1318 | if (lead_repl != NULL) { |
1319 | int c = 0; |
1320 | int off = 0; |
1321 | |
1322 | for (p = lead_flags; *p != NUL && *p != ':'; ) { |
1323 | if (*p == COM_RIGHT || *p == COM_LEFT) { |
1324 | c = *p++; |
1325 | } else if (ascii_isdigit(*p) || *p == '-') { |
1326 | off = getdigits_int(&p, true, 0); |
1327 | } else { |
1328 | p++; |
1329 | } |
1330 | } |
1331 | if (c == COM_RIGHT) { // right adjusted leader |
1332 | // find last non-white in the leader to line up with |
1333 | for (p = leader + lead_len - 1; p > leader |
1334 | && ascii_iswhite(*p); p--) { |
1335 | } |
1336 | p++; |
1337 | |
1338 | // Compute the length of the replaced characters in |
1339 | // screen characters, not bytes. |
1340 | { |
1341 | int repl_size = vim_strnsize(lead_repl, |
1342 | lead_repl_len); |
1343 | int old_size = 0; |
1344 | char_u *endp = p; |
1345 | int l; |
1346 | |
1347 | while (old_size < repl_size && p > leader) { |
1348 | MB_PTR_BACK(leader, p); |
1349 | old_size += ptr2cells(p); |
1350 | } |
1351 | l = lead_repl_len - (int)(endp - p); |
1352 | if (l != 0) { |
1353 | memmove(endp + l, endp, |
1354 | (size_t)((leader + lead_len) - endp)); |
1355 | } |
1356 | lead_len += l; |
1357 | } |
1358 | memmove(p, lead_repl, (size_t)lead_repl_len); |
1359 | if (p + lead_repl_len > leader + lead_len) { |
1360 | p[lead_repl_len] = NUL; |
1361 | } |
1362 | |
1363 | // blank-out any other chars from the old leader. |
1364 | while (--p >= leader) { |
1365 | int l = utf_head_off(leader, p); |
1366 | |
1367 | if (l > 1) { |
1368 | p -= l; |
1369 | if (ptr2cells(p) > 1) { |
1370 | p[1] = ' '; |
1371 | l--; |
1372 | } |
1373 | memmove(p + 1, p + l + 1, |
1374 | (size_t)((leader + lead_len) - (p + l + 1))); |
1375 | lead_len -= l; |
1376 | *p = ' '; |
1377 | } else if (!ascii_iswhite(*p)) { |
1378 | *p = ' '; |
1379 | } |
1380 | } |
1381 | } else { // left adjusted leader |
1382 | p = skipwhite(leader); |
1383 | // Compute the length of the replaced characters in |
1384 | // screen characters, not bytes. Move the part that is |
1385 | // not to be overwritten. |
1386 | { |
1387 | int repl_size = vim_strnsize(lead_repl, |
1388 | lead_repl_len); |
1389 | int i; |
1390 | int l; |
1391 | |
1392 | for (i = 0; i < lead_len && p[i] != NUL; i += l) { |
1393 | l = (*mb_ptr2len)(p + i); |
1394 | if (vim_strnsize(p, i + l) > repl_size) { |
1395 | break; |
1396 | } |
1397 | } |
1398 | if (i != lead_repl_len) { |
1399 | memmove(p + lead_repl_len, p + i, |
1400 | (size_t)(lead_len - i - (p - leader))); |
1401 | lead_len += lead_repl_len - i; |
1402 | } |
1403 | } |
1404 | memmove(p, lead_repl, (size_t)lead_repl_len); |
1405 | |
1406 | // Replace any remaining non-white chars in the old |
1407 | // leader by spaces. Keep Tabs, the indent must |
1408 | // remain the same. |
1409 | for (p += lead_repl_len; p < leader + lead_len; p++) { |
1410 | if (!ascii_iswhite(*p)) { |
1411 | // Don't put a space before a TAB. |
1412 | if (p + 1 < leader + lead_len && p[1] == TAB) { |
1413 | lead_len--; |
1414 | memmove(p, p + 1, (size_t)(leader + lead_len - p)); |
1415 | } else { |
1416 | int l = (*mb_ptr2len)(p); |
1417 | |
1418 | if (l > 1) { |
1419 | if (ptr2cells(p) > 1) { |
1420 | // Replace a double-wide char with |
1421 | // two spaces |
1422 | l--; |
1423 | *p++ = ' '; |
1424 | } |
1425 | memmove(p + 1, p + l, (size_t)(leader + lead_len - p)); |
1426 | lead_len -= l - 1; |
1427 | } |
1428 | *p = ' '; |
1429 | } |
1430 | } |
1431 | } |
1432 | *p = NUL; |
1433 | } |
1434 | |
1435 | // Recompute the indent, it may have changed. |
1436 | if (curbuf->b_p_ai |
1437 | || do_si |
1438 | ) { |
1439 | newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false); |
1440 | } |
1441 | |
1442 | // Add the indent offset |
1443 | if (newindent + off < 0) { |
1444 | off = -newindent; |
1445 | newindent = 0; |
1446 | } else { |
1447 | newindent += off; |
1448 | } |
1449 | |
1450 | // Correct trailing spaces for the shift, so that |
1451 | // alignment remains equal. |
1452 | while (off > 0 && lead_len > 0 |
1453 | && leader[lead_len - 1] == ' ') { |
1454 | // Don't do it when there is a tab before the space |
1455 | if (vim_strchr(skipwhite(leader), '\t') != NULL) { |
1456 | break; |
1457 | } |
1458 | lead_len--; |
1459 | off--; |
1460 | } |
1461 | |
1462 | // If the leader ends in white space, don't add an |
1463 | // extra space |
1464 | if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) { |
1465 | extra_space = false; |
1466 | } |
1467 | leader[lead_len] = NUL; |
1468 | } |
1469 | |
1470 | if (extra_space) { |
1471 | leader[lead_len++] = ' '; |
1472 | leader[lead_len] = NUL; |
1473 | } |
1474 | |
1475 | newcol = lead_len; |
1476 | |
1477 | // if a new indent will be set below, remove the indent that |
1478 | // is in the comment leader |
1479 | if (newindent |
1480 | || did_si |
1481 | ) { |
1482 | while (lead_len && ascii_iswhite(*leader)) { |
1483 | lead_len--; |
1484 | newcol--; |
1485 | leader++; |
1486 | } |
1487 | } |
1488 | |
1489 | did_si = can_si = false; |
1490 | } else if (comment_end != NULL) { |
1491 | // We have finished a comment, so we don't use the leader. |
1492 | // If this was a C-comment and 'ai' or 'si' is set do a normal |
1493 | // indent to align with the line containing the start of the |
1494 | // comment. |
1495 | if (comment_end[0] == '*' && comment_end[1] == '/' |
1496 | && (curbuf->b_p_ai || do_si)) { |
1497 | old_cursor = curwin->w_cursor; |
1498 | curwin->w_cursor.col = (colnr_T)(comment_end - saved_line); |
1499 | if ((pos = findmatch(NULL, NUL)) != NULL) { |
1500 | curwin->w_cursor.lnum = pos->lnum; |
1501 | newindent = get_indent(); |
1502 | } |
1503 | curwin->w_cursor = old_cursor; |
1504 | } |
1505 | } |
1506 | } |
1507 | |
1508 | // (State == INSERT || State == REPLACE), only when dir == FORWARD |
1509 | if (p_extra != NULL) { |
1510 | *p_extra = saved_char; // restore char that NUL replaced |
1511 | |
1512 | // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first |
1513 | // non-blank. |
1514 | // |
1515 | // When in REPLACE mode, put the deleted blanks on the replace stack, |
1516 | // preceded by a NUL, so they can be put back when a BS is entered. |
1517 | if (REPLACE_NORMAL(State)) { |
1518 | replace_push(NUL); // end of extra blanks |
1519 | } |
1520 | if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { |
1521 | while ((*p_extra == ' ' || *p_extra == '\t') |
1522 | && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { |
1523 | if (REPLACE_NORMAL(State)) { |
1524 | replace_push(*p_extra); |
1525 | } |
1526 | p_extra++; |
1527 | less_cols_off++; |
1528 | } |
1529 | } |
1530 | |
1531 | // columns for marks adjusted for removed columns |
1532 | less_cols = (int)(p_extra - saved_line); |
1533 | } |
1534 | |
1535 | if (p_extra == NULL) { |
1536 | p_extra = (char_u *)"" ; // append empty line |
1537 | } |
1538 | |
1539 | // concatenate leader and p_extra, if there is a leader |
1540 | if (lead_len > 0) { |
1541 | if (flags & OPENLINE_COM_LIST && second_line_indent > 0) { |
1542 | int i; |
1543 | int padding = second_line_indent |
1544 | - (newindent + (int)STRLEN(leader)); |
1545 | |
1546 | // Here whitespace is inserted after the comment char. |
1547 | // Below, set_indent(newindent, SIN_INSERT) will insert the |
1548 | // whitespace needed before the comment char. |
1549 | for (i = 0; i < padding; i++) { |
1550 | STRCAT(leader, " " ); |
1551 | less_cols--; |
1552 | newcol++; |
1553 | } |
1554 | } |
1555 | STRCAT(leader, p_extra); |
1556 | p_extra = leader; |
1557 | did_ai = true; // So truncating blanks works with comments |
1558 | less_cols -= lead_len; |
1559 | } else { |
1560 | end_comment_pending = NUL; // turns out there was no leader |
1561 | } |
1562 | |
1563 | old_cursor = curwin->w_cursor; |
1564 | if (dir == BACKWARD) { |
1565 | curwin->w_cursor.lnum--; |
1566 | } |
1567 | if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) { |
1568 | if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { |
1569 | goto theend; |
1570 | } |
1571 | // Postpone calling changed_lines(), because it would mess up folding |
1572 | // with markers. |
1573 | // Skip mark_adjust when adding a line after the last one, there can't |
1574 | // be marks there. But still needed in diff mode. |
1575 | if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count |
1576 | || curwin->w_p_diff) { |
1577 | mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); |
1578 | } |
1579 | did_append = true; |
1580 | } else { |
1581 | // In VREPLACE mode we are starting to replace the next line. |
1582 | curwin->w_cursor.lnum++; |
1583 | if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) { |
1584 | // In case we NL to a new line, BS to the previous one, and NL |
1585 | // again, we don't want to save the new line for undo twice. |
1586 | (void)u_save_cursor(); // errors are ignored! |
1587 | vr_lines_changed++; |
1588 | } |
1589 | ml_replace(curwin->w_cursor.lnum, p_extra, true); |
1590 | changed_bytes(curwin->w_cursor.lnum, 0); |
1591 | curwin->w_cursor.lnum--; |
1592 | did_append = false; |
1593 | } |
1594 | |
1595 | inhibit_delete_count++; |
1596 | if (newindent |
1597 | || did_si |
1598 | ) { |
1599 | curwin->w_cursor.lnum++; |
1600 | if (did_si) { |
1601 | int sw = get_sw_value(curbuf); |
1602 | |
1603 | if (p_sr) { |
1604 | newindent -= newindent % sw; |
1605 | } |
1606 | newindent += sw; |
1607 | } |
1608 | // Copy the indent |
1609 | if (curbuf->b_p_ci) { |
1610 | (void)copy_indent(newindent, saved_line); |
1611 | |
1612 | // Set the 'preserveindent' option so that any further screwing |
1613 | // with the line doesn't entirely destroy our efforts to preserve |
1614 | // it. It gets restored at the function end. |
1615 | curbuf->b_p_pi = true; |
1616 | } else { |
1617 | (void)set_indent(newindent, SIN_INSERT); |
1618 | } |
1619 | less_cols -= curwin->w_cursor.col; |
1620 | |
1621 | ai_col = curwin->w_cursor.col; |
1622 | |
1623 | // In REPLACE mode, for each character in the new indent, there must |
1624 | // be a NUL on the replace stack, for when it is deleted with BS |
1625 | if (REPLACE_NORMAL(State)) { |
1626 | for (colnr_T n = 0; n < curwin->w_cursor.col; n++) { |
1627 | replace_push(NUL); |
1628 | } |
1629 | } |
1630 | newcol += curwin->w_cursor.col; |
1631 | if (no_si) { |
1632 | did_si = false; |
1633 | } |
1634 | } |
1635 | inhibit_delete_count--; |
1636 | |
1637 | // In REPLACE mode, for each character in the extra leader, there must be |
1638 | // a NUL on the replace stack, for when it is deleted with BS. |
1639 | if (REPLACE_NORMAL(State)) { |
1640 | while (lead_len-- > 0) { |
1641 | replace_push(NUL); |
1642 | } |
1643 | } |
1644 | |
1645 | curwin->w_cursor = old_cursor; |
1646 | |
1647 | if (dir == FORWARD) { |
1648 | if (trunc_line || (State & INSERT)) { |
1649 | // truncate current line at cursor |
1650 | saved_line[curwin->w_cursor.col] = NUL; |
1651 | // Remove trailing white space, unless OPENLINE_KEEPTRAIL used. |
1652 | if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { |
1653 | truncate_spaces(saved_line); |
1654 | } |
1655 | ml_replace(curwin->w_cursor.lnum, saved_line, false); |
1656 | saved_line = NULL; |
1657 | if (did_append) { |
1658 | changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, |
1659 | curwin->w_cursor.lnum + 1, 1L, true); |
1660 | did_append = false; |
1661 | |
1662 | // Move marks after the line break to the new line. |
1663 | if (flags & OPENLINE_MARKFIX) { |
1664 | mark_col_adjust(curwin->w_cursor.lnum, |
1665 | curwin->w_cursor.col + less_cols_off, |
1666 | 1L, (long)-less_cols, 0); |
1667 | } |
1668 | } else { |
1669 | changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); |
1670 | } |
1671 | } |
1672 | |
1673 | // Put the cursor on the new line. Careful: the scrollup() above may |
1674 | // have moved w_cursor, we must use old_cursor. |
1675 | curwin->w_cursor.lnum = old_cursor.lnum + 1; |
1676 | } |
1677 | if (did_append) { |
1678 | changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); |
1679 | } |
1680 | |
1681 | curwin->w_cursor.col = newcol; |
1682 | curwin->w_cursor.coladd = 0; |
1683 | |
1684 | // In VREPLACE mode, we are handling the replace stack ourselves, so stop |
1685 | // fixthisline() from doing it (via change_indent()) by telling it we're in |
1686 | // normal INSERT mode. |
1687 | if (State & VREPLACE_FLAG) { |
1688 | vreplace_mode = State; // So we know to put things right later |
1689 | State = INSERT; |
1690 | } else { |
1691 | vreplace_mode = 0; |
1692 | } |
1693 | // May do lisp indenting. |
1694 | if (!p_paste |
1695 | && leader == NULL |
1696 | && curbuf->b_p_lisp |
1697 | && curbuf->b_p_ai) { |
1698 | fixthisline(get_lisp_indent); |
1699 | ai_col = (colnr_T)getwhitecols_curline(); |
1700 | } |
1701 | // May do indenting after opening a new line. |
1702 | if (!p_paste |
1703 | && (curbuf->b_p_cin |
1704 | || *curbuf->b_p_inde != NUL |
1705 | ) |
1706 | && in_cinkeys(dir == FORWARD |
1707 | ? KEY_OPEN_FORW |
1708 | : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { |
1709 | do_c_expr_indent(); |
1710 | ai_col = (colnr_T)getwhitecols_curline(); |
1711 | } |
1712 | if (vreplace_mode != 0) { |
1713 | State = vreplace_mode; |
1714 | } |
1715 | |
1716 | // Finally, VREPLACE gets the stuff on the new line, then puts back the |
1717 | // original line, and inserts the new stuff char by char, pushing old stuff |
1718 | // onto the replace stack (via ins_char()). |
1719 | if (State & VREPLACE_FLAG) { |
1720 | // Put new line in p_extra |
1721 | p_extra = vim_strsave(get_cursor_line_ptr()); |
1722 | |
1723 | // Put back original line |
1724 | ml_replace(curwin->w_cursor.lnum, next_line, false); |
1725 | |
1726 | // Insert new stuff into line again |
1727 | curwin->w_cursor.col = 0; |
1728 | curwin->w_cursor.coladd = 0; |
1729 | ins_bytes(p_extra); // will call changed_bytes() |
1730 | xfree(p_extra); |
1731 | next_line = NULL; |
1732 | } |
1733 | |
1734 | retval = true; // success! |
1735 | theend: |
1736 | curbuf->b_p_pi = saved_pi; |
1737 | xfree(saved_line); |
1738 | xfree(next_line); |
1739 | xfree(allocated); |
1740 | return retval; |
1741 | } // NOLINT(readability/fn_size) |
1742 | |
1743 | /// Delete from cursor to end of line. |
1744 | /// Caller must have prepared for undo. |
1745 | /// If "fixpos" is true fix the cursor position when done. |
1746 | void truncate_line(int fixpos) |
1747 | { |
1748 | char_u *newp; |
1749 | linenr_T lnum = curwin->w_cursor.lnum; |
1750 | colnr_T col = curwin->w_cursor.col; |
1751 | |
1752 | if (col == 0) { |
1753 | newp = vim_strsave((char_u *)"" ); |
1754 | } else { |
1755 | newp = vim_strnsave(ml_get(lnum), (size_t)col); |
1756 | } |
1757 | ml_replace(lnum, newp, false); |
1758 | |
1759 | // mark the buffer as changed and prepare for displaying |
1760 | changed_bytes(lnum, curwin->w_cursor.col); |
1761 | |
1762 | // If "fixpos" is true we don't want to end up positioned at the NUL. |
1763 | if (fixpos && curwin->w_cursor.col > 0) { |
1764 | curwin->w_cursor.col--; |
1765 | } |
1766 | } |
1767 | |
1768 | /// Delete "nlines" lines at the cursor. |
1769 | /// Saves the lines for undo first if "undo" is true. |
1770 | void del_lines(long nlines, int undo) |
1771 | { |
1772 | long n; |
1773 | linenr_T first = curwin->w_cursor.lnum; |
1774 | |
1775 | if (nlines <= 0) { |
1776 | return; |
1777 | } |
1778 | |
1779 | // save the deleted lines for undo |
1780 | if (undo && u_savedel(first, nlines) == FAIL) { |
1781 | return; |
1782 | } |
1783 | |
1784 | for (n = 0; n < nlines; ) { |
1785 | if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete |
1786 | break; |
1787 | } |
1788 | |
1789 | ml_delete(first, true); |
1790 | n++; |
1791 | |
1792 | // If we delete the last line in the file, stop |
1793 | if (first > curbuf->b_ml.ml_line_count) { |
1794 | break; |
1795 | } |
1796 | } |
1797 | |
1798 | // Correct the cursor position before calling deleted_lines_mark(), it may |
1799 | // trigger a callback to display the cursor. |
1800 | curwin->w_cursor.col = 0; |
1801 | check_cursor_lnum(); |
1802 | |
1803 | // adjust marks, mark the buffer as changed and prepare for displaying |
1804 | deleted_lines_mark(first, n); |
1805 | } |
1806 | |