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 | // screen.c: code for displaying on the screen |
5 | // |
6 | // Output to the screen (console, terminal emulator or GUI window) is minimized |
7 | // by remembering what is already on the screen, and only updating the parts |
8 | // that changed. |
9 | // |
10 | // The grid_*() functions write to the screen and handle updating grid->lines[]. |
11 | // |
12 | // update_screen() is the function that updates all windows and status lines. |
13 | // It is called from the main loop when must_redraw is non-zero. It may be |
14 | // called from other places when an immediate screen update is needed. |
15 | // |
16 | // The part of the buffer that is displayed in a window is set with: |
17 | // - w_topline (first buffer line in window) |
18 | // - w_topfill (filler lines above the first line) |
19 | // - w_leftcol (leftmost window cell in window), |
20 | // - w_skipcol (skipped window cells of first line) |
21 | // |
22 | // Commands that only move the cursor around in a window, do not need to take |
23 | // action to update the display. The main loop will check if w_topline is |
24 | // valid and update it (scroll the window) when needed. |
25 | // |
26 | // Commands that scroll a window change w_topline and must call |
27 | // check_cursor() to move the cursor into the visible part of the window, and |
28 | // call redraw_later(VALID) to have the window displayed by update_screen() |
29 | // later. |
30 | // |
31 | // Commands that change text in the buffer must call changed_bytes() or |
32 | // changed_lines() to mark the area that changed and will require updating |
33 | // later. The main loop will call update_screen(), which will update each |
34 | // window that shows the changed buffer. This assumes text above the change |
35 | // can remain displayed as it is. Text after the change may need updating for |
36 | // scrolling, folding and syntax highlighting. |
37 | // |
38 | // Commands that change how a window is displayed (e.g., setting 'list') or |
39 | // invalidate the contents of a window in another way (e.g., change fold |
40 | // settings), must call redraw_later(NOT_VALID) to have the whole window |
41 | // redisplayed by update_screen() later. |
42 | // |
43 | // Commands that change how a buffer is displayed (e.g., setting 'tabstop') |
44 | // must call redraw_curbuf_later(NOT_VALID) to have all the windows for the |
45 | // buffer redisplayed by update_screen() later. |
46 | // |
47 | // Commands that change highlighting and possibly cause a scroll too must call |
48 | // redraw_later(SOME_VALID) to update the whole window but still use scrolling |
49 | // to avoid redrawing everything. But the length of displayed lines must not |
50 | // change, use NOT_VALID then. |
51 | // |
52 | // Commands that move the window position must call redraw_later(NOT_VALID). |
53 | // TODO(neovim): should minimize redrawing by scrolling when possible. |
54 | // |
55 | // Commands that change everything (e.g., resizing the screen) must call |
56 | // redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). |
57 | // |
58 | // Things that are handled indirectly: |
59 | // - When messages scroll the screen up, msg_scrolled will be set and |
60 | // update_screen() called to redraw. |
61 | /// |
62 | |
63 | #include <assert.h> |
64 | #include <inttypes.h> |
65 | #include <stdbool.h> |
66 | #include <string.h> |
67 | |
68 | #include "nvim/log.h" |
69 | #include "nvim/vim.h" |
70 | #include "nvim/ascii.h" |
71 | #include "nvim/arabic.h" |
72 | #include "nvim/screen.h" |
73 | #include "nvim/buffer.h" |
74 | #include "nvim/charset.h" |
75 | #include "nvim/cursor.h" |
76 | #include "nvim/cursor_shape.h" |
77 | #include "nvim/diff.h" |
78 | #include "nvim/eval.h" |
79 | #include "nvim/ex_cmds.h" |
80 | #include "nvim/ex_cmds2.h" |
81 | #include "nvim/ex_getln.h" |
82 | #include "nvim/edit.h" |
83 | #include "nvim/fileio.h" |
84 | #include "nvim/fold.h" |
85 | #include "nvim/indent.h" |
86 | #include "nvim/getchar.h" |
87 | #include "nvim/highlight.h" |
88 | #include "nvim/main.h" |
89 | #include "nvim/mark.h" |
90 | #include "nvim/mbyte.h" |
91 | #include "nvim/memline.h" |
92 | #include "nvim/memory.h" |
93 | #include "nvim/menu.h" |
94 | #include "nvim/message.h" |
95 | #include "nvim/misc1.h" |
96 | #include "nvim/garray.h" |
97 | #include "nvim/move.h" |
98 | #include "nvim/normal.h" |
99 | #include "nvim/option.h" |
100 | #include "nvim/os_unix.h" |
101 | #include "nvim/path.h" |
102 | #include "nvim/popupmnu.h" |
103 | #include "nvim/quickfix.h" |
104 | #include "nvim/regexp.h" |
105 | #include "nvim/search.h" |
106 | #include "nvim/sign.h" |
107 | #include "nvim/spell.h" |
108 | #include "nvim/state.h" |
109 | #include "nvim/strings.h" |
110 | #include "nvim/syntax.h" |
111 | #include "nvim/terminal.h" |
112 | #include "nvim/ui.h" |
113 | #include "nvim/ui_compositor.h" |
114 | #include "nvim/undo.h" |
115 | #include "nvim/version.h" |
116 | #include "nvim/window.h" |
117 | #include "nvim/os/time.h" |
118 | #include "nvim/api/private/helpers.h" |
119 | |
120 | #define MB_FILLER_CHAR '<' /* character used when a double-width character |
121 | * doesn't fit. */ |
122 | #define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) |
123 | #define W_ENDROW(wp) (wp->w_winrow + wp->w_height) |
124 | |
125 | |
126 | // temporary buffer for rendering a single screenline, so it can be |
127 | // comparared with previous contents to calulate smallest delta. |
128 | static size_t linebuf_size = 0; |
129 | static schar_T *linebuf_char = NULL; |
130 | static sattr_T *linebuf_attr = NULL; |
131 | |
132 | static match_T search_hl; /* used for 'hlsearch' highlight matching */ |
133 | |
134 | static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */ |
135 | |
136 | StlClickDefinition *tab_page_click_defs = NULL; |
137 | |
138 | long tab_page_click_defs_size = 0; |
139 | |
140 | // for line_putchar. Contains the state that needs to be remembered from |
141 | // putting one character to the next. |
142 | typedef struct { |
143 | const char_u *p; |
144 | int prev_c; // previous Arabic character |
145 | int prev_c1; // first composing char for prev_c |
146 | } LineState; |
147 | #define LINE_STATE(p) { p, 0, 0 } |
148 | |
149 | /// Whether to call "ui_call_grid_resize" in win_grid_alloc |
150 | static bool send_grid_resize = false; |
151 | |
152 | static bool conceal_cursor_used = false; |
153 | |
154 | static bool = false; |
155 | static bool msg_grid_invalid = false; |
156 | |
157 | static bool resizing = false; |
158 | |
159 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
160 | # include "screen.c.generated.h" |
161 | #endif |
162 | #define SEARCH_HL_PRIORITY 0 |
163 | |
164 | /* |
165 | * Redraw the current window later, with update_screen(type). |
166 | * Set must_redraw only if not already set to a higher value. |
167 | * e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. |
168 | */ |
169 | void redraw_later(int type) |
170 | { |
171 | redraw_win_later(curwin, type); |
172 | } |
173 | |
174 | void redraw_win_later(win_T *wp, int type) |
175 | FUNC_ATTR_NONNULL_ALL |
176 | { |
177 | if (!exiting && wp->w_redr_type < type) { |
178 | wp->w_redr_type = type; |
179 | if (type >= NOT_VALID) |
180 | wp->w_lines_valid = 0; |
181 | if (must_redraw < type) /* must_redraw is the maximum of all windows */ |
182 | must_redraw = type; |
183 | } |
184 | } |
185 | |
186 | /* |
187 | * Mark all windows to be redrawn later. |
188 | */ |
189 | void redraw_all_later(int type) |
190 | { |
191 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
192 | redraw_win_later(wp, type); |
193 | } |
194 | // This may be needed when switching tabs. |
195 | if (must_redraw < type) { |
196 | must_redraw = type; |
197 | } |
198 | } |
199 | |
200 | void screen_invalidate_highlights(void) |
201 | { |
202 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
203 | redraw_win_later(wp, NOT_VALID); |
204 | wp->w_grid.valid = false; |
205 | } |
206 | } |
207 | |
208 | /* |
209 | * Mark all windows that are editing the current buffer to be updated later. |
210 | */ |
211 | void redraw_curbuf_later(int type) |
212 | { |
213 | redraw_buf_later(curbuf, type); |
214 | } |
215 | |
216 | void redraw_buf_later(buf_T *buf, int type) |
217 | { |
218 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
219 | if (wp->w_buffer == buf) { |
220 | redraw_win_later(wp, type); |
221 | } |
222 | } |
223 | } |
224 | |
225 | void redraw_buf_line_later(buf_T *buf, linenr_T line) |
226 | { |
227 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
228 | if (wp->w_buffer == buf |
229 | && line >= wp->w_topline && line < wp->w_botline) { |
230 | redrawWinline(wp, line); |
231 | } |
232 | } |
233 | } |
234 | |
235 | /* |
236 | * Changed something in the current window, at buffer line "lnum", that |
237 | * requires that line and possibly other lines to be redrawn. |
238 | * Used when entering/leaving Insert mode with the cursor on a folded line. |
239 | * Used to remove the "$" from a change command. |
240 | * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot |
241 | * may become invalid and the whole window will have to be redrawn. |
242 | */ |
243 | void |
244 | redrawWinline( |
245 | win_T *wp, |
246 | linenr_T lnum |
247 | ) |
248 | FUNC_ATTR_NONNULL_ALL |
249 | { |
250 | if (lnum >= wp->w_topline |
251 | && lnum < wp->w_botline) { |
252 | if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { |
253 | wp->w_redraw_top = lnum; |
254 | } |
255 | if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { |
256 | wp->w_redraw_bot = lnum; |
257 | } |
258 | redraw_win_later(wp, VALID); |
259 | } |
260 | } |
261 | |
262 | /* |
263 | * update all windows that are editing the current buffer |
264 | */ |
265 | void update_curbuf(int type) |
266 | { |
267 | redraw_curbuf_later(type); |
268 | update_screen(type); |
269 | } |
270 | |
271 | /// Redraw the parts of the screen that is marked for redraw. |
272 | /// |
273 | /// Most code shouldn't call this directly, rather use redraw_later() and |
274 | /// and redraw_all_later() to mark parts of the screen as needing a redraw. |
275 | /// |
276 | /// @param type set to a NOT_VALID to force redraw of entire screen |
277 | int update_screen(int type) |
278 | { |
279 | static int did_intro = FALSE; |
280 | int did_one; |
281 | |
282 | // Don't do anything if the screen structures are (not yet) valid. |
283 | // A VimResized autocmd can invoke redrawing in the middle of a resize, |
284 | // which would bypass the checks in screen_resize for popupmenu etc. |
285 | if (!default_grid.chars || resizing) { |
286 | return FAIL; |
287 | } |
288 | |
289 | if (must_redraw) { |
290 | if (type < must_redraw) /* use maximal type */ |
291 | type = must_redraw; |
292 | |
293 | /* must_redraw is reset here, so that when we run into some weird |
294 | * reason to redraw while busy redrawing (e.g., asynchronous |
295 | * scrolling), or update_topline() in win_update() will cause a |
296 | * scroll, the screen will be redrawn later or in win_update(). */ |
297 | must_redraw = 0; |
298 | } |
299 | |
300 | /* Need to update w_lines[]. */ |
301 | if (curwin->w_lines_valid == 0 && type < NOT_VALID) |
302 | type = NOT_VALID; |
303 | |
304 | /* Postpone the redrawing when it's not needed and when being called |
305 | * recursively. */ |
306 | if (!redrawing() || updating_screen) { |
307 | redraw_later(type); /* remember type for next time */ |
308 | must_redraw = type; |
309 | if (type > INVERTED_ALL) { |
310 | curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now |
311 | } |
312 | return FAIL; |
313 | } |
314 | |
315 | updating_screen = TRUE; |
316 | ++display_tick; /* let syntax code know we're in a next round of |
317 | * display updating */ |
318 | |
319 | // Tricky: vim code can reset msg_scrolled behind our back, so need |
320 | // separate bookkeeping for now. |
321 | if (msg_did_scroll) { |
322 | msg_did_scroll = false; |
323 | msg_scrolled_at_flush = 0; |
324 | } |
325 | |
326 | if (type >= CLEAR || !default_grid.valid) { |
327 | ui_comp_set_screen_valid(false); |
328 | } |
329 | |
330 | // if the screen was scrolled up when displaying a message, scroll it down |
331 | if (msg_scrolled || msg_grid_invalid) { |
332 | clear_cmdline = true; |
333 | int valid = MAX(Rows - msg_scrollsize(), 0); |
334 | if (msg_grid.chars) { |
335 | // non-displayed part of msg_grid is considered invalid. |
336 | for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) { |
337 | grid_clear_line(&msg_grid, msg_grid.line_offset[i], |
338 | (int)msg_grid.Columns, false); |
339 | } |
340 | } |
341 | if (msg_use_msgsep()) { |
342 | msg_grid.throttled = false; |
343 | // CLEAR is already handled |
344 | if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { |
345 | ui_comp_set_screen_valid(false); |
346 | for (int i = valid; i < Rows-p_ch; i++) { |
347 | grid_clear_line(&default_grid, default_grid.line_offset[i], |
348 | Columns, false); |
349 | } |
350 | } |
351 | msg_grid_set_pos(Rows-p_ch, false); |
352 | msg_grid_invalid = false; |
353 | } else if (msg_scrolled > Rows - 5) { // clearing is faster |
354 | type = CLEAR; |
355 | } else if (type != CLEAR) { |
356 | check_for_delay(false); |
357 | grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); |
358 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
359 | if (wp->w_floating) { |
360 | continue; |
361 | } |
362 | if (wp->w_winrow < msg_scrolled) { |
363 | if (W_ENDROW(wp) > msg_scrolled |
364 | && wp->w_redr_type < REDRAW_TOP |
365 | && wp->w_lines_valid > 0 |
366 | && wp->w_topline == wp->w_lines[0].wl_lnum) { |
367 | wp->w_upd_rows = msg_scrolled - wp->w_winrow; |
368 | wp->w_redr_type = REDRAW_TOP; |
369 | } else { |
370 | wp->w_redr_type = NOT_VALID; |
371 | if (W_ENDROW(wp) + wp->w_status_height |
372 | <= msg_scrolled) { |
373 | wp->w_redr_status = TRUE; |
374 | } |
375 | } |
376 | } |
377 | } |
378 | redraw_cmdline = TRUE; |
379 | redraw_tabline = TRUE; |
380 | } |
381 | msg_scrolled = 0; |
382 | msg_scrolled_at_flush = 0; |
383 | need_wait_return = false; |
384 | } |
385 | |
386 | win_ui_flush_positions(); |
387 | msg_ext_check_clear(); |
388 | |
389 | /* reset cmdline_row now (may have been changed temporarily) */ |
390 | compute_cmdrow(); |
391 | |
392 | /* Check for changed highlighting */ |
393 | if (need_highlight_changed) |
394 | highlight_changed(); |
395 | |
396 | if (type == CLEAR) { // first clear screen |
397 | screenclear(); // will reset clear_cmdline |
398 | cmdline_screen_cleared(); // clear external cmdline state |
399 | type = NOT_VALID; |
400 | // must_redraw may be set indirectly, avoid another redraw later |
401 | must_redraw = 0; |
402 | } else if (!default_grid.valid) { |
403 | grid_invalidate(&default_grid); |
404 | default_grid.valid = true; |
405 | } |
406 | |
407 | // After disabling msgsep the grid might not have been deallocated yet, |
408 | // hence we also need to check msg_grid.chars |
409 | if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { |
410 | grid_fill(&default_grid, Rows-p_ch, Rows, 0, Columns, ' ', ' ', 0); |
411 | } |
412 | |
413 | ui_comp_set_screen_valid(true); |
414 | |
415 | if (clear_cmdline) /* going to clear cmdline (done below) */ |
416 | check_for_delay(FALSE); |
417 | |
418 | /* Force redraw when width of 'number' or 'relativenumber' column |
419 | * changes. */ |
420 | if (curwin->w_redr_type < NOT_VALID |
421 | && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) |
422 | ? number_width(curwin) : 0)) |
423 | curwin->w_redr_type = NOT_VALID; |
424 | |
425 | /* |
426 | * Only start redrawing if there is really something to do. |
427 | */ |
428 | if (type == INVERTED) |
429 | update_curswant(); |
430 | if (curwin->w_redr_type < type |
431 | && !((type == VALID |
432 | && curwin->w_lines[0].wl_valid |
433 | && curwin->w_topfill == curwin->w_old_topfill |
434 | && curwin->w_botfill == curwin->w_old_botfill |
435 | && curwin->w_topline == curwin->w_lines[0].wl_lnum) |
436 | || (type == INVERTED |
437 | && VIsual_active |
438 | && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum |
439 | && curwin->w_old_visual_mode == VIsual_mode |
440 | && (curwin->w_valid & VALID_VIRTCOL) |
441 | && curwin->w_old_curswant == curwin->w_curswant) |
442 | )) |
443 | curwin->w_redr_type = type; |
444 | |
445 | // Redraw the tab pages line if needed. |
446 | if (redraw_tabline || type >= NOT_VALID) { |
447 | update_window_hl(curwin, type >= NOT_VALID); |
448 | FOR_ALL_TABS(tp) { |
449 | if (tp != curtab) { |
450 | update_window_hl(tp->tp_curwin, type >= NOT_VALID); |
451 | } |
452 | } |
453 | draw_tabline(); |
454 | } |
455 | |
456 | /* |
457 | * Correct stored syntax highlighting info for changes in each displayed |
458 | * buffer. Each buffer must only be done once. |
459 | */ |
460 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
461 | update_window_hl(wp, type >= NOT_VALID); |
462 | |
463 | if (wp->w_buffer->b_mod_set) { |
464 | win_T *wwp; |
465 | |
466 | // Check if we already did this buffer. |
467 | for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) { |
468 | if (wwp->w_buffer == wp->w_buffer) { |
469 | break; |
470 | } |
471 | } |
472 | if (wwp == wp && syntax_present(wp)) { |
473 | syn_stack_apply_changes(wp->w_buffer); |
474 | } |
475 | } |
476 | } |
477 | |
478 | /* |
479 | * Go from top to bottom through the windows, redrawing the ones that need |
480 | * it. |
481 | */ |
482 | did_one = FALSE; |
483 | search_hl.rm.regprog = NULL; |
484 | |
485 | |
486 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
487 | if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { |
488 | grid_invalidate(&wp->w_grid); |
489 | wp->w_redr_type = NOT_VALID; |
490 | } |
491 | |
492 | if (wp->w_redr_type != 0) { |
493 | if (!did_one) { |
494 | did_one = TRUE; |
495 | start_search_hl(); |
496 | } |
497 | win_update(wp); |
498 | } |
499 | |
500 | /* redraw status line after the window to minimize cursor movement */ |
501 | if (wp->w_redr_status) { |
502 | win_redr_status(wp); |
503 | } |
504 | } |
505 | |
506 | end_search_hl(); |
507 | |
508 | // May need to redraw the popup menu. |
509 | if (pum_drawn() && must_redraw_pum) { |
510 | pum_redraw(); |
511 | } |
512 | |
513 | send_grid_resize = false; |
514 | |
515 | /* Reset b_mod_set flags. Going through all windows is probably faster |
516 | * than going through all buffers (there could be many buffers). */ |
517 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
518 | wp->w_buffer->b_mod_set = false; |
519 | } |
520 | |
521 | updating_screen = FALSE; |
522 | |
523 | /* Clear or redraw the command line. Done last, because scrolling may |
524 | * mess up the command line. */ |
525 | if (clear_cmdline || redraw_cmdline) { |
526 | showmode(); |
527 | } |
528 | |
529 | /* May put up an introductory message when not editing a file */ |
530 | if (!did_intro) |
531 | maybe_intro_message(); |
532 | did_intro = TRUE; |
533 | |
534 | // either cmdline is cleared, not drawn or mode is last drawn |
535 | cmdline_was_last_drawn = false; |
536 | return OK; |
537 | } |
538 | |
539 | // Return true if the cursor line in window "wp" may be concealed, according |
540 | // to the 'concealcursor' option. |
541 | bool conceal_cursor_line(const win_T *wp) |
542 | FUNC_ATTR_NONNULL_ALL |
543 | { |
544 | int c; |
545 | |
546 | if (*wp->w_p_cocu == NUL) { |
547 | return false; |
548 | } |
549 | if (get_real_state() & VISUAL) { |
550 | c = 'v'; |
551 | } else if (State & INSERT) { |
552 | c = 'i'; |
553 | } else if (State & NORMAL) { |
554 | c = 'n'; |
555 | } else if (State & CMDLINE) { |
556 | c = 'c'; |
557 | } else { |
558 | return false; |
559 | } |
560 | return vim_strchr(wp->w_p_cocu, c) != NULL; |
561 | } |
562 | |
563 | // Check if the cursor line needs to be redrawn because of 'concealcursor'. |
564 | // |
565 | // When cursor is moved at the same time, both lines will be redrawn regardless. |
566 | void conceal_check_cursor_line(void) |
567 | { |
568 | bool should_conceal = conceal_cursor_line(curwin); |
569 | if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { |
570 | redrawWinline(curwin, curwin->w_cursor.lnum); |
571 | // Need to recompute cursor column, e.g., when starting Visual mode |
572 | // without concealing. */ |
573 | curs_columns(true); |
574 | } |
575 | } |
576 | |
577 | /// Whether cursorline is drawn in a special way |
578 | /// |
579 | /// If true, both old and new cursorline will need |
580 | /// need to be redrawn when moving cursor within windows. |
581 | bool win_cursorline_standout(const win_T *wp) |
582 | FUNC_ATTR_NONNULL_ALL |
583 | { |
584 | return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); |
585 | } |
586 | |
587 | /* |
588 | * Update a single window. |
589 | * |
590 | * This may cause the windows below it also to be redrawn (when clearing the |
591 | * screen or scrolling lines). |
592 | * |
593 | * How the window is redrawn depends on wp->w_redr_type. Each type also |
594 | * implies the one below it. |
595 | * NOT_VALID redraw the whole window |
596 | * SOME_VALID redraw the whole window but do scroll when possible |
597 | * REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID |
598 | * INVERTED redraw the changed part of the Visual area |
599 | * INVERTED_ALL redraw the whole Visual area |
600 | * VALID 1. scroll up/down to adjust for a changed w_topline |
601 | * 2. update lines at the top when scrolled down |
602 | * 3. redraw changed text: |
603 | * - if wp->w_buffer->b_mod_set set, update lines between |
604 | * b_mod_top and b_mod_bot. |
605 | * - if wp->w_redraw_top non-zero, redraw lines between |
606 | * wp->w_redraw_top and wp->w_redr_bot. |
607 | * - continue redrawing when syntax status is invalid. |
608 | * 4. if scrolled up, update lines at the bottom. |
609 | * This results in three areas that may need updating: |
610 | * top: from first row to top_end (when scrolled down) |
611 | * mid: from mid_start to mid_end (update inversion or changed text) |
612 | * bot: from bot_start to last row (when scrolled up) |
613 | */ |
614 | static void win_update(win_T *wp) |
615 | { |
616 | buf_T *buf = wp->w_buffer; |
617 | int type; |
618 | int top_end = 0; /* Below last row of the top area that needs |
619 | updating. 0 when no top area updating. */ |
620 | int mid_start = 999; /* first row of the mid area that needs |
621 | updating. 999 when no mid area updating. */ |
622 | int mid_end = 0; /* Below last row of the mid area that needs |
623 | updating. 0 when no mid area updating. */ |
624 | int bot_start = 999; /* first row of the bot area that needs |
625 | updating. 999 when no bot area updating */ |
626 | int scrolled_down = FALSE; /* TRUE when scrolled down when |
627 | w_topline got smaller a bit */ |
628 | bool top_to_mod = false; // redraw above mod_top |
629 | |
630 | int row; /* current window row to display */ |
631 | linenr_T lnum; /* current buffer lnum to display */ |
632 | int idx; /* current index in w_lines[] */ |
633 | int srow; /* starting row of the current line */ |
634 | |
635 | int eof = FALSE; /* if TRUE, we hit the end of the file */ |
636 | int didline = FALSE; /* if TRUE, we finished the last line */ |
637 | int i; |
638 | long j; |
639 | static int recursive = FALSE; /* being called recursively */ |
640 | int old_botline = wp->w_botline; |
641 | long fold_count; |
642 | // Remember what happened to the previous line. |
643 | #define DID_NONE 1 // didn't update a line |
644 | #define DID_LINE 2 // updated a normal line |
645 | #define DID_FOLD 3 // updated a folded line |
646 | int did_update = DID_NONE; |
647 | linenr_T syntax_last_parsed = 0; /* last parsed text line */ |
648 | linenr_T mod_top = 0; |
649 | linenr_T mod_bot = 0; |
650 | int save_got_int; |
651 | |
652 | // If we can compute a change in the automatic sizing of the sign column |
653 | // under 'signcolumn=auto:X' and signs currently placed in the buffer, better |
654 | // figuring it out here so we can redraw the entire screen for it. |
655 | buf_signcols(buf); |
656 | |
657 | type = wp->w_redr_type; |
658 | |
659 | win_grid_alloc(wp); |
660 | |
661 | if (type >= NOT_VALID) { |
662 | wp->w_redr_status = true; |
663 | wp->w_lines_valid = 0; |
664 | } |
665 | |
666 | // Window is zero-height: nothing to draw. |
667 | if (wp->w_grid.Rows == 0) { |
668 | wp->w_redr_type = 0; |
669 | return; |
670 | } |
671 | |
672 | // Window is zero-width: Only need to draw the separator. |
673 | if (wp->w_grid.Columns == 0) { |
674 | // draw the vertical separator right of this window |
675 | draw_vsep_win(wp, 0); |
676 | wp->w_redr_type = 0; |
677 | return; |
678 | } |
679 | |
680 | init_search_hl(wp); |
681 | |
682 | /* Force redraw when width of 'number' or 'relativenumber' column |
683 | * changes. */ |
684 | i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; |
685 | if (wp->w_nrwidth != i) { |
686 | type = NOT_VALID; |
687 | wp->w_nrwidth = i; |
688 | |
689 | if (buf->terminal) { |
690 | terminal_check_size(buf->terminal); |
691 | } |
692 | } else if (buf->b_mod_set |
693 | && buf->b_mod_xlines != 0 |
694 | && wp->w_redraw_top != 0) { |
695 | // When there are both inserted/deleted lines and specific lines to be |
696 | // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw |
697 | // everything (only happens when redrawing is off for while). |
698 | type = NOT_VALID; |
699 | } else { |
700 | /* |
701 | * Set mod_top to the first line that needs displaying because of |
702 | * changes. Set mod_bot to the first line after the changes. |
703 | */ |
704 | mod_top = wp->w_redraw_top; |
705 | if (wp->w_redraw_bot != 0) |
706 | mod_bot = wp->w_redraw_bot + 1; |
707 | else |
708 | mod_bot = 0; |
709 | if (buf->b_mod_set) { |
710 | if (mod_top == 0 || mod_top > buf->b_mod_top) { |
711 | mod_top = buf->b_mod_top; |
712 | /* Need to redraw lines above the change that may be included |
713 | * in a pattern match. */ |
714 | if (syntax_present(wp)) { |
715 | mod_top -= buf->b_s.b_syn_sync_linebreaks; |
716 | if (mod_top < 1) |
717 | mod_top = 1; |
718 | } |
719 | } |
720 | if (mod_bot == 0 || mod_bot < buf->b_mod_bot) |
721 | mod_bot = buf->b_mod_bot; |
722 | |
723 | // When 'hlsearch' is on and using a multi-line search pattern, a |
724 | // change in one line may make the Search highlighting in a |
725 | // previous line invalid. Simple solution: redraw all visible |
726 | // lines above the change. |
727 | // Same for a match pattern. |
728 | if (search_hl.rm.regprog != NULL |
729 | && re_multiline(search_hl.rm.regprog)) { |
730 | top_to_mod = true; |
731 | } else { |
732 | const matchitem_T *cur = wp->w_match_head; |
733 | while (cur != NULL) { |
734 | if (cur->match.regprog != NULL |
735 | && re_multiline(cur->match.regprog)) { |
736 | top_to_mod = true; |
737 | break; |
738 | } |
739 | cur = cur->next; |
740 | } |
741 | } |
742 | } |
743 | if (mod_top != 0 && hasAnyFolding(wp)) { |
744 | linenr_T lnumt, lnumb; |
745 | |
746 | /* |
747 | * A change in a line can cause lines above it to become folded or |
748 | * unfolded. Find the top most buffer line that may be affected. |
749 | * If the line was previously folded and displayed, get the first |
750 | * line of that fold. If the line is folded now, get the first |
751 | * folded line. Use the minimum of these two. |
752 | */ |
753 | |
754 | /* Find last valid w_lines[] entry above mod_top. Set lnumt to |
755 | * the line below it. If there is no valid entry, use w_topline. |
756 | * Find the first valid w_lines[] entry below mod_bot. Set lnumb |
757 | * to this line. If there is no valid entry, use MAXLNUM. */ |
758 | lnumt = wp->w_topline; |
759 | lnumb = MAXLNUM; |
760 | for (i = 0; i < wp->w_lines_valid; ++i) |
761 | if (wp->w_lines[i].wl_valid) { |
762 | if (wp->w_lines[i].wl_lastlnum < mod_top) |
763 | lnumt = wp->w_lines[i].wl_lastlnum + 1; |
764 | if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { |
765 | lnumb = wp->w_lines[i].wl_lnum; |
766 | // When there is a fold column it might need updating |
767 | // in the next line ("J" just above an open fold). |
768 | if (compute_foldcolumn(wp, 0) > 0) { |
769 | lnumb++; |
770 | } |
771 | } |
772 | } |
773 | |
774 | (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); |
775 | if (mod_top > lnumt) { |
776 | mod_top = lnumt; |
777 | } |
778 | |
779 | // Now do the same for the bottom line (one above mod_bot). |
780 | mod_bot--; |
781 | (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); |
782 | mod_bot++; |
783 | if (mod_bot < lnumb) { |
784 | mod_bot = lnumb; |
785 | } |
786 | } |
787 | |
788 | /* When a change starts above w_topline and the end is below |
789 | * w_topline, start redrawing at w_topline. |
790 | * If the end of the change is above w_topline: do like no change was |
791 | * made, but redraw the first line to find changes in syntax. */ |
792 | if (mod_top != 0 && mod_top < wp->w_topline) { |
793 | if (mod_bot > wp->w_topline) |
794 | mod_top = wp->w_topline; |
795 | else if (syntax_present(wp)) |
796 | top_end = 1; |
797 | } |
798 | |
799 | /* When line numbers are displayed need to redraw all lines below |
800 | * inserted/deleted lines. */ |
801 | if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) |
802 | mod_bot = MAXLNUM; |
803 | } |
804 | wp->w_redraw_top = 0; // reset for next time |
805 | wp->w_redraw_bot = 0; |
806 | |
807 | /* |
808 | * When only displaying the lines at the top, set top_end. Used when |
809 | * window has scrolled down for msg_scrolled. |
810 | */ |
811 | if (type == REDRAW_TOP) { |
812 | j = 0; |
813 | for (i = 0; i < wp->w_lines_valid; ++i) { |
814 | j += wp->w_lines[i].wl_size; |
815 | if (j >= wp->w_upd_rows) { |
816 | top_end = j; |
817 | break; |
818 | } |
819 | } |
820 | if (top_end == 0) |
821 | /* not found (cannot happen?): redraw everything */ |
822 | type = NOT_VALID; |
823 | else |
824 | /* top area defined, the rest is VALID */ |
825 | type = VALID; |
826 | } |
827 | |
828 | /* |
829 | * If there are no changes on the screen that require a complete redraw, |
830 | * handle three cases: |
831 | * 1: we are off the top of the screen by a few lines: scroll down |
832 | * 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up |
833 | * 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in |
834 | * w_lines[] that needs updating. |
835 | */ |
836 | if ((type == VALID || type == SOME_VALID |
837 | || type == INVERTED || type == INVERTED_ALL) |
838 | && !wp->w_botfill && !wp->w_old_botfill |
839 | ) { |
840 | if (mod_top != 0 && wp->w_topline == mod_top) { |
841 | /* |
842 | * w_topline is the first changed line, the scrolling will be done |
843 | * further down. |
844 | */ |
845 | } else if (wp->w_lines[0].wl_valid |
846 | && (wp->w_topline < wp->w_lines[0].wl_lnum |
847 | || (wp->w_topline == wp->w_lines[0].wl_lnum |
848 | && wp->w_topfill > wp->w_old_topfill) |
849 | )) { |
850 | /* |
851 | * New topline is above old topline: May scroll down. |
852 | */ |
853 | if (hasAnyFolding(wp)) { |
854 | linenr_T ln; |
855 | |
856 | /* count the number of lines we are off, counting a sequence |
857 | * of folded lines as one */ |
858 | j = 0; |
859 | for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { |
860 | j++; |
861 | if (j >= wp->w_grid.Rows - 2) { |
862 | break; |
863 | } |
864 | (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); |
865 | } |
866 | } else |
867 | j = wp->w_lines[0].wl_lnum - wp->w_topline; |
868 | if (j < wp->w_grid.Rows - 2) { // not too far off |
869 | i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); |
870 | /* insert extra lines for previously invisible filler lines */ |
871 | if (wp->w_lines[0].wl_lnum != wp->w_topline) |
872 | i += diff_check_fill(wp, wp->w_lines[0].wl_lnum) |
873 | - wp->w_old_topfill; |
874 | if (i < wp->w_grid.Rows - 2) { // less than a screen off |
875 | // Try to insert the correct number of lines. |
876 | // If not the last window, delete the lines at the bottom. |
877 | // win_ins_lines may fail when the terminal can't do it. |
878 | win_scroll_lines(wp, 0, i); |
879 | if (wp->w_lines_valid != 0) { |
880 | // Need to update rows that are new, stop at the |
881 | // first one that scrolled down. |
882 | top_end = i; |
883 | scrolled_down = true; |
884 | |
885 | // Move the entries that were scrolled, disable |
886 | // the entries for the lines to be redrawn. |
887 | if ((wp->w_lines_valid += j) > wp->w_grid.Rows) { |
888 | wp->w_lines_valid = wp->w_grid.Rows; |
889 | } |
890 | for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { |
891 | wp->w_lines[idx] = wp->w_lines[idx - j]; |
892 | } |
893 | while (idx >= 0) { |
894 | wp->w_lines[idx--].wl_valid = false; |
895 | } |
896 | } |
897 | } else { |
898 | mid_start = 0; // redraw all lines |
899 | } |
900 | } else { |
901 | mid_start = 0; // redraw all lines |
902 | } |
903 | } else { |
904 | /* |
905 | * New topline is at or below old topline: May scroll up. |
906 | * When topline didn't change, find first entry in w_lines[] that |
907 | * needs updating. |
908 | */ |
909 | |
910 | /* try to find wp->w_topline in wp->w_lines[].wl_lnum */ |
911 | j = -1; |
912 | row = 0; |
913 | for (i = 0; i < wp->w_lines_valid; i++) { |
914 | if (wp->w_lines[i].wl_valid |
915 | && wp->w_lines[i].wl_lnum == wp->w_topline) { |
916 | j = i; |
917 | break; |
918 | } |
919 | row += wp->w_lines[i].wl_size; |
920 | } |
921 | if (j == -1) { |
922 | /* if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all |
923 | * lines */ |
924 | mid_start = 0; |
925 | } else { |
926 | /* |
927 | * Try to delete the correct number of lines. |
928 | * wp->w_topline is at wp->w_lines[i].wl_lnum. |
929 | */ |
930 | /* If the topline didn't change, delete old filler lines, |
931 | * otherwise delete filler lines of the new topline... */ |
932 | if (wp->w_lines[0].wl_lnum == wp->w_topline) |
933 | row += wp->w_old_topfill; |
934 | else |
935 | row += diff_check_fill(wp, wp->w_topline); |
936 | /* ... but don't delete new filler lines. */ |
937 | row -= wp->w_topfill; |
938 | if (row > 0) { |
939 | win_scroll_lines(wp, 0, -row); |
940 | bot_start = wp->w_grid.Rows - row; |
941 | } |
942 | if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { |
943 | /* |
944 | * Skip the lines (below the deleted lines) that are still |
945 | * valid and don't need redrawing. Copy their info |
946 | * upwards, to compensate for the deleted lines. Set |
947 | * bot_start to the first row that needs redrawing. |
948 | */ |
949 | bot_start = 0; |
950 | idx = 0; |
951 | for (;; ) { |
952 | wp->w_lines[idx] = wp->w_lines[j]; |
953 | /* stop at line that didn't fit, unless it is still |
954 | * valid (no lines deleted) */ |
955 | if (row > 0 && bot_start + row |
956 | + (int)wp->w_lines[j].wl_size > wp->w_grid.Rows) { |
957 | wp->w_lines_valid = idx + 1; |
958 | break; |
959 | } |
960 | bot_start += wp->w_lines[idx++].wl_size; |
961 | |
962 | /* stop at the last valid entry in w_lines[].wl_size */ |
963 | if (++j >= wp->w_lines_valid) { |
964 | wp->w_lines_valid = idx; |
965 | break; |
966 | } |
967 | } |
968 | /* Correct the first entry for filler lines at the top |
969 | * when it won't get updated below. */ |
970 | if (wp->w_p_diff && bot_start > 0) |
971 | wp->w_lines[0].wl_size = |
972 | plines_win_nofill(wp, wp->w_topline, true) |
973 | + wp->w_topfill; |
974 | } |
975 | } |
976 | } |
977 | |
978 | // When starting redraw in the first line, redraw all lines. |
979 | if (mid_start == 0) { |
980 | mid_end = wp->w_grid.Rows; |
981 | } |
982 | } else { |
983 | /* Not VALID or INVERTED: redraw all lines. */ |
984 | mid_start = 0; |
985 | mid_end = wp->w_grid.Rows; |
986 | } |
987 | |
988 | if (type == SOME_VALID) { |
989 | /* SOME_VALID: redraw all lines. */ |
990 | mid_start = 0; |
991 | mid_end = wp->w_grid.Rows; |
992 | type = NOT_VALID; |
993 | } |
994 | |
995 | /* check if we are updating or removing the inverted part */ |
996 | if ((VIsual_active && buf == curwin->w_buffer) |
997 | || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { |
998 | linenr_T from, to; |
999 | |
1000 | if (VIsual_active) { |
1001 | if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { |
1002 | // If the type of Visual selection changed, redraw the whole |
1003 | // selection. Also when the ownership of the X selection is |
1004 | // gained or lost. |
1005 | if (curwin->w_cursor.lnum < VIsual.lnum) { |
1006 | from = curwin->w_cursor.lnum; |
1007 | to = VIsual.lnum; |
1008 | } else { |
1009 | from = VIsual.lnum; |
1010 | to = curwin->w_cursor.lnum; |
1011 | } |
1012 | /* redraw more when the cursor moved as well */ |
1013 | if (wp->w_old_cursor_lnum < from) |
1014 | from = wp->w_old_cursor_lnum; |
1015 | if (wp->w_old_cursor_lnum > to) |
1016 | to = wp->w_old_cursor_lnum; |
1017 | if (wp->w_old_visual_lnum < from) |
1018 | from = wp->w_old_visual_lnum; |
1019 | if (wp->w_old_visual_lnum > to) |
1020 | to = wp->w_old_visual_lnum; |
1021 | } else { |
1022 | /* |
1023 | * Find the line numbers that need to be updated: The lines |
1024 | * between the old cursor position and the current cursor |
1025 | * position. Also check if the Visual position changed. |
1026 | */ |
1027 | if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { |
1028 | from = curwin->w_cursor.lnum; |
1029 | to = wp->w_old_cursor_lnum; |
1030 | } else { |
1031 | from = wp->w_old_cursor_lnum; |
1032 | to = curwin->w_cursor.lnum; |
1033 | if (from == 0) /* Visual mode just started */ |
1034 | from = to; |
1035 | } |
1036 | |
1037 | if (VIsual.lnum != wp->w_old_visual_lnum |
1038 | || VIsual.col != wp->w_old_visual_col) { |
1039 | if (wp->w_old_visual_lnum < from |
1040 | && wp->w_old_visual_lnum != 0) |
1041 | from = wp->w_old_visual_lnum; |
1042 | if (wp->w_old_visual_lnum > to) |
1043 | to = wp->w_old_visual_lnum; |
1044 | if (VIsual.lnum < from) |
1045 | from = VIsual.lnum; |
1046 | if (VIsual.lnum > to) |
1047 | to = VIsual.lnum; |
1048 | } |
1049 | } |
1050 | |
1051 | /* |
1052 | * If in block mode and changed column or curwin->w_curswant: |
1053 | * update all lines. |
1054 | * First compute the actual start and end column. |
1055 | */ |
1056 | if (VIsual_mode == Ctrl_V) { |
1057 | colnr_T fromc, toc; |
1058 | int save_ve_flags = ve_flags; |
1059 | |
1060 | if (curwin->w_p_lbr) |
1061 | ve_flags = VE_ALL; |
1062 | |
1063 | getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); |
1064 | ve_flags = save_ve_flags; |
1065 | ++toc; |
1066 | if (curwin->w_curswant == MAXCOL) |
1067 | toc = MAXCOL; |
1068 | |
1069 | if (fromc != wp->w_old_cursor_fcol |
1070 | || toc != wp->w_old_cursor_lcol) { |
1071 | if (from > VIsual.lnum) |
1072 | from = VIsual.lnum; |
1073 | if (to < VIsual.lnum) |
1074 | to = VIsual.lnum; |
1075 | } |
1076 | wp->w_old_cursor_fcol = fromc; |
1077 | wp->w_old_cursor_lcol = toc; |
1078 | } |
1079 | } else { |
1080 | /* Use the line numbers of the old Visual area. */ |
1081 | if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { |
1082 | from = wp->w_old_cursor_lnum; |
1083 | to = wp->w_old_visual_lnum; |
1084 | } else { |
1085 | from = wp->w_old_visual_lnum; |
1086 | to = wp->w_old_cursor_lnum; |
1087 | } |
1088 | } |
1089 | |
1090 | /* |
1091 | * There is no need to update lines above the top of the window. |
1092 | */ |
1093 | if (from < wp->w_topline) |
1094 | from = wp->w_topline; |
1095 | |
1096 | /* |
1097 | * If we know the value of w_botline, use it to restrict the update to |
1098 | * the lines that are visible in the window. |
1099 | */ |
1100 | if (wp->w_valid & VALID_BOTLINE) { |
1101 | if (from >= wp->w_botline) |
1102 | from = wp->w_botline - 1; |
1103 | if (to >= wp->w_botline) |
1104 | to = wp->w_botline - 1; |
1105 | } |
1106 | |
1107 | /* |
1108 | * Find the minimal part to be updated. |
1109 | * Watch out for scrolling that made entries in w_lines[] invalid. |
1110 | * E.g., CTRL-U makes the first half of w_lines[] invalid and sets |
1111 | * top_end; need to redraw from top_end to the "to" line. |
1112 | * A middle mouse click with a Visual selection may change the text |
1113 | * above the Visual area and reset wl_valid, do count these for |
1114 | * mid_end (in srow). |
1115 | */ |
1116 | if (mid_start > 0) { |
1117 | lnum = wp->w_topline; |
1118 | idx = 0; |
1119 | srow = 0; |
1120 | if (scrolled_down) |
1121 | mid_start = top_end; |
1122 | else |
1123 | mid_start = 0; |
1124 | while (lnum < from && idx < wp->w_lines_valid) { /* find start */ |
1125 | if (wp->w_lines[idx].wl_valid) |
1126 | mid_start += wp->w_lines[idx].wl_size; |
1127 | else if (!scrolled_down) |
1128 | srow += wp->w_lines[idx].wl_size; |
1129 | ++idx; |
1130 | if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) |
1131 | lnum = wp->w_lines[idx].wl_lnum; |
1132 | else |
1133 | ++lnum; |
1134 | } |
1135 | srow += mid_start; |
1136 | mid_end = wp->w_grid.Rows; |
1137 | for (; idx < wp->w_lines_valid; idx++) { // find end |
1138 | if (wp->w_lines[idx].wl_valid |
1139 | && wp->w_lines[idx].wl_lnum >= to + 1) { |
1140 | /* Only update until first row of this line */ |
1141 | mid_end = srow; |
1142 | break; |
1143 | } |
1144 | srow += wp->w_lines[idx].wl_size; |
1145 | } |
1146 | } |
1147 | } |
1148 | |
1149 | if (VIsual_active && buf == curwin->w_buffer) { |
1150 | wp->w_old_visual_mode = VIsual_mode; |
1151 | wp->w_old_cursor_lnum = curwin->w_cursor.lnum; |
1152 | wp->w_old_visual_lnum = VIsual.lnum; |
1153 | wp->w_old_visual_col = VIsual.col; |
1154 | wp->w_old_curswant = curwin->w_curswant; |
1155 | } else { |
1156 | wp->w_old_visual_mode = 0; |
1157 | wp->w_old_cursor_lnum = 0; |
1158 | wp->w_old_visual_lnum = 0; |
1159 | wp->w_old_visual_col = 0; |
1160 | } |
1161 | |
1162 | /* reset got_int, otherwise regexp won't work */ |
1163 | save_got_int = got_int; |
1164 | got_int = 0; |
1165 | // Set the time limit to 'redrawtime'. |
1166 | proftime_T syntax_tm = profile_setlimit(p_rdt); |
1167 | syn_set_timeout(&syntax_tm); |
1168 | win_foldinfo.fi_level = 0; |
1169 | |
1170 | /* |
1171 | * Update all the window rows. |
1172 | */ |
1173 | idx = 0; /* first entry in w_lines[].wl_size */ |
1174 | row = 0; |
1175 | srow = 0; |
1176 | lnum = wp->w_topline; /* first line shown in window */ |
1177 | for (;; ) { |
1178 | /* stop updating when reached the end of the window (check for _past_ |
1179 | * the end of the window is at the end of the loop) */ |
1180 | if (row == wp->w_grid.Rows) { |
1181 | didline = true; |
1182 | break; |
1183 | } |
1184 | |
1185 | /* stop updating when hit the end of the file */ |
1186 | if (lnum > buf->b_ml.ml_line_count) { |
1187 | eof = TRUE; |
1188 | break; |
1189 | } |
1190 | |
1191 | /* Remember the starting row of the line that is going to be dealt |
1192 | * with. It is used further down when the line doesn't fit. */ |
1193 | srow = row; |
1194 | |
1195 | // Update a line when it is in an area that needs updating, when it |
1196 | // has changes or w_lines[idx] is invalid. |
1197 | // "bot_start" may be halfway a wrapped line after using |
1198 | // win_scroll_lines(), check if the current line includes it. |
1199 | // When syntax folding is being used, the saved syntax states will |
1200 | // already have been updated, we can't see where the syntax state is |
1201 | // the same again, just update until the end of the window. |
1202 | if (row < top_end |
1203 | || (row >= mid_start && row < mid_end) |
1204 | || top_to_mod |
1205 | || idx >= wp->w_lines_valid |
1206 | || (row + wp->w_lines[idx].wl_size > bot_start) |
1207 | || (mod_top != 0 |
1208 | && (lnum == mod_top |
1209 | || (lnum >= mod_top |
1210 | && (lnum < mod_bot |
1211 | || did_update == DID_FOLD |
1212 | || (did_update == DID_LINE |
1213 | && syntax_present(wp) |
1214 | && ((foldmethodIsSyntax(wp) |
1215 | && hasAnyFolding(wp)) |
1216 | || syntax_check_changed(lnum))) |
1217 | // match in fixed position might need redraw |
1218 | // if lines were inserted or deleted |
1219 | || (wp->w_match_head != NULL |
1220 | && buf->b_mod_xlines != 0)))))) { |
1221 | if (lnum == mod_top) { |
1222 | top_to_mod = false; |
1223 | } |
1224 | |
1225 | /* |
1226 | * When at start of changed lines: May scroll following lines |
1227 | * up or down to minimize redrawing. |
1228 | * Don't do this when the change continues until the end. |
1229 | * Don't scroll when dollar_vcol >= 0, keep the "$". |
1230 | */ |
1231 | if (lnum == mod_top |
1232 | && mod_bot != MAXLNUM |
1233 | && !(dollar_vcol >= 0 && mod_bot == mod_top + 1)) { |
1234 | int old_rows = 0; |
1235 | int new_rows = 0; |
1236 | int xtra_rows; |
1237 | linenr_T l; |
1238 | |
1239 | /* Count the old number of window rows, using w_lines[], which |
1240 | * should still contain the sizes for the lines as they are |
1241 | * currently displayed. */ |
1242 | for (i = idx; i < wp->w_lines_valid; ++i) { |
1243 | /* Only valid lines have a meaningful wl_lnum. Invalid |
1244 | * lines are part of the changed area. */ |
1245 | if (wp->w_lines[i].wl_valid |
1246 | && wp->w_lines[i].wl_lnum == mod_bot) |
1247 | break; |
1248 | old_rows += wp->w_lines[i].wl_size; |
1249 | if (wp->w_lines[i].wl_valid |
1250 | && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { |
1251 | /* Must have found the last valid entry above mod_bot. |
1252 | * Add following invalid entries. */ |
1253 | ++i; |
1254 | while (i < wp->w_lines_valid |
1255 | && !wp->w_lines[i].wl_valid) |
1256 | old_rows += wp->w_lines[i++].wl_size; |
1257 | break; |
1258 | } |
1259 | } |
1260 | |
1261 | if (i >= wp->w_lines_valid) { |
1262 | /* We can't find a valid line below the changed lines, |
1263 | * need to redraw until the end of the window. |
1264 | * Inserting/deleting lines has no use. */ |
1265 | bot_start = 0; |
1266 | } else { |
1267 | /* Able to count old number of rows: Count new window |
1268 | * rows, and may insert/delete lines */ |
1269 | j = idx; |
1270 | for (l = lnum; l < mod_bot; l++) { |
1271 | if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { |
1272 | new_rows++; |
1273 | } else if (l == wp->w_topline) { |
1274 | new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; |
1275 | } else { |
1276 | new_rows += plines_win(wp, l, true); |
1277 | } |
1278 | j++; |
1279 | if (new_rows > wp->w_grid.Rows - row - 2) { |
1280 | // it's getting too much, must redraw the rest |
1281 | new_rows = 9999; |
1282 | break; |
1283 | } |
1284 | } |
1285 | xtra_rows = new_rows - old_rows; |
1286 | if (xtra_rows < 0) { |
1287 | /* May scroll text up. If there is not enough |
1288 | * remaining text or scrolling fails, must redraw the |
1289 | * rest. If scrolling works, must redraw the text |
1290 | * below the scrolled text. */ |
1291 | if (row - xtra_rows >= wp->w_grid.Rows - 2) { |
1292 | mod_bot = MAXLNUM; |
1293 | } else { |
1294 | win_scroll_lines(wp, row, xtra_rows); |
1295 | bot_start = wp->w_grid.Rows + xtra_rows; |
1296 | } |
1297 | } else if (xtra_rows > 0) { |
1298 | /* May scroll text down. If there is not enough |
1299 | * remaining text of scrolling fails, must redraw the |
1300 | * rest. */ |
1301 | if (row + xtra_rows >= wp->w_grid.Rows - 2) { |
1302 | mod_bot = MAXLNUM; |
1303 | } else { |
1304 | win_scroll_lines(wp, row + old_rows, xtra_rows); |
1305 | if (top_end > row + old_rows) { |
1306 | // Scrolled the part at the top that requires |
1307 | // updating down. |
1308 | top_end += xtra_rows; |
1309 | } |
1310 | } |
1311 | } |
1312 | |
1313 | /* When not updating the rest, may need to move w_lines[] |
1314 | * entries. */ |
1315 | if (mod_bot != MAXLNUM && i != j) { |
1316 | if (j < i) { |
1317 | int x = row + new_rows; |
1318 | |
1319 | /* move entries in w_lines[] upwards */ |
1320 | for (;; ) { |
1321 | /* stop at last valid entry in w_lines[] */ |
1322 | if (i >= wp->w_lines_valid) { |
1323 | wp->w_lines_valid = j; |
1324 | break; |
1325 | } |
1326 | wp->w_lines[j] = wp->w_lines[i]; |
1327 | /* stop at a line that won't fit */ |
1328 | if (x + (int)wp->w_lines[j].wl_size |
1329 | > wp->w_grid.Rows) { |
1330 | wp->w_lines_valid = j + 1; |
1331 | break; |
1332 | } |
1333 | x += wp->w_lines[j++].wl_size; |
1334 | ++i; |
1335 | } |
1336 | if (bot_start > x) |
1337 | bot_start = x; |
1338 | } else { /* j > i */ |
1339 | /* move entries in w_lines[] downwards */ |
1340 | j -= i; |
1341 | wp->w_lines_valid += j; |
1342 | if (wp->w_lines_valid > wp->w_grid.Rows) { |
1343 | wp->w_lines_valid = wp->w_grid.Rows; |
1344 | } |
1345 | for (i = wp->w_lines_valid; i - j >= idx; i--) { |
1346 | wp->w_lines[i] = wp->w_lines[i - j]; |
1347 | } |
1348 | |
1349 | /* The w_lines[] entries for inserted lines are |
1350 | * now invalid, but wl_size may be used above. |
1351 | * Reset to zero. */ |
1352 | while (i >= idx) { |
1353 | wp->w_lines[i].wl_size = 0; |
1354 | wp->w_lines[i--].wl_valid = FALSE; |
1355 | } |
1356 | } |
1357 | } |
1358 | } |
1359 | } |
1360 | |
1361 | /* |
1362 | * When lines are folded, display one line for all of them. |
1363 | * Otherwise, display normally (can be several display lines when |
1364 | * 'wrap' is on). |
1365 | */ |
1366 | fold_count = foldedCount(wp, lnum, &win_foldinfo); |
1367 | if (fold_count != 0) { |
1368 | fold_line(wp, fold_count, &win_foldinfo, lnum, row); |
1369 | ++row; |
1370 | --fold_count; |
1371 | wp->w_lines[idx].wl_folded = TRUE; |
1372 | wp->w_lines[idx].wl_lastlnum = lnum + fold_count; |
1373 | did_update = DID_FOLD; |
1374 | } else if (idx < wp->w_lines_valid |
1375 | && wp->w_lines[idx].wl_valid |
1376 | && wp->w_lines[idx].wl_lnum == lnum |
1377 | && lnum > wp->w_topline |
1378 | && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) |
1379 | && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows |
1380 | && diff_check_fill(wp, lnum) == 0 |
1381 | ) { |
1382 | /* This line is not going to fit. Don't draw anything here, |
1383 | * will draw "@ " lines below. */ |
1384 | row = wp->w_grid.Rows + 1; |
1385 | } else { |
1386 | prepare_search_hl(wp, lnum); |
1387 | /* Let the syntax stuff know we skipped a few lines. */ |
1388 | if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum |
1389 | && syntax_present(wp)) |
1390 | syntax_end_parsing(syntax_last_parsed + 1); |
1391 | |
1392 | /* |
1393 | * Display one line. |
1394 | */ |
1395 | row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false); |
1396 | |
1397 | wp->w_lines[idx].wl_folded = FALSE; |
1398 | wp->w_lines[idx].wl_lastlnum = lnum; |
1399 | did_update = DID_LINE; |
1400 | syntax_last_parsed = lnum; |
1401 | } |
1402 | |
1403 | wp->w_lines[idx].wl_lnum = lnum; |
1404 | wp->w_lines[idx].wl_valid = true; |
1405 | |
1406 | if (row > wp->w_grid.Rows) { // past end of grid |
1407 | // we may need the size of that too long line later on |
1408 | if (dollar_vcol == -1) { |
1409 | wp->w_lines[idx].wl_size = plines_win(wp, lnum, true); |
1410 | } |
1411 | idx++; |
1412 | break; |
1413 | } |
1414 | if (dollar_vcol == -1) |
1415 | wp->w_lines[idx].wl_size = row - srow; |
1416 | ++idx; |
1417 | lnum += fold_count + 1; |
1418 | } else { |
1419 | if (wp->w_p_rnu) { |
1420 | // 'relativenumber' set: The text doesn't need to be drawn, but |
1421 | // the number column nearly always does. |
1422 | fold_count = foldedCount(wp, lnum, &win_foldinfo); |
1423 | if (fold_count != 0) { |
1424 | fold_line(wp, fold_count, &win_foldinfo, lnum, row); |
1425 | } else { |
1426 | (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true); |
1427 | } |
1428 | } |
1429 | |
1430 | // This line does not need to be drawn, advance to the next one. |
1431 | row += wp->w_lines[idx++].wl_size; |
1432 | if (row > wp->w_grid.Rows) { // past end of screen |
1433 | break; |
1434 | } |
1435 | lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; |
1436 | did_update = DID_NONE; |
1437 | } |
1438 | |
1439 | if (lnum > buf->b_ml.ml_line_count) { |
1440 | eof = TRUE; |
1441 | break; |
1442 | } |
1443 | } |
1444 | /* |
1445 | * End of loop over all window lines. |
1446 | */ |
1447 | |
1448 | |
1449 | if (idx > wp->w_lines_valid) |
1450 | wp->w_lines_valid = idx; |
1451 | |
1452 | /* |
1453 | * Let the syntax stuff know we stop parsing here. |
1454 | */ |
1455 | if (syntax_last_parsed != 0 && syntax_present(wp)) |
1456 | syntax_end_parsing(syntax_last_parsed + 1); |
1457 | |
1458 | /* |
1459 | * If we didn't hit the end of the file, and we didn't finish the last |
1460 | * line we were working on, then the line didn't fit. |
1461 | */ |
1462 | wp->w_empty_rows = 0; |
1463 | wp->w_filler_rows = 0; |
1464 | if (!eof && !didline) { |
1465 | int at_attr = hl_combine_attr(wp->w_hl_attr_normal, |
1466 | win_hl_attr(wp, HLF_AT)); |
1467 | if (lnum == wp->w_topline) { |
1468 | /* |
1469 | * Single line that does not fit! |
1470 | * Don't overwrite it, it can be edited. |
1471 | */ |
1472 | wp->w_botline = lnum + 1; |
1473 | } else if (diff_check_fill(wp, lnum) >= wp->w_grid.Rows - srow) { |
1474 | // Window ends in filler lines. |
1475 | wp->w_botline = lnum; |
1476 | wp->w_filler_rows = wp->w_grid.Rows - srow; |
1477 | } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" |
1478 | int scr_row = wp->w_grid.Rows - 1; |
1479 | |
1480 | // Last line isn't finished: Display "@@@" in the last screen line. |
1481 | grid_puts_len(&wp->w_grid, (char_u *)"@@" , 2, scr_row, 0, at_attr); |
1482 | |
1483 | grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns, |
1484 | '@', ' ', at_attr); |
1485 | set_empty_rows(wp, srow); |
1486 | wp->w_botline = lnum; |
1487 | } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" |
1488 | // Last line isn't finished: Display "@@@" at the end. |
1489 | grid_fill(&wp->w_grid, wp->w_grid.Rows - 1, wp->w_grid.Rows, |
1490 | wp->w_grid.Columns - 3, wp->w_grid.Columns, '@', '@', at_attr); |
1491 | set_empty_rows(wp, srow); |
1492 | wp->w_botline = lnum; |
1493 | } else { |
1494 | win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, HLF_AT); |
1495 | wp->w_botline = lnum; |
1496 | } |
1497 | } else { |
1498 | if (eof) { // we hit the end of the file |
1499 | wp->w_botline = buf->b_ml.ml_line_count + 1; |
1500 | j = diff_check_fill(wp, wp->w_botline); |
1501 | if (j > 0 && !wp->w_botfill) { |
1502 | // display filler lines at the end of the file |
1503 | if (char2cells(wp->w_p_fcs_chars.diff) > 1) { |
1504 | i = '-'; |
1505 | } else { |
1506 | i = wp->w_p_fcs_chars.diff; |
1507 | } |
1508 | if (row + j > wp->w_grid.Rows) { |
1509 | j = wp->w_grid.Rows - row; |
1510 | } |
1511 | win_draw_end(wp, i, i, true, row, row + (int)j, HLF_DED); |
1512 | row += j; |
1513 | } |
1514 | } else if (dollar_vcol == -1) |
1515 | wp->w_botline = lnum; |
1516 | |
1517 | // make sure the rest of the screen is blank |
1518 | // write the 'eob' character to rows that aren't part of the file. |
1519 | win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.Rows, |
1520 | HLF_EOB); |
1521 | } |
1522 | |
1523 | if (wp->w_redr_type >= REDRAW_TOP) { |
1524 | draw_vsep_win(wp, 0); |
1525 | } |
1526 | syn_set_timeout(NULL); |
1527 | |
1528 | /* Reset the type of redrawing required, the window has been updated. */ |
1529 | wp->w_redr_type = 0; |
1530 | wp->w_old_topfill = wp->w_topfill; |
1531 | wp->w_old_botfill = wp->w_botfill; |
1532 | |
1533 | if (dollar_vcol == -1) { |
1534 | /* |
1535 | * There is a trick with w_botline. If we invalidate it on each |
1536 | * change that might modify it, this will cause a lot of expensive |
1537 | * calls to plines() in update_topline() each time. Therefore the |
1538 | * value of w_botline is often approximated, and this value is used to |
1539 | * compute the value of w_topline. If the value of w_botline was |
1540 | * wrong, check that the value of w_topline is correct (cursor is on |
1541 | * the visible part of the text). If it's not, we need to redraw |
1542 | * again. Mostly this just means scrolling up a few lines, so it |
1543 | * doesn't look too bad. Only do this for the current window (where |
1544 | * changes are relevant). |
1545 | */ |
1546 | wp->w_valid |= VALID_BOTLINE; |
1547 | if (wp == curwin && wp->w_botline != old_botline && !recursive) { |
1548 | recursive = TRUE; |
1549 | curwin->w_valid &= ~VALID_TOPLINE; |
1550 | update_topline(); /* may invalidate w_botline again */ |
1551 | if (must_redraw != 0) { |
1552 | /* Don't update for changes in buffer again. */ |
1553 | i = curbuf->b_mod_set; |
1554 | curbuf->b_mod_set = false; |
1555 | win_update(curwin); |
1556 | must_redraw = 0; |
1557 | curbuf->b_mod_set = i; |
1558 | } |
1559 | recursive = FALSE; |
1560 | } |
1561 | } |
1562 | |
1563 | /* restore got_int, unless CTRL-C was hit while redrawing */ |
1564 | if (!got_int) |
1565 | got_int = save_got_int; |
1566 | } |
1567 | |
1568 | /// Returns width of the signcolumn that should be used for the whole window |
1569 | /// |
1570 | /// @param wp window we want signcolumn width from |
1571 | /// @return max width of signcolumn (cell unit) |
1572 | /// |
1573 | /// @note Returns a constant for now but hopefully we can improve neovim so that |
1574 | /// the returned value width adapts to the maximum number of marks to draw |
1575 | /// for the window |
1576 | /// TODO(teto) |
1577 | int win_signcol_width(win_T *wp) |
1578 | { |
1579 | // 2 is vim default value |
1580 | return 2; |
1581 | } |
1582 | |
1583 | /// Call grid_fill() with columns adjusted for 'rightleft' if needed. |
1584 | /// Return the new offset. |
1585 | static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, |
1586 | int endrow, int attr) |
1587 | { |
1588 | int nn = off + width; |
1589 | |
1590 | if (nn > wp->w_grid.Columns) { |
1591 | nn = wp->w_grid.Columns; |
1592 | } |
1593 | |
1594 | if (wp->w_p_rl) { |
1595 | grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - nn, W_ENDCOL(wp) - off, |
1596 | c1, c2, attr); |
1597 | } else { |
1598 | grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr); |
1599 | } |
1600 | |
1601 | return nn; |
1602 | } |
1603 | |
1604 | /// Clear lines near the end of the window and mark the unused lines with "c1". |
1605 | /// Use "c2" as filler character. |
1606 | /// When "draw_margin" is true, then draw the sign/fold/number columns. |
1607 | static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, |
1608 | int endrow, hlf_T hl) |
1609 | { |
1610 | assert(hl >= 0 && hl < HLF_COUNT); |
1611 | int n = 0; |
1612 | |
1613 | if (draw_margin) { |
1614 | // draw the fold column |
1615 | int fdc = compute_foldcolumn(wp, 0); |
1616 | if (fdc > 0) { |
1617 | n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow, |
1618 | win_hl_attr(wp, HLF_FC)); |
1619 | } |
1620 | // draw the sign column |
1621 | int count = win_signcol_count(wp); |
1622 | if (count > 0) { |
1623 | n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, |
1624 | endrow, win_hl_attr(wp, HLF_SC)); |
1625 | } |
1626 | // draw the number column |
1627 | if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) { |
1628 | n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow, |
1629 | win_hl_attr(wp, HLF_N)); |
1630 | } |
1631 | } |
1632 | |
1633 | int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl)); |
1634 | |
1635 | if (wp->w_p_rl) { |
1636 | grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, |
1637 | c2, c2, attr); |
1638 | grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - 1 - n, W_ENDCOL(wp) - n, |
1639 | c1, c2, attr); |
1640 | } else { |
1641 | grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.Columns, c1, c2, attr); |
1642 | } |
1643 | |
1644 | set_empty_rows(wp, row); |
1645 | } |
1646 | |
1647 | |
1648 | /* |
1649 | * Advance **color_cols and return TRUE when there are columns to draw. |
1650 | */ |
1651 | static int advance_color_col(int vcol, int **color_cols) |
1652 | { |
1653 | while (**color_cols >= 0 && vcol > **color_cols) |
1654 | ++*color_cols; |
1655 | return **color_cols >= 0; |
1656 | } |
1657 | |
1658 | // Compute the width of the foldcolumn. Based on 'foldcolumn' and how much |
1659 | // space is available for window "wp", minus "col". |
1660 | static int compute_foldcolumn(win_T *wp, int col) |
1661 | { |
1662 | int fdc = wp->w_p_fdc; |
1663 | int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw; |
1664 | int wwidth = wp->w_grid.Columns; |
1665 | |
1666 | if (fdc > wwidth - (col + wmw)) { |
1667 | fdc = wwidth - (col + wmw); |
1668 | } |
1669 | return fdc; |
1670 | } |
1671 | |
1672 | /// Put a single char from an UTF-8 buffer into a line buffer. |
1673 | /// |
1674 | /// Handles composing chars and arabic shaping state. |
1675 | static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl) |
1676 | { |
1677 | const char_u *p = s->p; |
1678 | int cells = utf_ptr2cells(p); |
1679 | int c_len = utfc_ptr2len(p); |
1680 | int u8c, u8cc[MAX_MCO]; |
1681 | if (cells > maxcells) { |
1682 | return -1; |
1683 | } |
1684 | u8c = utfc_ptr2char(p, u8cc); |
1685 | if (*p < 0x80 && u8cc[0] == 0) { |
1686 | schar_from_ascii(dest[0], *p); |
1687 | s->prev_c = u8c; |
1688 | } else { |
1689 | if (p_arshape && !p_tbidi && arabic_char(u8c)) { |
1690 | // Do Arabic shaping. |
1691 | int pc, pc1, nc; |
1692 | int pcc[MAX_MCO]; |
1693 | int firstbyte = *p; |
1694 | |
1695 | // The idea of what is the previous and next |
1696 | // character depends on 'rightleft'. |
1697 | if (rl) { |
1698 | pc = s->prev_c; |
1699 | pc1 = s->prev_c1; |
1700 | nc = utf_ptr2char(p + c_len); |
1701 | s->prev_c1 = u8cc[0]; |
1702 | } else { |
1703 | pc = utfc_ptr2char(p + c_len, pcc); |
1704 | nc = s->prev_c; |
1705 | pc1 = pcc[0]; |
1706 | } |
1707 | s->prev_c = u8c; |
1708 | |
1709 | u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); |
1710 | } else { |
1711 | s->prev_c = u8c; |
1712 | } |
1713 | schar_from_cc(dest[0], u8c, u8cc); |
1714 | } |
1715 | if (cells > 1) { |
1716 | dest[1][0] = 0; |
1717 | } |
1718 | s->p += c_len; |
1719 | return cells; |
1720 | } |
1721 | |
1722 | /* |
1723 | * Display one folded line. |
1724 | */ |
1725 | static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T lnum, int row) |
1726 | { |
1727 | char_u buf[FOLD_TEXT_LEN]; |
1728 | pos_T *top, *bot; |
1729 | linenr_T lnume = lnum + fold_count - 1; |
1730 | int len; |
1731 | char_u *text; |
1732 | int fdc; |
1733 | int col; |
1734 | int txtcol; |
1735 | int off; |
1736 | |
1737 | /* Build the fold line: |
1738 | * 1. Add the cmdwin_type for the command-line window |
1739 | * 2. Add the 'foldcolumn' |
1740 | * 3. Add the 'number' or 'relativenumber' column |
1741 | * 4. Compose the text |
1742 | * 5. Add the text |
1743 | * 6. set highlighting for the Visual area an other text |
1744 | */ |
1745 | col = 0; |
1746 | off = 0; |
1747 | |
1748 | /* |
1749 | * 1. Add the cmdwin_type for the command-line window |
1750 | * Ignores 'rightleft', this window is never right-left. |
1751 | */ |
1752 | if (cmdwin_type != 0 && wp == curwin) { |
1753 | schar_from_ascii(linebuf_char[off], cmdwin_type); |
1754 | linebuf_attr[off] = win_hl_attr(wp, HLF_AT); |
1755 | col++; |
1756 | } |
1757 | |
1758 | // 2. Add the 'foldcolumn' |
1759 | // Reduce the width when there is not enough space. |
1760 | fdc = compute_foldcolumn(wp, col); |
1761 | if (fdc > 0) { |
1762 | fill_foldcolumn(buf, wp, TRUE, lnum); |
1763 | if (wp->w_p_rl) { |
1764 | int i; |
1765 | |
1766 | copy_text_attr(off + wp->w_grid.Columns - fdc - col, buf, fdc, |
1767 | win_hl_attr(wp, HLF_FC)); |
1768 | // reverse the fold column |
1769 | for (i = 0; i < fdc; i++) { |
1770 | schar_from_ascii(linebuf_char[off + wp->w_grid.Columns - i - 1 - col], |
1771 | buf[i]); |
1772 | } |
1773 | } else { |
1774 | copy_text_attr(off + col, buf, fdc, win_hl_attr(wp, HLF_FC)); |
1775 | } |
1776 | col += fdc; |
1777 | } |
1778 | |
1779 | # define RL_MEMSET(p, v, l) \ |
1780 | do { \ |
1781 | if (wp->w_p_rl) { \ |
1782 | for (int ri = 0; ri < l; ri++) { \ |
1783 | linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \ |
1784 | } \ |
1785 | } else { \ |
1786 | for (int ri = 0; ri < l; ri++) { \ |
1787 | linebuf_attr[off + (p) + ri] = v; \ |
1788 | } \ |
1789 | } \ |
1790 | } while (0) |
1791 | |
1792 | /* Set all attributes of the 'number' or 'relativenumber' column and the |
1793 | * text */ |
1794 | RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col); |
1795 | |
1796 | // If signs are being displayed, add spaces. |
1797 | if (win_signcol_count(wp) > 0) { |
1798 | len = wp->w_grid.Columns - col; |
1799 | if (len > 0) { |
1800 | int len_max = win_signcol_width(wp) * win_signcol_count(wp); |
1801 | if (len > len_max) { |
1802 | len = len_max; |
1803 | } |
1804 | char_u space_buf[18] = " " ; |
1805 | assert((size_t)len_max <= sizeof(space_buf)); |
1806 | copy_text_attr(off + col, space_buf, len, |
1807 | win_hl_attr(wp, HLF_FL)); |
1808 | col += len; |
1809 | } |
1810 | } |
1811 | |
1812 | /* |
1813 | * 3. Add the 'number' or 'relativenumber' column |
1814 | */ |
1815 | if (wp->w_p_nu || wp->w_p_rnu) { |
1816 | len = wp->w_grid.Columns - col; |
1817 | if (len > 0) { |
1818 | int w = number_width(wp); |
1819 | long num; |
1820 | char *fmt = "%*ld " ; |
1821 | |
1822 | if (len > w + 1) |
1823 | len = w + 1; |
1824 | |
1825 | if (wp->w_p_nu && !wp->w_p_rnu) |
1826 | /* 'number' + 'norelativenumber' */ |
1827 | num = (long)lnum; |
1828 | else { |
1829 | /* 'relativenumber', don't use negative numbers */ |
1830 | num = labs((long)get_cursor_rel_lnum(wp, lnum)); |
1831 | if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { |
1832 | /* 'number' + 'relativenumber': cursor line shows absolute |
1833 | * line number */ |
1834 | num = lnum; |
1835 | fmt = "%-*ld " ; |
1836 | } |
1837 | } |
1838 | |
1839 | snprintf((char *)buf, FOLD_TEXT_LEN, fmt, w, num); |
1840 | if (wp->w_p_rl) { |
1841 | // the line number isn't reversed |
1842 | copy_text_attr(off + wp->w_grid.Columns - len - col, buf, len, |
1843 | win_hl_attr(wp, HLF_FL)); |
1844 | } else { |
1845 | copy_text_attr(off + col, buf, len, win_hl_attr(wp, HLF_FL)); |
1846 | } |
1847 | col += len; |
1848 | } |
1849 | } |
1850 | |
1851 | /* |
1852 | * 4. Compose the folded-line string with 'foldtext', if set. |
1853 | */ |
1854 | text = get_foldtext(wp, lnum, lnume, foldinfo, buf); |
1855 | |
1856 | txtcol = col; /* remember where text starts */ |
1857 | |
1858 | // 5. move the text to linebuf_char[off]. Fill up with "fold". |
1859 | // Right-left text is put in columns 0 - number-col, normal text is put |
1860 | // in columns number-col - window-width. |
1861 | int idx; |
1862 | |
1863 | if (wp->w_p_rl) { |
1864 | idx = off; |
1865 | } else { |
1866 | idx = off + col; |
1867 | } |
1868 | |
1869 | LineState s = LINE_STATE(text); |
1870 | |
1871 | while (*s.p != NUL) { |
1872 | // TODO(bfredl): cargo-culted from the old Vim code: |
1873 | // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; } |
1874 | // This is obvious wrong. If Vim ever fixes this, solve for "cells" again |
1875 | // in the correct condition. |
1876 | int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0); |
1877 | int cells = line_putchar(&s, &linebuf_char[idx], maxcells, wp->w_p_rl); |
1878 | if (cells == -1) { |
1879 | break; |
1880 | } |
1881 | col += cells; |
1882 | idx += cells; |
1883 | } |
1884 | |
1885 | /* Fill the rest of the line with the fold filler */ |
1886 | if (wp->w_p_rl) |
1887 | col -= txtcol; |
1888 | |
1889 | schar_T sc; |
1890 | schar_from_char(sc, wp->w_p_fcs_chars.fold); |
1891 | while (col < wp->w_grid.Columns |
1892 | - (wp->w_p_rl ? txtcol : 0) |
1893 | ) { |
1894 | schar_copy(linebuf_char[off+col++], sc); |
1895 | } |
1896 | |
1897 | if (text != buf) |
1898 | xfree(text); |
1899 | |
1900 | /* |
1901 | * 6. set highlighting for the Visual area an other text. |
1902 | * If all folded lines are in the Visual area, highlight the line. |
1903 | */ |
1904 | if (VIsual_active && wp->w_buffer == curwin->w_buffer) { |
1905 | if (ltoreq(curwin->w_cursor, VIsual)) { |
1906 | /* Visual is after curwin->w_cursor */ |
1907 | top = &curwin->w_cursor; |
1908 | bot = &VIsual; |
1909 | } else { |
1910 | /* Visual is before curwin->w_cursor */ |
1911 | top = &VIsual; |
1912 | bot = &curwin->w_cursor; |
1913 | } |
1914 | if (lnum >= top->lnum |
1915 | && lnume <= bot->lnum |
1916 | && (VIsual_mode != 'v' |
1917 | || ((lnum > top->lnum |
1918 | || (lnum == top->lnum |
1919 | && top->col == 0)) |
1920 | && (lnume < bot->lnum |
1921 | || (lnume == bot->lnum |
1922 | && (bot->col - (*p_sel == 'e')) |
1923 | >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume, |
1924 | FALSE))))))) { |
1925 | if (VIsual_mode == Ctrl_V) { |
1926 | // Visual block mode: highlight the chars part of the block |
1927 | if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_grid.Columns) { |
1928 | if (wp->w_old_cursor_lcol != MAXCOL |
1929 | && wp->w_old_cursor_lcol + txtcol |
1930 | < (colnr_T)wp->w_grid.Columns) { |
1931 | len = wp->w_old_cursor_lcol; |
1932 | } else { |
1933 | len = wp->w_grid.Columns - txtcol; |
1934 | } |
1935 | RL_MEMSET(wp->w_old_cursor_fcol + txtcol, win_hl_attr(wp, HLF_V), |
1936 | len - (int)wp->w_old_cursor_fcol); |
1937 | } |
1938 | } else { |
1939 | // Set all attributes of the text |
1940 | RL_MEMSET(txtcol, win_hl_attr(wp, HLF_V), wp->w_grid.Columns - txtcol); |
1941 | } |
1942 | } |
1943 | } |
1944 | |
1945 | // Show colorcolumn in the fold line, but let cursorcolumn override it. |
1946 | if (wp->w_p_cc_cols) { |
1947 | int i = 0; |
1948 | int j = wp->w_p_cc_cols[i]; |
1949 | int old_txtcol = txtcol; |
1950 | |
1951 | while (j > -1) { |
1952 | txtcol += j; |
1953 | if (wp->w_p_wrap) { |
1954 | txtcol -= wp->w_skipcol; |
1955 | } else { |
1956 | txtcol -= wp->w_leftcol; |
1957 | } |
1958 | if (txtcol >= 0 && txtcol < wp->w_grid.Columns) { |
1959 | linebuf_attr[off + txtcol] = |
1960 | hl_combine_attr(linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_MC)); |
1961 | } |
1962 | txtcol = old_txtcol; |
1963 | j = wp->w_p_cc_cols[++i]; |
1964 | } |
1965 | } |
1966 | |
1967 | /* Show 'cursorcolumn' in the fold line. */ |
1968 | if (wp->w_p_cuc) { |
1969 | txtcol += wp->w_virtcol; |
1970 | if (wp->w_p_wrap) |
1971 | txtcol -= wp->w_skipcol; |
1972 | else |
1973 | txtcol -= wp->w_leftcol; |
1974 | if (txtcol >= 0 && txtcol < wp->w_grid.Columns) { |
1975 | linebuf_attr[off + txtcol] = hl_combine_attr( |
1976 | linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_CUC)); |
1977 | } |
1978 | } |
1979 | |
1980 | grid_put_linebuf(&wp->w_grid, row, 0, wp->w_grid.Columns, wp->w_grid.Columns, |
1981 | false, wp, wp->w_hl_attr_normal, false); |
1982 | |
1983 | /* |
1984 | * Update w_cline_height and w_cline_folded if the cursor line was |
1985 | * updated (saves a call to plines() later). |
1986 | */ |
1987 | if (wp == curwin |
1988 | && lnum <= curwin->w_cursor.lnum |
1989 | && lnume >= curwin->w_cursor.lnum) { |
1990 | curwin->w_cline_row = row; |
1991 | curwin->w_cline_height = 1; |
1992 | curwin->w_cline_folded = true; |
1993 | curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); |
1994 | conceal_cursor_used = conceal_cursor_line(curwin); |
1995 | } |
1996 | } |
1997 | |
1998 | |
1999 | /// Copy "buf[len]" to linebuf_char["off"] and set attributes to "attr". |
2000 | /// |
2001 | /// Only works for ASCII text! |
2002 | static void copy_text_attr(int off, char_u *buf, int len, int attr) |
2003 | { |
2004 | int i; |
2005 | |
2006 | for (i = 0; i < len; i++) { |
2007 | schar_from_ascii(linebuf_char[off + i], buf[i]); |
2008 | linebuf_attr[off + i] = attr; |
2009 | } |
2010 | } |
2011 | |
2012 | /* |
2013 | * Fill the foldcolumn at "p" for window "wp". |
2014 | * Only to be called when 'foldcolumn' > 0. |
2015 | */ |
2016 | static void |
2017 | fill_foldcolumn ( |
2018 | char_u *p, |
2019 | win_T *wp, |
2020 | int closed, /* TRUE of FALSE */ |
2021 | linenr_T lnum /* current line number */ |
2022 | ) |
2023 | { |
2024 | int i = 0; |
2025 | int level; |
2026 | int first_level; |
2027 | int empty; |
2028 | int fdc = compute_foldcolumn(wp, 0); |
2029 | |
2030 | // Init to all spaces. |
2031 | memset(p, ' ', (size_t)fdc); |
2032 | |
2033 | level = win_foldinfo.fi_level; |
2034 | if (level > 0) { |
2035 | // If there is only one column put more info in it. |
2036 | empty = (fdc == 1) ? 0 : 1; |
2037 | |
2038 | // If the column is too narrow, we start at the lowest level that |
2039 | // fits and use numbers to indicated the depth. |
2040 | first_level = level - fdc - closed + 1 + empty; |
2041 | if (first_level < 1) { |
2042 | first_level = 1; |
2043 | } |
2044 | |
2045 | for (i = 0; i + empty < fdc; i++) { |
2046 | if (win_foldinfo.fi_lnum == lnum |
2047 | && first_level + i >= win_foldinfo.fi_low_level) { |
2048 | p[i] = '-'; |
2049 | } else if (first_level == 1) { |
2050 | p[i] = '|'; |
2051 | } else if (first_level + i <= 9) { |
2052 | p[i] = '0' + first_level + i; |
2053 | } else { |
2054 | p[i] = '>'; |
2055 | } |
2056 | if (first_level + i == level) { |
2057 | break; |
2058 | } |
2059 | } |
2060 | } |
2061 | if (closed) { |
2062 | p[i >= fdc ? i - 1 : i] = '+'; |
2063 | } |
2064 | } |
2065 | |
2066 | /* |
2067 | * Display line "lnum" of window 'wp' on the screen. |
2068 | * Start at row "startrow", stop when "endrow" is reached. |
2069 | * wp->w_virtcol needs to be valid. |
2070 | * |
2071 | * Return the number of last row the line occupies. |
2072 | */ |
2073 | static int |
2074 | win_line ( |
2075 | win_T *wp, |
2076 | linenr_T lnum, |
2077 | int startrow, |
2078 | int endrow, |
2079 | bool nochange, // not updating for changed text |
2080 | bool number_only // only update the number column |
2081 | ) |
2082 | { |
2083 | int c = 0; // init for GCC |
2084 | long vcol = 0; // virtual column (for tabs) |
2085 | long vcol_sbr = -1; // virtual column after showbreak |
2086 | long vcol_prev = -1; // "vcol" of previous character |
2087 | char_u *line; // current line |
2088 | char_u *ptr; // current position in "line" |
2089 | int row; // row in the window, excl w_winrow |
2090 | ScreenGrid *grid = &wp->w_grid; // grid specfic to the window |
2091 | |
2092 | char_u [57]; // sign, line number and 'fdc' must |
2093 | // fit in here |
2094 | int = 0; // number of extra chars |
2095 | char_u * = NULL; // string of extra chars, plus NUL |
2096 | char_u * = NULL; // p_extra needs to be freed |
2097 | int = NUL; // extra chars, all the same |
2098 | int c_final = NUL; // final char, mandatory if set |
2099 | int = 0; // attributes when n_extra != 0 |
2100 | static char_u *at_end_str = (char_u *)"" ; // used for p_extra when displaying |
2101 | // curwin->w_p_lcs_chars.eol at |
2102 | // end-of-line |
2103 | int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used |
2104 | int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used |
2105 | |
2106 | // saved "extra" items for when draw_state becomes WL_LINE (again) |
2107 | int = 0; |
2108 | char_u * = NULL; |
2109 | int = 0; |
2110 | int saved_c_final = 0; |
2111 | int saved_char_attr = 0; |
2112 | |
2113 | int n_attr = 0; /* chars with special attr */ |
2114 | int saved_attr2 = 0; /* char_attr saved for n_attr */ |
2115 | int n_attr3 = 0; /* chars with overruling special attr */ |
2116 | int saved_attr3 = 0; /* char_attr saved for n_attr3 */ |
2117 | |
2118 | int n_skip = 0; /* nr of chars to skip for 'nowrap' */ |
2119 | |
2120 | int fromcol = 0, tocol = 0; // start/end of inverting |
2121 | int fromcol_prev = -2; // start of inverting after cursor |
2122 | int noinvcur = false; // don't invert the cursor |
2123 | pos_T *top, *bot; |
2124 | int lnum_in_visual_area = false; |
2125 | pos_T pos; |
2126 | long v; |
2127 | |
2128 | int char_attr = 0; /* attributes for next character */ |
2129 | int attr_pri = FALSE; /* char_attr has priority */ |
2130 | int area_highlighting = FALSE; /* Visual or incsearch highlighting |
2131 | in this line */ |
2132 | int attr = 0; /* attributes for area highlighting */ |
2133 | int area_attr = 0; /* attributes desired by highlighting */ |
2134 | int search_attr = 0; /* attributes desired by 'hlsearch' */ |
2135 | int vcol_save_attr = 0; /* saved attr for 'cursorcolumn' */ |
2136 | int syntax_attr = 0; /* attributes desired by syntax */ |
2137 | int has_syntax = FALSE; /* this buffer has syntax highl. */ |
2138 | int save_did_emsg; |
2139 | int eol_hl_off = 0; // 1 if highlighted char after EOL |
2140 | int draw_color_col = false; // highlight colorcolumn |
2141 | int *color_cols = NULL; // pointer to according columns array |
2142 | bool has_spell = false; // this buffer has spell checking |
2143 | # define SPWORDLEN 150 |
2144 | char_u nextline[SPWORDLEN * 2]; /* text with start of the next line */ |
2145 | int nextlinecol = 0; /* column where nextline[] starts */ |
2146 | int nextline_idx = 0; /* index in nextline[] where next line |
2147 | starts */ |
2148 | int spell_attr = 0; /* attributes desired by spelling */ |
2149 | int word_end = 0; /* last byte with same spell_attr */ |
2150 | static linenr_T checked_lnum = 0; /* line number for "checked_col" */ |
2151 | static int checked_col = 0; /* column in "checked_lnum" up to which |
2152 | * there are no spell errors */ |
2153 | static int cap_col = -1; // column to check for Cap word |
2154 | static linenr_T capcol_lnum = 0; // line number where "cap_col" |
2155 | int cur_checked_col = 0; // checked column for current line |
2156 | int = 0; // has syntax or linebreak |
2157 | int multi_attr = 0; // attributes desired by multibyte |
2158 | int mb_l = 1; // multi-byte byte length |
2159 | int mb_c = 0; // decoded multi-byte character |
2160 | bool mb_utf8 = false; // screen char is UTF-8 char |
2161 | int u8cc[MAX_MCO]; // composing UTF-8 chars |
2162 | int filler_lines; // nr of filler lines to be drawn |
2163 | int filler_todo; // nr of filler lines still to do + 1 |
2164 | hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting |
2165 | int change_start = MAXCOL; // first col of changed area |
2166 | int change_end = -1; // last col of changed area |
2167 | colnr_T trailcol = MAXCOL; // start of trailing spaces |
2168 | int need_showbreak = false; // overlong line, skip first x chars |
2169 | int line_attr = 0; // attribute for the whole line |
2170 | int line_attr_lowprio = 0; // low-priority attribute for the line |
2171 | matchitem_T *cur; // points to the match list |
2172 | match_T *shl; // points to search_hl or a match |
2173 | int shl_flag; // flag to indicate whether search_hl |
2174 | // has been processed or not |
2175 | bool prevcol_hl_flag; // flag to indicate whether prevcol |
2176 | // equals startcol of search_hl or one |
2177 | // of the matches |
2178 | int prev_c = 0; // previous Arabic character |
2179 | int prev_c1 = 0; // first composing char for prev_c |
2180 | |
2181 | bool search_attr_from_match = false; // if search_attr is from :match |
2182 | BufhlLineInfo bufhl_info; // bufhl data for this line |
2183 | bool has_bufhl = false; // this buffer has highlight matches |
2184 | bool do_virttext = false; // draw virtual text for this line |
2185 | |
2186 | /* draw_state: items that are drawn in sequence: */ |
2187 | #define WL_START 0 /* nothing done yet */ |
2188 | # define WL_CMDLINE WL_START + 1 /* cmdline window column */ |
2189 | # define WL_FOLD WL_CMDLINE + 1 /* 'foldcolumn' */ |
2190 | # define WL_SIGN WL_FOLD + 1 /* column for signs */ |
2191 | #define WL_NR WL_SIGN + 1 /* line number */ |
2192 | # define WL_BRI WL_NR + 1 /* 'breakindent' */ |
2193 | # define WL_SBR WL_BRI + 1 /* 'showbreak' or 'diff' */ |
2194 | #define WL_LINE WL_SBR + 1 /* text in the line */ |
2195 | int draw_state = WL_START; /* what to draw next */ |
2196 | |
2197 | int syntax_flags = 0; |
2198 | int syntax_seqnr = 0; |
2199 | int prev_syntax_id = 0; |
2200 | int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); |
2201 | int is_concealing = false; |
2202 | int boguscols = 0; ///< nonexistent columns added to |
2203 | ///< force wrapping |
2204 | int vcol_off = 0; ///< offset for concealed characters |
2205 | int did_wcol = false; |
2206 | int match_conc = 0; ///< cchar for match functions |
2207 | int old_boguscols = 0; |
2208 | # define VCOL_HLC (vcol - vcol_off) |
2209 | # define FIX_FOR_BOGUSCOLS \ |
2210 | { \ |
2211 | n_extra += vcol_off; \ |
2212 | vcol -= vcol_off; \ |
2213 | vcol_off = 0; \ |
2214 | col -= boguscols; \ |
2215 | old_boguscols = boguscols; \ |
2216 | boguscols = 0; \ |
2217 | } |
2218 | |
2219 | if (startrow > endrow) /* past the end already! */ |
2220 | return startrow; |
2221 | |
2222 | row = startrow; |
2223 | |
2224 | if (!number_only) { |
2225 | // To speed up the loop below, set extra_check when there is linebreak, |
2226 | // trailing white space and/or syntax processing to be done. |
2227 | extra_check = wp->w_p_lbr; |
2228 | if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) { |
2229 | // Prepare for syntax highlighting in this line. When there is an |
2230 | // error, stop syntax highlighting. |
2231 | save_did_emsg = did_emsg; |
2232 | did_emsg = false; |
2233 | syntax_start(wp, lnum); |
2234 | if (did_emsg) { |
2235 | wp->w_s->b_syn_error = true; |
2236 | } else { |
2237 | did_emsg = save_did_emsg; |
2238 | if (!wp->w_s->b_syn_slow) { |
2239 | has_syntax = true; |
2240 | extra_check = true; |
2241 | } |
2242 | } |
2243 | } |
2244 | |
2245 | if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) { |
2246 | if (kv_size(bufhl_info.line->items)) { |
2247 | has_bufhl = true; |
2248 | extra_check = true; |
2249 | } |
2250 | if (kv_size(bufhl_info.line->virt_text)) { |
2251 | do_virttext = true; |
2252 | } |
2253 | } |
2254 | |
2255 | // Check for columns to display for 'colorcolumn'. |
2256 | color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; |
2257 | if (color_cols != NULL) { |
2258 | draw_color_col = advance_color_col(VCOL_HLC, &color_cols); |
2259 | } |
2260 | |
2261 | if (wp->w_p_spell |
2262 | && *wp->w_s->b_p_spl != NUL |
2263 | && !GA_EMPTY(&wp->w_s->b_langp) |
2264 | && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { |
2265 | // Prepare for spell checking. |
2266 | has_spell = true; |
2267 | extra_check = true; |
2268 | |
2269 | // Get the start of the next line, so that words that wrap to the next |
2270 | // line are found too: "et<line-break>al.". |
2271 | // Trick: skip a few chars for C/shell/Vim comments |
2272 | nextline[SPWORDLEN] = NUL; |
2273 | if (lnum < wp->w_buffer->b_ml.ml_line_count) { |
2274 | line = ml_get_buf(wp->w_buffer, lnum + 1, false); |
2275 | spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); |
2276 | } |
2277 | |
2278 | // When a word wrapped from the previous line the start of the current |
2279 | // line is valid. |
2280 | if (lnum == checked_lnum) { |
2281 | cur_checked_col = checked_col; |
2282 | } |
2283 | checked_lnum = 0; |
2284 | |
2285 | // When there was a sentence end in the previous line may require a |
2286 | // word starting with capital in this line. In line 1 always check |
2287 | // the first word. |
2288 | if (lnum != capcol_lnum) { |
2289 | cap_col = -1; |
2290 | } |
2291 | if (lnum == 1) { |
2292 | cap_col = 0; |
2293 | } |
2294 | capcol_lnum = 0; |
2295 | } |
2296 | |
2297 | // |
2298 | // handle visual active in this window |
2299 | // |
2300 | fromcol = -10; |
2301 | tocol = MAXCOL; |
2302 | if (VIsual_active && wp->w_buffer == curwin->w_buffer) { |
2303 | // Visual is after curwin->w_cursor |
2304 | if (ltoreq(curwin->w_cursor, VIsual)) { |
2305 | top = &curwin->w_cursor; |
2306 | bot = &VIsual; |
2307 | } else { // Visual is before curwin->w_cursor |
2308 | top = &VIsual; |
2309 | bot = &curwin->w_cursor; |
2310 | } |
2311 | lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); |
2312 | if (VIsual_mode == Ctrl_V) { // block mode |
2313 | if (lnum_in_visual_area) { |
2314 | fromcol = wp->w_old_cursor_fcol; |
2315 | tocol = wp->w_old_cursor_lcol; |
2316 | } |
2317 | } else { // non-block mode |
2318 | if (lnum > top->lnum && lnum <= bot->lnum) { |
2319 | fromcol = 0; |
2320 | } else if (lnum == top->lnum) { |
2321 | if (VIsual_mode == 'V') { // linewise |
2322 | fromcol = 0; |
2323 | } else { |
2324 | getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); |
2325 | if (gchar_pos(top) == NUL) { |
2326 | tocol = fromcol + 1; |
2327 | } |
2328 | } |
2329 | } |
2330 | if (VIsual_mode != 'V' && lnum == bot->lnum) { |
2331 | if (*p_sel == 'e' && bot->col == 0 |
2332 | && bot->coladd == 0) { |
2333 | fromcol = -10; |
2334 | tocol = MAXCOL; |
2335 | } else if (bot->col == MAXCOL) { |
2336 | tocol = MAXCOL; |
2337 | } else { |
2338 | pos = *bot; |
2339 | if (*p_sel == 'e') { |
2340 | getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); |
2341 | } else { |
2342 | getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); |
2343 | tocol++; |
2344 | } |
2345 | } |
2346 | } |
2347 | } |
2348 | |
2349 | // Check if the char under the cursor should be inverted (highlighted). |
2350 | if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin |
2351 | && cursor_is_block_during_visual(*p_sel == 'e')) { |
2352 | noinvcur = true; |
2353 | } |
2354 | |
2355 | // if inverting in this line set area_highlighting |
2356 | if (fromcol >= 0) { |
2357 | area_highlighting = true; |
2358 | attr = win_hl_attr(wp, HLF_V); |
2359 | } |
2360 | // handle 'incsearch' and ":s///c" highlighting |
2361 | } else if (highlight_match |
2362 | && wp == curwin |
2363 | && lnum >= curwin->w_cursor.lnum |
2364 | && lnum <= curwin->w_cursor.lnum + search_match_lines) { |
2365 | if (lnum == curwin->w_cursor.lnum) { |
2366 | getvcol(curwin, &(curwin->w_cursor), |
2367 | (colnr_T *)&fromcol, NULL, NULL); |
2368 | } else { |
2369 | fromcol = 0; |
2370 | } |
2371 | if (lnum == curwin->w_cursor.lnum + search_match_lines) { |
2372 | pos.lnum = lnum; |
2373 | pos.col = search_match_endcol; |
2374 | getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); |
2375 | } else { |
2376 | tocol = MAXCOL; |
2377 | } |
2378 | // do at least one character; happens when past end of line |
2379 | if (fromcol == tocol) { |
2380 | tocol = fromcol + 1; |
2381 | } |
2382 | area_highlighting = true; |
2383 | attr = win_hl_attr(wp, HLF_I); |
2384 | } |
2385 | } |
2386 | |
2387 | filler_lines = diff_check(wp, lnum); |
2388 | if (filler_lines < 0) { |
2389 | if (filler_lines == -1) { |
2390 | if (diff_find_change(wp, lnum, &change_start, &change_end)) |
2391 | diff_hlf = HLF_ADD; /* added line */ |
2392 | else if (change_start == 0) |
2393 | diff_hlf = HLF_TXD; /* changed text */ |
2394 | else |
2395 | diff_hlf = HLF_CHD; /* changed line */ |
2396 | } else |
2397 | diff_hlf = HLF_ADD; /* added line */ |
2398 | filler_lines = 0; |
2399 | area_highlighting = TRUE; |
2400 | } |
2401 | if (lnum == wp->w_topline) |
2402 | filler_lines = wp->w_topfill; |
2403 | filler_todo = filler_lines; |
2404 | |
2405 | // Cursor line highlighting for 'cursorline' in the current window. |
2406 | if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { |
2407 | // Do not show the cursor line when Visual mode is active, because it's |
2408 | // not clear what is selected then. |
2409 | if (!(wp == curwin && VIsual_active)) { |
2410 | int cul_attr = win_hl_attr(wp, HLF_CUL); |
2411 | HlAttrs ae = syn_attr2entry(cul_attr); |
2412 | |
2413 | // We make a compromise here (#7383): |
2414 | // * low-priority CursorLine if fg is not set |
2415 | // * high-priority ("same as Vim" priority) CursorLine if fg is set |
2416 | if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { |
2417 | line_attr_lowprio = cul_attr; |
2418 | } else { |
2419 | if (!(State & INSERT) && bt_quickfix(wp->w_buffer) |
2420 | && qf_current_entry(wp) == lnum) { |
2421 | line_attr = hl_combine_attr(cul_attr, line_attr); |
2422 | } else { |
2423 | line_attr = cul_attr; |
2424 | } |
2425 | } |
2426 | } |
2427 | // Update w_last_cursorline even if Visual mode is active. |
2428 | wp->w_last_cursorline = wp->w_cursor.lnum; |
2429 | } |
2430 | |
2431 | // If this line has a sign with line highlighting set line_attr. |
2432 | v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); |
2433 | if (v != 0) { |
2434 | line_attr = sign_get_attr((int)v, SIGN_LINEHL); |
2435 | } |
2436 | |
2437 | // Highlight the current line in the quickfix window. |
2438 | if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { |
2439 | line_attr = win_hl_attr(wp, HLF_QFL); |
2440 | } |
2441 | |
2442 | if (line_attr_lowprio || line_attr) { |
2443 | area_highlighting = true; |
2444 | } |
2445 | |
2446 | line = ml_get_buf(wp->w_buffer, lnum, FALSE); |
2447 | ptr = line; |
2448 | |
2449 | if (has_spell && !number_only) { |
2450 | // For checking first word with a capital skip white space. |
2451 | if (cap_col == 0) { |
2452 | cap_col = (int)getwhitecols(line); |
2453 | } |
2454 | |
2455 | /* To be able to spell-check over line boundaries copy the end of the |
2456 | * current line into nextline[]. Above the start of the next line was |
2457 | * copied to nextline[SPWORDLEN]. */ |
2458 | if (nextline[SPWORDLEN] == NUL) { |
2459 | /* No next line or it is empty. */ |
2460 | nextlinecol = MAXCOL; |
2461 | nextline_idx = 0; |
2462 | } else { |
2463 | v = (long)STRLEN(line); |
2464 | if (v < SPWORDLEN) { |
2465 | /* Short line, use it completely and append the start of the |
2466 | * next line. */ |
2467 | nextlinecol = 0; |
2468 | memmove(nextline, line, (size_t)v); |
2469 | STRMOVE(nextline + v, nextline + SPWORDLEN); |
2470 | nextline_idx = v + 1; |
2471 | } else { |
2472 | /* Long line, use only the last SPWORDLEN bytes. */ |
2473 | nextlinecol = v - SPWORDLEN; |
2474 | memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 |
2475 | nextline_idx = SPWORDLEN + 1; |
2476 | } |
2477 | } |
2478 | } |
2479 | |
2480 | if (wp->w_p_list) { |
2481 | if (curwin->w_p_lcs_chars.space |
2482 | || wp->w_p_lcs_chars.trail |
2483 | || wp->w_p_lcs_chars.nbsp) { |
2484 | extra_check = true; |
2485 | } |
2486 | // find start of trailing whitespace |
2487 | if (wp->w_p_lcs_chars.trail) { |
2488 | trailcol = (colnr_T)STRLEN(ptr); |
2489 | while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { |
2490 | trailcol--; |
2491 | } |
2492 | trailcol += (colnr_T) (ptr - line); |
2493 | } |
2494 | } |
2495 | |
2496 | /* |
2497 | * 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the |
2498 | * first character to be displayed. |
2499 | */ |
2500 | if (wp->w_p_wrap) |
2501 | v = wp->w_skipcol; |
2502 | else |
2503 | v = wp->w_leftcol; |
2504 | if (v > 0 && !number_only) { |
2505 | char_u *prev_ptr = ptr; |
2506 | while (vcol < v && *ptr != NUL) { |
2507 | c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); |
2508 | vcol += c; |
2509 | prev_ptr = ptr; |
2510 | MB_PTR_ADV(ptr); |
2511 | } |
2512 | |
2513 | // When: |
2514 | // - 'cuc' is set, or |
2515 | // - 'colorcolumn' is set, or |
2516 | // - 'virtualedit' is set, or |
2517 | // - the visual mode is active, |
2518 | // the end of the line may be before the start of the displayed part. |
2519 | if (vcol < v && (wp->w_p_cuc |
2520 | || draw_color_col |
2521 | || virtual_active() |
2522 | || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { |
2523 | vcol = v; |
2524 | } |
2525 | |
2526 | /* Handle a character that's not completely on the screen: Put ptr at |
2527 | * that character but skip the first few screen characters. */ |
2528 | if (vcol > v) { |
2529 | vcol -= c; |
2530 | ptr = prev_ptr; |
2531 | // If the character fits on the screen, don't need to skip it. |
2532 | // Except for a TAB. |
2533 | if (utf_ptr2cells(ptr) >= c || *ptr == TAB) { |
2534 | n_skip = v - vcol; |
2535 | } |
2536 | } |
2537 | |
2538 | /* |
2539 | * Adjust for when the inverted text is before the screen, |
2540 | * and when the start of the inverted text is before the screen. |
2541 | */ |
2542 | if (tocol <= vcol) |
2543 | fromcol = 0; |
2544 | else if (fromcol >= 0 && fromcol < vcol) |
2545 | fromcol = vcol; |
2546 | |
2547 | /* When w_skipcol is non-zero, first line needs 'showbreak' */ |
2548 | if (wp->w_p_wrap) |
2549 | need_showbreak = TRUE; |
2550 | /* When spell checking a word we need to figure out the start of the |
2551 | * word and if it's badly spelled or not. */ |
2552 | if (has_spell) { |
2553 | size_t len; |
2554 | colnr_T linecol = (colnr_T)(ptr - line); |
2555 | hlf_T spell_hlf = HLF_COUNT; |
2556 | |
2557 | pos = wp->w_cursor; |
2558 | wp->w_cursor.lnum = lnum; |
2559 | wp->w_cursor.col = linecol; |
2560 | len = spell_move_to(wp, FORWARD, TRUE, TRUE, &spell_hlf); |
2561 | |
2562 | /* spell_move_to() may call ml_get() and make "line" invalid */ |
2563 | line = ml_get_buf(wp->w_buffer, lnum, FALSE); |
2564 | ptr = line + linecol; |
2565 | |
2566 | if (len == 0 || (int)wp->w_cursor.col > ptr - line) { |
2567 | /* no bad word found at line start, don't check until end of a |
2568 | * word */ |
2569 | spell_hlf = HLF_COUNT; |
2570 | word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); |
2571 | } else { |
2572 | /* bad word found, use attributes until end of word */ |
2573 | assert(len <= INT_MAX); |
2574 | word_end = wp->w_cursor.col + (int)len + 1; |
2575 | |
2576 | /* Turn index into actual attributes. */ |
2577 | if (spell_hlf != HLF_COUNT) |
2578 | spell_attr = highlight_attr[spell_hlf]; |
2579 | } |
2580 | wp->w_cursor = pos; |
2581 | |
2582 | // Need to restart syntax highlighting for this line. |
2583 | if (has_syntax) { |
2584 | syntax_start(wp, lnum); |
2585 | } |
2586 | } |
2587 | } |
2588 | |
2589 | /* |
2590 | * Correct highlighting for cursor that can't be disabled. |
2591 | * Avoids having to check this for each character. |
2592 | */ |
2593 | if (fromcol >= 0) { |
2594 | if (noinvcur) { |
2595 | if ((colnr_T)fromcol == wp->w_virtcol) { |
2596 | /* highlighting starts at cursor, let it start just after the |
2597 | * cursor */ |
2598 | fromcol_prev = fromcol; |
2599 | fromcol = -1; |
2600 | } else if ((colnr_T)fromcol < wp->w_virtcol) |
2601 | /* restart highlighting after the cursor */ |
2602 | fromcol_prev = wp->w_virtcol; |
2603 | } |
2604 | if (fromcol >= tocol) |
2605 | fromcol = -1; |
2606 | } |
2607 | |
2608 | /* |
2609 | * Handle highlighting the last used search pattern and matches. |
2610 | * Do this for both search_hl and the match list. |
2611 | */ |
2612 | cur = wp->w_match_head; |
2613 | shl_flag = false; |
2614 | while ((cur != NULL || !shl_flag) && !number_only) { |
2615 | if (!shl_flag) { |
2616 | shl = &search_hl; |
2617 | shl_flag = true; |
2618 | } else { |
2619 | shl = &cur->hl; // -V595 |
2620 | } |
2621 | shl->startcol = MAXCOL; |
2622 | shl->endcol = MAXCOL; |
2623 | shl->attr_cur = 0; |
2624 | shl->is_addpos = false; |
2625 | v = (long)(ptr - line); |
2626 | if (cur != NULL) { |
2627 | cur->pos.cur = 0; |
2628 | } |
2629 | next_search_hl(wp, shl, lnum, (colnr_T)v, |
2630 | shl == &search_hl ? NULL : cur); |
2631 | if (wp->w_s->b_syn_slow) { |
2632 | has_syntax = false; |
2633 | } |
2634 | |
2635 | // Need to get the line again, a multi-line regexp may have made it |
2636 | // invalid. |
2637 | line = ml_get_buf(wp->w_buffer, lnum, false); |
2638 | ptr = line + v; |
2639 | |
2640 | if (shl->lnum != 0 && shl->lnum <= lnum) { |
2641 | if (shl->lnum == lnum) { |
2642 | shl->startcol = shl->rm.startpos[0].col; |
2643 | } else { |
2644 | shl->startcol = 0; |
2645 | } |
2646 | if (lnum == shl->lnum + shl->rm.endpos[0].lnum |
2647 | - shl->rm.startpos[0].lnum) { |
2648 | shl->endcol = shl->rm.endpos[0].col; |
2649 | } else { |
2650 | shl->endcol = MAXCOL; |
2651 | } |
2652 | // Highlight one character for an empty match. |
2653 | if (shl->startcol == shl->endcol) { |
2654 | if (line[shl->endcol] != NUL) { |
2655 | shl->endcol += (*mb_ptr2len)(line + shl->endcol); |
2656 | } else { |
2657 | ++shl->endcol; |
2658 | } |
2659 | } |
2660 | if ((long)shl->startcol < v) { // match at leftcol |
2661 | shl->attr_cur = shl->attr; |
2662 | search_attr = shl->attr; |
2663 | search_attr_from_match = shl != &search_hl; |
2664 | } |
2665 | area_highlighting = true; |
2666 | } |
2667 | if (shl != &search_hl && cur != NULL) |
2668 | cur = cur->next; |
2669 | } |
2670 | |
2671 | unsigned off = 0; // Offset relative start of line |
2672 | int col = 0; // Visual column on screen. |
2673 | if (wp->w_p_rl) { |
2674 | // Rightleft window: process the text in the normal direction, but put |
2675 | // it in linebuf_char[off] from right to left. Start at the |
2676 | // rightmost column of the window. |
2677 | col = grid->Columns - 1; |
2678 | off += col; |
2679 | } |
2680 | |
2681 | // wont highlight after 1024 columns |
2682 | int term_attrs[1024] = {0}; |
2683 | if (wp->w_buffer->terminal) { |
2684 | terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); |
2685 | extra_check = true; |
2686 | } |
2687 | |
2688 | int sign_idx = 0; |
2689 | // Repeat for the whole displayed line. |
2690 | for (;; ) { |
2691 | int has_match_conc = 0; ///< match wants to conceal |
2692 | bool did_decrement_ptr = false; |
2693 | // Skip this quickly when working on the text. |
2694 | if (draw_state != WL_LINE) { |
2695 | if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { |
2696 | draw_state = WL_CMDLINE; |
2697 | if (cmdwin_type != 0 && wp == curwin) { |
2698 | /* Draw the cmdline character. */ |
2699 | n_extra = 1; |
2700 | c_extra = cmdwin_type; |
2701 | c_final = NUL; |
2702 | char_attr = win_hl_attr(wp, HLF_AT); |
2703 | } |
2704 | } |
2705 | |
2706 | if (draw_state == WL_FOLD - 1 && n_extra == 0) { |
2707 | int fdc = compute_foldcolumn(wp, 0); |
2708 | |
2709 | draw_state = WL_FOLD; |
2710 | if (fdc > 0) { |
2711 | // Draw the 'foldcolumn'. Allocate a buffer, "extra" may |
2712 | // already be in use. |
2713 | xfree(p_extra_free); |
2714 | p_extra_free = xmalloc(12 + 1); |
2715 | fill_foldcolumn(p_extra_free, wp, false, lnum); |
2716 | n_extra = fdc; |
2717 | p_extra_free[n_extra] = NUL; |
2718 | p_extra = p_extra_free; |
2719 | c_extra = NUL; |
2720 | c_final = NUL; |
2721 | char_attr = win_hl_attr(wp, HLF_FC); |
2722 | } |
2723 | } |
2724 | |
2725 | //sign column |
2726 | if (draw_state == WL_SIGN - 1 && n_extra == 0) { |
2727 | draw_state = WL_SIGN; |
2728 | /* Show the sign column when there are any signs in this |
2729 | * buffer or when using Netbeans. */ |
2730 | int count = win_signcol_count(wp); |
2731 | if (count > 0) { |
2732 | int text_sign; |
2733 | // Draw cells with the sign value or blank. |
2734 | c_extra = ' '; |
2735 | c_final = NUL; |
2736 | char_attr = win_hl_attr(wp, HLF_SC); |
2737 | n_extra = win_signcol_width(wp); |
2738 | |
2739 | if (row == startrow + filler_lines && filler_todo <= 0) { |
2740 | text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, |
2741 | sign_idx, count); |
2742 | if (text_sign != 0) { |
2743 | p_extra = sign_get_text(text_sign); |
2744 | if (p_extra != NULL) { |
2745 | int symbol_blen = (int)STRLEN(p_extra); |
2746 | |
2747 | c_extra = NUL; |
2748 | c_final = NUL; |
2749 | |
2750 | // TODO(oni-link): Is sign text already extended to |
2751 | // full cell width? |
2752 | assert((size_t)win_signcol_width(wp) |
2753 | >= mb_string2cells(p_extra)); |
2754 | // symbol(s) bytes + (filling spaces) (one byte each) |
2755 | n_extra = symbol_blen + |
2756 | (win_signcol_width(wp) - mb_string2cells(p_extra)); |
2757 | |
2758 | assert(sizeof(extra) > (size_t)symbol_blen); |
2759 | memset(extra, ' ', sizeof(extra)); |
2760 | memcpy(extra, p_extra, symbol_blen); |
2761 | |
2762 | p_extra = extra; |
2763 | p_extra[n_extra] = NUL; |
2764 | } |
2765 | char_attr = sign_get_attr(text_sign, SIGN_TEXT); |
2766 | } |
2767 | } |
2768 | |
2769 | sign_idx++; |
2770 | if (sign_idx < count) { |
2771 | draw_state = WL_SIGN - 1; |
2772 | } |
2773 | } |
2774 | } |
2775 | |
2776 | if (draw_state == WL_NR - 1 && n_extra == 0) { |
2777 | draw_state = WL_NR; |
2778 | /* Display the absolute or relative line number. After the |
2779 | * first fill with blanks when the 'n' flag isn't in 'cpo' */ |
2780 | if ((wp->w_p_nu || wp->w_p_rnu) |
2781 | && (row == startrow |
2782 | + filler_lines |
2783 | || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { |
2784 | /* Draw the line number (empty space after wrapping). */ |
2785 | if (row == startrow |
2786 | + filler_lines |
2787 | ) { |
2788 | long num; |
2789 | char *fmt = "%*ld " ; |
2790 | |
2791 | if (wp->w_p_nu && !wp->w_p_rnu) |
2792 | /* 'number' + 'norelativenumber' */ |
2793 | num = (long)lnum; |
2794 | else { |
2795 | /* 'relativenumber', don't use negative numbers */ |
2796 | num = labs((long)get_cursor_rel_lnum(wp, lnum)); |
2797 | if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { |
2798 | /* 'number' + 'relativenumber' */ |
2799 | num = lnum; |
2800 | fmt = "%-*ld " ; |
2801 | } |
2802 | } |
2803 | |
2804 | sprintf((char *)extra, fmt, |
2805 | number_width(wp), num); |
2806 | if (wp->w_skipcol > 0) |
2807 | for (p_extra = extra; *p_extra == ' '; ++p_extra) |
2808 | *p_extra = '-'; |
2809 | if (wp->w_p_rl) { // reverse line numbers |
2810 | // like rl_mirror(), but keep the space at the end |
2811 | char_u *p2 = skiptowhite(extra) - 1; |
2812 | for (char_u *p1 = extra; p1 < p2; p1++, p2--) { |
2813 | const int t = *p1; |
2814 | *p1 = *p2; |
2815 | *p2 = t; |
2816 | } |
2817 | } |
2818 | p_extra = extra; |
2819 | c_extra = NUL; |
2820 | c_final = NUL; |
2821 | } else { |
2822 | c_extra = ' '; |
2823 | c_final = NUL; |
2824 | } |
2825 | n_extra = number_width(wp) + 1; |
2826 | char_attr = win_hl_attr(wp, HLF_N); |
2827 | |
2828 | int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL, |
2829 | 0, 1); |
2830 | if (num_sign != 0) { |
2831 | // :sign defined with "numhl" highlight. |
2832 | char_attr = sign_get_attr(num_sign, SIGN_NUMHL); |
2833 | } else if ((wp->w_p_cul || wp->w_p_rnu) |
2834 | && lnum == wp->w_cursor.lnum) { |
2835 | // When 'cursorline' is set highlight the line number of |
2836 | // the current line differently. |
2837 | // TODO(vim): Can we use CursorLine instead of CursorLineNr |
2838 | // when CursorLineNr isn't set? |
2839 | char_attr = win_hl_attr(wp, HLF_CLN); |
2840 | } |
2841 | } |
2842 | } |
2843 | |
2844 | if (wp->w_p_brisbr && draw_state == WL_BRI - 1 |
2845 | && n_extra == 0 && *p_sbr != NUL) { |
2846 | // draw indent after showbreak value |
2847 | draw_state = WL_BRI; |
2848 | } else if (wp->w_p_brisbr && draw_state == WL_SBR && n_extra == 0) { |
2849 | // after the showbreak, draw the breakindent |
2850 | draw_state = WL_BRI - 1; |
2851 | } |
2852 | |
2853 | // draw 'breakindent': indent wrapped text accordingly |
2854 | if (draw_state == WL_BRI - 1 && n_extra == 0) { |
2855 | draw_state = WL_BRI; |
2856 | // if need_showbreak is set, breakindent also applies |
2857 | if (wp->w_p_bri && (row != startrow || need_showbreak) |
2858 | && filler_lines == 0) { |
2859 | char_attr = 0; |
2860 | |
2861 | if (diff_hlf != (hlf_T)0) { |
2862 | char_attr = win_hl_attr(wp, diff_hlf); |
2863 | if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { |
2864 | char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL)); |
2865 | } |
2866 | } |
2867 | p_extra = NULL; |
2868 | c_extra = ' '; |
2869 | n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE)); |
2870 | /* Correct end of highlighted area for 'breakindent', |
2871 | required wen 'linebreak' is also set. */ |
2872 | if (tocol == vcol) |
2873 | tocol += n_extra; |
2874 | } |
2875 | } |
2876 | |
2877 | if (draw_state == WL_SBR - 1 && n_extra == 0) { |
2878 | draw_state = WL_SBR; |
2879 | if (filler_todo > 0) { |
2880 | // draw "deleted" diff line(s) |
2881 | if (char2cells(wp->w_p_fcs_chars.diff) > 1) { |
2882 | c_extra = '-'; |
2883 | c_final = NUL; |
2884 | } else { |
2885 | c_extra = wp->w_p_fcs_chars.diff; |
2886 | c_final = NUL; |
2887 | } |
2888 | if (wp->w_p_rl) { |
2889 | n_extra = col + 1; |
2890 | } else { |
2891 | n_extra = grid->Columns - col; |
2892 | } |
2893 | char_attr = win_hl_attr(wp, HLF_DED); |
2894 | } |
2895 | if (*p_sbr != NUL && need_showbreak) { |
2896 | /* Draw 'showbreak' at the start of each broken line. */ |
2897 | p_extra = p_sbr; |
2898 | c_extra = NUL; |
2899 | c_final = NUL; |
2900 | n_extra = (int)STRLEN(p_sbr); |
2901 | char_attr = win_hl_attr(wp, HLF_AT); |
2902 | need_showbreak = false; |
2903 | vcol_sbr = vcol + MB_CHARLEN(p_sbr); |
2904 | /* Correct end of highlighted area for 'showbreak', |
2905 | * required when 'linebreak' is also set. */ |
2906 | if (tocol == vcol) |
2907 | tocol += n_extra; |
2908 | /* combine 'showbreak' with 'cursorline' */ |
2909 | if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { |
2910 | char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL)); |
2911 | } |
2912 | } |
2913 | } |
2914 | |
2915 | if (draw_state == WL_LINE - 1 && n_extra == 0) { |
2916 | sign_idx = 0; |
2917 | draw_state = WL_LINE; |
2918 | if (saved_n_extra) { |
2919 | /* Continue item from end of wrapped line. */ |
2920 | n_extra = saved_n_extra; |
2921 | c_extra = saved_c_extra; |
2922 | c_final = saved_c_final; |
2923 | p_extra = saved_p_extra; |
2924 | char_attr = saved_char_attr; |
2925 | } else { |
2926 | char_attr = 0; |
2927 | } |
2928 | } |
2929 | } |
2930 | |
2931 | // When still displaying '$' of change command, stop at cursor |
2932 | if ((dollar_vcol >= 0 && wp == curwin |
2933 | && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol |
2934 | && filler_todo <= 0) |
2935 | || (number_only && draw_state > WL_NR)) { |
2936 | grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, |
2937 | wp->w_hl_attr_normal, false); |
2938 | // Pretend we have finished updating the window. Except when |
2939 | // 'cursorcolumn' is set. |
2940 | if (wp->w_p_cuc) { |
2941 | row = wp->w_cline_row + wp->w_cline_height; |
2942 | } else { |
2943 | row = grid->Rows; |
2944 | } |
2945 | break; |
2946 | } |
2947 | |
2948 | if (draw_state == WL_LINE && (area_highlighting || has_spell)) { |
2949 | // handle Visual or match highlighting in this line |
2950 | if (vcol == fromcol |
2951 | || (vcol + 1 == fromcol && n_extra == 0 |
2952 | && utf_ptr2cells(ptr) > 1) |
2953 | || ((int)vcol_prev == fromcol_prev |
2954 | && vcol_prev < vcol // not at margin |
2955 | && vcol < tocol)) { |
2956 | area_attr = attr; // start highlighting |
2957 | } else if (area_attr != 0 && (vcol == tocol |
2958 | || (noinvcur |
2959 | && (colnr_T)vcol == wp->w_virtcol))) { |
2960 | area_attr = 0; // stop highlighting |
2961 | } |
2962 | |
2963 | if (!n_extra) { |
2964 | /* |
2965 | * Check for start/end of search pattern match. |
2966 | * After end, check for start/end of next match. |
2967 | * When another match, have to check for start again. |
2968 | * Watch out for matching an empty string! |
2969 | * Do this for 'search_hl' and the match list (ordered by |
2970 | * priority). |
2971 | */ |
2972 | v = (long)(ptr - line); |
2973 | cur = wp->w_match_head; |
2974 | shl_flag = FALSE; |
2975 | while (cur != NULL || shl_flag == FALSE) { |
2976 | if (shl_flag == FALSE |
2977 | && ((cur != NULL |
2978 | && cur->priority > SEARCH_HL_PRIORITY) |
2979 | || cur == NULL)) { |
2980 | shl = &search_hl; |
2981 | shl_flag = TRUE; |
2982 | } else |
2983 | shl = &cur->hl; |
2984 | if (cur != NULL) { |
2985 | cur->pos.cur = 0; |
2986 | } |
2987 | bool pos_inprogress = true; // mark that a position match search is |
2988 | // in progress |
2989 | while (shl->rm.regprog != NULL |
2990 | || (cur != NULL && pos_inprogress)) { |
2991 | if (shl->startcol != MAXCOL |
2992 | && v >= (long)shl->startcol |
2993 | && v < (long)shl->endcol) { |
2994 | int tmp_col = v + MB_PTR2LEN(ptr); |
2995 | |
2996 | if (shl->endcol < tmp_col) { |
2997 | shl->endcol = tmp_col; |
2998 | } |
2999 | shl->attr_cur = shl->attr; |
3000 | // Match with the "Conceal" group results in hiding |
3001 | // the match. |
3002 | if (cur != NULL |
3003 | && shl != &search_hl |
3004 | && syn_name2id((char_u *)"Conceal" ) == cur->hlg_id) { |
3005 | has_match_conc = v == (long)shl->startcol ? 2 : 1; |
3006 | match_conc = cur->conceal_char; |
3007 | } else { |
3008 | has_match_conc = match_conc = 0; |
3009 | } |
3010 | } else if (v == (long)shl->endcol) { |
3011 | shl->attr_cur = 0; |
3012 | |
3013 | next_search_hl(wp, shl, lnum, (colnr_T)v, |
3014 | shl == &search_hl ? NULL : cur); |
3015 | pos_inprogress = !(cur == NULL || cur->pos.cur == 0); |
3016 | |
3017 | /* Need to get the line again, a multi-line regexp |
3018 | * may have made it invalid. */ |
3019 | line = ml_get_buf(wp->w_buffer, lnum, FALSE); |
3020 | ptr = line + v; |
3021 | |
3022 | if (shl->lnum == lnum) { |
3023 | shl->startcol = shl->rm.startpos[0].col; |
3024 | if (shl->rm.endpos[0].lnum == 0) |
3025 | shl->endcol = shl->rm.endpos[0].col; |
3026 | else |
3027 | shl->endcol = MAXCOL; |
3028 | |
3029 | if (shl->startcol == shl->endcol) { |
3030 | // highlight empty match, try again after it |
3031 | shl->endcol += (*mb_ptr2len)(line + shl->endcol); |
3032 | } |
3033 | |
3034 | /* Loop to check if the match starts at the |
3035 | * current position */ |
3036 | continue; |
3037 | } |
3038 | } |
3039 | break; |
3040 | } |
3041 | if (shl != &search_hl && cur != NULL) |
3042 | cur = cur->next; |
3043 | } |
3044 | |
3045 | /* Use attributes from match with highest priority among |
3046 | * 'search_hl' and the match list. */ |
3047 | search_attr_from_match = false; |
3048 | search_attr = search_hl.attr_cur; |
3049 | cur = wp->w_match_head; |
3050 | shl_flag = FALSE; |
3051 | while (cur != NULL || shl_flag == FALSE) { |
3052 | if (shl_flag == FALSE |
3053 | && ((cur != NULL |
3054 | && cur->priority > SEARCH_HL_PRIORITY) |
3055 | || cur == NULL)) { |
3056 | shl = &search_hl; |
3057 | shl_flag = TRUE; |
3058 | } else |
3059 | shl = &cur->hl; |
3060 | if (shl->attr_cur != 0) { |
3061 | search_attr = shl->attr_cur; |
3062 | search_attr_from_match = shl != &search_hl; |
3063 | } |
3064 | if (shl != &search_hl && cur != NULL) |
3065 | cur = cur->next; |
3066 | } |
3067 | // Only highlight one character after the last column. |
3068 | if (*ptr == NUL |
3069 | && (wp->w_p_list && lcs_eol_one == -1)) { |
3070 | search_attr = 0; |
3071 | } |
3072 | } |
3073 | |
3074 | if (diff_hlf != (hlf_T)0) { |
3075 | if (diff_hlf == HLF_CHD && ptr - line >= change_start |
3076 | && n_extra == 0) { |
3077 | diff_hlf = HLF_TXD; // changed text |
3078 | } |
3079 | if (diff_hlf == HLF_TXD && ptr - line > change_end |
3080 | && n_extra == 0) { |
3081 | diff_hlf = HLF_CHD; // changed line |
3082 | } |
3083 | line_attr = win_hl_attr(wp, diff_hlf); |
3084 | // Overlay CursorLine onto diff-mode highlight. |
3085 | if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { |
3086 | line_attr = 0 != line_attr_lowprio // Low-priority CursorLine |
3087 | ? hl_combine_attr(hl_combine_attr(win_hl_attr(wp, HLF_CUL), |
3088 | line_attr), |
3089 | hl_get_underline()) |
3090 | : hl_combine_attr(line_attr, win_hl_attr(wp, HLF_CUL)); |
3091 | } |
3092 | } |
3093 | |
3094 | // Decide which of the highlight attributes to use. |
3095 | attr_pri = true; |
3096 | |
3097 | if (area_attr != 0) { |
3098 | char_attr = hl_combine_attr(line_attr, area_attr); |
3099 | } else if (search_attr != 0) { |
3100 | char_attr = hl_combine_attr(line_attr, search_attr); |
3101 | } |
3102 | // Use line_attr when not in the Visual or 'incsearch' area |
3103 | // (area_attr may be 0 when "noinvcur" is set). |
3104 | else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL) |
3105 | || vcol < fromcol || vcol_prev < fromcol_prev |
3106 | || vcol >= tocol)) { |
3107 | char_attr = line_attr; |
3108 | } else { |
3109 | attr_pri = false; |
3110 | if (has_syntax) { |
3111 | char_attr = syntax_attr; |
3112 | } else { |
3113 | char_attr = 0; |
3114 | } |
3115 | } |
3116 | } |
3117 | |
3118 | // Get the next character to put on the screen. |
3119 | // |
3120 | // The "p_extra" points to the extra stuff that is inserted to |
3121 | // represent special characters (non-printable stuff) and other |
3122 | // things. When all characters are the same, c_extra is used. |
3123 | // If c_final is set, it will compulsorily be used at the end. |
3124 | // "p_extra" must end in a NUL to avoid mb_ptr2len() reads past |
3125 | // "p_extra[n_extra]". |
3126 | // For the '$' of the 'list' option, n_extra == 1, p_extra == "". |
3127 | if (n_extra > 0) { |
3128 | if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { |
3129 | c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; |
3130 | mb_c = c; // doesn't handle non-utf-8 multi-byte! |
3131 | if (utf_char2len(c) > 1) { |
3132 | mb_utf8 = true; |
3133 | u8cc[0] = 0; |
3134 | c = 0xc0; |
3135 | } else { |
3136 | mb_utf8 = false; |
3137 | } |
3138 | } else { |
3139 | c = *p_extra; |
3140 | mb_c = c; |
3141 | // If the UTF-8 character is more than one byte: |
3142 | // Decode it into "mb_c". |
3143 | mb_l = utfc_ptr2len(p_extra); |
3144 | mb_utf8 = false; |
3145 | if (mb_l > n_extra) { |
3146 | mb_l = 1; |
3147 | } else if (mb_l > 1) { |
3148 | mb_c = utfc_ptr2char(p_extra, u8cc); |
3149 | mb_utf8 = true; |
3150 | c = 0xc0; |
3151 | } |
3152 | if (mb_l == 0) { // at the NUL at end-of-line |
3153 | mb_l = 1; |
3154 | } |
3155 | |
3156 | // If a double-width char doesn't fit display a '>' in the last column. |
3157 | if ((wp->w_p_rl ? (col <= 0) : (col >= grid->Columns - 1)) |
3158 | && (*mb_char2cells)(mb_c) == 2) { |
3159 | c = '>'; |
3160 | mb_c = c; |
3161 | mb_l = 1; |
3162 | (void)mb_l; |
3163 | multi_attr = win_hl_attr(wp, HLF_AT); |
3164 | |
3165 | // put the pointer back to output the double-width |
3166 | // character at the start of the next line. |
3167 | n_extra++; |
3168 | p_extra--; |
3169 | } else { |
3170 | n_extra -= mb_l - 1; |
3171 | p_extra += mb_l - 1; |
3172 | } |
3173 | p_extra++; |
3174 | } |
3175 | n_extra--; |
3176 | } else { |
3177 | int c0; |
3178 | |
3179 | if (p_extra_free != NULL) { |
3180 | XFREE_CLEAR(p_extra_free); |
3181 | } |
3182 | |
3183 | // Get a character from the line itself. |
3184 | c0 = c = *ptr; |
3185 | mb_c = c; |
3186 | // If the UTF-8 character is more than one byte: Decode it |
3187 | // into "mb_c". |
3188 | mb_l = utfc_ptr2len(ptr); |
3189 | mb_utf8 = false; |
3190 | if (mb_l > 1) { |
3191 | mb_c = utfc_ptr2char(ptr, u8cc); |
3192 | // Overlong encoded ASCII or ASCII with composing char |
3193 | // is displayed normally, except a NUL. |
3194 | if (mb_c < 0x80) { |
3195 | c0 = c = mb_c; |
3196 | } |
3197 | mb_utf8 = true; |
3198 | |
3199 | // At start of the line we can have a composing char. |
3200 | // Draw it as a space with a composing char. |
3201 | if (utf_iscomposing(mb_c)) { |
3202 | int i; |
3203 | |
3204 | for (i = MAX_MCO - 1; i > 0; i--) { |
3205 | u8cc[i] = u8cc[i - 1]; |
3206 | } |
3207 | u8cc[0] = mb_c; |
3208 | mb_c = ' '; |
3209 | } |
3210 | } |
3211 | |
3212 | if ((mb_l == 1 && c >= 0x80) |
3213 | || (mb_l >= 1 && mb_c == 0) |
3214 | || (mb_l > 1 && (!vim_isprintc(mb_c)))) { |
3215 | // Illegal UTF-8 byte: display as <xx>. |
3216 | // Non-BMP character : display as ? or fullwidth ?. |
3217 | transchar_hex((char *)extra, mb_c); |
3218 | if (wp->w_p_rl) { // reverse |
3219 | rl_mirror(extra); |
3220 | } |
3221 | |
3222 | p_extra = extra; |
3223 | c = *p_extra; |
3224 | mb_c = mb_ptr2char_adv((const char_u **)&p_extra); |
3225 | mb_utf8 = (c >= 0x80); |
3226 | n_extra = (int)STRLEN(p_extra); |
3227 | c_extra = NUL; |
3228 | c_final = NUL; |
3229 | if (area_attr == 0 && search_attr == 0) { |
3230 | n_attr = n_extra + 1; |
3231 | extra_attr = win_hl_attr(wp, HLF_8); |
3232 | saved_attr2 = char_attr; // save current attr |
3233 | } |
3234 | } else if (mb_l == 0) { // at the NUL at end-of-line |
3235 | mb_l = 1; |
3236 | } else if (p_arshape && !p_tbidi && arabic_char(mb_c)) { |
3237 | // Do Arabic shaping. |
3238 | int pc, pc1, nc; |
3239 | int pcc[MAX_MCO]; |
3240 | |
3241 | // The idea of what is the previous and next |
3242 | // character depends on 'rightleft'. |
3243 | if (wp->w_p_rl) { |
3244 | pc = prev_c; |
3245 | pc1 = prev_c1; |
3246 | nc = utf_ptr2char(ptr + mb_l); |
3247 | prev_c1 = u8cc[0]; |
3248 | } else { |
3249 | pc = utfc_ptr2char(ptr + mb_l, pcc); |
3250 | nc = prev_c; |
3251 | pc1 = pcc[0]; |
3252 | } |
3253 | prev_c = mb_c; |
3254 | |
3255 | mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); |
3256 | } else { |
3257 | prev_c = mb_c; |
3258 | } |
3259 | // If a double-width char doesn't fit display a '>' in the |
3260 | // last column; the character is displayed at the start of the |
3261 | // next line. |
3262 | if ((wp->w_p_rl ? (col <= 0) : |
3263 | (col >= grid->Columns - 1)) |
3264 | && (*mb_char2cells)(mb_c) == 2) { |
3265 | c = '>'; |
3266 | mb_c = c; |
3267 | mb_utf8 = false; |
3268 | mb_l = 1; |
3269 | multi_attr = win_hl_attr(wp, HLF_AT); |
3270 | // Put pointer back so that the character will be |
3271 | // displayed at the start of the next line. |
3272 | ptr--; |
3273 | did_decrement_ptr = true; |
3274 | } else if (*ptr != NUL) { |
3275 | ptr += mb_l - 1; |
3276 | } |
3277 | |
3278 | // If a double-width char doesn't fit at the left side display a '<' in |
3279 | // the first column. Don't do this for unprintable characters. |
3280 | if (n_skip > 0 && mb_l > 1 && n_extra == 0) { |
3281 | n_extra = 1; |
3282 | c_extra = MB_FILLER_CHAR; |
3283 | c_final = NUL; |
3284 | c = ' '; |
3285 | if (area_attr == 0 && search_attr == 0) { |
3286 | n_attr = n_extra + 1; |
3287 | extra_attr = win_hl_attr(wp, HLF_AT); |
3288 | saved_attr2 = char_attr; // save current attr |
3289 | } |
3290 | mb_c = c; |
3291 | mb_utf8 = false; |
3292 | mb_l = 1; |
3293 | } |
3294 | ptr++; |
3295 | |
3296 | if (extra_check) { |
3297 | bool can_spell = true; |
3298 | |
3299 | /* Get syntax attribute, unless still at the start of the line |
3300 | * (double-wide char that doesn't fit). */ |
3301 | v = (long)(ptr - line); |
3302 | if (has_syntax && v > 0) { |
3303 | /* Get the syntax attribute for the character. If there |
3304 | * is an error, disable syntax highlighting. */ |
3305 | save_did_emsg = did_emsg; |
3306 | did_emsg = FALSE; |
3307 | |
3308 | syntax_attr = get_syntax_attr((colnr_T)v - 1, |
3309 | has_spell ? &can_spell : NULL, false); |
3310 | |
3311 | if (did_emsg) { |
3312 | wp->w_s->b_syn_error = TRUE; |
3313 | has_syntax = FALSE; |
3314 | } else |
3315 | did_emsg = save_did_emsg; |
3316 | |
3317 | /* Need to get the line again, a multi-line regexp may |
3318 | * have made it invalid. */ |
3319 | line = ml_get_buf(wp->w_buffer, lnum, FALSE); |
3320 | ptr = line + v; |
3321 | |
3322 | if (!attr_pri) { |
3323 | char_attr = syntax_attr; |
3324 | } else { |
3325 | char_attr = hl_combine_attr(syntax_attr, char_attr); |
3326 | } |
3327 | // no concealing past the end of the line, it interferes |
3328 | // with line highlighting. |
3329 | if (c == NUL) { |
3330 | syntax_flags = 0; |
3331 | } else { |
3332 | syntax_flags = get_syntax_info(&syntax_seqnr); |
3333 | } |
3334 | } else if (!attr_pri) { |
3335 | char_attr = 0; |
3336 | } |
3337 | |
3338 | /* Check spelling (unless at the end of the line). |
3339 | * Only do this when there is no syntax highlighting, the |
3340 | * @Spell cluster is not used or the current syntax item |
3341 | * contains the @Spell cluster. */ |
3342 | if (has_spell && v >= word_end && v > cur_checked_col) { |
3343 | spell_attr = 0; |
3344 | if (!attr_pri) { |
3345 | char_attr = syntax_attr; |
3346 | } |
3347 | if (c != 0 && (!has_syntax || can_spell)) { |
3348 | char_u *prev_ptr; |
3349 | char_u *p; |
3350 | int len; |
3351 | hlf_T spell_hlf = HLF_COUNT; |
3352 | prev_ptr = ptr - mb_l; |
3353 | v -= mb_l - 1; |
3354 | |
3355 | /* Use nextline[] if possible, it has the start of the |
3356 | * next line concatenated. */ |
3357 | if ((prev_ptr - line) - nextlinecol >= 0) { |
3358 | p = nextline + ((prev_ptr - line) - nextlinecol); |
3359 | } else { |
3360 | p = prev_ptr; |
3361 | } |
3362 | cap_col -= (int)(prev_ptr - line); |
3363 | size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); |
3364 | assert(tmplen <= INT_MAX); |
3365 | len = (int)tmplen; |
3366 | word_end = v + len; |
3367 | |
3368 | /* In Insert mode only highlight a word that |
3369 | * doesn't touch the cursor. */ |
3370 | if (spell_hlf != HLF_COUNT |
3371 | && (State & INSERT) != 0 |
3372 | && wp->w_cursor.lnum == lnum |
3373 | && wp->w_cursor.col >= |
3374 | (colnr_T)(prev_ptr - line) |
3375 | && wp->w_cursor.col < (colnr_T)word_end) { |
3376 | spell_hlf = HLF_COUNT; |
3377 | spell_redraw_lnum = lnum; |
3378 | } |
3379 | |
3380 | if (spell_hlf == HLF_COUNT && p != prev_ptr |
3381 | && (p - nextline) + len > nextline_idx) { |
3382 | /* Remember that the good word continues at the |
3383 | * start of the next line. */ |
3384 | checked_lnum = lnum + 1; |
3385 | checked_col = (int)((p - nextline) + len - nextline_idx); |
3386 | } |
3387 | |
3388 | /* Turn index into actual attributes. */ |
3389 | if (spell_hlf != HLF_COUNT) |
3390 | spell_attr = highlight_attr[spell_hlf]; |
3391 | |
3392 | if (cap_col > 0) { |
3393 | if (p != prev_ptr |
3394 | && (p - nextline) + cap_col >= nextline_idx) { |
3395 | /* Remember that the word in the next line |
3396 | * must start with a capital. */ |
3397 | capcol_lnum = lnum + 1; |
3398 | cap_col = (int)((p - nextline) + cap_col |
3399 | - nextline_idx); |
3400 | } else |
3401 | /* Compute the actual column. */ |
3402 | cap_col += (int)(prev_ptr - line); |
3403 | } |
3404 | } |
3405 | } |
3406 | if (spell_attr != 0) { |
3407 | if (!attr_pri) |
3408 | char_attr = hl_combine_attr(char_attr, spell_attr); |
3409 | else |
3410 | char_attr = hl_combine_attr(spell_attr, char_attr); |
3411 | } |
3412 | |
3413 | if (has_bufhl && v > 0) { |
3414 | int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v); |
3415 | if (bufhl_attr != 0) { |
3416 | if (!attr_pri) { |
3417 | char_attr = hl_combine_attr(char_attr, bufhl_attr); |
3418 | } else { |
3419 | char_attr = hl_combine_attr(bufhl_attr, char_attr); |
3420 | } |
3421 | } |
3422 | } |
3423 | |
3424 | if (wp->w_buffer->terminal) { |
3425 | char_attr = hl_combine_attr(term_attrs[vcol], char_attr); |
3426 | } |
3427 | |
3428 | // Found last space before word: check for line break. |
3429 | if (wp->w_p_lbr && c0 == c && vim_isbreak(c) |
3430 | && !vim_isbreak((int)(*ptr))) { |
3431 | int mb_off = utf_head_off(line, ptr - 1); |
3432 | char_u *p = ptr - (mb_off + 1); |
3433 | // TODO: is passing p for start of the line OK? |
3434 | n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; |
3435 | if (c == TAB && n_extra + col > grid->Columns) { |
3436 | n_extra = (int)wp->w_buffer->b_p_ts |
3437 | - vcol % (int)wp->w_buffer->b_p_ts - 1; |
3438 | } |
3439 | c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; |
3440 | c_final = NUL; |
3441 | if (ascii_iswhite(c)) { |
3442 | if (c == TAB) |
3443 | /* See "Tab alignment" below. */ |
3444 | FIX_FOR_BOGUSCOLS; |
3445 | if (!wp->w_p_list) { |
3446 | c = ' '; |
3447 | } |
3448 | } |
3449 | } |
3450 | |
3451 | // 'list': change char 160 to 'nbsp' and space to 'space'. |
3452 | if (wp->w_p_list |
3453 | && (((c == 160 |
3454 | || (mb_utf8 && (mb_c == 160 || mb_c == 0x202f))) |
3455 | && curwin->w_p_lcs_chars.nbsp) |
3456 | || (c == ' ' && curwin->w_p_lcs_chars.space |
3457 | && ptr - line <= trailcol))) { |
3458 | c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; |
3459 | n_attr = 1; |
3460 | extra_attr = win_hl_attr(wp, HLF_0); |
3461 | saved_attr2 = char_attr; // save current attr |
3462 | mb_c = c; |
3463 | if (utf_char2len(c) > 1) { |
3464 | mb_utf8 = true; |
3465 | u8cc[0] = 0; |
3466 | c = 0xc0; |
3467 | } else { |
3468 | mb_utf8 = false; |
3469 | } |
3470 | } |
3471 | |
3472 | if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') { |
3473 | c = wp->w_p_lcs_chars.trail; |
3474 | n_attr = 1; |
3475 | extra_attr = win_hl_attr(wp, HLF_0); |
3476 | saved_attr2 = char_attr; // save current attr |
3477 | mb_c = c; |
3478 | if (utf_char2len(c) > 1) { |
3479 | mb_utf8 = true; |
3480 | u8cc[0] = 0; |
3481 | c = 0xc0; |
3482 | } else { |
3483 | mb_utf8 = false; |
3484 | } |
3485 | } |
3486 | } |
3487 | |
3488 | /* |
3489 | * Handling of non-printable characters. |
3490 | */ |
3491 | if (!vim_isprintc(c)) { |
3492 | // when getting a character from the file, we may have to |
3493 | // turn it into something else on the way to putting it on the screen. |
3494 | if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { |
3495 | int tab_len = 0; |
3496 | long vcol_adjusted = vcol; // removed showbreak length |
3497 | // Only adjust the tab_len, when at the first column after the |
3498 | // showbreak value was drawn. |
3499 | if (*p_sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { |
3500 | vcol_adjusted = vcol - MB_CHARLEN(p_sbr); |
3501 | } |
3502 | // tab amount depends on current column |
3503 | tab_len = (int)wp->w_buffer->b_p_ts |
3504 | - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1; |
3505 | |
3506 | if (!wp->w_p_lbr || !wp->w_p_list) { |
3507 | n_extra = tab_len; |
3508 | } else { |
3509 | char_u *p; |
3510 | int i; |
3511 | int = n_extra; |
3512 | |
3513 | if (vcol_off > 0) { |
3514 | // there are characters to conceal |
3515 | tab_len += vcol_off; |
3516 | } |
3517 | // boguscols before FIX_FOR_BOGUSCOLS macro from above. |
3518 | if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 |
3519 | && n_extra > tab_len) { |
3520 | tab_len += n_extra - tab_len; |
3521 | } |
3522 | |
3523 | /* if n_extra > 0, it gives the number of chars to use for |
3524 | * a tab, else we need to calculate the width for a tab */ |
3525 | int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2)); |
3526 | if (n_extra > 0) { |
3527 | len += n_extra - tab_len; |
3528 | } |
3529 | c = wp->w_p_lcs_chars.tab1; |
3530 | p = xmalloc(len + 1); |
3531 | memset(p, ' ', len); |
3532 | p[len] = NUL; |
3533 | xfree(p_extra_free); |
3534 | p_extra_free = p; |
3535 | for (i = 0; i < tab_len; i++) { |
3536 | utf_char2bytes(wp->w_p_lcs_chars.tab2, p); |
3537 | p += mb_char2len(wp->w_p_lcs_chars.tab2); |
3538 | n_extra += mb_char2len(wp->w_p_lcs_chars.tab2) |
3539 | - (saved_nextra > 0 ? 1: 0); |
3540 | } |
3541 | p_extra = p_extra_free; |
3542 | |
3543 | // n_extra will be increased by FIX_FOX_BOGUSCOLS |
3544 | // macro below, so need to adjust for that here |
3545 | if (vcol_off > 0) { |
3546 | n_extra -= vcol_off; |
3547 | } |
3548 | } |
3549 | |
3550 | { |
3551 | int vc_saved = vcol_off; |
3552 | |
3553 | // Tab alignment should be identical regardless of |
3554 | // 'conceallevel' value. So tab compensates of all |
3555 | // previous concealed characters, and thus resets |
3556 | // vcol_off and boguscols accumulated so far in the |
3557 | // line. Note that the tab can be longer than |
3558 | // 'tabstop' when there are concealed characters. |
3559 | FIX_FOR_BOGUSCOLS; |
3560 | |
3561 | // Make sure, the highlighting for the tab char will be |
3562 | // correctly set further below (effectively reverts the |
3563 | // FIX_FOR_BOGSUCOLS macro. |
3564 | if (n_extra == tab_len + vc_saved && wp->w_p_list |
3565 | && wp->w_p_lcs_chars.tab1) { |
3566 | tab_len += vc_saved; |
3567 | } |
3568 | } |
3569 | |
3570 | mb_utf8 = false; // don't draw as UTF-8 |
3571 | if (wp->w_p_list) { |
3572 | c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) |
3573 | ? wp->w_p_lcs_chars.tab3 |
3574 | : wp->w_p_lcs_chars.tab1; |
3575 | if (wp->w_p_lbr) { |
3576 | c_extra = NUL; /* using p_extra from above */ |
3577 | } else { |
3578 | c_extra = wp->w_p_lcs_chars.tab2; |
3579 | } |
3580 | c_final = wp->w_p_lcs_chars.tab3; |
3581 | n_attr = tab_len + 1; |
3582 | extra_attr = win_hl_attr(wp, HLF_0); |
3583 | saved_attr2 = char_attr; // save current attr |
3584 | mb_c = c; |
3585 | if (utf_char2len(c) > 1) { |
3586 | mb_utf8 = true; |
3587 | u8cc[0] = 0; |
3588 | c = 0xc0; |
3589 | } |
3590 | } else { |
3591 | c_final = NUL; |
3592 | c_extra = ' '; |
3593 | c = ' '; |
3594 | } |
3595 | } else if (c == NUL |
3596 | && (wp->w_p_list |
3597 | || ((fromcol >= 0 || fromcol_prev >= 0) |
3598 | && tocol > vcol |
3599 | && VIsual_mode != Ctrl_V |
3600 | && (wp->w_p_rl ? (col >= 0) : (col < grid->Columns)) |
3601 | && !(noinvcur |
3602 | && lnum == wp->w_cursor.lnum |
3603 | && (colnr_T)vcol == wp->w_virtcol))) |
3604 | && lcs_eol_one > 0) { |
3605 | // Display a '$' after the line or highlight an extra |
3606 | // character if the line break is included. |
3607 | // For a diff line the highlighting continues after the "$". |
3608 | if (diff_hlf == (hlf_T)0 |
3609 | && line_attr == 0 |
3610 | && line_attr_lowprio == 0) { |
3611 | // In virtualedit, visual selections may extend beyond end of line |
3612 | if (area_highlighting && virtual_active() |
3613 | && tocol != MAXCOL && vcol < tocol) { |
3614 | n_extra = 0; |
3615 | } else { |
3616 | p_extra = at_end_str; |
3617 | n_extra = 1; |
3618 | c_extra = NUL; |
3619 | c_final = NUL; |
3620 | } |
3621 | } |
3622 | if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { |
3623 | c = wp->w_p_lcs_chars.eol; |
3624 | } else { |
3625 | c = ' '; |
3626 | } |
3627 | lcs_eol_one = -1; |
3628 | ptr--; // put it back at the NUL |
3629 | extra_attr = win_hl_attr(wp, HLF_AT); |
3630 | n_attr = 1; |
3631 | mb_c = c; |
3632 | if (utf_char2len(c) > 1) { |
3633 | mb_utf8 = true; |
3634 | u8cc[0] = 0; |
3635 | c = 0xc0; |
3636 | } else { |
3637 | mb_utf8 = false; // don't draw as UTF-8 |
3638 | } |
3639 | } else if (c != NUL) { |
3640 | p_extra = transchar(c); |
3641 | if (n_extra == 0) { |
3642 | n_extra = byte2cells(c) - 1; |
3643 | } |
3644 | if ((dy_flags & DY_UHEX) && wp->w_p_rl) |
3645 | rl_mirror(p_extra); /* reverse "<12>" */ |
3646 | c_extra = NUL; |
3647 | c_final = NUL; |
3648 | if (wp->w_p_lbr) { |
3649 | char_u *p; |
3650 | |
3651 | c = *p_extra; |
3652 | p = xmalloc(n_extra + 1); |
3653 | memset(p, ' ', n_extra); |
3654 | STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); |
3655 | p[n_extra] = NUL; |
3656 | xfree(p_extra_free); |
3657 | p_extra_free = p_extra = p; |
3658 | } else { |
3659 | n_extra = byte2cells(c) - 1; |
3660 | c = *p_extra++; |
3661 | } |
3662 | n_attr = n_extra + 1; |
3663 | extra_attr = win_hl_attr(wp, HLF_8); |
3664 | saved_attr2 = char_attr; // save current attr |
3665 | mb_utf8 = false; // don't draw as UTF-8 |
3666 | } else if (VIsual_active |
3667 | && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') |
3668 | && virtual_active() |
3669 | && tocol != MAXCOL |
3670 | && vcol < tocol |
3671 | && (wp->w_p_rl ? (col >= 0) : (col < grid->Columns))) { |
3672 | c = ' '; |
3673 | ptr--; // put it back at the NUL |
3674 | } |
3675 | } |
3676 | |
3677 | if (wp->w_p_cole > 0 |
3678 | && (wp != curwin || lnum != wp->w_cursor.lnum |
3679 | || conceal_cursor_line(wp)) |
3680 | && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0) |
3681 | && !(lnum_in_visual_area |
3682 | && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { |
3683 | char_attr = conceal_attr; |
3684 | if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1) |
3685 | && (syn_get_sub_char() != NUL || match_conc |
3686 | || wp->w_p_cole == 1) |
3687 | && wp->w_p_cole != 3) { |
3688 | // First time at this concealed item: display one |
3689 | // character. |
3690 | if (match_conc) { |
3691 | c = match_conc; |
3692 | } else if (syn_get_sub_char() != NUL) { |
3693 | c = syn_get_sub_char(); |
3694 | } else if (wp->w_p_lcs_chars.conceal != NUL) { |
3695 | c = wp->w_p_lcs_chars.conceal; |
3696 | } else { |
3697 | c = ' '; |
3698 | } |
3699 | |
3700 | prev_syntax_id = syntax_seqnr; |
3701 | |
3702 | if (n_extra > 0) |
3703 | vcol_off += n_extra; |
3704 | vcol += n_extra; |
3705 | if (wp->w_p_wrap && n_extra > 0) { |
3706 | if (wp->w_p_rl) { |
3707 | col -= n_extra; |
3708 | boguscols -= n_extra; |
3709 | } else { |
3710 | boguscols += n_extra; |
3711 | col += n_extra; |
3712 | } |
3713 | } |
3714 | n_extra = 0; |
3715 | n_attr = 0; |
3716 | } else if (n_skip == 0) { |
3717 | is_concealing = TRUE; |
3718 | n_skip = 1; |
3719 | } |
3720 | mb_c = c; |
3721 | if (utf_char2len(c) > 1) { |
3722 | mb_utf8 = true; |
3723 | u8cc[0] = 0; |
3724 | c = 0xc0; |
3725 | } else { |
3726 | mb_utf8 = false; // don't draw as UTF-8 |
3727 | } |
3728 | } else { |
3729 | prev_syntax_id = 0; |
3730 | is_concealing = FALSE; |
3731 | } |
3732 | |
3733 | if (n_skip > 0 && did_decrement_ptr) { |
3734 | // not showing the '>', put pointer back to avoid getting stuck |
3735 | ptr++; |
3736 | } |
3737 | } |
3738 | |
3739 | /* In the cursor line and we may be concealing characters: correct |
3740 | * the cursor column when we reach its position. */ |
3741 | if (!did_wcol && draw_state == WL_LINE |
3742 | && wp == curwin && lnum == wp->w_cursor.lnum |
3743 | && conceal_cursor_line(wp) |
3744 | && (int)wp->w_virtcol <= vcol + n_skip) { |
3745 | if (wp->w_p_rl) { |
3746 | wp->w_wcol = grid->Columns - col + boguscols - 1; |
3747 | } else { |
3748 | wp->w_wcol = col - boguscols; |
3749 | } |
3750 | wp->w_wrow = row; |
3751 | did_wcol = true; |
3752 | } |
3753 | |
3754 | // Don't override visual selection highlighting. |
3755 | if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) { |
3756 | char_attr = hl_combine_attr(char_attr, extra_attr); |
3757 | } |
3758 | |
3759 | /* |
3760 | * Handle the case where we are in column 0 but not on the first |
3761 | * character of the line and the user wants us to show us a |
3762 | * special character (via 'listchars' option "precedes:<char>". |
3763 | */ |
3764 | if (lcs_prec_todo != NUL |
3765 | && wp->w_p_list |
3766 | && (wp->w_p_wrap ? wp->w_skipcol > 0 : wp->w_leftcol > 0) |
3767 | && filler_todo <= 0 |
3768 | && draw_state > WL_NR |
3769 | && c != NUL) { |
3770 | c = wp->w_p_lcs_chars.prec; |
3771 | lcs_prec_todo = NUL; |
3772 | if ((*mb_char2cells)(mb_c) > 1) { |
3773 | // Double-width character being overwritten by the "precedes" |
3774 | // character, need to fill up half the character. |
3775 | c_extra = MB_FILLER_CHAR; |
3776 | c_final = NUL; |
3777 | n_extra = 1; |
3778 | n_attr = 2; |
3779 | extra_attr = win_hl_attr(wp, HLF_AT); |
3780 | } |
3781 | mb_c = c; |
3782 | if (utf_char2len(c) > 1) { |
3783 | mb_utf8 = true; |
3784 | u8cc[0] = 0; |
3785 | c = 0xc0; |
3786 | } else { |
3787 | mb_utf8 = false; // don't draw as UTF-8 |
3788 | } |
3789 | saved_attr3 = char_attr; // save current attr |
3790 | char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr |
3791 | n_attr3 = 1; |
3792 | } |
3793 | |
3794 | // At end of the text line or just after the last character. |
3795 | if (c == NUL) { |
3796 | long prevcol = (long)(ptr - line) - 1; |
3797 | |
3798 | // we're not really at that column when skipping some text |
3799 | if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { |
3800 | prevcol++; |
3801 | } |
3802 | |
3803 | // Invert at least one char, used for Visual and empty line or |
3804 | // highlight match at end of line. If it's beyond the last |
3805 | // char on the screen, just overwrite that one (tricky!) Not |
3806 | // needed when a '$' was displayed for 'list'. |
3807 | prevcol_hl_flag = false; |
3808 | if (!search_hl.is_addpos && prevcol == (long)search_hl.startcol) { |
3809 | prevcol_hl_flag = true; |
3810 | } else { |
3811 | cur = wp->w_match_head; |
3812 | while (cur != NULL) { |
3813 | if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol) { |
3814 | prevcol_hl_flag = true; |
3815 | break; |
3816 | } |
3817 | cur = cur->next; |
3818 | } |
3819 | } |
3820 | if (wp->w_p_lcs_chars.eol == lcs_eol_one |
3821 | && ((area_attr != 0 && vcol == fromcol |
3822 | && (VIsual_mode != Ctrl_V |
3823 | || lnum == VIsual.lnum |
3824 | || lnum == curwin->w_cursor.lnum)) |
3825 | // highlight 'hlsearch' match at end of line |
3826 | || prevcol_hl_flag)) { |
3827 | int n = 0; |
3828 | |
3829 | if (wp->w_p_rl) { |
3830 | if (col < 0) |
3831 | n = 1; |
3832 | } else { |
3833 | if (col >= grid->Columns) { |
3834 | n = -1; |
3835 | } |
3836 | } |
3837 | if (n != 0) { |
3838 | /* At the window boundary, highlight the last character |
3839 | * instead (better than nothing). */ |
3840 | off += n; |
3841 | col += n; |
3842 | } else { |
3843 | // Add a blank character to highlight. |
3844 | schar_from_ascii(linebuf_char[off], ' '); |
3845 | } |
3846 | if (area_attr == 0) { |
3847 | /* Use attributes from match with highest priority among |
3848 | * 'search_hl' and the match list. */ |
3849 | char_attr = search_hl.attr; |
3850 | cur = wp->w_match_head; |
3851 | shl_flag = FALSE; |
3852 | while (cur != NULL || shl_flag == FALSE) { |
3853 | if (shl_flag == FALSE |
3854 | && ((cur != NULL |
3855 | && cur->priority > SEARCH_HL_PRIORITY) |
3856 | || cur == NULL)) { |
3857 | shl = &search_hl; |
3858 | shl_flag = TRUE; |
3859 | } else |
3860 | shl = &cur->hl; |
3861 | if ((ptr - line) - 1 == (long)shl->startcol |
3862 | && (shl == &search_hl || !shl->is_addpos)) { |
3863 | char_attr = shl->attr; |
3864 | } |
3865 | if (shl != &search_hl && cur != NULL) { |
3866 | cur = cur->next; |
3867 | } |
3868 | } |
3869 | } |
3870 | |
3871 | int eol_attr = char_attr; |
3872 | if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { |
3873 | eol_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUL), eol_attr); |
3874 | } |
3875 | linebuf_attr[off] = eol_attr; |
3876 | if (wp->w_p_rl) { |
3877 | --col; |
3878 | --off; |
3879 | } else { |
3880 | ++col; |
3881 | ++off; |
3882 | } |
3883 | ++vcol; |
3884 | eol_hl_off = 1; |
3885 | } |
3886 | // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. |
3887 | if (wp->w_p_wrap) { |
3888 | v = wp->w_skipcol; |
3889 | } else { |
3890 | v = wp->w_leftcol; |
3891 | } |
3892 | |
3893 | /* check if line ends before left margin */ |
3894 | if (vcol < v + col - win_col_off(wp)) |
3895 | vcol = v + col - win_col_off(wp); |
3896 | /* Get rid of the boguscols now, we want to draw until the right |
3897 | * edge for 'cursorcolumn'. */ |
3898 | col -= boguscols; |
3899 | // boguscols = 0; // Disabled because value never read after this |
3900 | |
3901 | if (draw_color_col) |
3902 | draw_color_col = advance_color_col(VCOL_HLC, &color_cols); |
3903 | |
3904 | if (((wp->w_p_cuc |
3905 | && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off |
3906 | && (int)wp->w_virtcol < |
3907 | grid->Columns * (row - startrow + 1) + v |
3908 | && lnum != wp->w_cursor.lnum) |
3909 | || draw_color_col || line_attr_lowprio || line_attr |
3910 | || diff_hlf != (hlf_T)0 || do_virttext)) { |
3911 | int rightmost_vcol = 0; |
3912 | int i; |
3913 | |
3914 | VirtText virt_text = do_virttext ? bufhl_info.line->virt_text |
3915 | : (VirtText)KV_INITIAL_VALUE; |
3916 | size_t virt_pos = 0; |
3917 | LineState s = LINE_STATE((char_u *)"" ); |
3918 | int virt_attr = 0; |
3919 | |
3920 | // Make sure alignment is the same regardless |
3921 | // if listchars=eol:X is used or not. |
3922 | bool delay_virttext = wp->w_p_lcs_chars.eol == lcs_eol_one |
3923 | && eol_hl_off == 0; |
3924 | |
3925 | if (wp->w_p_cuc) { |
3926 | rightmost_vcol = wp->w_virtcol; |
3927 | } |
3928 | |
3929 | if (draw_color_col) { |
3930 | // determine rightmost colorcolumn to possibly draw |
3931 | for (i = 0; color_cols[i] >= 0; i++) { |
3932 | if (rightmost_vcol < color_cols[i]) { |
3933 | rightmost_vcol = color_cols[i]; |
3934 | } |
3935 | } |
3936 | } |
3937 | |
3938 | int cuc_attr = win_hl_attr(wp, HLF_CUC); |
3939 | int mc_attr = win_hl_attr(wp, HLF_MC); |
3940 | |
3941 | int diff_attr = 0; |
3942 | if (diff_hlf == HLF_TXD) { |
3943 | diff_hlf = HLF_CHD; |
3944 | } |
3945 | if (diff_hlf != 0) { |
3946 | diff_attr = win_hl_attr(wp, diff_hlf); |
3947 | } |
3948 | |
3949 | int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); |
3950 | if (base_attr || line_attr) { |
3951 | rightmost_vcol = INT_MAX; |
3952 | } |
3953 | |
3954 | int col_stride = wp->w_p_rl ? -1 : 1; |
3955 | |
3956 | while (wp->w_p_rl ? col >= 0 : col < grid->Columns) { |
3957 | int cells = -1; |
3958 | if (do_virttext && !delay_virttext) { |
3959 | if (*s.p == NUL) { |
3960 | if (virt_pos < virt_text.size) { |
3961 | s.p = (char_u *)kv_A(virt_text, virt_pos).text; |
3962 | int hl_id = kv_A(virt_text, virt_pos).hl_id; |
3963 | virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; |
3964 | virt_pos++; |
3965 | } else { |
3966 | do_virttext = false; |
3967 | } |
3968 | } |
3969 | if (*s.p != NUL) { |
3970 | cells = line_putchar(&s, &linebuf_char[off], grid->Columns - col, |
3971 | false); |
3972 | } |
3973 | } |
3974 | delay_virttext = false; |
3975 | |
3976 | if (cells == -1) { |
3977 | schar_from_ascii(linebuf_char[off], ' '); |
3978 | cells = 1; |
3979 | } |
3980 | col += cells * col_stride; |
3981 | if (draw_color_col) { |
3982 | draw_color_col = advance_color_col(VCOL_HLC, &color_cols); |
3983 | } |
3984 | |
3985 | int col_attr = base_attr; |
3986 | |
3987 | if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { |
3988 | col_attr = cuc_attr; |
3989 | } else if (draw_color_col && VCOL_HLC == *color_cols) { |
3990 | col_attr = mc_attr; |
3991 | } |
3992 | |
3993 | if (do_virttext) { |
3994 | col_attr = hl_combine_attr(col_attr, virt_attr); |
3995 | } |
3996 | |
3997 | col_attr = hl_combine_attr(col_attr, line_attr); |
3998 | |
3999 | linebuf_attr[off] = col_attr; |
4000 | if (cells == 2) { |
4001 | linebuf_attr[off+1] = col_attr; |
4002 | } |
4003 | off += cells * col_stride; |
4004 | |
4005 | if (VCOL_HLC >= rightmost_vcol && *s.p == NUL |
4006 | && virt_pos >= virt_text.size) { |
4007 | break; |
4008 | } |
4009 | |
4010 | ++vcol; |
4011 | } |
4012 | } |
4013 | |
4014 | // TODO(bfredl): integrate with the common beyond-the-end-loop |
4015 | if (wp->w_buffer->terminal) { |
4016 | // terminal buffers may need to highlight beyond the end of the |
4017 | // logical line |
4018 | while (col < grid->Columns) { |
4019 | schar_from_ascii(linebuf_char[off], ' '); |
4020 | linebuf_attr[off++] = term_attrs[vcol++]; |
4021 | col++; |
4022 | } |
4023 | } |
4024 | grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, |
4025 | wp->w_hl_attr_normal, false); |
4026 | row++; |
4027 | |
4028 | /* |
4029 | * Update w_cline_height and w_cline_folded if the cursor line was |
4030 | * updated (saves a call to plines() later). |
4031 | */ |
4032 | if (wp == curwin && lnum == curwin->w_cursor.lnum) { |
4033 | curwin->w_cline_row = startrow; |
4034 | curwin->w_cline_height = row - startrow; |
4035 | curwin->w_cline_folded = false; |
4036 | curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); |
4037 | conceal_cursor_used = conceal_cursor_line(curwin); |
4038 | } |
4039 | |
4040 | break; |
4041 | } |
4042 | |
4043 | // Show "extends" character from 'listchars' if beyond the line end and |
4044 | // 'list' is set. |
4045 | if (wp->w_p_lcs_chars.ext != NUL |
4046 | && wp->w_p_list |
4047 | && !wp->w_p_wrap |
4048 | && filler_todo <= 0 |
4049 | && (wp->w_p_rl ? col == 0 : col == grid->Columns - 1) |
4050 | && (*ptr != NUL |
4051 | || lcs_eol_one > 0 |
4052 | || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { |
4053 | c = wp->w_p_lcs_chars.ext; |
4054 | char_attr = win_hl_attr(wp, HLF_AT); |
4055 | mb_c = c; |
4056 | if (utf_char2len(c) > 1) { |
4057 | mb_utf8 = true; |
4058 | u8cc[0] = 0; |
4059 | c = 0xc0; |
4060 | } else { |
4061 | mb_utf8 = false; |
4062 | } |
4063 | } |
4064 | |
4065 | // advance to the next 'colorcolumn' |
4066 | if (draw_color_col) { |
4067 | draw_color_col = advance_color_col(VCOL_HLC, &color_cols); |
4068 | } |
4069 | |
4070 | // Highlight the cursor column if 'cursorcolumn' is set. But don't |
4071 | // highlight the cursor position itself. |
4072 | // Also highlight the 'colorcolumn' if it is different than |
4073 | // 'cursorcolumn' |
4074 | vcol_save_attr = -1; |
4075 | if (draw_state == WL_LINE && !lnum_in_visual_area |
4076 | && search_attr == 0 && area_attr == 0) { |
4077 | if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol |
4078 | && lnum != wp->w_cursor.lnum) { |
4079 | vcol_save_attr = char_attr; |
4080 | char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); |
4081 | } else if (draw_color_col && VCOL_HLC == *color_cols) { |
4082 | vcol_save_attr = char_attr; |
4083 | char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); |
4084 | } |
4085 | } |
4086 | |
4087 | // Apply lowest-priority line attr now, so everything can override it. |
4088 | if (draw_state == WL_LINE) { |
4089 | char_attr = hl_combine_attr(line_attr_lowprio, char_attr); |
4090 | } |
4091 | |
4092 | // Store character to be displayed. |
4093 | // Skip characters that are left of the screen for 'nowrap'. |
4094 | vcol_prev = vcol; |
4095 | if (draw_state < WL_LINE || n_skip <= 0) { |
4096 | // |
4097 | // Store the character. |
4098 | // |
4099 | if (wp->w_p_rl && (*mb_char2cells)(mb_c) > 1) { |
4100 | // A double-wide character is: put first halve in left cell. |
4101 | off--; |
4102 | col--; |
4103 | } |
4104 | if (mb_utf8) { |
4105 | schar_from_cc(linebuf_char[off], mb_c, u8cc); |
4106 | } else { |
4107 | schar_from_ascii(linebuf_char[off], c); |
4108 | } |
4109 | if (multi_attr) { |
4110 | linebuf_attr[off] = multi_attr; |
4111 | multi_attr = 0; |
4112 | } else { |
4113 | linebuf_attr[off] = char_attr; |
4114 | } |
4115 | |
4116 | if ((*mb_char2cells)(mb_c) > 1) { |
4117 | // Need to fill two screen columns. |
4118 | off++; |
4119 | col++; |
4120 | // UTF-8: Put a 0 in the second screen char. |
4121 | linebuf_char[off][0] = 0; |
4122 | if (draw_state > WL_NR && filler_todo <= 0) { |
4123 | vcol++; |
4124 | } |
4125 | // When "tocol" is halfway through a character, set it to the end of |
4126 | // the character, otherwise highlighting won't stop. |
4127 | if (tocol == vcol) { |
4128 | tocol++; |
4129 | } |
4130 | if (wp->w_p_rl) { |
4131 | /* now it's time to backup one cell */ |
4132 | --off; |
4133 | --col; |
4134 | } |
4135 | } |
4136 | if (wp->w_p_rl) { |
4137 | --off; |
4138 | --col; |
4139 | } else { |
4140 | ++off; |
4141 | ++col; |
4142 | } |
4143 | } else if (wp->w_p_cole > 0 && is_concealing) { |
4144 | --n_skip; |
4145 | ++vcol_off; |
4146 | if (n_extra > 0) |
4147 | vcol_off += n_extra; |
4148 | if (wp->w_p_wrap) { |
4149 | /* |
4150 | * Special voodoo required if 'wrap' is on. |
4151 | * |
4152 | * Advance the column indicator to force the line |
4153 | * drawing to wrap early. This will make the line |
4154 | * take up the same screen space when parts are concealed, |
4155 | * so that cursor line computations aren't messed up. |
4156 | * |
4157 | * To avoid the fictitious advance of 'col' causing |
4158 | * trailing junk to be written out of the screen line |
4159 | * we are building, 'boguscols' keeps track of the number |
4160 | * of bad columns we have advanced. |
4161 | */ |
4162 | if (n_extra > 0) { |
4163 | vcol += n_extra; |
4164 | if (wp->w_p_rl) { |
4165 | col -= n_extra; |
4166 | boguscols -= n_extra; |
4167 | } else { |
4168 | col += n_extra; |
4169 | boguscols += n_extra; |
4170 | } |
4171 | n_extra = 0; |
4172 | n_attr = 0; |
4173 | } |
4174 | |
4175 | |
4176 | if ((*mb_char2cells)(mb_c) > 1) { |
4177 | // Need to fill two screen columns. |
4178 | if (wp->w_p_rl) { |
4179 | --boguscols; |
4180 | --col; |
4181 | } else { |
4182 | ++boguscols; |
4183 | ++col; |
4184 | } |
4185 | } |
4186 | |
4187 | if (wp->w_p_rl) { |
4188 | --boguscols; |
4189 | --col; |
4190 | } else { |
4191 | ++boguscols; |
4192 | ++col; |
4193 | } |
4194 | } else { |
4195 | if (n_extra > 0) { |
4196 | vcol += n_extra; |
4197 | n_extra = 0; |
4198 | n_attr = 0; |
4199 | } |
4200 | } |
4201 | |
4202 | } else |
4203 | --n_skip; |
4204 | |
4205 | /* Only advance the "vcol" when after the 'number' or 'relativenumber' |
4206 | * column. */ |
4207 | if (draw_state > WL_NR |
4208 | && filler_todo <= 0 |
4209 | ) |
4210 | ++vcol; |
4211 | |
4212 | if (vcol_save_attr >= 0) |
4213 | char_attr = vcol_save_attr; |
4214 | |
4215 | /* restore attributes after "predeces" in 'listchars' */ |
4216 | if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) |
4217 | char_attr = saved_attr3; |
4218 | |
4219 | /* restore attributes after last 'listchars' or 'number' char */ |
4220 | if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) |
4221 | char_attr = saved_attr2; |
4222 | |
4223 | /* |
4224 | * At end of screen line and there is more to come: Display the line |
4225 | * so far. If there is no more to display it is caught above. |
4226 | */ |
4227 | if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) |
4228 | && (*ptr != NUL |
4229 | || filler_todo > 0 |
4230 | || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL |
4231 | && p_extra != at_end_str) |
4232 | || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) |
4233 | ) { |
4234 | bool wrap = wp->w_p_wrap // Wrapping enabled. |
4235 | && filler_todo <= 0 // Not drawing diff filler lines. |
4236 | && lcs_eol_one != -1 // Haven't printed the lcs_eol character. |
4237 | && row != endrow - 1 // Not the last line being displayed. |
4238 | && (grid->Columns == Columns // Window spans the width of the screen, |
4239 | || ui_has(kUIMultigrid)) // or has dedicated grid. |
4240 | && !wp->w_p_rl; // Not right-to-left. |
4241 | grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl, |
4242 | wp, wp->w_hl_attr_normal, wrap); |
4243 | if (wrap) { |
4244 | ScreenGrid *current_grid = grid; |
4245 | int current_row = row, dummy_col = 0; // dummy_col unused |
4246 | screen_adjust_grid(¤t_grid, ¤t_row, &dummy_col); |
4247 | |
4248 | // Force a redraw of the first column of the next line. |
4249 | current_grid->attrs[current_grid->line_offset[current_row+1]] = -1; |
4250 | |
4251 | // Remember that the line wraps, used for modeless copy. |
4252 | current_grid->line_wraps[current_row] = true; |
4253 | } |
4254 | |
4255 | boguscols = 0; |
4256 | row++; |
4257 | |
4258 | /* When not wrapping and finished diff lines, or when displayed |
4259 | * '$' and highlighting until last column, break here. */ |
4260 | if ((!wp->w_p_wrap |
4261 | && filler_todo <= 0 |
4262 | ) || lcs_eol_one == -1) |
4263 | break; |
4264 | |
4265 | // When the window is too narrow draw all "@" lines. |
4266 | if (draw_state != WL_LINE && filler_todo <= 0) { |
4267 | win_draw_end(wp, '@', ' ', true, row, wp->w_grid.Rows, HLF_AT); |
4268 | row = endrow; |
4269 | } |
4270 | |
4271 | /* When line got too long for screen break here. */ |
4272 | if (row == endrow) { |
4273 | ++row; |
4274 | break; |
4275 | } |
4276 | |
4277 | col = 0; |
4278 | off = 0; |
4279 | if (wp->w_p_rl) { |
4280 | col = grid->Columns - 1; // col is not used if breaking! |
4281 | off += col; |
4282 | } |
4283 | |
4284 | /* reset the drawing state for the start of a wrapped line */ |
4285 | draw_state = WL_START; |
4286 | saved_n_extra = n_extra; |
4287 | saved_p_extra = p_extra; |
4288 | saved_c_extra = c_extra; |
4289 | saved_c_final = c_final; |
4290 | saved_char_attr = char_attr; |
4291 | n_extra = 0; |
4292 | lcs_prec_todo = wp->w_p_lcs_chars.prec; |
4293 | if (filler_todo <= 0) { |
4294 | need_showbreak = true; |
4295 | } |
4296 | filler_todo--; |
4297 | // When the filler lines are actually below the last line of the |
4298 | // file, don't draw the line itself, break here. |
4299 | if (filler_todo == 0 && wp->w_botfill) { |
4300 | break; |
4301 | } |
4302 | } |
4303 | |
4304 | } /* for every character in the line */ |
4305 | |
4306 | /* After an empty line check first word for capital. */ |
4307 | if (*skipwhite(line) == NUL) { |
4308 | capcol_lnum = lnum + 1; |
4309 | cap_col = 0; |
4310 | } |
4311 | |
4312 | xfree(p_extra_free); |
4313 | return row; |
4314 | } |
4315 | |
4316 | /// Determine if dedicated window grid should be used or the default_grid |
4317 | /// |
4318 | /// If UI did not request multigrid support, draw all windows on the |
4319 | /// default_grid. |
4320 | /// |
4321 | /// NB: this function can only been used with window grids in a context where |
4322 | /// win_grid_alloc already has been called! |
4323 | /// |
4324 | /// If the default_grid is used, adjust window relative positions to global |
4325 | /// screen positions. |
4326 | void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) |
4327 | { |
4328 | if (!(*grid)->chars && *grid != &default_grid) { |
4329 | *row_off += (*grid)->row_offset; |
4330 | *col_off += (*grid)->col_offset; |
4331 | if (*grid == &msg_grid_adj && msg_grid.chars) { |
4332 | *grid = &msg_grid; |
4333 | } else { |
4334 | *grid = &default_grid; |
4335 | } |
4336 | } |
4337 | } |
4338 | |
4339 | |
4340 | /* |
4341 | * Check whether the given character needs redrawing: |
4342 | * - the (first byte of the) character is different |
4343 | * - the attributes are different |
4344 | * - the character is multi-byte and the next byte is different |
4345 | * - the character is two cells wide and the second cell differs. |
4346 | */ |
4347 | static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to, |
4348 | int cols) |
4349 | { |
4350 | return (cols > 0 |
4351 | && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to]) |
4352 | || linebuf_attr[off_from] != grid->attrs[off_to] |
4353 | || (line_off2cells(linebuf_char, off_from, off_from + cols) > 1 |
4354 | && schar_cmp(linebuf_char[off_from + 1], |
4355 | grid->chars[off_to + 1]))) |
4356 | || p_wd < 0)); |
4357 | } |
4358 | |
4359 | /// Move one buffered line to the window grid, but only the characters that |
4360 | /// have actually changed. Handle insert/delete character. |
4361 | /// "coloff" gives the first column on the grid for this line. |
4362 | /// "endcol" gives the columns where valid characters are. |
4363 | /// "clear_width" is the width of the window. It's > 0 if the rest of the line |
4364 | /// needs to be cleared, negative otherwise. |
4365 | /// "rlflag" is TRUE in a rightleft window: |
4366 | /// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" |
4367 | /// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" |
4368 | /// If "wrap" is true, then hint to the UI that "row" contains a line |
4369 | /// which has wrapped into the next row. |
4370 | static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, |
4371 | int clear_width, int rlflag, win_T *wp, |
4372 | int bg_attr, bool wrap) |
4373 | { |
4374 | unsigned off_from; |
4375 | unsigned off_to; |
4376 | unsigned max_off_from; |
4377 | unsigned max_off_to; |
4378 | int col = 0; |
4379 | bool redraw_this; // Does character need redraw? |
4380 | bool redraw_next; // redraw_this for next character |
4381 | bool clear_next = false; |
4382 | int char_cells; // 1: normal char |
4383 | // 2: occupies two display cells |
4384 | int start_dirty = -1, end_dirty = 0; |
4385 | |
4386 | // TODO(bfredl): check all callsites and eliminate |
4387 | // Check for illegal row and col, just in case |
4388 | if (row >= grid->Rows) { |
4389 | row = grid->Rows - 1; |
4390 | } |
4391 | if (endcol > grid->Columns) { |
4392 | endcol = grid->Columns; |
4393 | } |
4394 | |
4395 | screen_adjust_grid(&grid, &row, &coloff); |
4396 | |
4397 | // Safety check. Avoids clang warnings down the call stack. |
4398 | if (grid->chars == NULL || row >= grid->Rows || coloff >= grid->Columns) { |
4399 | DLOG("invalid state, skipped" ); |
4400 | return; |
4401 | } |
4402 | |
4403 | off_from = 0; |
4404 | off_to = grid->line_offset[row] + coloff; |
4405 | max_off_from = linebuf_size; |
4406 | max_off_to = grid->line_offset[row] + grid->Columns; |
4407 | |
4408 | if (rlflag) { |
4409 | /* Clear rest first, because it's left of the text. */ |
4410 | if (clear_width > 0) { |
4411 | while (col <= endcol && grid->chars[off_to][0] == ' ' |
4412 | && grid->chars[off_to][1] == NUL |
4413 | && grid->attrs[off_to] == bg_attr |
4414 | ) { |
4415 | ++off_to; |
4416 | ++col; |
4417 | } |
4418 | if (col <= endcol) { |
4419 | grid_fill(grid, row, row + 1, col + coloff, endcol + coloff + 1, |
4420 | ' ', ' ', bg_attr); |
4421 | } |
4422 | } |
4423 | col = endcol + 1; |
4424 | off_to = grid->line_offset[row] + col + coloff; |
4425 | off_from += col; |
4426 | endcol = (clear_width > 0 ? clear_width : -clear_width); |
4427 | } |
4428 | |
4429 | if (bg_attr) { |
4430 | for (int c = col; c < endcol; c++) { |
4431 | linebuf_attr[off_from+c] = |
4432 | hl_combine_attr(bg_attr, linebuf_attr[off_from+c]); |
4433 | } |
4434 | } |
4435 | |
4436 | redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col); |
4437 | |
4438 | while (col < endcol) { |
4439 | char_cells = 1; |
4440 | if (col + 1 < endcol) { |
4441 | char_cells = line_off2cells(linebuf_char, off_from, max_off_from); |
4442 | } |
4443 | redraw_this = redraw_next; |
4444 | redraw_next = grid_char_needs_redraw(grid, off_from + char_cells, |
4445 | off_to + char_cells, |
4446 | endcol - col - char_cells); |
4447 | |
4448 | if (redraw_this) { |
4449 | if (start_dirty == -1) { |
4450 | start_dirty = col; |
4451 | } |
4452 | end_dirty = col + char_cells; |
4453 | // When writing a single-width character over a double-width |
4454 | // character and at the end of the redrawn text, need to clear out |
4455 | // the right halve of the old character. |
4456 | // Also required when writing the right halve of a double-width |
4457 | // char over the left halve of an existing one |
4458 | if (col + char_cells == endcol |
4459 | && ((char_cells == 1 |
4460 | && grid_off2cells(grid, off_to, max_off_to) > 1) |
4461 | || (char_cells == 2 |
4462 | && grid_off2cells(grid, off_to, max_off_to) == 1 |
4463 | && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) { |
4464 | clear_next = true; |
4465 | } |
4466 | |
4467 | schar_copy(grid->chars[off_to], linebuf_char[off_from]); |
4468 | if (char_cells == 2) { |
4469 | schar_copy(grid->chars[off_to+1], linebuf_char[off_from+1]); |
4470 | } |
4471 | |
4472 | grid->attrs[off_to] = linebuf_attr[off_from]; |
4473 | // For simplicity set the attributes of second half of a |
4474 | // double-wide character equal to the first half. |
4475 | if (char_cells == 2) { |
4476 | grid->attrs[off_to + 1] = linebuf_attr[off_from]; |
4477 | } |
4478 | } |
4479 | |
4480 | off_to += char_cells; |
4481 | off_from += char_cells; |
4482 | col += char_cells; |
4483 | } |
4484 | |
4485 | if (clear_next) { |
4486 | /* Clear the second half of a double-wide character of which the left |
4487 | * half was overwritten with a single-wide character. */ |
4488 | schar_from_ascii(grid->chars[off_to], ' '); |
4489 | end_dirty++; |
4490 | } |
4491 | |
4492 | int clear_end = -1; |
4493 | if (clear_width > 0 && !rlflag) { |
4494 | // blank out the rest of the line |
4495 | // TODO(bfredl): we could cache winline widths |
4496 | while (col < clear_width) { |
4497 | if (grid->chars[off_to][0] != ' ' |
4498 | || grid->chars[off_to][1] != NUL |
4499 | || grid->attrs[off_to] != bg_attr) { |
4500 | grid->chars[off_to][0] = ' '; |
4501 | grid->chars[off_to][1] = NUL; |
4502 | grid->attrs[off_to] = bg_attr; |
4503 | if (start_dirty == -1) { |
4504 | start_dirty = col; |
4505 | end_dirty = col; |
4506 | } else if (clear_end == -1) { |
4507 | end_dirty = endcol; |
4508 | } |
4509 | clear_end = col+1; |
4510 | } |
4511 | col++; |
4512 | off_to++; |
4513 | } |
4514 | } |
4515 | |
4516 | if (clear_width > 0 || wp->w_width != grid->Columns) { |
4517 | // If we cleared after the end of the line, it did not wrap. |
4518 | // For vsplit, line wrapping is not possible. |
4519 | grid->line_wraps[row] = false; |
4520 | } |
4521 | |
4522 | if (clear_end < end_dirty) { |
4523 | clear_end = end_dirty; |
4524 | } |
4525 | if (start_dirty == -1) { |
4526 | start_dirty = end_dirty; |
4527 | } |
4528 | if (clear_end > start_dirty) { |
4529 | ui_line(grid, row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end, |
4530 | bg_attr, wrap); |
4531 | } |
4532 | } |
4533 | |
4534 | /* |
4535 | * Mirror text "str" for right-left displaying. |
4536 | * Only works for single-byte characters (e.g., numbers). |
4537 | */ |
4538 | void rl_mirror(char_u *str) |
4539 | { |
4540 | char_u *p1, *p2; |
4541 | int t; |
4542 | |
4543 | for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; ++p1, --p2) { |
4544 | t = *p1; |
4545 | *p1 = *p2; |
4546 | *p2 = t; |
4547 | } |
4548 | } |
4549 | |
4550 | /* |
4551 | * mark all status lines for redraw; used after first :cd |
4552 | */ |
4553 | void status_redraw_all(void) |
4554 | { |
4555 | |
4556 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
4557 | if (wp->w_status_height) { |
4558 | wp->w_redr_status = TRUE; |
4559 | redraw_later(VALID); |
4560 | } |
4561 | } |
4562 | } |
4563 | |
4564 | /// Marks all status lines of the current buffer for redraw. |
4565 | void status_redraw_curbuf(void) |
4566 | { |
4567 | status_redraw_buf(curbuf); |
4568 | } |
4569 | |
4570 | /// Marks all status lines of the specified buffer for redraw. |
4571 | void status_redraw_buf(buf_T *buf) |
4572 | { |
4573 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
4574 | if (wp->w_status_height != 0 && wp->w_buffer == buf) { |
4575 | wp->w_redr_status = true; |
4576 | redraw_later(VALID); |
4577 | } |
4578 | } |
4579 | } |
4580 | |
4581 | /* |
4582 | * Redraw all status lines that need to be redrawn. |
4583 | */ |
4584 | void redraw_statuslines(void) |
4585 | { |
4586 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
4587 | if (wp->w_redr_status) { |
4588 | win_redr_status(wp); |
4589 | } |
4590 | } |
4591 | if (redraw_tabline) |
4592 | draw_tabline(); |
4593 | } |
4594 | |
4595 | /* |
4596 | * Redraw all status lines at the bottom of frame "frp". |
4597 | */ |
4598 | void win_redraw_last_status(const frame_T *frp) |
4599 | FUNC_ATTR_NONNULL_ARG(1) |
4600 | { |
4601 | if (frp->fr_layout == FR_LEAF) { |
4602 | frp->fr_win->w_redr_status = true; |
4603 | } else if (frp->fr_layout == FR_ROW) { |
4604 | FOR_ALL_FRAMES(frp, frp->fr_child) { |
4605 | win_redraw_last_status(frp); |
4606 | } |
4607 | } else { |
4608 | assert(frp->fr_layout == FR_COL); |
4609 | frp = frp->fr_child; |
4610 | while (frp->fr_next != NULL) { |
4611 | frp = frp->fr_next; |
4612 | } |
4613 | win_redraw_last_status(frp); |
4614 | } |
4615 | } |
4616 | |
4617 | /* |
4618 | * Draw the verticap separator right of window "wp" starting with line "row". |
4619 | */ |
4620 | static void draw_vsep_win(win_T *wp, int row) |
4621 | { |
4622 | int hl; |
4623 | int c; |
4624 | |
4625 | if (wp->w_vsep_width) { |
4626 | // draw the vertical separator right of this window |
4627 | c = fillchar_vsep(wp, &hl); |
4628 | grid_fill(&default_grid, wp->w_winrow + row, W_ENDROW(wp), |
4629 | W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); |
4630 | } |
4631 | } |
4632 | |
4633 | |
4634 | /* |
4635 | * Get the length of an item as it will be shown in the status line. |
4636 | */ |
4637 | static int status_match_len(expand_T *xp, char_u *s) |
4638 | { |
4639 | int len = 0; |
4640 | |
4641 | int = (xp->xp_context == EXPAND_MENUS |
4642 | || xp->xp_context == EXPAND_MENUNAMES); |
4643 | |
4644 | /* Check for menu separators - replace with '|'. */ |
4645 | if (emenu && menu_is_separator(s)) |
4646 | return 1; |
4647 | |
4648 | while (*s != NUL) { |
4649 | s += skip_status_match_char(xp, s); |
4650 | len += ptr2cells(s); |
4651 | MB_PTR_ADV(s); |
4652 | } |
4653 | |
4654 | return len; |
4655 | } |
4656 | |
4657 | /* |
4658 | * Return the number of characters that should be skipped in a status match. |
4659 | * These are backslashes used for escaping. Do show backslashes in help tags. |
4660 | */ |
4661 | static int skip_status_match_char(expand_T *xp, char_u *s) |
4662 | { |
4663 | if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) |
4664 | || ((xp->xp_context == EXPAND_MENUS |
4665 | || xp->xp_context == EXPAND_MENUNAMES) |
4666 | && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL))) |
4667 | ) { |
4668 | #ifndef BACKSLASH_IN_FILENAME |
4669 | if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') |
4670 | return 2; |
4671 | #endif |
4672 | return 1; |
4673 | } |
4674 | return 0; |
4675 | } |
4676 | |
4677 | /* |
4678 | * Show wildchar matches in the status line. |
4679 | * Show at least the "match" item. |
4680 | * We start at item 'first_match' in the list and show all matches that fit. |
4681 | * |
4682 | * If inversion is possible we use it. Else '=' characters are used. |
4683 | */ |
4684 | void |
4685 | win_redr_status_matches ( |
4686 | expand_T *xp, |
4687 | int num_matches, |
4688 | char_u **matches, /* list of matches */ |
4689 | int match, |
4690 | int showtail |
4691 | ) |
4692 | { |
4693 | #define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) |
4694 | int row; |
4695 | char_u *buf; |
4696 | int len; |
4697 | int clen; /* length in screen cells */ |
4698 | int fillchar; |
4699 | int attr; |
4700 | int i; |
4701 | int highlight = TRUE; |
4702 | char_u *selstart = NULL; |
4703 | int selstart_col = 0; |
4704 | char_u *selend = NULL; |
4705 | static int first_match = 0; |
4706 | int add_left = FALSE; |
4707 | char_u *s; |
4708 | int ; |
4709 | int l; |
4710 | |
4711 | if (matches == NULL) /* interrupted completion? */ |
4712 | return; |
4713 | |
4714 | buf = xmalloc(Columns * MB_MAXBYTES + 1); |
4715 | |
4716 | if (match == -1) { /* don't show match but original text */ |
4717 | match = 0; |
4718 | highlight = FALSE; |
4719 | } |
4720 | /* count 1 for the ending ">" */ |
4721 | clen = status_match_len(xp, L_MATCH(match)) + 3; |
4722 | if (match == 0) |
4723 | first_match = 0; |
4724 | else if (match < first_match) { |
4725 | /* jumping left, as far as we can go */ |
4726 | first_match = match; |
4727 | add_left = TRUE; |
4728 | } else { |
4729 | /* check if match fits on the screen */ |
4730 | for (i = first_match; i < match; ++i) |
4731 | clen += status_match_len(xp, L_MATCH(i)) + 2; |
4732 | if (first_match > 0) |
4733 | clen += 2; |
4734 | // jumping right, put match at the left |
4735 | if ((long)clen > Columns) { |
4736 | first_match = match; |
4737 | /* if showing the last match, we can add some on the left */ |
4738 | clen = 2; |
4739 | for (i = match; i < num_matches; ++i) { |
4740 | clen += status_match_len(xp, L_MATCH(i)) + 2; |
4741 | if ((long)clen >= Columns) { |
4742 | break; |
4743 | } |
4744 | } |
4745 | if (i == num_matches) |
4746 | add_left = TRUE; |
4747 | } |
4748 | } |
4749 | if (add_left) |
4750 | while (first_match > 0) { |
4751 | clen += status_match_len(xp, L_MATCH(first_match - 1)) + 2; |
4752 | if ((long)clen >= Columns) { |
4753 | break; |
4754 | } |
4755 | first_match--; |
4756 | } |
4757 | |
4758 | fillchar = fillchar_status(&attr, curwin); |
4759 | |
4760 | if (first_match == 0) { |
4761 | *buf = NUL; |
4762 | len = 0; |
4763 | } else { |
4764 | STRCPY(buf, "< " ); |
4765 | len = 2; |
4766 | } |
4767 | clen = len; |
4768 | |
4769 | i = first_match; |
4770 | while ((long)(clen + status_match_len(xp, L_MATCH(i)) + 2) < Columns) { |
4771 | if (i == match) { |
4772 | selstart = buf + len; |
4773 | selstart_col = clen; |
4774 | } |
4775 | |
4776 | s = L_MATCH(i); |
4777 | /* Check for menu separators - replace with '|' */ |
4778 | emenu = (xp->xp_context == EXPAND_MENUS |
4779 | || xp->xp_context == EXPAND_MENUNAMES); |
4780 | if (emenu && menu_is_separator(s)) { |
4781 | STRCPY(buf + len, transchar('|')); |
4782 | l = (int)STRLEN(buf + len); |
4783 | len += l; |
4784 | clen += l; |
4785 | } else |
4786 | for (; *s != NUL; ++s) { |
4787 | s += skip_status_match_char(xp, s); |
4788 | clen += ptr2cells(s); |
4789 | if ((l = (*mb_ptr2len)(s)) > 1) { |
4790 | STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) |
4791 | s += l - 1; |
4792 | len += l; |
4793 | } else { |
4794 | STRCPY(buf + len, transchar_byte(*s)); |
4795 | len += (int)STRLEN(buf + len); |
4796 | } |
4797 | } |
4798 | if (i == match) |
4799 | selend = buf + len; |
4800 | |
4801 | *(buf + len++) = ' '; |
4802 | *(buf + len++) = ' '; |
4803 | clen += 2; |
4804 | if (++i == num_matches) |
4805 | break; |
4806 | } |
4807 | |
4808 | if (i != num_matches) { |
4809 | *(buf + len++) = '>'; |
4810 | ++clen; |
4811 | } |
4812 | |
4813 | buf[len] = NUL; |
4814 | |
4815 | row = cmdline_row - 1; |
4816 | if (row >= 0) { |
4817 | if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) { |
4818 | if (msg_scrolled > 0) { |
4819 | /* Put the wildmenu just above the command line. If there is |
4820 | * no room, scroll the screen one line up. */ |
4821 | if (cmdline_row == Rows - 1) { |
4822 | msg_scroll_up(false); |
4823 | msg_scrolled++; |
4824 | } else { |
4825 | cmdline_row++; |
4826 | row++; |
4827 | } |
4828 | wild_menu_showing = WM_SCROLLED; |
4829 | } else { |
4830 | /* Create status line if needed by setting 'laststatus' to 2. |
4831 | * Set 'winminheight' to zero to avoid that the window is |
4832 | * resized. */ |
4833 | if (lastwin->w_status_height == 0) { |
4834 | save_p_ls = p_ls; |
4835 | save_p_wmh = p_wmh; |
4836 | p_ls = 2; |
4837 | p_wmh = 0; |
4838 | last_status(FALSE); |
4839 | } |
4840 | wild_menu_showing = WM_SHOWN; |
4841 | } |
4842 | } |
4843 | |
4844 | // Tricky: wildmenu can be drawn either over a status line, or at empty |
4845 | // scrolled space in the message output |
4846 | ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) |
4847 | ? &msg_grid_adj : &default_grid; |
4848 | |
4849 | grid_puts(grid, buf, row, 0, attr); |
4850 | if (selstart != NULL && highlight) { |
4851 | *selend = NUL; |
4852 | grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); |
4853 | } |
4854 | |
4855 | grid_fill(grid, row, row + 1, clen, Columns, |
4856 | fillchar, fillchar, attr); |
4857 | } |
4858 | |
4859 | win_redraw_last_status(topframe); |
4860 | xfree(buf); |
4861 | } |
4862 | |
4863 | /// Redraw the status line of window `wp`. |
4864 | /// |
4865 | /// If inversion is possible we use it. Else '=' characters are used. |
4866 | static void win_redr_status(win_T *wp) |
4867 | { |
4868 | int row; |
4869 | char_u *p; |
4870 | int len; |
4871 | int fillchar; |
4872 | int attr; |
4873 | int this_ru_col; |
4874 | static int busy = FALSE; |
4875 | |
4876 | // May get here recursively when 'statusline' (indirectly) |
4877 | // invokes ":redrawstatus". Simply ignore the call then. |
4878 | if (busy |
4879 | // Also ignore if wildmenu is showing. |
4880 | || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { |
4881 | return; |
4882 | } |
4883 | busy = true; |
4884 | |
4885 | wp->w_redr_status = FALSE; |
4886 | if (wp->w_status_height == 0) { |
4887 | // no status line, can only be last window |
4888 | redraw_cmdline = true; |
4889 | } else if (!redrawing()) { |
4890 | // Don't redraw right now, do it later. Don't update status line when |
4891 | // popup menu is visible and may be drawn over it |
4892 | wp->w_redr_status = true; |
4893 | } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { |
4894 | /* redraw custom status line */ |
4895 | redraw_custom_statusline(wp); |
4896 | } else { |
4897 | fillchar = fillchar_status(&attr, wp); |
4898 | |
4899 | get_trans_bufname(wp->w_buffer); |
4900 | p = NameBuff; |
4901 | len = (int)STRLEN(p); |
4902 | |
4903 | if (bt_help(wp->w_buffer) |
4904 | || wp->w_p_pvw |
4905 | || bufIsChanged(wp->w_buffer) |
4906 | || wp->w_buffer->b_p_ro) { |
4907 | *(p + len++) = ' '; |
4908 | } |
4909 | if (bt_help(wp->w_buffer)) { |
4910 | STRCPY(p + len, _("[Help]" )); |
4911 | len += (int)STRLEN(p + len); |
4912 | } |
4913 | if (wp->w_p_pvw) { |
4914 | STRCPY(p + len, _("[Preview]" )); |
4915 | len += (int)STRLEN(p + len); |
4916 | } |
4917 | if (bufIsChanged(wp->w_buffer)) { |
4918 | STRCPY(p + len, "[+]" ); |
4919 | len += 3; |
4920 | } |
4921 | if (wp->w_buffer->b_p_ro) { |
4922 | STRCPY(p + len, _("[RO]" )); |
4923 | // len += (int)STRLEN(p + len); // dead assignment |
4924 | } |
4925 | |
4926 | this_ru_col = ru_col - (Columns - wp->w_width); |
4927 | if (this_ru_col < (wp->w_width + 1) / 2) { |
4928 | this_ru_col = (wp->w_width + 1) / 2; |
4929 | } |
4930 | if (this_ru_col <= 1) { |
4931 | p = (char_u *)"<" ; // No room for file name! |
4932 | len = 1; |
4933 | } else { |
4934 | int clen = 0, i; |
4935 | |
4936 | // Count total number of display cells. |
4937 | clen = (int)mb_string2cells(p); |
4938 | |
4939 | // Find first character that will fit. |
4940 | // Going from start to end is much faster for DBCS. |
4941 | for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; |
4942 | i += utfc_ptr2len(p + i)) { |
4943 | clen -= utf_ptr2cells(p + i); |
4944 | } |
4945 | len = clen; |
4946 | if (i > 0) { |
4947 | p = p + i - 1; |
4948 | *p = '<'; |
4949 | ++len; |
4950 | } |
4951 | } |
4952 | |
4953 | row = W_ENDROW(wp); |
4954 | grid_puts(&default_grid, p, row, wp->w_wincol, attr); |
4955 | grid_fill(&default_grid, row, row + 1, len + wp->w_wincol, |
4956 | this_ru_col + wp->w_wincol, fillchar, fillchar, attr); |
4957 | |
4958 | if (get_keymap_str(wp, (char_u *)"<%s>" , NameBuff, MAXPATHL) |
4959 | && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) |
4960 | grid_puts(&default_grid, NameBuff, row, |
4961 | (int)(this_ru_col - STRLEN(NameBuff) - 1), attr); |
4962 | |
4963 | win_redr_ruler(wp, TRUE); |
4964 | } |
4965 | |
4966 | /* |
4967 | * May need to draw the character below the vertical separator. |
4968 | */ |
4969 | if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { |
4970 | if (stl_connected(wp)) { |
4971 | fillchar = fillchar_status(&attr, wp); |
4972 | } else { |
4973 | fillchar = fillchar_vsep(wp, &attr); |
4974 | } |
4975 | grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); |
4976 | } |
4977 | busy = FALSE; |
4978 | } |
4979 | |
4980 | /* |
4981 | * Redraw the status line according to 'statusline' and take care of any |
4982 | * errors encountered. |
4983 | */ |
4984 | static void redraw_custom_statusline(win_T *wp) |
4985 | { |
4986 | static int entered = false; |
4987 | int saved_did_emsg = did_emsg; |
4988 | |
4989 | /* When called recursively return. This can happen when the statusline |
4990 | * contains an expression that triggers a redraw. */ |
4991 | if (entered) |
4992 | return; |
4993 | entered = TRUE; |
4994 | |
4995 | did_emsg = false; |
4996 | win_redr_custom(wp, false); |
4997 | if (did_emsg) { |
4998 | // When there is an error disable the statusline, otherwise the |
4999 | // display is messed up with errors and a redraw triggers the problem |
5000 | // again and again. |
5001 | set_string_option_direct((char_u *)"statusline" , -1, |
5002 | (char_u *)"" , OPT_FREE | (*wp->w_p_stl != NUL |
5003 | ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); |
5004 | } |
5005 | did_emsg |= saved_did_emsg; |
5006 | entered = false; |
5007 | } |
5008 | |
5009 | /* |
5010 | * Return TRUE if the status line of window "wp" is connected to the status |
5011 | * line of the window right of it. If not, then it's a vertical separator. |
5012 | * Only call if (wp->w_vsep_width != 0). |
5013 | */ |
5014 | int stl_connected(win_T *wp) |
5015 | { |
5016 | frame_T *fr; |
5017 | |
5018 | fr = wp->w_frame; |
5019 | while (fr->fr_parent != NULL) { |
5020 | if (fr->fr_parent->fr_layout == FR_COL) { |
5021 | if (fr->fr_next != NULL) |
5022 | break; |
5023 | } else { |
5024 | if (fr->fr_next != NULL) |
5025 | return TRUE; |
5026 | } |
5027 | fr = fr->fr_parent; |
5028 | } |
5029 | return FALSE; |
5030 | } |
5031 | |
5032 | |
5033 | /* |
5034 | * Get the value to show for the language mappings, active 'keymap'. |
5035 | */ |
5036 | int |
5037 | get_keymap_str ( |
5038 | win_T *wp, |
5039 | char_u *fmt, // format string containing one %s item |
5040 | char_u *buf, // buffer for the result |
5041 | int len // length of buffer |
5042 | ) |
5043 | { |
5044 | char_u *p; |
5045 | |
5046 | if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) |
5047 | return FALSE; |
5048 | |
5049 | { |
5050 | buf_T *old_curbuf = curbuf; |
5051 | win_T *old_curwin = curwin; |
5052 | char_u *s; |
5053 | |
5054 | curbuf = wp->w_buffer; |
5055 | curwin = wp; |
5056 | STRCPY(buf, "b:keymap_name" ); /* must be writable */ |
5057 | ++emsg_skip; |
5058 | s = p = eval_to_string(buf, NULL, FALSE); |
5059 | --emsg_skip; |
5060 | curbuf = old_curbuf; |
5061 | curwin = old_curwin; |
5062 | if (p == NULL || *p == NUL) { |
5063 | if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { |
5064 | p = wp->w_buffer->b_p_keymap; |
5065 | } else { |
5066 | p = (char_u *)"lang" ; |
5067 | } |
5068 | } |
5069 | if (vim_snprintf((char *)buf, len, (char *)fmt, p) > len - 1) { |
5070 | buf[0] = NUL; |
5071 | } |
5072 | xfree(s); |
5073 | } |
5074 | return buf[0] != NUL; |
5075 | } |
5076 | |
5077 | /* |
5078 | * Redraw the status line or ruler of window "wp". |
5079 | * When "wp" is NULL redraw the tab pages line from 'tabline'. |
5080 | */ |
5081 | static void |
5082 | win_redr_custom ( |
5083 | win_T *wp, |
5084 | int draw_ruler /* TRUE or FALSE */ |
5085 | ) |
5086 | { |
5087 | static int entered = FALSE; |
5088 | int attr; |
5089 | int curattr; |
5090 | int row; |
5091 | int col = 0; |
5092 | int maxwidth; |
5093 | int width; |
5094 | int n; |
5095 | int len; |
5096 | int fillchar; |
5097 | char_u buf[MAXPATHL]; |
5098 | char_u *stl; |
5099 | char_u *p; |
5100 | struct stl_hlrec hltab[STL_MAX_ITEM]; |
5101 | StlClickRecord tabtab[STL_MAX_ITEM]; |
5102 | int use_sandbox = false; |
5103 | win_T *ewp; |
5104 | int p_crb_save; |
5105 | |
5106 | /* There is a tiny chance that this gets called recursively: When |
5107 | * redrawing a status line triggers redrawing the ruler or tabline. |
5108 | * Avoid trouble by not allowing recursion. */ |
5109 | if (entered) |
5110 | return; |
5111 | entered = TRUE; |
5112 | |
5113 | /* setup environment for the task at hand */ |
5114 | if (wp == NULL) { |
5115 | /* Use 'tabline'. Always at the first line of the screen. */ |
5116 | stl = p_tal; |
5117 | row = 0; |
5118 | fillchar = ' '; |
5119 | attr = HL_ATTR(HLF_TPF); |
5120 | maxwidth = Columns; |
5121 | use_sandbox = was_set_insecurely((char_u *)"tabline" , 0); |
5122 | } else { |
5123 | row = W_ENDROW(wp); |
5124 | fillchar = fillchar_status(&attr, wp); |
5125 | maxwidth = wp->w_width; |
5126 | |
5127 | if (draw_ruler) { |
5128 | stl = p_ruf; |
5129 | /* advance past any leading group spec - implicit in ru_col */ |
5130 | if (*stl == '%') { |
5131 | if (*++stl == '-') |
5132 | stl++; |
5133 | if (atoi((char *)stl)) |
5134 | while (ascii_isdigit(*stl)) |
5135 | stl++; |
5136 | if (*stl++ != '(') |
5137 | stl = p_ruf; |
5138 | } |
5139 | col = ru_col - (Columns - wp->w_width); |
5140 | if (col < (wp->w_width + 1) / 2) { |
5141 | col = (wp->w_width + 1) / 2; |
5142 | } |
5143 | maxwidth = wp->w_width - col; |
5144 | if (!wp->w_status_height) { |
5145 | row = Rows - 1; |
5146 | maxwidth--; // writing in last column may cause scrolling |
5147 | fillchar = ' '; |
5148 | attr = 0; |
5149 | } |
5150 | |
5151 | use_sandbox = was_set_insecurely((char_u *)"rulerformat" , 0); |
5152 | } else { |
5153 | if (*wp->w_p_stl != NUL) |
5154 | stl = wp->w_p_stl; |
5155 | else |
5156 | stl = p_stl; |
5157 | use_sandbox = was_set_insecurely((char_u *)"statusline" , |
5158 | *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); |
5159 | } |
5160 | |
5161 | col += wp->w_wincol; |
5162 | } |
5163 | |
5164 | if (maxwidth <= 0) |
5165 | goto theend; |
5166 | |
5167 | /* Temporarily reset 'cursorbind', we don't want a side effect from moving |
5168 | * the cursor away and back. */ |
5169 | ewp = wp == NULL ? curwin : wp; |
5170 | p_crb_save = ewp->w_p_crb; |
5171 | ewp->w_p_crb = FALSE; |
5172 | |
5173 | /* Make a copy, because the statusline may include a function call that |
5174 | * might change the option value and free the memory. */ |
5175 | stl = vim_strsave(stl); |
5176 | width = build_stl_str_hl(ewp, buf, sizeof(buf), |
5177 | stl, use_sandbox, |
5178 | fillchar, maxwidth, hltab, tabtab); |
5179 | xfree(stl); |
5180 | ewp->w_p_crb = p_crb_save; |
5181 | |
5182 | // Make all characters printable. |
5183 | p = (char_u *)transstr((const char *)buf); |
5184 | len = STRLCPY(buf, p, sizeof(buf)); |
5185 | len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; |
5186 | xfree(p); |
5187 | |
5188 | /* fill up with "fillchar" */ |
5189 | while (width < maxwidth && len < (int)sizeof(buf) - 1) { |
5190 | len += utf_char2bytes(fillchar, buf + len); |
5191 | width++; |
5192 | } |
5193 | buf[len] = NUL; |
5194 | |
5195 | /* |
5196 | * Draw each snippet with the specified highlighting. |
5197 | */ |
5198 | grid_puts_line_start(&default_grid, row); |
5199 | |
5200 | curattr = attr; |
5201 | p = buf; |
5202 | for (n = 0; hltab[n].start != NULL; n++) { |
5203 | int textlen = (int)(hltab[n].start - p); |
5204 | grid_puts_len(&default_grid, p, textlen, row, col, curattr); |
5205 | col += vim_strnsize(p, textlen); |
5206 | p = hltab[n].start; |
5207 | |
5208 | if (hltab[n].userhl == 0) |
5209 | curattr = attr; |
5210 | else if (hltab[n].userhl < 0) |
5211 | curattr = syn_id2attr(-hltab[n].userhl); |
5212 | else if (wp != NULL && wp != curwin && wp->w_status_height != 0) |
5213 | curattr = highlight_stlnc[hltab[n].userhl - 1]; |
5214 | else |
5215 | curattr = highlight_user[hltab[n].userhl - 1]; |
5216 | } |
5217 | // Make sure to use an empty string instead of p, if p is beyond buf + len. |
5218 | grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col, |
5219 | curattr); |
5220 | |
5221 | grid_puts_line_flush(false); |
5222 | |
5223 | if (wp == NULL) { |
5224 | // Fill the tab_page_click_defs array for clicking in the tab pages line. |
5225 | col = 0; |
5226 | len = 0; |
5227 | p = buf; |
5228 | StlClickDefinition cur_click_def = { |
5229 | .type = kStlClickDisabled, |
5230 | }; |
5231 | for (n = 0; tabtab[n].start != NULL; n++) { |
5232 | len += vim_strnsize(p, (int)(tabtab[n].start - (char *) p)); |
5233 | while (col < len) { |
5234 | tab_page_click_defs[col++] = cur_click_def; |
5235 | } |
5236 | p = (char_u *) tabtab[n].start; |
5237 | cur_click_def = tabtab[n].def; |
5238 | } |
5239 | while (col < Columns) { |
5240 | tab_page_click_defs[col++] = cur_click_def; |
5241 | } |
5242 | } |
5243 | |
5244 | theend: |
5245 | entered = FALSE; |
5246 | } |
5247 | |
5248 | // Low-level functions to manipulate invidual character cells on the |
5249 | // screen grid. |
5250 | |
5251 | /// Put a ASCII character in a screen cell. |
5252 | static void schar_from_ascii(char_u *p, const char c) |
5253 | { |
5254 | p[0] = c; |
5255 | p[1] = 0; |
5256 | } |
5257 | |
5258 | /// Put a unicode character in a screen cell. |
5259 | static int schar_from_char(char_u *p, int c) |
5260 | { |
5261 | int len = utf_char2bytes(c, p); |
5262 | p[len] = NUL; |
5263 | return len; |
5264 | } |
5265 | |
5266 | /// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. |
5267 | static int schar_from_cc(char_u *p, int c, int u8cc[MAX_MCO]) |
5268 | { |
5269 | int len = utf_char2bytes(c, p); |
5270 | for (int i = 0; i < MAX_MCO; i++) { |
5271 | if (u8cc[i] == 0) { |
5272 | break; |
5273 | } |
5274 | len += utf_char2bytes(u8cc[i], p + len); |
5275 | } |
5276 | p[len] = 0; |
5277 | return len; |
5278 | } |
5279 | |
5280 | /// compare the contents of two screen cells. |
5281 | static int schar_cmp(char_u *sc1, char_u *sc2) |
5282 | { |
5283 | return STRNCMP(sc1, sc2, sizeof(schar_T)); |
5284 | } |
5285 | |
5286 | /// copy the contents of screen cell `sc2` into cell `sc1` |
5287 | static void schar_copy(char_u *sc1, char_u *sc2) |
5288 | { |
5289 | STRLCPY(sc1, sc2, sizeof(schar_T)); |
5290 | } |
5291 | |
5292 | static int line_off2cells(schar_T *line, size_t off, size_t max_off) |
5293 | { |
5294 | return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1; |
5295 | } |
5296 | |
5297 | /// Return number of display cells for char at grid->chars[off]. |
5298 | /// We make sure that the offset used is less than "max_off". |
5299 | static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off) |
5300 | { |
5301 | return line_off2cells(grid->chars, off, max_off); |
5302 | } |
5303 | |
5304 | /// Return true if the character at "row"/"col" on the screen is the left side |
5305 | /// of a double-width character. |
5306 | /// |
5307 | /// Caller must make sure "row" and "col" are not invalid! |
5308 | bool grid_lefthalve(ScreenGrid *grid, int row, int col) |
5309 | { |
5310 | screen_adjust_grid(&grid, &row, &col); |
5311 | |
5312 | return grid_off2cells(grid, grid->line_offset[row] + col, |
5313 | grid->line_offset[row] + grid->Columns) > 1; |
5314 | } |
5315 | |
5316 | /// Correct a position on the screen, if it's the right half of a double-wide |
5317 | /// char move it to the left half. Returns the corrected column. |
5318 | int grid_fix_col(ScreenGrid *grid, int col, int row) |
5319 | { |
5320 | int coloff = 0; |
5321 | screen_adjust_grid(&grid, &row, &coloff); |
5322 | |
5323 | col += coloff; |
5324 | if (grid->chars != NULL && col > 0 |
5325 | && grid->chars[grid->line_offset[row] + col][0] == 0) { |
5326 | return col - 1 - coloff; |
5327 | } |
5328 | return col - coloff; |
5329 | } |
5330 | |
5331 | /// output a single character directly to the grid |
5332 | void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) |
5333 | { |
5334 | char_u buf[MB_MAXBYTES + 1]; |
5335 | |
5336 | buf[utf_char2bytes(c, buf)] = NUL; |
5337 | grid_puts(grid, buf, row, col, attr); |
5338 | } |
5339 | |
5340 | /// get a single character directly from grid.chars into "bytes[]". |
5341 | /// Also return its attribute in *attrp; |
5342 | void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, |
5343 | int *attrp) |
5344 | { |
5345 | unsigned off; |
5346 | |
5347 | screen_adjust_grid(&grid, &row, &col); |
5348 | |
5349 | // safety check |
5350 | if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) { |
5351 | off = grid->line_offset[row] + col; |
5352 | *attrp = grid->attrs[off]; |
5353 | schar_copy(bytes, grid->chars[off]); |
5354 | } |
5355 | } |
5356 | |
5357 | |
5358 | /// put string '*text' on the window grid at position 'row' and 'col', with |
5359 | /// attributes 'attr', and update chars[] and attrs[]. |
5360 | /// Note: only outputs within one row, message is truncated at grid boundary! |
5361 | /// Note: if grid, row and/or col is invalid, nothing is done. |
5362 | void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) |
5363 | { |
5364 | grid_puts_len(grid, text, -1, row, col, attr); |
5365 | } |
5366 | |
5367 | static ScreenGrid *put_dirty_grid = NULL; |
5368 | static int put_dirty_row = -1; |
5369 | static int put_dirty_first = INT_MAX; |
5370 | static int put_dirty_last = 0; |
5371 | |
5372 | /// Start a group of grid_puts_len calls that builds a single grid line. |
5373 | /// |
5374 | /// Must be matched with a grid_puts_line_flush call before moving to |
5375 | /// another line. |
5376 | void grid_puts_line_start(ScreenGrid *grid, int row) |
5377 | { |
5378 | int col = 0; // unused |
5379 | screen_adjust_grid(&grid, &row, &col); |
5380 | assert(put_dirty_row == -1); |
5381 | put_dirty_row = row; |
5382 | put_dirty_grid = grid; |
5383 | } |
5384 | |
5385 | /// like grid_puts(), but output "text[len]". When "len" is -1 output up to |
5386 | /// a NUL. |
5387 | void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, |
5388 | int col, int attr) |
5389 | { |
5390 | unsigned off; |
5391 | char_u *ptr = text; |
5392 | int len = textlen; |
5393 | int c; |
5394 | unsigned max_off; |
5395 | int mbyte_blen = 1; |
5396 | int mbyte_cells = 1; |
5397 | int u8c = 0; |
5398 | int u8cc[MAX_MCO]; |
5399 | int clear_next_cell = FALSE; |
5400 | int prev_c = 0; /* previous Arabic character */ |
5401 | int pc, nc, nc1; |
5402 | int pcc[MAX_MCO]; |
5403 | int need_redraw; |
5404 | bool do_flush = false; |
5405 | |
5406 | screen_adjust_grid(&grid, &row, &col); |
5407 | |
5408 | // Safety check. The check for negative row and column is to fix issue |
5409 | // vim/vim#4102. TODO(neovim): find out why row/col could be negative. |
5410 | if (grid->chars == NULL |
5411 | || row >= grid->Rows || row < 0 |
5412 | || col >= grid->Columns || col < 0) { |
5413 | return; |
5414 | } |
5415 | |
5416 | if (put_dirty_row == -1) { |
5417 | grid_puts_line_start(grid, row); |
5418 | do_flush = true; |
5419 | } else { |
5420 | if (grid != put_dirty_grid || row != put_dirty_row) { |
5421 | abort(); |
5422 | } |
5423 | } |
5424 | off = grid->line_offset[row] + col; |
5425 | |
5426 | /* When drawing over the right halve of a double-wide char clear out the |
5427 | * left halve. Only needed in a terminal. */ |
5428 | if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { |
5429 | // redraw the previous cell, make it empty |
5430 | put_dirty_first = -1; |
5431 | put_dirty_last = MAX(put_dirty_last, 1); |
5432 | } |
5433 | |
5434 | max_off = grid->line_offset[row] + grid->Columns; |
5435 | while (col < grid->Columns |
5436 | && (len < 0 || (int)(ptr - text) < len) |
5437 | && *ptr != NUL) { |
5438 | c = *ptr; |
5439 | // check if this is the first byte of a multibyte |
5440 | if (len > 0) { |
5441 | mbyte_blen = utfc_ptr2len_len(ptr, (int)((text + len) - ptr)); |
5442 | } else { |
5443 | mbyte_blen = utfc_ptr2len(ptr); |
5444 | } |
5445 | if (len >= 0) { |
5446 | u8c = utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)); |
5447 | } else { |
5448 | u8c = utfc_ptr2char(ptr, u8cc); |
5449 | } |
5450 | mbyte_cells = utf_char2cells(u8c); |
5451 | if (p_arshape && !p_tbidi && arabic_char(u8c)) { |
5452 | // Do Arabic shaping. |
5453 | if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) { |
5454 | // Past end of string to be displayed. |
5455 | nc = NUL; |
5456 | nc1 = NUL; |
5457 | } else { |
5458 | nc = utfc_ptr2char_len(ptr + mbyte_blen, pcc, |
5459 | (int)((text + len) - ptr - mbyte_blen)); |
5460 | nc1 = pcc[0]; |
5461 | } |
5462 | pc = prev_c; |
5463 | prev_c = u8c; |
5464 | u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc); |
5465 | } else { |
5466 | prev_c = u8c; |
5467 | } |
5468 | if (col + mbyte_cells > grid->Columns) { |
5469 | // Only 1 cell left, but character requires 2 cells: |
5470 | // display a '>' in the last column to avoid wrapping. */ |
5471 | c = '>'; |
5472 | mbyte_cells = 1; |
5473 | } |
5474 | |
5475 | schar_T buf; |
5476 | schar_from_cc(buf, u8c, u8cc); |
5477 | |
5478 | |
5479 | need_redraw = schar_cmp(grid->chars[off], buf) |
5480 | || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) |
5481 | || grid->attrs[off] != attr |
5482 | || exmode_active; |
5483 | |
5484 | if (need_redraw) { |
5485 | // When at the end of the text and overwriting a two-cell |
5486 | // character with a one-cell character, need to clear the next |
5487 | // cell. Also when overwriting the left halve of a two-cell char |
5488 | // with the right halve of a two-cell char. Do this only once |
5489 | // (utf8_off2cells() may return 2 on the right halve). |
5490 | if (clear_next_cell) { |
5491 | clear_next_cell = false; |
5492 | } else if ((len < 0 ? ptr[mbyte_blen] == NUL |
5493 | : ptr + mbyte_blen >= text + len) |
5494 | && ((mbyte_cells == 1 |
5495 | && grid_off2cells(grid, off, max_off) > 1) |
5496 | || (mbyte_cells == 2 |
5497 | && grid_off2cells(grid, off, max_off) == 1 |
5498 | && grid_off2cells(grid, off + 1, max_off) > 1))) { |
5499 | clear_next_cell = true; |
5500 | } |
5501 | |
5502 | schar_copy(grid->chars[off], buf); |
5503 | grid->attrs[off] = attr; |
5504 | if (mbyte_cells == 2) { |
5505 | grid->chars[off + 1][0] = 0; |
5506 | grid->attrs[off + 1] = attr; |
5507 | } |
5508 | put_dirty_first = MIN(put_dirty_first, col); |
5509 | put_dirty_last = MAX(put_dirty_last, col+mbyte_cells); |
5510 | } |
5511 | |
5512 | off += mbyte_cells; |
5513 | col += mbyte_cells; |
5514 | ptr += mbyte_blen; |
5515 | if (clear_next_cell) { |
5516 | // This only happens at the end, display one space next. |
5517 | ptr = (char_u *)" " ; |
5518 | len = -1; |
5519 | } |
5520 | } |
5521 | |
5522 | if (do_flush) { |
5523 | grid_puts_line_flush(true); |
5524 | } |
5525 | } |
5526 | |
5527 | /// End a group of grid_puts_len calls and send the screen buffer to the UI |
5528 | /// layer. |
5529 | /// |
5530 | /// @param set_cursor Move the visible cursor to the end of the changed region. |
5531 | /// This is a workaround for not yet refactored code paths |
5532 | /// and shouldn't be used in new code. |
5533 | void grid_puts_line_flush(bool set_cursor) |
5534 | { |
5535 | assert(put_dirty_row != -1); |
5536 | if (put_dirty_first < put_dirty_last) { |
5537 | if (set_cursor) { |
5538 | ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, |
5539 | MIN(put_dirty_last, put_dirty_grid->Columns-1)); |
5540 | } |
5541 | if (!put_dirty_grid->throttled) { |
5542 | ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, |
5543 | put_dirty_last, 0, false); |
5544 | } else if (put_dirty_grid->dirty_col) { |
5545 | if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { |
5546 | put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; |
5547 | } |
5548 | } |
5549 | put_dirty_first = INT_MAX; |
5550 | put_dirty_last = 0; |
5551 | } |
5552 | put_dirty_row = -1; |
5553 | put_dirty_grid = NULL; |
5554 | } |
5555 | |
5556 | /* |
5557 | * Prepare for 'hlsearch' highlighting. |
5558 | */ |
5559 | static void start_search_hl(void) |
5560 | { |
5561 | if (p_hls && !no_hlsearch) { |
5562 | last_pat_prog(&search_hl.rm); |
5563 | // Set the time limit to 'redrawtime'. |
5564 | search_hl.tm = profile_setlimit(p_rdt); |
5565 | } |
5566 | } |
5567 | |
5568 | /* |
5569 | * Clean up for 'hlsearch' highlighting. |
5570 | */ |
5571 | static void end_search_hl(void) |
5572 | { |
5573 | if (search_hl.rm.regprog != NULL) { |
5574 | vim_regfree(search_hl.rm.regprog); |
5575 | search_hl.rm.regprog = NULL; |
5576 | } |
5577 | } |
5578 | |
5579 | |
5580 | /* |
5581 | * Init for calling prepare_search_hl(). |
5582 | */ |
5583 | static void init_search_hl(win_T *wp) |
5584 | { |
5585 | matchitem_T *cur; |
5586 | |
5587 | /* Setup for match and 'hlsearch' highlighting. Disable any previous |
5588 | * match */ |
5589 | cur = wp->w_match_head; |
5590 | while (cur != NULL) { |
5591 | cur->hl.rm = cur->match; |
5592 | if (cur->hlg_id == 0) |
5593 | cur->hl.attr = 0; |
5594 | else |
5595 | cur->hl.attr = syn_id2attr(cur->hlg_id); |
5596 | cur->hl.buf = wp->w_buffer; |
5597 | cur->hl.lnum = 0; |
5598 | cur->hl.first_lnum = 0; |
5599 | /* Set the time limit to 'redrawtime'. */ |
5600 | cur->hl.tm = profile_setlimit(p_rdt); |
5601 | cur = cur->next; |
5602 | } |
5603 | search_hl.buf = wp->w_buffer; |
5604 | search_hl.lnum = 0; |
5605 | search_hl.first_lnum = 0; |
5606 | search_hl.attr = win_hl_attr(wp, HLF_L); |
5607 | |
5608 | // time limit is set at the toplevel, for all windows |
5609 | } |
5610 | |
5611 | /* |
5612 | * Advance to the match in window "wp" line "lnum" or past it. |
5613 | */ |
5614 | static void prepare_search_hl(win_T *wp, linenr_T lnum) |
5615 | { |
5616 | matchitem_T *cur; /* points to the match list */ |
5617 | match_T *shl; /* points to search_hl or a match */ |
5618 | int shl_flag; /* flag to indicate whether search_hl |
5619 | has been processed or not */ |
5620 | int n; |
5621 | |
5622 | /* |
5623 | * When using a multi-line pattern, start searching at the top |
5624 | * of the window or just after a closed fold. |
5625 | * Do this both for search_hl and the match list. |
5626 | */ |
5627 | cur = wp->w_match_head; |
5628 | shl_flag = false; |
5629 | while (cur != NULL || shl_flag == false) { |
5630 | if (shl_flag == false) { |
5631 | shl = &search_hl; |
5632 | shl_flag = true; |
5633 | } else { |
5634 | shl = &cur->hl; // -V595 |
5635 | } |
5636 | if (shl->rm.regprog != NULL |
5637 | && shl->lnum == 0 |
5638 | && re_multiline(shl->rm.regprog)) { |
5639 | if (shl->first_lnum == 0) { |
5640 | for (shl->first_lnum = lnum; |
5641 | shl->first_lnum > wp->w_topline; |
5642 | shl->first_lnum--) { |
5643 | if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { |
5644 | break; |
5645 | } |
5646 | } |
5647 | } |
5648 | if (cur != NULL) { |
5649 | cur->pos.cur = 0; |
5650 | } |
5651 | bool pos_inprogress = true; // mark that a position match search is |
5652 | // in progress |
5653 | n = 0; |
5654 | while (shl->first_lnum < lnum && (shl->rm.regprog != NULL |
5655 | || (cur != NULL && pos_inprogress))) { |
5656 | next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n, |
5657 | shl == &search_hl ? NULL : cur); |
5658 | pos_inprogress = !(cur == NULL || cur->pos.cur == 0); |
5659 | if (shl->lnum != 0) { |
5660 | shl->first_lnum = shl->lnum |
5661 | + shl->rm.endpos[0].lnum |
5662 | - shl->rm.startpos[0].lnum; |
5663 | n = shl->rm.endpos[0].col; |
5664 | } else { |
5665 | ++shl->first_lnum; |
5666 | n = 0; |
5667 | } |
5668 | } |
5669 | } |
5670 | if (shl != &search_hl && cur != NULL) |
5671 | cur = cur->next; |
5672 | } |
5673 | } |
5674 | |
5675 | /* |
5676 | * Search for a next 'hlsearch' or match. |
5677 | * Uses shl->buf. |
5678 | * Sets shl->lnum and shl->rm contents. |
5679 | * Note: Assumes a previous match is always before "lnum", unless |
5680 | * shl->lnum is zero. |
5681 | * Careful: Any pointers for buffer lines will become invalid. |
5682 | */ |
5683 | static void |
5684 | next_search_hl ( |
5685 | win_T *win, |
5686 | match_T *shl, /* points to search_hl or a match */ |
5687 | linenr_T lnum, |
5688 | colnr_T mincol, /* minimal column for a match */ |
5689 | matchitem_T *cur /* to retrieve match positions if any */ |
5690 | ) |
5691 | { |
5692 | linenr_T l; |
5693 | colnr_T matchcol; |
5694 | long nmatched = 0; |
5695 | int save_called_emsg = called_emsg; |
5696 | |
5697 | if (shl->lnum != 0) { |
5698 | /* Check for three situations: |
5699 | * 1. If the "lnum" is below a previous match, start a new search. |
5700 | * 2. If the previous match includes "mincol", use it. |
5701 | * 3. Continue after the previous match. |
5702 | */ |
5703 | l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; |
5704 | if (lnum > l) |
5705 | shl->lnum = 0; |
5706 | else if (lnum < l || shl->rm.endpos[0].col > mincol) |
5707 | return; |
5708 | } |
5709 | |
5710 | /* |
5711 | * Repeat searching for a match until one is found that includes "mincol" |
5712 | * or none is found in this line. |
5713 | */ |
5714 | called_emsg = FALSE; |
5715 | for (;; ) { |
5716 | /* Stop searching after passing the time limit. */ |
5717 | if (profile_passed_limit(shl->tm)) { |
5718 | shl->lnum = 0; /* no match found in time */ |
5719 | break; |
5720 | } |
5721 | /* Three situations: |
5722 | * 1. No useful previous match: search from start of line. |
5723 | * 2. Not Vi compatible or empty match: continue at next character. |
5724 | * Break the loop if this is beyond the end of the line. |
5725 | * 3. Vi compatible searching: continue at end of previous match. |
5726 | */ |
5727 | if (shl->lnum == 0) |
5728 | matchcol = 0; |
5729 | else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL |
5730 | || (shl->rm.endpos[0].lnum == 0 |
5731 | && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { |
5732 | char_u *ml; |
5733 | |
5734 | matchcol = shl->rm.startpos[0].col; |
5735 | ml = ml_get_buf(shl->buf, lnum, FALSE) + matchcol; |
5736 | if (*ml == NUL) { |
5737 | ++matchcol; |
5738 | shl->lnum = 0; |
5739 | break; |
5740 | } |
5741 | matchcol += mb_ptr2len(ml); |
5742 | } else { |
5743 | matchcol = shl->rm.endpos[0].col; |
5744 | } |
5745 | |
5746 | shl->lnum = lnum; |
5747 | if (shl->rm.regprog != NULL) { |
5748 | /* Remember whether shl->rm is using a copy of the regprog in |
5749 | * cur->match. */ |
5750 | bool regprog_is_copy = (shl != &search_hl |
5751 | && cur != NULL |
5752 | && shl == &cur->hl |
5753 | && cur->match.regprog == cur->hl.rm.regprog); |
5754 | int timed_out = false; |
5755 | |
5756 | nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, |
5757 | &(shl->tm), &timed_out); |
5758 | // Copy the regprog, in case it got freed and recompiled. |
5759 | if (regprog_is_copy) { |
5760 | cur->match.regprog = cur->hl.rm.regprog; |
5761 | } |
5762 | if (called_emsg || got_int || timed_out) { |
5763 | // Error while handling regexp: stop using this regexp. |
5764 | if (shl == &search_hl) { |
5765 | // don't free regprog in the match list, it's a copy |
5766 | vim_regfree(shl->rm.regprog); |
5767 | set_no_hlsearch(true); |
5768 | } |
5769 | shl->rm.regprog = NULL; |
5770 | shl->lnum = 0; |
5771 | got_int = FALSE; // avoid the "Type :quit to exit Vim" message |
5772 | break; |
5773 | } |
5774 | } else if (cur != NULL) { |
5775 | nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); |
5776 | } |
5777 | if (nmatched == 0) { |
5778 | shl->lnum = 0; /* no match found */ |
5779 | break; |
5780 | } |
5781 | if (shl->rm.startpos[0].lnum > 0 |
5782 | || shl->rm.startpos[0].col >= mincol |
5783 | || nmatched > 1 |
5784 | || shl->rm.endpos[0].col > mincol) { |
5785 | shl->lnum += shl->rm.startpos[0].lnum; |
5786 | break; /* useful match found */ |
5787 | } |
5788 | |
5789 | // Restore called_emsg for assert_fails(). |
5790 | called_emsg = save_called_emsg; |
5791 | } |
5792 | } |
5793 | |
5794 | /// If there is a match fill "shl" and return one. |
5795 | /// Return zero otherwise. |
5796 | static int |
5797 | next_search_hl_pos( |
5798 | match_T *shl, // points to a match |
5799 | linenr_T lnum, |
5800 | posmatch_T *posmatch, // match positions |
5801 | colnr_T mincol // minimal column for a match |
5802 | ) |
5803 | { |
5804 | int i; |
5805 | int found = -1; |
5806 | |
5807 | shl->lnum = 0; |
5808 | for (i = posmatch->cur; i < MAXPOSMATCH; i++) { |
5809 | llpos_T *pos = &posmatch->pos[i]; |
5810 | |
5811 | if (pos->lnum == 0) { |
5812 | break; |
5813 | } |
5814 | if (pos->len == 0 && pos->col < mincol) { |
5815 | continue; |
5816 | } |
5817 | if (pos->lnum == lnum) { |
5818 | if (found >= 0) { |
5819 | // if this match comes before the one at "found" then swap |
5820 | // them |
5821 | if (pos->col < posmatch->pos[found].col) { |
5822 | llpos_T tmp = *pos; |
5823 | |
5824 | *pos = posmatch->pos[found]; |
5825 | posmatch->pos[found] = tmp; |
5826 | } |
5827 | } else { |
5828 | found = i; |
5829 | } |
5830 | } |
5831 | } |
5832 | posmatch->cur = 0; |
5833 | if (found >= 0) { |
5834 | colnr_T start = posmatch->pos[found].col == 0 |
5835 | ? 0: posmatch->pos[found].col - 1; |
5836 | colnr_T end = posmatch->pos[found].col == 0 |
5837 | ? MAXCOL : start + posmatch->pos[found].len; |
5838 | |
5839 | shl->lnum = lnum; |
5840 | shl->rm.startpos[0].lnum = 0; |
5841 | shl->rm.startpos[0].col = start; |
5842 | shl->rm.endpos[0].lnum = 0; |
5843 | shl->rm.endpos[0].col = end; |
5844 | shl->is_addpos = true; |
5845 | posmatch->cur = found + 1; |
5846 | return 1; |
5847 | } |
5848 | return 0; |
5849 | } |
5850 | |
5851 | |
5852 | /// Fill the grid from 'start_row' to 'end_row', from 'start_col' to 'end_col' |
5853 | /// with character 'c1' in first column followed by 'c2' in the other columns. |
5854 | /// Use attributes 'attr'. |
5855 | void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, |
5856 | int end_col, int c1, int c2, int attr) |
5857 | { |
5858 | schar_T sc; |
5859 | |
5860 | int row_off = 0, col_off = 0; |
5861 | screen_adjust_grid(&grid, &row_off, &col_off); |
5862 | start_row += row_off; |
5863 | end_row += row_off; |
5864 | start_col += col_off; |
5865 | end_col += col_off; |
5866 | |
5867 | // safety check |
5868 | if (end_row > grid->Rows) { |
5869 | end_row = grid->Rows; |
5870 | } |
5871 | if (end_col > grid->Columns) { |
5872 | end_col = grid->Columns; |
5873 | } |
5874 | |
5875 | // nothing to do |
5876 | if (start_row >= end_row || start_col >= end_col) { |
5877 | return; |
5878 | } |
5879 | |
5880 | for (int row = start_row; row < end_row; row++) { |
5881 | // When drawing over the right halve of a double-wide char clear |
5882 | // out the left halve. When drawing over the left halve of a |
5883 | // double wide-char clear out the right halve. Only needed in a |
5884 | // terminal. |
5885 | if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { |
5886 | grid_puts_len(grid, (char_u *)" " , 1, row, start_col - 1, 0); |
5887 | } |
5888 | if (end_col < grid->Columns |
5889 | && grid_fix_col(grid, end_col, row) != end_col) { |
5890 | grid_puts_len(grid, (char_u *)" " , 1, row, end_col, 0); |
5891 | } |
5892 | |
5893 | // if grid was resized (in ext_multigrid mode), the UI has no redraw updates |
5894 | // for the newly resized grid. It is better mark everything as dirty and |
5895 | // send all the updates. |
5896 | int dirty_first = INT_MAX; |
5897 | int dirty_last = 0; |
5898 | |
5899 | int col = start_col; |
5900 | schar_from_char(sc, c1); |
5901 | int lineoff = grid->line_offset[row]; |
5902 | for (col = start_col; col < end_col; col++) { |
5903 | int off = lineoff + col; |
5904 | if (schar_cmp(grid->chars[off], sc) |
5905 | || grid->attrs[off] != attr) { |
5906 | schar_copy(grid->chars[off], sc); |
5907 | grid->attrs[off] = attr; |
5908 | if (dirty_first == INT_MAX) { |
5909 | dirty_first = col; |
5910 | } |
5911 | dirty_last = col+1; |
5912 | } |
5913 | if (col == start_col) { |
5914 | schar_from_char(sc, c2); |
5915 | } |
5916 | } |
5917 | if (dirty_last > dirty_first) { |
5918 | // TODO(bfredl): support a cleared suffix even with a batched line? |
5919 | if (put_dirty_row == row) { |
5920 | put_dirty_first = MIN(put_dirty_first, dirty_first); |
5921 | put_dirty_last = MAX(put_dirty_last, dirty_last); |
5922 | } else if (grid->throttled) { |
5923 | // Note: assumes msg_grid is the only throttled grid |
5924 | assert(grid == &msg_grid); |
5925 | int dirty = 0; |
5926 | if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') { |
5927 | dirty = dirty_last; |
5928 | } else if (c1 != ' ') { |
5929 | dirty = dirty_first + 1; |
5930 | } |
5931 | if (grid->dirty_col && dirty > grid->dirty_col[row]) { |
5932 | grid->dirty_col[row] = dirty; |
5933 | } |
5934 | } else { |
5935 | int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); |
5936 | ui_line(grid, row, dirty_first, last, dirty_last, attr, false); |
5937 | } |
5938 | } |
5939 | |
5940 | if (end_col == grid->Columns) { |
5941 | grid->line_wraps[row] = false; |
5942 | } |
5943 | } |
5944 | } |
5945 | |
5946 | /* |
5947 | * Check if there should be a delay. Used before clearing or redrawing the |
5948 | * screen or the command line. |
5949 | */ |
5950 | void check_for_delay(int check_msg_scroll) |
5951 | { |
5952 | if ((emsg_on_display || (check_msg_scroll && msg_scroll)) |
5953 | && !did_wait_return |
5954 | && emsg_silent == 0) { |
5955 | ui_flush(); |
5956 | os_delay(1000L, true); |
5957 | emsg_on_display = FALSE; |
5958 | if (check_msg_scroll) |
5959 | msg_scroll = FALSE; |
5960 | } |
5961 | } |
5962 | |
5963 | /// (Re)allocates a window grid if size changed while in ext_multigrid mode. |
5964 | /// Updates size, offsets and handle for the grid regardless. |
5965 | /// |
5966 | /// If "doclear" is true, don't try to copy from the old grid rather clear the |
5967 | /// resized grid. |
5968 | void win_grid_alloc(win_T *wp) |
5969 | { |
5970 | ScreenGrid *grid = &wp->w_grid; |
5971 | |
5972 | int rows = wp->w_height_inner; |
5973 | int cols = wp->w_width_inner; |
5974 | |
5975 | bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; |
5976 | bool has_allocation = (grid->chars != NULL); |
5977 | |
5978 | if (grid->Rows != rows) { |
5979 | wp->w_lines_valid = 0; |
5980 | xfree(wp->w_lines); |
5981 | wp->w_lines = xcalloc(rows+1, sizeof(wline_T)); |
5982 | } |
5983 | |
5984 | int was_resized = false; |
5985 | if ((has_allocation != want_allocation) |
5986 | || grid->Rows != rows |
5987 | || grid->Columns != cols) { |
5988 | if (want_allocation) { |
5989 | grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid); |
5990 | grid->valid = true; |
5991 | } else { |
5992 | // Single grid mode, all rendering will be redirected to default_grid. |
5993 | // Only keep track of the size and offset of the window. |
5994 | grid_free(grid); |
5995 | grid->Rows = rows; |
5996 | grid->Columns = cols; |
5997 | grid->valid = false; |
5998 | } |
5999 | was_resized = true; |
6000 | } else if (want_allocation && has_allocation && !wp->w_grid.valid) { |
6001 | grid_invalidate(grid); |
6002 | grid->valid = true; |
6003 | } |
6004 | |
6005 | grid->row_offset = wp->w_winrow; |
6006 | grid->col_offset = wp->w_wincol; |
6007 | |
6008 | // send grid resize event if: |
6009 | // - a grid was just resized |
6010 | // - screen_resize was called and all grid sizes must be sent |
6011 | // - the UI wants multigrid event (necessary) |
6012 | if ((send_grid_resize || was_resized) && want_allocation) { |
6013 | ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); |
6014 | } |
6015 | } |
6016 | |
6017 | /// assign a handle to the grid. The grid need not be allocated. |
6018 | void grid_assign_handle(ScreenGrid *grid) |
6019 | { |
6020 | static int last_grid_handle = DEFAULT_GRID_HANDLE; |
6021 | |
6022 | // only assign a grid handle if not already |
6023 | if (grid->handle == 0) { |
6024 | grid->handle = ++last_grid_handle; |
6025 | } |
6026 | } |
6027 | |
6028 | /// Resize the screen to Rows and Columns. |
6029 | /// |
6030 | /// Allocate default_grid.chars[] and other grid arrays. |
6031 | /// |
6032 | /// There may be some time between setting Rows and Columns and (re)allocating |
6033 | /// default_grid arrays. This happens when starting up and when |
6034 | /// (manually) changing the shell size. Always use default_grid.Rows and |
6035 | /// default_grid.Columns to access items in default_grid.chars[]. Use Rows |
6036 | /// and Columns for positioning text etc. where the final size of the shell is |
6037 | /// needed. |
6038 | void screenalloc(void) |
6039 | { |
6040 | // It's possible that we produce an out-of-memory message below, which |
6041 | // will cause this function to be called again. To break the loop, just |
6042 | // return here. |
6043 | if (resizing) { |
6044 | return; |
6045 | } |
6046 | resizing = true; |
6047 | |
6048 | int retry_count = 0; |
6049 | |
6050 | retry: |
6051 | // Allocation of the screen buffers is done only when the size changes and |
6052 | // when Rows and Columns have been set and we have started doing full |
6053 | // screen stuff. |
6054 | if ((default_grid.chars != NULL |
6055 | && Rows == default_grid.Rows |
6056 | && Columns == default_grid.Columns |
6057 | ) |
6058 | || Rows == 0 |
6059 | || Columns == 0 |
6060 | || (!full_screen && default_grid.chars == NULL)) { |
6061 | resizing = false; |
6062 | return; |
6063 | } |
6064 | |
6065 | /* |
6066 | * Note that the window sizes are updated before reallocating the arrays, |
6067 | * thus we must not redraw here! |
6068 | */ |
6069 | ++RedrawingDisabled; |
6070 | |
6071 | // win_new_shellsize will recompute floats position, but tell the |
6072 | // compositor to not redraw them yet |
6073 | ui_comp_set_screen_valid(false); |
6074 | if (msg_grid.chars) { |
6075 | msg_grid_invalid = true; |
6076 | } |
6077 | |
6078 | win_new_shellsize(); /* fit the windows in the new sized shell */ |
6079 | |
6080 | comp_col(); /* recompute columns for shown command and ruler */ |
6081 | |
6082 | // We're changing the size of the screen. |
6083 | // - Allocate new arrays for default_grid |
6084 | // - Move lines from the old arrays into the new arrays, clear extra |
6085 | // lines (unless the screen is going to be cleared). |
6086 | // - Free the old arrays. |
6087 | // |
6088 | // If anything fails, make grid arrays NULL, so we don't do anything! |
6089 | // Continuing with the old arrays may result in a crash, because the |
6090 | // size is wrong. |
6091 | |
6092 | grid_alloc(&default_grid, Rows, Columns, true, true); |
6093 | StlClickDefinition *new_tab_page_click_defs = xcalloc( |
6094 | (size_t)Columns, sizeof(*new_tab_page_click_defs)); |
6095 | |
6096 | clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); |
6097 | xfree(tab_page_click_defs); |
6098 | |
6099 | tab_page_click_defs = new_tab_page_click_defs; |
6100 | tab_page_click_defs_size = Columns; |
6101 | |
6102 | default_grid.row_offset = 0; |
6103 | default_grid.col_offset = 0; |
6104 | default_grid.handle = DEFAULT_GRID_HANDLE; |
6105 | |
6106 | must_redraw = CLEAR; // need to clear the screen later |
6107 | |
6108 | RedrawingDisabled--; |
6109 | |
6110 | /* |
6111 | * Do not apply autocommands more than 3 times to avoid an endless loop |
6112 | * in case applying autocommands always changes Rows or Columns. |
6113 | */ |
6114 | if (starting == 0 && ++retry_count <= 3) { |
6115 | apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, FALSE, curbuf); |
6116 | /* In rare cases, autocommands may have altered Rows or Columns, |
6117 | * jump back to check if we need to allocate the screen again. */ |
6118 | goto retry; |
6119 | } |
6120 | |
6121 | resizing = false; |
6122 | } |
6123 | |
6124 | void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) |
6125 | { |
6126 | int new_row; |
6127 | ScreenGrid new = *grid; |
6128 | assert(rows >= 0 && columns >= 0); |
6129 | size_t ncells = (size_t)rows * columns; |
6130 | new.chars = xmalloc(ncells * sizeof(schar_T)); |
6131 | new.attrs = xmalloc(ncells * sizeof(sattr_T)); |
6132 | new.line_offset = xmalloc((size_t)(rows * sizeof(unsigned))); |
6133 | new.line_wraps = xmalloc((size_t)(rows * sizeof(char_u))); |
6134 | |
6135 | new.Rows = rows; |
6136 | new.Columns = columns; |
6137 | |
6138 | for (new_row = 0; new_row < new.Rows; new_row++) { |
6139 | new.line_offset[new_row] = new_row * new.Columns; |
6140 | new.line_wraps[new_row] = false; |
6141 | |
6142 | grid_clear_line(&new, new.line_offset[new_row], columns, valid); |
6143 | |
6144 | if (copy) { |
6145 | // If the screen is not going to be cleared, copy as much as |
6146 | // possible from the old screen to the new one and clear the rest |
6147 | // (used when resizing the window at the "--more--" prompt or when |
6148 | // executing an external command, for the GUI). |
6149 | if (new_row < grid->Rows && grid->chars != NULL) { |
6150 | int len = MIN(grid->Columns, new.Columns); |
6151 | memmove(new.chars + new.line_offset[new_row], |
6152 | grid->chars + grid->line_offset[new_row], |
6153 | (size_t)len * sizeof(schar_T)); |
6154 | memmove(new.attrs + new.line_offset[new_row], |
6155 | grid->attrs + grid->line_offset[new_row], |
6156 | (size_t)len * sizeof(sattr_T)); |
6157 | } |
6158 | } |
6159 | } |
6160 | grid_free(grid); |
6161 | *grid = new; |
6162 | |
6163 | // Share a single scratch buffer for all grids, by |
6164 | // ensuring it is as wide as the widest grid. |
6165 | if (linebuf_size < (size_t)columns) { |
6166 | xfree(linebuf_char); |
6167 | xfree(linebuf_attr); |
6168 | linebuf_char = xmalloc(columns * sizeof(schar_T)); |
6169 | linebuf_attr = xmalloc(columns * sizeof(sattr_T)); |
6170 | linebuf_size = columns; |
6171 | } |
6172 | } |
6173 | |
6174 | void grid_free(ScreenGrid *grid) |
6175 | { |
6176 | xfree(grid->chars); |
6177 | xfree(grid->attrs); |
6178 | xfree(grid->line_offset); |
6179 | xfree(grid->line_wraps); |
6180 | |
6181 | grid->chars = NULL; |
6182 | grid->attrs = NULL; |
6183 | grid->line_offset = NULL; |
6184 | grid->line_wraps = NULL; |
6185 | } |
6186 | |
6187 | /// Doesn't allow reinit, so must only be called by free_all_mem! |
6188 | void screen_free_all_mem(void) |
6189 | { |
6190 | grid_free(&default_grid); |
6191 | xfree(linebuf_char); |
6192 | xfree(linebuf_attr); |
6193 | } |
6194 | |
6195 | /// Clear tab_page_click_defs table |
6196 | /// |
6197 | /// @param[out] tpcd Table to clear. |
6198 | /// @param[in] tpcd_size Size of the table. |
6199 | void clear_tab_page_click_defs(StlClickDefinition *const tpcd, |
6200 | const long tpcd_size) |
6201 | { |
6202 | if (tpcd != NULL) { |
6203 | for (long i = 0; i < tpcd_size; i++) { |
6204 | if (i == 0 || tpcd[i].func != tpcd[i - 1].func) { |
6205 | xfree(tpcd[i].func); |
6206 | } |
6207 | } |
6208 | memset(tpcd, 0, (size_t) tpcd_size * sizeof(tpcd[0])); |
6209 | } |
6210 | } |
6211 | |
6212 | void screenclear(void) |
6213 | { |
6214 | check_for_delay(false); |
6215 | screenalloc(); // allocate screen buffers if size changed |
6216 | |
6217 | int i; |
6218 | |
6219 | if (starting == NO_SCREEN || default_grid.chars == NULL) { |
6220 | return; |
6221 | } |
6222 | |
6223 | // blank out the default grid |
6224 | for (i = 0; i < default_grid.Rows; i++) { |
6225 | grid_clear_line(&default_grid, default_grid.line_offset[i], |
6226 | (int)default_grid.Columns, true); |
6227 | default_grid.line_wraps[i] = false; |
6228 | } |
6229 | |
6230 | ui_call_grid_clear(1); // clear the display |
6231 | ui_comp_set_screen_valid(true); |
6232 | |
6233 | clear_cmdline = false; |
6234 | mode_displayed = false; |
6235 | |
6236 | redraw_all_later(NOT_VALID); |
6237 | redraw_cmdline = true; |
6238 | redraw_tabline = true; |
6239 | redraw_popupmenu = true; |
6240 | pum_invalidate(); |
6241 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
6242 | if (wp->w_floating) { |
6243 | wp->w_redr_type = CLEAR; |
6244 | } |
6245 | } |
6246 | if (must_redraw == CLEAR) { |
6247 | must_redraw = NOT_VALID; // no need to clear again |
6248 | } |
6249 | compute_cmdrow(); |
6250 | msg_row = cmdline_row; // put cursor on last line for messages |
6251 | msg_col = 0; |
6252 | msg_scrolled = 0; // can't scroll back |
6253 | msg_didany = false; |
6254 | msg_didout = false; |
6255 | if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { |
6256 | grid_invalidate(&msg_grid); |
6257 | msg_grid_validate(); |
6258 | msg_grid_invalid = false; |
6259 | clear_cmdline = true; |
6260 | } |
6261 | } |
6262 | |
6263 | /// clear a line in the grid starting at "off" until "width" characters |
6264 | /// are cleared. |
6265 | void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) |
6266 | { |
6267 | for (int col = 0; col < width; col++) { |
6268 | schar_from_ascii(grid->chars[off + col], ' '); |
6269 | } |
6270 | int fill = valid ? 0 : -1; |
6271 | (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); |
6272 | } |
6273 | |
6274 | void grid_invalidate(ScreenGrid *grid) |
6275 | { |
6276 | (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); |
6277 | } |
6278 | |
6279 | bool grid_invalid_row(ScreenGrid *grid, int row) |
6280 | { |
6281 | return grid->attrs[grid->line_offset[row]] < 0; |
6282 | } |
6283 | |
6284 | |
6285 | |
6286 | /// Copy part of a grid line for vertically split window. |
6287 | static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) |
6288 | { |
6289 | unsigned off_to = grid->line_offset[to] + col; |
6290 | unsigned off_from = grid->line_offset[from] + col; |
6291 | |
6292 | memmove(grid->chars + off_to, grid->chars + off_from, |
6293 | width * sizeof(schar_T)); |
6294 | memmove(grid->attrs + off_to, grid->attrs + off_from, |
6295 | width * sizeof(sattr_T)); |
6296 | } |
6297 | |
6298 | /* |
6299 | * Set cursor to its position in the current window. |
6300 | */ |
6301 | void setcursor(void) |
6302 | { |
6303 | if (redrawing()) { |
6304 | validate_cursor(); |
6305 | |
6306 | ScreenGrid *grid = &curwin->w_grid; |
6307 | int row = curwin->w_wrow; |
6308 | int col = curwin->w_wcol; |
6309 | if (curwin->w_p_rl) { |
6310 | // With 'rightleft' set and the cursor on a double-wide character, |
6311 | // position it on the leftmost column. |
6312 | col = curwin->w_width_inner - curwin->w_wcol |
6313 | - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2 |
6314 | && vim_isprintc(gchar_cursor())) ? 2 : 1); |
6315 | } |
6316 | |
6317 | screen_adjust_grid(&grid, &row, &col); |
6318 | ui_grid_cursor_goto(grid->handle, row, col); |
6319 | } |
6320 | } |
6321 | |
6322 | /// Scroll 'line_count' lines at 'row' in window 'wp'. |
6323 | /// |
6324 | /// Positive `line_count' means scrolling down, so that more space is available |
6325 | /// at 'row'. Negative `line_count` implies deleting lines at `row`. |
6326 | void win_scroll_lines(win_T *wp, int row, int line_count) |
6327 | { |
6328 | if (!redrawing() || line_count == 0) { |
6329 | return; |
6330 | } |
6331 | |
6332 | // No lines are being moved, just draw over the entire area |
6333 | if (row + abs(line_count) >= wp->w_grid.Rows) { |
6334 | return; |
6335 | } |
6336 | |
6337 | if (line_count < 0) { |
6338 | grid_del_lines(&wp->w_grid, row, -line_count, |
6339 | wp->w_grid.Rows, 0, wp->w_grid.Columns); |
6340 | } else { |
6341 | grid_ins_lines(&wp->w_grid, row, line_count, |
6342 | wp->w_grid.Rows, 0, wp->w_grid.Columns); |
6343 | } |
6344 | } |
6345 | |
6346 | /* |
6347 | * The rest of the routines in this file perform screen manipulations. The |
6348 | * given operation is performed physically on the screen. The corresponding |
6349 | * change is also made to the internal screen image. In this way, the editor |
6350 | * anticipates the effect of editing changes on the appearance of the screen. |
6351 | * That way, when we call screenupdate a complete redraw isn't usually |
6352 | * necessary. Another advantage is that we can keep adding code to anticipate |
6353 | * screen changes, and in the meantime, everything still works. |
6354 | */ |
6355 | |
6356 | |
6357 | /// insert lines on the screen and move the existing lines down |
6358 | /// 'line_count' is the number of lines to be inserted. |
6359 | /// 'end' is the line after the scrolled part. Normally it is Rows. |
6360 | /// 'col' is the column from with we start inserting. |
6361 | // |
6362 | /// 'row', 'col' and 'end' are relative to the start of the region. |
6363 | void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, |
6364 | int width) |
6365 | { |
6366 | int i; |
6367 | int j; |
6368 | unsigned temp; |
6369 | |
6370 | int row_off = 0; |
6371 | screen_adjust_grid(&grid, &row_off, &col); |
6372 | row += row_off; |
6373 | end += row_off; |
6374 | |
6375 | if (line_count <= 0) { |
6376 | return; |
6377 | } |
6378 | |
6379 | // Shift line_offset[] line_count down to reflect the inserted lines. |
6380 | // Clear the inserted lines. |
6381 | for (i = 0; i < line_count; i++) { |
6382 | if (width != grid->Columns) { |
6383 | // need to copy part of a line |
6384 | j = end - 1 - i; |
6385 | while ((j -= line_count) >= row) { |
6386 | linecopy(grid, j + line_count, j, col, width); |
6387 | } |
6388 | j += line_count; |
6389 | grid_clear_line(grid, grid->line_offset[j] + col, width, false); |
6390 | grid->line_wraps[j] = false; |
6391 | } else { |
6392 | j = end - 1 - i; |
6393 | temp = grid->line_offset[j]; |
6394 | while ((j -= line_count) >= row) { |
6395 | grid->line_offset[j + line_count] = grid->line_offset[j]; |
6396 | grid->line_wraps[j + line_count] = grid->line_wraps[j]; |
6397 | } |
6398 | grid->line_offset[j + line_count] = temp; |
6399 | grid->line_wraps[j + line_count] = false; |
6400 | grid_clear_line(grid, temp, (int)grid->Columns, false); |
6401 | } |
6402 | } |
6403 | |
6404 | if (!grid->throttled) { |
6405 | ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); |
6406 | } |
6407 | |
6408 | return; |
6409 | } |
6410 | |
6411 | /// delete lines on the screen and move lines up. |
6412 | /// 'end' is the line after the scrolled part. Normally it is Rows. |
6413 | /// When scrolling region used 'off' is the offset from the top for the region. |
6414 | /// 'row' and 'end' are relative to the start of the region. |
6415 | void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, |
6416 | int width) |
6417 | { |
6418 | int j; |
6419 | int i; |
6420 | unsigned temp; |
6421 | |
6422 | int row_off = 0; |
6423 | screen_adjust_grid(&grid, &row_off, &col); |
6424 | row += row_off; |
6425 | end += row_off; |
6426 | |
6427 | if (line_count <= 0) { |
6428 | return; |
6429 | } |
6430 | |
6431 | // Now shift line_offset[] line_count up to reflect the deleted lines. |
6432 | // Clear the inserted lines. |
6433 | for (i = 0; i < line_count; i++) { |
6434 | if (width != grid->Columns) { |
6435 | // need to copy part of a line |
6436 | j = row + i; |
6437 | while ((j += line_count) <= end - 1) { |
6438 | linecopy(grid, j - line_count, j, col, width); |
6439 | } |
6440 | j -= line_count; |
6441 | grid_clear_line(grid, grid->line_offset[j] + col, width, false); |
6442 | grid->line_wraps[j] = false; |
6443 | } else { |
6444 | // whole width, moving the line pointers is faster |
6445 | j = row + i; |
6446 | temp = grid->line_offset[j]; |
6447 | while ((j += line_count) <= end - 1) { |
6448 | grid->line_offset[j - line_count] = grid->line_offset[j]; |
6449 | grid->line_wraps[j - line_count] = grid->line_wraps[j]; |
6450 | } |
6451 | grid->line_offset[j - line_count] = temp; |
6452 | grid->line_wraps[j - line_count] = false; |
6453 | grid_clear_line(grid, temp, (int)grid->Columns, false); |
6454 | } |
6455 | } |
6456 | |
6457 | if (!grid->throttled) { |
6458 | ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); |
6459 | } |
6460 | |
6461 | return; |
6462 | } |
6463 | |
6464 | |
6465 | // Show the current mode and ruler. |
6466 | // |
6467 | // If clear_cmdline is TRUE, clear the rest of the cmdline. |
6468 | // If clear_cmdline is FALSE there may be a message there that needs to be |
6469 | // cleared only if a mode is shown. |
6470 | // Return the length of the message (0 if no message). |
6471 | int showmode(void) |
6472 | { |
6473 | int need_clear; |
6474 | int length = 0; |
6475 | int do_mode; |
6476 | int attr; |
6477 | int nwr_save; |
6478 | int sub_attr; |
6479 | |
6480 | if (ui_has(kUIMessages) && clear_cmdline) { |
6481 | msg_ext_clear(true); |
6482 | } |
6483 | |
6484 | // don't make non-flushed message part of the showmode |
6485 | msg_ext_ui_flush(); |
6486 | |
6487 | msg_grid_validate(); |
6488 | |
6489 | do_mode = ((p_smd && msg_silent == 0) |
6490 | && ((State & TERM_FOCUS) |
6491 | || (State & INSERT) |
6492 | || restart_edit |
6493 | || VIsual_active)); |
6494 | if (do_mode || reg_recording != 0) { |
6495 | // Don't show mode right now, when not redrawing or inside a mapping. |
6496 | // Call char_avail() only when we are going to show something, because |
6497 | // it takes a bit of time. |
6498 | if (!redrawing() || (char_avail() && !KeyTyped) || msg_silent != 0) { |
6499 | redraw_cmdline = TRUE; /* show mode later */ |
6500 | return 0; |
6501 | } |
6502 | |
6503 | nwr_save = need_wait_return; |
6504 | |
6505 | /* wait a bit before overwriting an important message */ |
6506 | check_for_delay(FALSE); |
6507 | |
6508 | /* if the cmdline is more than one line high, erase top lines */ |
6509 | need_clear = clear_cmdline; |
6510 | if (clear_cmdline && cmdline_row < Rows - 1) { |
6511 | msg_clr_cmdline(); // will reset clear_cmdline |
6512 | } |
6513 | |
6514 | /* Position on the last line in the window, column 0 */ |
6515 | msg_pos_mode(); |
6516 | attr = HL_ATTR(HLF_CM); // Highlight mode |
6517 | |
6518 | // When the screen is too narrow to show the entire mode messsage, |
6519 | // avoid scrolling and truncate instead. |
6520 | msg_no_more = true; |
6521 | int save_lines_left = lines_left; |
6522 | lines_left = 0; |
6523 | |
6524 | if (do_mode) { |
6525 | MSG_PUTS_ATTR("--" , attr); |
6526 | // CTRL-X in Insert mode |
6527 | if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) { |
6528 | // These messages can get long, avoid a wrap in a narrow window. |
6529 | // Prefer showing edit_submode_extra. With external messages there |
6530 | // is no imposed limit. |
6531 | if (ui_has(kUIMessages)) { |
6532 | length = INT_MAX; |
6533 | } else { |
6534 | length = (Rows - msg_row) * Columns - 3; |
6535 | } |
6536 | if (edit_submode_extra != NULL) { |
6537 | length -= vim_strsize(edit_submode_extra); |
6538 | } |
6539 | if (length > 0) { |
6540 | if (edit_submode_pre != NULL) |
6541 | length -= vim_strsize(edit_submode_pre); |
6542 | if (length - vim_strsize(edit_submode) > 0) { |
6543 | if (edit_submode_pre != NULL) { |
6544 | msg_puts_attr((const char *)edit_submode_pre, attr); |
6545 | } |
6546 | msg_puts_attr((const char *)edit_submode, attr); |
6547 | } |
6548 | if (edit_submode_extra != NULL) { |
6549 | MSG_PUTS_ATTR(" " , attr); // Add a space in between. |
6550 | if ((int)edit_submode_highl < (int)HLF_COUNT) { |
6551 | sub_attr = win_hl_attr(curwin, edit_submode_highl); |
6552 | } else { |
6553 | sub_attr = attr; |
6554 | } |
6555 | msg_puts_attr((const char *)edit_submode_extra, sub_attr); |
6556 | } |
6557 | } |
6558 | } else { |
6559 | if (State & TERM_FOCUS) { |
6560 | MSG_PUTS_ATTR(_(" TERMINAL" ), attr); |
6561 | } else if (State & VREPLACE_FLAG) |
6562 | MSG_PUTS_ATTR(_(" VREPLACE" ), attr); |
6563 | else if (State & REPLACE_FLAG) |
6564 | MSG_PUTS_ATTR(_(" REPLACE" ), attr); |
6565 | else if (State & INSERT) { |
6566 | if (p_ri) |
6567 | MSG_PUTS_ATTR(_(" REVERSE" ), attr); |
6568 | MSG_PUTS_ATTR(_(" INSERT" ), attr); |
6569 | } else if (restart_edit == 'I' || restart_edit == 'i' |
6570 | || restart_edit == 'a') { |
6571 | MSG_PUTS_ATTR(_(" (insert)" ), attr); |
6572 | } else if (restart_edit == 'R') { |
6573 | MSG_PUTS_ATTR(_(" (replace)" ), attr); |
6574 | } else if (restart_edit == 'V') { |
6575 | MSG_PUTS_ATTR(_(" (vreplace)" ), attr); |
6576 | } |
6577 | if (p_hkmap) { |
6578 | MSG_PUTS_ATTR(_(" Hebrew" ), attr); |
6579 | } |
6580 | if (State & LANGMAP) { |
6581 | if (curwin->w_p_arab) { |
6582 | MSG_PUTS_ATTR(_(" Arabic" ), attr); |
6583 | } else if (get_keymap_str(curwin, (char_u *)" (%s)" , |
6584 | NameBuff, MAXPATHL)) { |
6585 | MSG_PUTS_ATTR(NameBuff, attr); |
6586 | } |
6587 | } |
6588 | if ((State & INSERT) && p_paste) |
6589 | MSG_PUTS_ATTR(_(" (paste)" ), attr); |
6590 | |
6591 | if (VIsual_active) { |
6592 | char *p; |
6593 | |
6594 | /* Don't concatenate separate words to avoid translation |
6595 | * problems. */ |
6596 | switch ((VIsual_select ? 4 : 0) |
6597 | + (VIsual_mode == Ctrl_V) * 2 |
6598 | + (VIsual_mode == 'V')) { |
6599 | case 0: p = N_(" VISUAL" ); break; |
6600 | case 1: p = N_(" VISUAL LINE" ); break; |
6601 | case 2: p = N_(" VISUAL BLOCK" ); break; |
6602 | case 4: p = N_(" SELECT" ); break; |
6603 | case 5: p = N_(" SELECT LINE" ); break; |
6604 | default: p = N_(" SELECT BLOCK" ); break; |
6605 | } |
6606 | MSG_PUTS_ATTR(_(p), attr); |
6607 | } |
6608 | MSG_PUTS_ATTR(" --" , attr); |
6609 | } |
6610 | |
6611 | need_clear = TRUE; |
6612 | } |
6613 | if (reg_recording != 0 |
6614 | && edit_submode == NULL // otherwise it gets too long |
6615 | ) { |
6616 | recording_mode(attr); |
6617 | need_clear = true; |
6618 | } |
6619 | |
6620 | mode_displayed = TRUE; |
6621 | if (need_clear || clear_cmdline) |
6622 | msg_clr_eos(); |
6623 | msg_didout = FALSE; /* overwrite this message */ |
6624 | length = msg_col; |
6625 | msg_col = 0; |
6626 | msg_no_more = false; |
6627 | lines_left = save_lines_left; |
6628 | need_wait_return = nwr_save; // never ask for hit-return for this |
6629 | } else if (clear_cmdline && msg_silent == 0) { |
6630 | // Clear the whole command line. Will reset "clear_cmdline". |
6631 | msg_clr_cmdline(); |
6632 | } |
6633 | |
6634 | // NB: also handles clearing the showmode if it was emtpy or disabled |
6635 | msg_ext_flush_showmode(); |
6636 | |
6637 | /* In Visual mode the size of the selected area must be redrawn. */ |
6638 | if (VIsual_active) |
6639 | clear_showcmd(); |
6640 | |
6641 | // If the last window has no status line, the ruler is after the mode |
6642 | // message and must be redrawn |
6643 | win_T *last = lastwin_nofloating(); |
6644 | if (redrawing() && last->w_status_height == 0) { |
6645 | win_redr_ruler(last, true); |
6646 | } |
6647 | redraw_cmdline = false; |
6648 | clear_cmdline = false; |
6649 | |
6650 | return length; |
6651 | } |
6652 | |
6653 | /* |
6654 | * Position for a mode message. |
6655 | */ |
6656 | static void msg_pos_mode(void) |
6657 | { |
6658 | msg_col = 0; |
6659 | msg_row = Rows - 1; |
6660 | } |
6661 | |
6662 | /// Delete mode message. Used when ESC is typed which is expected to end |
6663 | /// Insert mode (but Insert mode didn't end yet!). |
6664 | /// Caller should check "mode_displayed". |
6665 | void unshowmode(bool force) |
6666 | { |
6667 | // Don't delete it right now, when not redrawing or inside a mapping. |
6668 | if (!redrawing() || (!force && char_avail() && !KeyTyped)) { |
6669 | redraw_cmdline = true; // delete mode later |
6670 | } else { |
6671 | clearmode(); |
6672 | } |
6673 | } |
6674 | |
6675 | // Clear the mode message. |
6676 | void clearmode(void) |
6677 | { |
6678 | const int save_msg_row = msg_row; |
6679 | const int save_msg_col = msg_col; |
6680 | |
6681 | msg_ext_ui_flush(); |
6682 | msg_pos_mode(); |
6683 | if (reg_recording != 0) { |
6684 | recording_mode(HL_ATTR(HLF_CM)); |
6685 | } |
6686 | msg_clr_eos(); |
6687 | msg_ext_flush_showmode(); |
6688 | |
6689 | msg_col = save_msg_col; |
6690 | msg_row = save_msg_row; |
6691 | } |
6692 | |
6693 | static void recording_mode(int attr) |
6694 | { |
6695 | MSG_PUTS_ATTR(_("recording" ), attr); |
6696 | if (!shortmess(SHM_RECORDING)) { |
6697 | char_u s[4]; |
6698 | snprintf((char *)s, ARRAY_SIZE(s), " @%c" , reg_recording); |
6699 | MSG_PUTS_ATTR(s, attr); |
6700 | } |
6701 | } |
6702 | |
6703 | /* |
6704 | * Draw the tab pages line at the top of the Vim window. |
6705 | */ |
6706 | void draw_tabline(void) |
6707 | { |
6708 | int tabcount = 0; |
6709 | int tabwidth = 0; |
6710 | int col = 0; |
6711 | int scol = 0; |
6712 | int attr; |
6713 | win_T *wp; |
6714 | win_T *cwp; |
6715 | int wincount; |
6716 | int modified; |
6717 | int c; |
6718 | int len; |
6719 | int attr_nosel = HL_ATTR(HLF_TP); |
6720 | int attr_fill = HL_ATTR(HLF_TPF); |
6721 | char_u *p; |
6722 | int room; |
6723 | int use_sep_chars = (t_colors < 8 |
6724 | ); |
6725 | |
6726 | if (default_grid.chars == NULL) { |
6727 | return; |
6728 | } |
6729 | redraw_tabline = false; |
6730 | |
6731 | if (ui_has(kUITabline)) { |
6732 | ui_ext_tabline_update(); |
6733 | return; |
6734 | } |
6735 | |
6736 | if (tabline_height() < 1) |
6737 | return; |
6738 | |
6739 | |
6740 | // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect. |
6741 | assert(Columns == tab_page_click_defs_size); |
6742 | clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); |
6743 | |
6744 | /* Use the 'tabline' option if it's set. */ |
6745 | if (*p_tal != NUL) { |
6746 | int saved_did_emsg = did_emsg; |
6747 | |
6748 | // Check for an error. If there is one we would loop in redrawing the |
6749 | // screen. Avoid that by making 'tabline' empty. |
6750 | did_emsg = false; |
6751 | win_redr_custom(NULL, false); |
6752 | if (did_emsg) { |
6753 | set_string_option_direct((char_u *)"tabline" , -1, |
6754 | (char_u *)"" , OPT_FREE, SID_ERROR); |
6755 | } |
6756 | did_emsg |= saved_did_emsg; |
6757 | } else { |
6758 | FOR_ALL_TABS(tp) { |
6759 | ++tabcount; |
6760 | } |
6761 | |
6762 | if (tabcount > 0) { |
6763 | tabwidth = (Columns - 1 + tabcount / 2) / tabcount; |
6764 | } |
6765 | |
6766 | if (tabwidth < 6) { |
6767 | tabwidth = 6; |
6768 | } |
6769 | |
6770 | attr = attr_nosel; |
6771 | tabcount = 0; |
6772 | |
6773 | FOR_ALL_TABS(tp) { |
6774 | if (col >= Columns - 4) { |
6775 | break; |
6776 | } |
6777 | |
6778 | scol = col; |
6779 | |
6780 | if (tp == curtab) { |
6781 | cwp = curwin; |
6782 | wp = firstwin; |
6783 | } else { |
6784 | cwp = tp->tp_curwin; |
6785 | wp = tp->tp_firstwin; |
6786 | } |
6787 | |
6788 | |
6789 | if (tp->tp_topframe == topframe) { |
6790 | attr = win_hl_attr(cwp, HLF_TPS); |
6791 | } |
6792 | if (use_sep_chars && col > 0) { |
6793 | grid_putchar(&default_grid, '|', 0, col++, attr); |
6794 | } |
6795 | |
6796 | if (tp->tp_topframe != topframe) { |
6797 | attr = win_hl_attr(cwp, HLF_TP); |
6798 | } |
6799 | |
6800 | grid_putchar(&default_grid, ' ', 0, col++, attr); |
6801 | |
6802 | modified = false; |
6803 | |
6804 | for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount) { |
6805 | if (bufIsChanged(wp->w_buffer)) { |
6806 | modified = true; |
6807 | } |
6808 | } |
6809 | |
6810 | |
6811 | if (modified || wincount > 1) { |
6812 | if (wincount > 1) { |
6813 | vim_snprintf((char *)NameBuff, MAXPATHL, "%d" , wincount); |
6814 | len = (int)STRLEN(NameBuff); |
6815 | if (col + len >= Columns - 3) { |
6816 | break; |
6817 | } |
6818 | grid_puts_len(&default_grid, NameBuff, len, 0, col, |
6819 | hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); |
6820 | col += len; |
6821 | } |
6822 | if (modified) { |
6823 | grid_puts_len(&default_grid, (char_u *)"+" , 1, 0, col++, attr); |
6824 | } |
6825 | grid_putchar(&default_grid, ' ', 0, col++, attr); |
6826 | } |
6827 | |
6828 | room = scol - col + tabwidth - 1; |
6829 | if (room > 0) { |
6830 | /* Get buffer name in NameBuff[] */ |
6831 | get_trans_bufname(cwp->w_buffer); |
6832 | (void)shorten_dir(NameBuff); |
6833 | len = vim_strsize(NameBuff); |
6834 | p = NameBuff; |
6835 | while (len > room) { |
6836 | len -= ptr2cells(p); |
6837 | MB_PTR_ADV(p); |
6838 | } |
6839 | if (len > Columns - col - 1) { |
6840 | len = Columns - col - 1; |
6841 | } |
6842 | |
6843 | grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr); |
6844 | col += len; |
6845 | } |
6846 | grid_putchar(&default_grid, ' ', 0, col++, attr); |
6847 | |
6848 | // Store the tab page number in tab_page_click_defs[], so that |
6849 | // jump_to_mouse() knows where each one is. |
6850 | tabcount++; |
6851 | while (scol < col) { |
6852 | tab_page_click_defs[scol++] = (StlClickDefinition) { |
6853 | .type = kStlClickTabSwitch, |
6854 | .tabnr = tabcount, |
6855 | .func = NULL, |
6856 | }; |
6857 | } |
6858 | } |
6859 | |
6860 | if (use_sep_chars) |
6861 | c = '_'; |
6862 | else |
6863 | c = ' '; |
6864 | grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); |
6865 | |
6866 | /* Put an "X" for closing the current tab if there are several. */ |
6867 | if (first_tabpage->tp_next != NULL) { |
6868 | grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); |
6869 | tab_page_click_defs[Columns - 1] = (StlClickDefinition) { |
6870 | .type = kStlClickTabClose, |
6871 | .tabnr = 999, |
6872 | .func = NULL, |
6873 | }; |
6874 | } |
6875 | } |
6876 | |
6877 | /* Reset the flag here again, in case evaluating 'tabline' causes it to be |
6878 | * set. */ |
6879 | redraw_tabline = FALSE; |
6880 | } |
6881 | |
6882 | void ui_ext_tabline_update(void) |
6883 | { |
6884 | Array tabs = ARRAY_DICT_INIT; |
6885 | FOR_ALL_TABS(tp) { |
6886 | Dictionary tab_info = ARRAY_DICT_INIT; |
6887 | PUT(tab_info, "tab" , TABPAGE_OBJ(tp->handle)); |
6888 | |
6889 | win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; |
6890 | get_trans_bufname(cwp->w_buffer); |
6891 | PUT(tab_info, "name" , STRING_OBJ(cstr_to_string((char *)NameBuff))); |
6892 | |
6893 | ADD(tabs, DICTIONARY_OBJ(tab_info)); |
6894 | } |
6895 | ui_call_tabline_update(curtab->handle, tabs); |
6896 | } |
6897 | |
6898 | /* |
6899 | * Get buffer name for "buf" into NameBuff[]. |
6900 | * Takes care of special buffer names and translates special characters. |
6901 | */ |
6902 | void get_trans_bufname(buf_T *buf) |
6903 | { |
6904 | if (buf_spname(buf) != NULL) |
6905 | STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); |
6906 | else |
6907 | home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE); |
6908 | trans_characters(NameBuff, MAXPATHL); |
6909 | } |
6910 | |
6911 | /* |
6912 | * Get the character to use in a status line. Get its attributes in "*attr". |
6913 | */ |
6914 | static int fillchar_status(int *attr, win_T *wp) |
6915 | { |
6916 | int fill; |
6917 | bool is_curwin = (wp == curwin); |
6918 | if (is_curwin) { |
6919 | *attr = win_hl_attr(wp, HLF_S); |
6920 | fill = wp->w_p_fcs_chars.stl; |
6921 | } else { |
6922 | *attr = win_hl_attr(wp, HLF_SNC); |
6923 | fill = wp->w_p_fcs_chars.stlnc; |
6924 | } |
6925 | /* Use fill when there is highlighting, and highlighting of current |
6926 | * window differs, or the fillchars differ, or this is not the |
6927 | * current window */ |
6928 | if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) |
6929 | || !is_curwin || ONE_WINDOW) |
6930 | || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { |
6931 | return fill; |
6932 | } |
6933 | if (is_curwin) { |
6934 | return '^'; |
6935 | } |
6936 | return '='; |
6937 | } |
6938 | |
6939 | /* |
6940 | * Get the character to use in a separator between vertically split windows. |
6941 | * Get its attributes in "*attr". |
6942 | */ |
6943 | static int fillchar_vsep(win_T *wp, int *attr) |
6944 | { |
6945 | *attr = win_hl_attr(wp, HLF_C); |
6946 | return wp->w_p_fcs_chars.vert; |
6947 | } |
6948 | |
6949 | /* |
6950 | * Return TRUE if redrawing should currently be done. |
6951 | */ |
6952 | int redrawing(void) |
6953 | { |
6954 | return !RedrawingDisabled |
6955 | && !(p_lz && char_avail() && !KeyTyped && !do_redraw); |
6956 | } |
6957 | |
6958 | /* |
6959 | * Return TRUE if printing messages should currently be done. |
6960 | */ |
6961 | int messaging(void) |
6962 | { |
6963 | return !(p_lz && char_avail() && !KeyTyped); |
6964 | } |
6965 | |
6966 | /* |
6967 | * Show current status info in ruler and various other places |
6968 | * If always is FALSE, only show ruler if position has changed. |
6969 | */ |
6970 | void showruler(int always) |
6971 | { |
6972 | if (!always && !redrawing()) |
6973 | return; |
6974 | if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { |
6975 | redraw_custom_statusline(curwin); |
6976 | } else { |
6977 | win_redr_ruler(curwin, always); |
6978 | } |
6979 | |
6980 | if (need_maketitle |
6981 | || (p_icon && (stl_syntax & STL_IN_ICON)) |
6982 | || (p_title && (stl_syntax & STL_IN_TITLE)) |
6983 | ) |
6984 | maketitle(); |
6985 | /* Redraw the tab pages line if needed. */ |
6986 | if (redraw_tabline) |
6987 | draw_tabline(); |
6988 | } |
6989 | |
6990 | static void win_redr_ruler(win_T *wp, int always) |
6991 | { |
6992 | static bool did_show_ext_ruler = false; |
6993 | |
6994 | // If 'ruler' off or redrawing disabled, don't do anything |
6995 | if (!p_ru) { |
6996 | return; |
6997 | } |
6998 | |
6999 | /* |
7000 | * Check if cursor.lnum is valid, since win_redr_ruler() may be called |
7001 | * after deleting lines, before cursor.lnum is corrected. |
7002 | */ |
7003 | if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) |
7004 | return; |
7005 | |
7006 | /* Don't draw the ruler while doing insert-completion, it might overwrite |
7007 | * the (long) mode message. */ |
7008 | if (wp == lastwin && lastwin->w_status_height == 0) |
7009 | if (edit_submode != NULL) |
7010 | return; |
7011 | |
7012 | if (*p_ruf) { |
7013 | int save_called_emsg = called_emsg; |
7014 | |
7015 | called_emsg = FALSE; |
7016 | win_redr_custom(wp, TRUE); |
7017 | if (called_emsg) |
7018 | set_string_option_direct((char_u *)"rulerformat" , -1, |
7019 | (char_u *)"" , OPT_FREE, SID_ERROR); |
7020 | called_emsg |= save_called_emsg; |
7021 | return; |
7022 | } |
7023 | |
7024 | /* |
7025 | * Check if not in Insert mode and the line is empty (will show "0-1"). |
7026 | */ |
7027 | int empty_line = FALSE; |
7028 | if (!(State & INSERT) |
7029 | && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE) == NUL) |
7030 | empty_line = TRUE; |
7031 | |
7032 | /* |
7033 | * Only draw the ruler when something changed. |
7034 | */ |
7035 | validate_virtcol_win(wp); |
7036 | if ( redraw_cmdline |
7037 | || always |
7038 | || wp->w_cursor.lnum != wp->w_ru_cursor.lnum |
7039 | || wp->w_cursor.col != wp->w_ru_cursor.col |
7040 | || wp->w_virtcol != wp->w_ru_virtcol |
7041 | || wp->w_cursor.coladd != wp->w_ru_cursor.coladd |
7042 | || wp->w_topline != wp->w_ru_topline |
7043 | || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count |
7044 | || wp->w_topfill != wp->w_ru_topfill |
7045 | || empty_line != wp->w_ru_empty) { |
7046 | |
7047 | int width; |
7048 | int row; |
7049 | int fillchar; |
7050 | int attr; |
7051 | int off; |
7052 | bool part_of_status = false; |
7053 | |
7054 | if (wp->w_status_height) { |
7055 | row = W_ENDROW(wp); |
7056 | fillchar = fillchar_status(&attr, wp); |
7057 | off = wp->w_wincol; |
7058 | width = wp->w_width; |
7059 | part_of_status = true; |
7060 | } else { |
7061 | row = Rows - 1; |
7062 | fillchar = ' '; |
7063 | attr = 0; |
7064 | width = Columns; |
7065 | off = 0; |
7066 | } |
7067 | |
7068 | // In list mode virtcol needs to be recomputed |
7069 | colnr_T virtcol = wp->w_virtcol; |
7070 | if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { |
7071 | wp->w_p_list = false; |
7072 | getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); |
7073 | wp->w_p_list = true; |
7074 | } |
7075 | |
7076 | #define RULER_BUF_LEN 70 |
7077 | char_u buffer[RULER_BUF_LEN]; |
7078 | |
7079 | /* |
7080 | * Some sprintfs return the length, some return a pointer. |
7081 | * To avoid portability problems we use strlen() here. |
7082 | */ |
7083 | vim_snprintf((char *)buffer, RULER_BUF_LEN, "%" PRId64 "," , |
7084 | (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L |
7085 | : (int64_t)wp->w_cursor.lnum); |
7086 | size_t len = STRLEN(buffer); |
7087 | col_print(buffer + len, RULER_BUF_LEN - len, |
7088 | empty_line ? 0 : (int)wp->w_cursor.col + 1, |
7089 | (int)virtcol + 1); |
7090 | |
7091 | /* |
7092 | * Add a "50%" if there is room for it. |
7093 | * On the last line, don't print in the last column (scrolls the |
7094 | * screen up on some terminals). |
7095 | */ |
7096 | int i = (int)STRLEN(buffer); |
7097 | get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); |
7098 | int o = i + vim_strsize(buffer + i + 1); |
7099 | if (wp->w_status_height == 0) { // can't use last char of screen |
7100 | o++; |
7101 | } |
7102 | int this_ru_col = ru_col - (Columns - width); |
7103 | if (this_ru_col < 0) { |
7104 | this_ru_col = 0; |
7105 | } |
7106 | // Never use more than half the window/screen width, leave the other half |
7107 | // for the filename. |
7108 | if (this_ru_col < (width + 1) / 2) { |
7109 | this_ru_col = (width + 1) / 2; |
7110 | } |
7111 | if (this_ru_col + o < width) { |
7112 | // Need at least 3 chars left for get_rel_pos() + NUL. |
7113 | while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { |
7114 | i += utf_char2bytes(fillchar, buffer + i); |
7115 | o++; |
7116 | } |
7117 | get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); |
7118 | } |
7119 | |
7120 | if (ui_has(kUIMessages) && !part_of_status) { |
7121 | Array content = ARRAY_DICT_INIT; |
7122 | Array chunk = ARRAY_DICT_INIT; |
7123 | ADD(chunk, INTEGER_OBJ(attr)); |
7124 | ADD(chunk, STRING_OBJ(cstr_to_string((char *)buffer))); |
7125 | ADD(content, ARRAY_OBJ(chunk)); |
7126 | ui_call_msg_ruler(content); |
7127 | did_show_ext_ruler = true; |
7128 | } else { |
7129 | if (did_show_ext_ruler) { |
7130 | ui_call_msg_ruler((Array)ARRAY_DICT_INIT); |
7131 | did_show_ext_ruler = false; |
7132 | } |
7133 | // Truncate at window boundary. |
7134 | o = 0; |
7135 | for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { |
7136 | o += utf_ptr2cells(buffer + i); |
7137 | if (this_ru_col + o > width) { |
7138 | buffer[i] = NUL; |
7139 | break; |
7140 | } |
7141 | } |
7142 | |
7143 | ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; |
7144 | grid_puts(grid, buffer, row, this_ru_col + off, attr); |
7145 | grid_fill(grid, row, row + 1, |
7146 | this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, |
7147 | fillchar, attr); |
7148 | } |
7149 | |
7150 | wp->w_ru_cursor = wp->w_cursor; |
7151 | wp->w_ru_virtcol = wp->w_virtcol; |
7152 | wp->w_ru_empty = empty_line; |
7153 | wp->w_ru_topline = wp->w_topline; |
7154 | wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; |
7155 | wp->w_ru_topfill = wp->w_topfill; |
7156 | } |
7157 | } |
7158 | |
7159 | /* |
7160 | * Return the width of the 'number' and 'relativenumber' column. |
7161 | * Caller may need to check if 'number' or 'relativenumber' is set. |
7162 | * Otherwise it depends on 'numberwidth' and the line count. |
7163 | */ |
7164 | int number_width(win_T *wp) |
7165 | { |
7166 | int n; |
7167 | linenr_T lnum; |
7168 | |
7169 | if (wp->w_p_rnu && !wp->w_p_nu) { |
7170 | // cursor line shows "0" |
7171 | lnum = wp->w_height_inner; |
7172 | } else { |
7173 | // cursor line shows absolute line number |
7174 | lnum = wp->w_buffer->b_ml.ml_line_count; |
7175 | } |
7176 | |
7177 | if (lnum == wp->w_nrwidth_line_count) |
7178 | return wp->w_nrwidth_width; |
7179 | wp->w_nrwidth_line_count = lnum; |
7180 | |
7181 | n = 0; |
7182 | do { |
7183 | lnum /= 10; |
7184 | ++n; |
7185 | } while (lnum > 0); |
7186 | |
7187 | /* 'numberwidth' gives the minimal width plus one */ |
7188 | if (n < wp->w_p_nuw - 1) |
7189 | n = wp->w_p_nuw - 1; |
7190 | |
7191 | wp->w_nrwidth_width = n; |
7192 | return n; |
7193 | } |
7194 | |
7195 | /// Set dimensions of the Nvim application "shell". |
7196 | void screen_resize(int width, int height) |
7197 | { |
7198 | static int busy = FALSE; |
7199 | |
7200 | // Avoid recursiveness, can happen when setting the window size causes |
7201 | // another window-changed signal. |
7202 | if (updating_screen || busy) { |
7203 | return; |
7204 | } |
7205 | |
7206 | if (width < 0 || height < 0) /* just checking... */ |
7207 | return; |
7208 | |
7209 | if (State == HITRETURN || State == SETWSIZE) { |
7210 | /* postpone the resizing */ |
7211 | State = SETWSIZE; |
7212 | return; |
7213 | } |
7214 | |
7215 | /* curwin->w_buffer can be NULL when we are closing a window and the |
7216 | * buffer has already been closed and removing a scrollbar causes a resize |
7217 | * event. Don't resize then, it will happen after entering another buffer. |
7218 | */ |
7219 | if (curwin->w_buffer == NULL) |
7220 | return; |
7221 | |
7222 | ++busy; |
7223 | |
7224 | Rows = height; |
7225 | Columns = width; |
7226 | check_shellsize(); |
7227 | height = Rows; |
7228 | width = Columns; |
7229 | p_lines = Rows; |
7230 | p_columns = Columns; |
7231 | ui_call_grid_resize(1, width, height); |
7232 | |
7233 | send_grid_resize = true; |
7234 | |
7235 | /* The window layout used to be adjusted here, but it now happens in |
7236 | * screenalloc() (also invoked from screenclear()). That is because the |
7237 | * "busy" check above may skip this, but not screenalloc(). */ |
7238 | |
7239 | if (State != ASKMORE && State != EXTERNCMD && State != CONFIRM) { |
7240 | screenclear(); |
7241 | } |
7242 | |
7243 | if (starting != NO_SCREEN) { |
7244 | maketitle(); |
7245 | changed_line_abv_curs(); |
7246 | invalidate_botline(); |
7247 | |
7248 | /* |
7249 | * We only redraw when it's needed: |
7250 | * - While at the more prompt or executing an external command, don't |
7251 | * redraw, but position the cursor. |
7252 | * - While editing the command line, only redraw that. |
7253 | * - in Ex mode, don't redraw anything. |
7254 | * - Otherwise, redraw right now, and position the cursor. |
7255 | * Always need to call update_screen() or screenalloc(), to make |
7256 | * sure Rows/Columns and the size of the screen is correct! |
7257 | */ |
7258 | if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM |
7259 | || exmode_active) { |
7260 | screenalloc(); |
7261 | if (msg_grid.chars) { |
7262 | msg_grid_validate(); |
7263 | } |
7264 | // TODO(bfredl): sometimes messes up the output. Implement clear+redraw |
7265 | // also for the pager? (or: what if the pager was just a modal window?) |
7266 | ui_comp_set_screen_valid(true); |
7267 | repeat_message(); |
7268 | } else { |
7269 | if (curwin->w_p_scb) |
7270 | do_check_scrollbind(TRUE); |
7271 | if (State & CMDLINE) { |
7272 | redraw_popupmenu = false; |
7273 | update_screen(NOT_VALID); |
7274 | redrawcmdline(); |
7275 | if (pum_drawn()) { |
7276 | cmdline_pum_display(false); |
7277 | } |
7278 | } else { |
7279 | update_topline(); |
7280 | if (pum_drawn()) { |
7281 | // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. |
7282 | // For now make sure the nested update_screen(0) won't redraw the |
7283 | // pum at the old position. Try to untangle this later. |
7284 | redraw_popupmenu = false; |
7285 | ins_compl_show_pum(); |
7286 | } |
7287 | update_screen(NOT_VALID); |
7288 | if (redrawing()) { |
7289 | setcursor(); |
7290 | } |
7291 | } |
7292 | } |
7293 | ui_flush(); |
7294 | } |
7295 | busy--; |
7296 | } |
7297 | |
7298 | /// Check if the new Nvim application "shell" dimensions are valid. |
7299 | /// Correct it if it's too small or way too big. |
7300 | void check_shellsize(void) |
7301 | { |
7302 | if (Rows < min_rows()) { |
7303 | // need room for one window and command line |
7304 | Rows = min_rows(); |
7305 | } |
7306 | limit_screen_size(); |
7307 | } |
7308 | |
7309 | // Limit Rows and Columns to avoid an overflow in Rows * Columns. |
7310 | void limit_screen_size(void) |
7311 | { |
7312 | if (Columns < MIN_COLUMNS) { |
7313 | Columns = MIN_COLUMNS; |
7314 | } else if (Columns > 10000) { |
7315 | Columns = 10000; |
7316 | } |
7317 | |
7318 | if (Rows > 1000) { |
7319 | Rows = 1000; |
7320 | } |
7321 | } |
7322 | |
7323 | void win_new_shellsize(void) |
7324 | { |
7325 | static long old_Rows = 0; |
7326 | static long old_Columns = 0; |
7327 | |
7328 | if (old_Rows != Rows) { |
7329 | // if 'window' uses the whole screen, keep it using that */ |
7330 | if (p_window == old_Rows - 1 || old_Rows == 0) { |
7331 | p_window = Rows - 1; |
7332 | } |
7333 | old_Rows = Rows; |
7334 | shell_new_rows(); // update window sizes |
7335 | } |
7336 | if (old_Columns != Columns) { |
7337 | old_Columns = Columns; |
7338 | shell_new_columns(); // update window sizes |
7339 | } |
7340 | } |
7341 | |
7342 | win_T *get_win_by_grid_handle(handle_T handle) |
7343 | { |
7344 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
7345 | if (wp->w_grid.handle == handle) { |
7346 | return wp; |
7347 | } |
7348 | } |
7349 | return NULL; |
7350 | } |
7351 | |