mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(api/ui): win_extmarks
This commit is contained in:
		| @@ -2706,6 +2706,11 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) | |||||||
|                                 "hl_group" is used as highlight for the cchar |                                 "hl_group" is used as highlight for the cchar | ||||||
|                                 if provided, otherwise it defaults to |                                 if provided, otherwise it defaults to | ||||||
|                                 |hl-Conceal|. |                                 |hl-Conceal|. | ||||||
|  |                               • ui_watched: boolean that indicates the mark | ||||||
|  |                                 should be drawn by a UI. When set, the UI will | ||||||
|  |                                 receive win_extmark events. Note: the mark is | ||||||
|  |                                 positioned by virt_text attributes. Can be | ||||||
|  |                                 used together with virt_text. | ||||||
|  |  | ||||||
|                 Return: ~ |                 Return: ~ | ||||||
|                     Id of the created/updated extmark |                     Id of the created/updated extmark | ||||||
|   | |||||||
| @@ -627,6 +627,10 @@ tabs. | |||||||
| 	`botline` is set to one more than the line count of the buffer, if | 	`botline` is set to one more than the line count of the buffer, if | ||||||
| 	there are filler lines past the end. | 	there are filler lines past the end. | ||||||
|  |  | ||||||
|  | ["win_extmark", grid, win, ns_id, mark_id, row, col] | ||||||
|  | 	Updates the position of an extmark which is currently visible in a | ||||||
|  | 	window. Only emitted if the mark has the `ui_watched` attribute.  | ||||||
|  |  | ||||||
| ============================================================================== | ============================================================================== | ||||||
| Popupmenu Events						 *ui-popupmenu* | Popupmenu Events						 *ui-popupmenu* | ||||||
|  |  | ||||||
|   | |||||||
| @@ -146,6 +146,10 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) | |||||||
|           STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos]))); |           STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos]))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (decor->ui_watched) { | ||||||
|  |       PUT(dict, "ui_watched", BOOLEAN_OBJ(true)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (kv_size(decor->virt_lines)) { |     if (kv_size(decor->virt_lines)) { | ||||||
|       Array all_chunks = ARRAY_DICT_INIT; |       Array all_chunks = ARRAY_DICT_INIT; | ||||||
|       bool virt_lines_leftcol = false; |       bool virt_lines_leftcol = false; | ||||||
| @@ -170,7 +174,7 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) | |||||||
|       PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); |       PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (decor->hl_id || kv_size(decor->virt_text)) { |     if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) { | ||||||
|       PUT(dict, "priority", INTEGER_OBJ(decor->priority)); |       PUT(dict, "priority", INTEGER_OBJ(decor->priority)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -472,6 +476,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e | |||||||
| ///                   When a character is supplied it is used as |:syn-cchar|. | ///                   When a character is supplied it is used as |:syn-cchar|. | ||||||
| ///                   "hl_group" is used as highlight for the cchar if provided, | ///                   "hl_group" is used as highlight for the cchar if provided, | ||||||
| ///                   otherwise it defaults to |hl-Conceal|. | ///                   otherwise it defaults to |hl-Conceal|. | ||||||
|  | ///               - ui_watched: boolean that indicates the mark should be drawn | ||||||
|  | ///                   by a UI. When set, the UI will receive win_extmark events. | ||||||
|  | ///                   Note: the mark is positioned by virt_text attributes. Can be | ||||||
|  | ///                   used together with virt_text. | ||||||
| /// | /// | ||||||
| /// @param[out]  err   Error details, if any | /// @param[out]  err   Error details, if any | ||||||
| /// @return Id of the created/updated extmark | /// @return Id of the created/updated extmark | ||||||
| @@ -709,6 +717,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer | |||||||
|   bool ephemeral = false; |   bool ephemeral = false; | ||||||
|   OPTION_TO_BOOL(ephemeral, ephemeral, false); |   OPTION_TO_BOOL(ephemeral, ephemeral, false); | ||||||
|  |  | ||||||
|  |   OPTION_TO_BOOL(decor.ui_watched, ui_watched, false); | ||||||
|  |  | ||||||
|   if (line < 0) { |   if (line < 0) { | ||||||
|     api_set_error(err, kErrorTypeValidation, "line value outside range"); |     api_set_error(err, kErrorTypeValidation, "line value outside range"); | ||||||
|     goto error; |     goto error; | ||||||
| @@ -762,7 +772,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer | |||||||
|  |  | ||||||
|   // TODO(bfredl): synergize these two branches even more |   // TODO(bfredl): synergize these two branches even more | ||||||
|   if (ephemeral && decor_state.buf == buf) { |   if (ephemeral && decor_state.buf == buf) { | ||||||
|     decor_add_ephemeral((int)line, (int)col, line2, col2, &decor); |     decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id); | ||||||
|   } else { |   } else { | ||||||
|     if (ephemeral) { |     if (ephemeral) { | ||||||
|       api_set_error(err, kErrorTypeException, "not yet implemented"); |       api_set_error(err, kErrorTypeException, "not yet implemented"); | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ return { | |||||||
|     "line_hl_group"; |     "line_hl_group"; | ||||||
|     "cursorline_hl_group"; |     "cursorline_hl_group"; | ||||||
|     "conceal"; |     "conceal"; | ||||||
|  |     "ui_watched"; | ||||||
|   }; |   }; | ||||||
|   keymap = { |   keymap = { | ||||||
|     "noremap"; |     "noremap"; | ||||||
|   | |||||||
| @@ -810,3 +810,4 @@ static void remote_ui_inspect(UI *ui, Dictionary *info) | |||||||
|   UIData *data = ui->data; |   UIData *data = ui->data; | ||||||
|   PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); |   PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  |  | ||||||
| #include "nvim/api/private/defs.h" | #include "nvim/api/private/defs.h" | ||||||
|  | #include "nvim/map.h" | ||||||
|  |  | ||||||
| #ifdef INCLUDE_GENERATED_DECLARATIONS | #ifdef INCLUDE_GENERATED_DECLARATIONS | ||||||
| # include "api/ui.h.generated.h" | # include "api/ui.h.generated.h" | ||||||
|   | |||||||
| @@ -123,6 +123,10 @@ void win_viewport(Integer grid, Window win, Integer topline, | |||||||
|                   Integer line_count) |                   Integer line_count) | ||||||
|   FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; |   FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; | ||||||
|  |  | ||||||
|  | void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, | ||||||
|  |                  Integer row, Integer col) | ||||||
|  |   FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; | ||||||
|  |  | ||||||
| void popupmenu_show(Array items, Integer selected, | void popupmenu_show(Array items, Integer selected, | ||||||
|                     Integer row, Integer col, Integer grid) |                     Integer row, Integer col, Integer grid) | ||||||
|   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; |   FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com | ||||||
|  |  | ||||||
| #include "nvim/buffer.h" | #include "nvim/buffer.h" | ||||||
|  | #include "nvim/api/ui.h" | ||||||
| #include "nvim/decoration.h" | #include "nvim/decoration.h" | ||||||
| #include "nvim/extmark.h" | #include "nvim/extmark.h" | ||||||
| #include "nvim/highlight.h" | #include "nvim/highlight.h" | ||||||
| @@ -73,7 +74,8 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (decor && kv_size(decor->virt_text)) { |   if (decor && (kv_size(decor->virt_text) | ||||||
|  |                 || decor->ui_watched)) { | ||||||
|     redraw_buf_line_later(buf, row1 + 1); |     redraw_buf_line_later(buf, row1 + 1); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -195,9 +197,11 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) | |||||||
|     Decoration decor = get_decor(mark); |     Decoration decor = get_decor(mark); | ||||||
|  |  | ||||||
|     // Exclude non-paired marks unless they contain virt_text or a sign |     // Exclude non-paired marks unless they contain virt_text or a sign | ||||||
|  |     // or they are ui-watched | ||||||
|     if (!mt_paired(mark) |     if (!mt_paired(mark) | ||||||
|         && !kv_size(decor.virt_text) |         && !kv_size(decor.virt_text) | ||||||
|         && !decor_has_sign(&decor)) { |         && !decor_has_sign(&decor) | ||||||
|  |         && !decor.ui_watched) { | ||||||
|       goto next_mark; |       goto next_mark; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -206,21 +210,22 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) | |||||||
|     // Exclude start marks if the end mark position is above the top row |     // Exclude start marks if the end mark position is above the top row | ||||||
|     // Exclude end marks if we have already added the start mark |     // Exclude end marks if we have already added the start mark | ||||||
|     if ((mt_start(mark) && altpos.row < top_row |     if ((mt_start(mark) && altpos.row < top_row | ||||||
|          && !kv_size(decor.virt_text)) |          && !kv_size(decor.virt_text) | ||||||
|  |          && !decor.ui_watched) | ||||||
|         || (mt_end(mark) && altpos.row >= top_row)) { |         || (mt_end(mark) && altpos.row >= top_row)) { | ||||||
|       goto next_mark; |       goto next_mark; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (mt_end(mark)) { |     if (mt_end(mark)) { | ||||||
|       decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, |       decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, | ||||||
|                 &decor, false); |                 &decor, false, mark.ns, mark.id); | ||||||
|     } else { |     } else { | ||||||
|       if (altpos.row == -1) { |       if (altpos.row == -1) { | ||||||
|         altpos.row = mark.pos.row; |         altpos.row = mark.pos.row; | ||||||
|         altpos.col = mark.pos.col; |         altpos.col = mark.pos.col; | ||||||
|       } |       } | ||||||
|       decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, |       decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, | ||||||
|                 &decor, false); |                 &decor, false, mark.ns, mark.id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| next_mark: | next_mark: | ||||||
| @@ -246,13 +251,13 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) | |||||||
| } | } | ||||||
|  |  | ||||||
| static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col, | static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col, | ||||||
|                       Decoration *decor, bool owned) |                       Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id) | ||||||
| { | { | ||||||
|   int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; |   int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; | ||||||
|  |  | ||||||
|   DecorRange range = { start_row, start_col, end_row, end_col, |   DecorRange range = { start_row, start_col, end_row, end_col, | ||||||
|                        *decor, attr_id, |                        *decor, attr_id, | ||||||
|                        kv_size(decor->virt_text) && owned, -1 }; |                        kv_size(decor->virt_text) && owned, -1, ns_id, mark_id }; | ||||||
|  |  | ||||||
|   kv_pushp(state->active); |   kv_pushp(state->active); | ||||||
|   size_t index; |   size_t index; | ||||||
| @@ -298,13 +303,13 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * | |||||||
|  |  | ||||||
|     if (endpos.row < mark.pos.row |     if (endpos.row < mark.pos.row | ||||||
|         || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) { |         || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) { | ||||||
|       if (!kv_size(decor.virt_text)) { |       if (!kv_size(decor.virt_text) && !decor.ui_watched) { | ||||||
|         goto next_mark; |         goto next_mark; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, |     decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, | ||||||
|               &decor, false); |               &decor, false, mark.ns, mark.id); | ||||||
|  |  | ||||||
| next_mark: | next_mark: | ||||||
|     marktree_itr_next(buf->b_marktree, state->itr); |     marktree_itr_next(buf->b_marktree, state->itr); | ||||||
| @@ -321,7 +326,8 @@ next_mark: | |||||||
|     bool active = false, keep = true; |     bool active = false, keep = true; | ||||||
|     if (item.end_row < state->row |     if (item.end_row < state->row | ||||||
|         || (item.end_row == state->row && item.end_col <= col)) { |         || (item.end_row == state->row && item.end_col <= col)) { | ||||||
|       if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { |       if (!(item.start_row >= state->row | ||||||
|  |             && (kv_size(item.decor.virt_text) || item.decor.ui_watched))) { | ||||||
|         keep = false; |         keep = false; | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
| @@ -349,7 +355,7 @@ next_mark: | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if ((item.start_row == state->row && item.start_col <= col) |     if ((item.start_row == state->row && item.start_col <= col) | ||||||
|         && kv_size(item.decor.virt_text) |         && (kv_size(item.decor.virt_text) || item.decor.ui_watched) | ||||||
|         && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { |         && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { | ||||||
|       item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col; |       item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col; | ||||||
|     } |     } | ||||||
| @@ -517,7 +523,8 @@ bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col) | |||||||
|   bool has_virttext = false; |   bool has_virttext = false; | ||||||
|   for (size_t i = 0; i < kv_size(state->active); i++) { |   for (size_t i = 0; i < kv_size(state->active); i++) { | ||||||
|     DecorRange item = kv_A(state->active, i); |     DecorRange item = kv_A(state->active, i); | ||||||
|     if (item.start_row == state->row && kv_size(item.decor.virt_text)) { |     if (item.start_row == state->row | ||||||
|  |         && (kv_size(item.decor.virt_text) || item.decor.ui_watched)) { | ||||||
|       has_virttext = true; |       has_virttext = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -528,13 +535,14 @@ bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col) | |||||||
|   return has_virttext; |   return has_virttext; | ||||||
| } | } | ||||||
|  |  | ||||||
| void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor) | void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, | ||||||
|  |                          Decoration *decor, uint64_t ns_id, uint64_t mark_id) | ||||||
| { | { | ||||||
|   if (end_row == -1) { |   if (end_row == -1) { | ||||||
|     end_row = start_row; |     end_row = start_row; | ||||||
|     end_col = start_col; |     end_col = start_col; | ||||||
|   } |   } | ||||||
|   decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true); |   decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -60,10 +60,11 @@ struct Decoration { | |||||||
|   // TODO(bfredl): in principle this should be a schar_T, but we |   // TODO(bfredl): in principle this should be a schar_T, but we | ||||||
|   // probably want some kind of glyph cache for that.. |   // probably want some kind of glyph cache for that.. | ||||||
|   int conceal_char; |   int conceal_char; | ||||||
|  |   bool ui_watched;  // watched for win_extmark | ||||||
| }; | }; | ||||||
| #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ | #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ | ||||||
|                           kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ |                           kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ | ||||||
|                           0, 0, NULL, 0, 0, 0, 0, 0 } |                           0, 0, NULL, 0, 0, 0, 0, 0, false } | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|   int start_row; |   int start_row; | ||||||
| @@ -74,6 +75,8 @@ typedef struct { | |||||||
|   int attr_id;  // cached lookup of decor.hl_id |   int attr_id;  // cached lookup of decor.hl_id | ||||||
|   bool virt_text_owned; |   bool virt_text_owned; | ||||||
|   int win_col; |   int win_col; | ||||||
|  |   uint64_t ns_id; | ||||||
|  |   uint64_t mark_id; | ||||||
| } DecorRange; | } DecorRange; | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|   | |||||||
| @@ -69,7 +69,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col | |||||||
|   if (decor) { |   if (decor) { | ||||||
|     if (kv_size(decor->virt_text) |     if (kv_size(decor->virt_text) | ||||||
|         || kv_size(decor->virt_lines) |         || kv_size(decor->virt_lines) | ||||||
|         || decor_has_sign(decor)) { |         || decor_has_sign(decor) | ||||||
|  |         || decor->ui_watched) { | ||||||
|       decor_full = true; |       decor_full = true; | ||||||
|       decor = xmemdup(decor, sizeof *decor); |       decor = xmemdup(decor, sizeof *decor); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -67,6 +67,7 @@ | |||||||
|  |  | ||||||
| #include "nvim/api/extmark.h" | #include "nvim/api/extmark.h" | ||||||
| #include "nvim/api/private/helpers.h" | #include "nvim/api/private/helpers.h" | ||||||
|  | #include "nvim/api/ui.h" | ||||||
| #include "nvim/api/vim.h" | #include "nvim/api/vim.h" | ||||||
| #include "nvim/arabic.h" | #include "nvim/arabic.h" | ||||||
| #include "nvim/ascii.h" | #include "nvim/ascii.h" | ||||||
| @@ -161,6 +162,14 @@ static bool msg_grid_invalid = false; | |||||||
|  |  | ||||||
| static bool resizing = false; | static bool resizing = false; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |   NS ns_id; | ||||||
|  |   uint64_t mark_id; | ||||||
|  |   int win_row; | ||||||
|  |   int win_col; | ||||||
|  | } WinExtmark; | ||||||
|  | static kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE); | ||||||
|  |  | ||||||
|  |  | ||||||
| #ifdef INCLUDE_GENERATED_DECLARATIONS | #ifdef INCLUDE_GENERATED_DECLARATIONS | ||||||
| # include "screen.c.generated.h" | # include "screen.c.generated.h" | ||||||
| @@ -1314,6 +1323,8 @@ static void win_update(win_T *wp, DecorProviders *providers) | |||||||
|   srow = 0; |   srow = 0; | ||||||
|   lnum = wp->w_topline;  // first line shown in window |   lnum = wp->w_topline;  // first line shown in window | ||||||
|  |  | ||||||
|  |   win_extmark_arr.size = 0; | ||||||
|  |  | ||||||
|   decor_redraw_reset(buf, &decor_state); |   decor_redraw_reset(buf, &decor_state); | ||||||
|  |  | ||||||
|   DecorProviders line_providers; |   DecorProviders line_providers; | ||||||
| @@ -1692,6 +1703,16 @@ static void win_update(win_T *wp, DecorProviders *providers) | |||||||
|   wp->w_old_topfill = wp->w_topfill; |   wp->w_old_topfill = wp->w_topfill; | ||||||
|   wp->w_old_botfill = wp->w_botfill; |   wp->w_old_botfill = wp->w_botfill; | ||||||
|  |  | ||||||
|  |   // Send win_extmarks if needed | ||||||
|  |   if (kv_size(win_extmark_arr) > 0) { | ||||||
|  |     for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { | ||||||
|  |       ui_call_win_extmark( | ||||||
|  |           wp->w_grid_alloc.handle, wp->handle, | ||||||
|  |           kv_A(win_extmark_arr, n).ns_id, kv_A(win_extmark_arr, n).mark_id, | ||||||
|  |           kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (dollar_vcol == -1) { |   if (dollar_vcol == -1) { | ||||||
|     /* |     /* | ||||||
|      * There is a trick with w_botline.  If we invalidate it on each |      * There is a trick with w_botline.  If we invalidate it on each | ||||||
| @@ -1964,7 +1985,7 @@ static inline void provider_err_virt_text(linenr_T lnum, char *err) | |||||||
|           ((VirtTextChunk){ .text = provider_err, |           ((VirtTextChunk){ .text = provider_err, | ||||||
|                             .hl_id = hl_err })); |                             .hl_id = hl_err })); | ||||||
|   err_decor.virt_text_width = mb_string2cells((char_u *)err); |   err_decor.virt_text_width = mb_string2cells((char_u *)err); | ||||||
|   decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor); |   decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) | static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) | ||||||
| @@ -2881,7 +2902,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc | |||||||
|           && vcol >= (long)wp->w_virtcol) |           && vcol >= (long)wp->w_virtcol) | ||||||
|          || (number_only && draw_state > WL_NR)) |          || (number_only && draw_state > WL_NR)) | ||||||
|         && filler_todo <= 0) { |         && filler_todo <= 0) { | ||||||
|       draw_virt_text(buf, win_col_offset, &col, grid->Columns); |       draw_virt_text(wp, buf, win_col_offset, &col, grid->Columns, row); | ||||||
|       grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, |       grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, | ||||||
|                        wp->w_hl_attr_normal, false); |                        wp->w_hl_attr_normal, false); | ||||||
|       // Pretend we have finished updating the window.  Except when |       // Pretend we have finished updating the window.  Except when | ||||||
| @@ -3951,7 +3972,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       draw_virt_text(buf, win_col_offset, &col, grid->Columns); |       draw_virt_text(wp, buf, win_col_offset, &col, grid->Columns, row); | ||||||
|       grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, |       grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, | ||||||
|                        wp->w_hl_attr_normal, false); |                        wp->w_hl_attr_normal, false); | ||||||
|       row++; |       row++; | ||||||
| @@ -4195,7 +4216,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc | |||||||
|                               kHlModeReplace, grid->Columns, offset); |                               kHlModeReplace, grid->Columns, offset); | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns); |         draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->Columns, row); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl, |       grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl, | ||||||
| @@ -4274,14 +4295,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc | |||||||
|   return row; |   return row; | ||||||
| } | } | ||||||
|  |  | ||||||
| void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col) | void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, int win_row) | ||||||
| { | { | ||||||
|   DecorState *state = &decor_state; |   DecorState *state = &decor_state; | ||||||
|   int right_pos = max_col; |   int right_pos = max_col; | ||||||
|   bool do_eol = state->eol_col > -1; |   bool do_eol = state->eol_col > -1; | ||||||
|   for (size_t i = 0; i < kv_size(state->active); i++) { |   for (size_t i = 0; i < kv_size(state->active); i++) { | ||||||
|     DecorRange *item = &kv_A(state->active, i); |     DecorRange *item = &kv_A(state->active, i); | ||||||
|     if (!(item->start_row == state->row && kv_size(item->decor.virt_text))) { |     if (!(item->start_row == state->row | ||||||
|  |           && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
|     if (item->win_col == -1) { |     if (item->win_col == -1) { | ||||||
| @@ -4297,9 +4319,17 @@ void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col) | |||||||
|     if (item->win_col < 0) { |     if (item->win_col < 0) { | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
|  |     int col; | ||||||
|     int col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, |     if (item->decor.ui_watched) { | ||||||
|                                   item->decor.hl_mode, max_col, item->win_col - col_off); |       // send mark position to UI | ||||||
|  |       col = item->win_col; | ||||||
|  |       WinExtmark m = { item->ns_id, item->mark_id, win_row, col }; | ||||||
|  |       kv_push(win_extmark_arr, m); | ||||||
|  |     } | ||||||
|  |     if (kv_size(item->decor.virt_text)) { | ||||||
|  |       col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, | ||||||
|  |                                 item->decor.hl_mode, max_col, item->win_col - col_off); | ||||||
|  |     } | ||||||
|     item->win_col = -2;  // deactivate |     item->win_col = -2;  // deactivate | ||||||
|     if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { |     if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { | ||||||
|       state->eol_col = col + 1; |       state->eol_col = col + 1; | ||||||
|   | |||||||
| @@ -1606,3 +1606,209 @@ describe('Extmarks buffer api with many marks', function() | |||||||
|     eq({}, get_marks(ns2)) |     eq({}, get_marks(ns2)) | ||||||
|   end) |   end) | ||||||
| end) | end) | ||||||
|  |  | ||||||
|  | describe('API/win_extmark', function() | ||||||
|  |   local screen | ||||||
|  |   local marks, line1, line2 | ||||||
|  |   local ns | ||||||
|  |  | ||||||
|  |   before_each(function() | ||||||
|  |     -- Initialize some namespaces and insert text into a buffer | ||||||
|  |     marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} | ||||||
|  |  | ||||||
|  |     line1 = "non ui-watched line" | ||||||
|  |     line2 = "ui-watched line" | ||||||
|  |  | ||||||
|  |     clear() | ||||||
|  |  | ||||||
|  |     insert(line1) | ||||||
|  |     feed("o<esc>") | ||||||
|  |     insert(line2) | ||||||
|  |     ns = request('nvim_create_namespace', "extmark-ui") | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('sends and only sends ui-watched marks to ui', function() | ||||||
|  |     screen = Screen.new(20, 4) | ||||||
|  |     screen:attach() | ||||||
|  |     -- should send this | ||||||
|  |     set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) | ||||||
|  |     -- should not send this | ||||||
|  |     set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |       non ui-watched line | | ||||||
|  |       ui-watched lin^e     | | ||||||
|  |       ~                   | | ||||||
|  |                           | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- positioned at the end of the 2nd line | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('sends multiple ui-watched marks to ui', function() | ||||||
|  |     screen = Screen.new(20, 4) | ||||||
|  |     screen:attach() | ||||||
|  |     -- should send all of these | ||||||
|  |     set_extmark(ns, marks[1], 1, 0, { ui_watched = true, virt_text_pos = "overlay" }) | ||||||
|  |     set_extmark(ns, marks[2], 1, 2, { ui_watched = true, virt_text_pos = "overlay" }) | ||||||
|  |     set_extmark(ns, marks[3], 1, 4, { ui_watched = true, virt_text_pos = "overlay" }) | ||||||
|  |     set_extmark(ns, marks[4], 1, 6, { ui_watched = true, virt_text_pos = "overlay" }) | ||||||
|  |     set_extmark(ns, marks[5], 1, 8, { ui_watched = true }) | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |       non ui-watched line | | ||||||
|  |       ui-watched lin^e     | | ||||||
|  |       ~                   | | ||||||
|  |                           | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- earlier notifications | ||||||
|  |           { {id = 1000}, 1, 1, 1, 0 }, | ||||||
|  |           { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, | ||||||
|  |           { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, { {id = 1000}, 1, 3, 1, 4 }, | ||||||
|  |           { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, { {id = 1000}, 1, 3, 1, 4 }, { {id = 1000}, 1, 4, 1, 6 }, | ||||||
|  |           -- final | ||||||
|  |           --   overlay | ||||||
|  |           { {id = 1000}, 1, 1, 1, 0 }, | ||||||
|  |           { {id = 1000}, 1, 2, 1, 2 }, | ||||||
|  |           { {id = 1000}, 1, 3, 1, 4 }, | ||||||
|  |           { {id = 1000}, 1, 4, 1, 6 }, | ||||||
|  |           --   eol | ||||||
|  |           { {id = 1000}, 1, 5, 1, 16 }, | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('updates ui-watched marks', function() | ||||||
|  |     screen = Screen.new(20, 4) | ||||||
|  |     screen:attach() | ||||||
|  |     -- should send this | ||||||
|  |     set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) | ||||||
|  |     -- should not send this | ||||||
|  |     set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) | ||||||
|  |     -- make some changes | ||||||
|  |     insert(" update") | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |       non ui-watched line | | ||||||
|  |       ui-watched linupdat^e| | ||||||
|  |       e                   | | ||||||
|  |                           | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- positioned at the end of the 2nd line | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |           -- updated and wrapped to 3rd line | ||||||
|  |           { {id = 1000}, 1, 1, 2, 2 }, | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     feed("<c-e>") | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |       ui-watched linupdat^e| | ||||||
|  |       e                   | | ||||||
|  |       ~                   | | ||||||
|  |                           | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- positioned at the end of the 2nd line | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |           -- updated and wrapped to 3rd line | ||||||
|  |           { {id = 1000}, 1, 1, 2, 2 }, | ||||||
|  |           -- scrolled up one line, should be handled by grid scroll | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('sends ui-watched to splits', function() | ||||||
|  |     screen = Screen.new(20, 8) | ||||||
|  |     screen:attach({ext_multigrid=true}) | ||||||
|  |     -- should send this | ||||||
|  |     set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) | ||||||
|  |     -- should not send this | ||||||
|  |     set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) | ||||||
|  |     command('split') | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |         ## grid 1 | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [No Name] [+]       | | ||||||
|  |           [2:--------------------]| | ||||||
|  |           [2:--------------------]| | ||||||
|  |           [No Name] [+]       | | ||||||
|  |           [3:--------------------]| | ||||||
|  |         ## grid 2 | ||||||
|  |           non ui-watched line | | ||||||
|  |           ui-watched line     | | ||||||
|  |         ## grid 3 | ||||||
|  |                               | | ||||||
|  |         ## grid 4 | ||||||
|  |           non ui-watched line | | ||||||
|  |           ui-watched lin^e     | | ||||||
|  |           ~                   | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- positioned at the end of the 2nd line | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |           -- updated after split | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |         }, | ||||||
|  |         [4] = { | ||||||
|  |           -- only after split | ||||||
|  |           { {id = 1001}, 1, 1, 1, 16 }, | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     -- make some changes | ||||||
|  |     insert(" update") | ||||||
|  |     screen:expect({ | ||||||
|  |       grid = [[ | ||||||
|  |         ## grid 1 | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [4:--------------------]| | ||||||
|  |           [No Name] [+]       | | ||||||
|  |           [2:--------------------]| | ||||||
|  |           [2:--------------------]| | ||||||
|  |           [No Name] [+]       | | ||||||
|  |           [3:--------------------]| | ||||||
|  |         ## grid 2 | ||||||
|  |           non ui-watched line | | ||||||
|  |           ui-watched linupd@@@| | ||||||
|  |         ## grid 3 | ||||||
|  |                               | | ||||||
|  |         ## grid 4 | ||||||
|  |           non ui-watched line | | ||||||
|  |           ui-watched linupdat^e| | ||||||
|  |           e                   | | ||||||
|  |     ]], | ||||||
|  |       extmarks = { | ||||||
|  |         [2] = { | ||||||
|  |           -- positioned at the end of the 2nd line | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |           -- updated after split | ||||||
|  |           { {id = 1000}, 1, 1, 1, 16 }, | ||||||
|  |         }, | ||||||
|  |         [4] = { | ||||||
|  |           { {id = 1001}, 1, 1, 1, 16 }, | ||||||
|  |           -- updated | ||||||
|  |           { {id = 1001}, 1, 1, 2, 2 }, | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   end) | ||||||
|  | end) | ||||||
|   | |||||||
| @@ -179,6 +179,7 @@ function Screen.new(width, height) | |||||||
|     _width = width, |     _width = width, | ||||||
|     _height = height, |     _height = height, | ||||||
|     _grids = {}, |     _grids = {}, | ||||||
|  |     _grid_win_extmarks = {}, | ||||||
|     _cursor = { |     _cursor = { | ||||||
|       grid = 1, row = 1, col = 1 |       grid = 1, row = 1, col = 1 | ||||||
|     }, |     }, | ||||||
| @@ -278,6 +279,8 @@ local ext_keys = { | |||||||
| --              attributes in the final state are an error. | --              attributes in the final state are an error. | ||||||
| --              Use screen:set_default_attr_ids() to define attributes for many | --              Use screen:set_default_attr_ids() to define attributes for many | ||||||
| --              expect() calls. | --              expect() calls. | ||||||
|  | -- extmarks:    Expected win_extmarks accumulated for the grids. For each grid, | ||||||
|  | --              the win_extmark messages are accumulated into an array. | ||||||
| -- condition:   Function asserting some arbitrary condition. Return value is | -- condition:   Function asserting some arbitrary condition. Return value is | ||||||
| --              ignored, throw an error (use eq() or similar) to signal failure. | --              ignored, throw an error (use eq() or similar) to signal failure. | ||||||
| -- any:         Lua pattern string expected to match a screen line. NB: the | -- any:         Lua pattern string expected to match a screen line. NB: the | ||||||
| @@ -320,7 +323,7 @@ function Screen:expect(expected, attr_ids, ...) | |||||||
|     assert(not (attr_ids ~= nil)) |     assert(not (attr_ids ~= nil)) | ||||||
|     local is_key = {grid=true, attr_ids=true, condition=true, mouse_enabled=true, |     local is_key = {grid=true, attr_ids=true, condition=true, mouse_enabled=true, | ||||||
|                     any=true, mode=true, unchanged=true, intermediate=true, |                     any=true, mode=true, unchanged=true, intermediate=true, | ||||||
|                     reset=true, timeout=true, request_cb=true, hl_groups=true} |                     reset=true, timeout=true, request_cb=true, hl_groups=true, extmarks=true} | ||||||
|     for _, v in ipairs(ext_keys) do |     for _, v in ipairs(ext_keys) do | ||||||
|       is_key[v] = true |       is_key[v] = true | ||||||
|     end |     end | ||||||
| @@ -459,6 +462,25 @@ screen:redraw_debug() to show all intermediate screen states.  ]]) | |||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  |     if expected.extmarks ~= nil then | ||||||
|  |       for gridid, expected_marks in pairs(expected.extmarks) do | ||||||
|  |         local stored_marks = self._grid_win_extmarks[gridid] | ||||||
|  |         if stored_marks == nil then | ||||||
|  |           return 'no win_extmark for grid '..tostring(gridid) | ||||||
|  |         end | ||||||
|  |         local status, res = pcall(eq, expected_marks, stored_marks, "extmarks for grid "..tostring(gridid)) | ||||||
|  |         if not status then | ||||||
|  |           return tostring(res) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |       for gridid, _ in pairs(self._grid_win_extmarks) do | ||||||
|  |         local expected_marks = expected.extmarks[gridid] | ||||||
|  |         if expected_marks == nil then | ||||||
|  |           return 'unexpected win_extmark for grid '..tostring(gridid) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end, expected) |   end, expected) | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -703,6 +725,7 @@ function Screen:_reset() | |||||||
|   self.cmdline_block = {} |   self.cmdline_block = {} | ||||||
|   self.wildmenu_items = nil |   self.wildmenu_items = nil | ||||||
|   self.wildmenu_pos = nil |   self.wildmenu_pos = nil | ||||||
|  |   self._grid_win_extmarks = {} | ||||||
| end | end | ||||||
|  |  | ||||||
| function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) | function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) | ||||||
| @@ -803,6 +826,13 @@ function Screen:_handle_win_close(grid) | |||||||
|   self.float_pos[grid] = nil |   self.float_pos[grid] = nil | ||||||
| end | end | ||||||
|  |  | ||||||
|  | function Screen:_handle_win_extmark(grid, ...) | ||||||
|  |   if self._grid_win_extmarks[grid] == nil then | ||||||
|  |     self._grid_win_extmarks[grid] = {} | ||||||
|  |   end | ||||||
|  |   table.insert(self._grid_win_extmarks[grid], {...}) | ||||||
|  | end | ||||||
|  |  | ||||||
| function Screen:_handle_busy_start() | function Screen:_handle_busy_start() | ||||||
|   self._busy = true |   self._busy = true | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Yatao Li
					Yatao Li