diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/buffer.c | 48 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 18 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 253 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/extmark.h | 3 | ||||
-rw-r--r-- | src/nvim/extmark_defs.h | 16 | ||||
-rw-r--r-- | src/nvim/globals.h | 8 | ||||
-rw-r--r-- | src/nvim/lua/executor.h | 9 | ||||
-rw-r--r-- | src/nvim/map.c | 1 | ||||
-rw-r--r-- | src/nvim/marktree.h | 1 | ||||
-rw-r--r-- | src/nvim/screen.c | 259 | ||||
-rw-r--r-- | src/nvim/types.h | 2 |
12 files changed, 360 insertions, 261 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2811a2da12..c8dd85b39d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -7,6 +7,7 @@ #include <stdint.h> #include <stdlib.h> #include <limits.h> + #include <lauxlib.h> #include "nvim/api/buffer.h" @@ -1285,6 +1286,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - hl_group : name of the highlight group used to highlight /// this mark. /// - virt_text : virtual text to link to this mark. +/// - ephemeral : for use with |nvim_set_decoration_provider| +/// callbacks. The mark will only be used for the current +/// redraw cycle, and not be permantently stored in the +/// buffer. /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, @@ -1318,6 +1323,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, return 0; } + bool ephemeral = false; + uint64_t id = 0; int line2 = -1, hl_id = 0; colnr_T col2 = 0; @@ -1386,6 +1393,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("ephemeral", k.data)) { + ephemeral = api_is_truthy(*v, "ephemeral", false, err); + if (ERROR_SET(err)) { + goto error; + } } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; @@ -1410,17 +1422,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } - Decoration *decor = NULL; - if (kv_size(virt_text)) { - decor = xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->virt_text = virt_text; - } else if (hl_id) { - decor = decoration_hl(hl_id); - } + // TODO(bfredl): synergize these two branches even more + if (ephemeral && redrawn_win && redrawn_win->w_buffer == buf) { + int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; + VirtText *vt_allocated = NULL; + if (kv_size(virt_text)) { + vt_allocated = xmalloc(sizeof *vt_allocated); + *vt_allocated = virt_text; + } + decorations_add_ephemeral(attr_id, (int)line, (colnr_T)col, + (int)line2, (colnr_T)col2, vt_allocated); + } else { + if (ephemeral) { + api_set_error(err, kErrorTypeException, "not yet implemented"); + goto error; + } + Decoration *decor = NULL; + if (kv_size(virt_text)) { + decor = xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->virt_text = virt_text; + } else if (hl_id) { + decor = decoration_hl(hl_id); + } - id = extmark_set(buf, (uint64_t)ns_id, id, - (int)line, (colnr_T)col, line2, col2, decor, kExtmarkNoUndo); + id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, + line2, col2, decor, kExtmarkNoUndo); + } return (Integer)id; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index e0d5862e02..84517c99fc 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1619,14 +1619,28 @@ free_exit: return virt_text; } -bool api_is_truthy(Object obj, const char *what, Error *err) +bool api_is_truthy(Object obj, const char *what, bool nil_truthy, Error *err) { if (obj.type == kObjectTypeBoolean) { return obj.data.boolean; } else if (obj.type == kObjectTypeInteger) { - return obj.data.integer; // C semantics: non-zery int is true + return obj.data.integer; // C semantics: non-zero int is true + } else if (obj.type == kObjectTypeNil) { + return nil_truthy; // caller decides what NIL (missing retval in lua) means } else { api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what); return false; } } + +const char *describe_ns(NS ns_id) +{ + String name; + handle_T id; + map_foreach(namespace_ids, name, id, { + if ((NS)id == ns_id && name.size) { + return name.data; + } + }) + return "(UNKNOWN PLUGIN)"; +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1de1472fc2..876b052a8e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2604,166 +2604,135 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) return ret; } -/// Set attrs in nvim__buf_set_lua_hl callbacks -/// -/// TODO(bfredl): This is rather pedestrian. The final -/// interface should probably be derived from a reformed -/// bufhl/virttext interface with full support for multi-line -/// ranges etc -void nvim__put_attr(Integer line, Integer col, Dictionary opts, Error *err) - FUNC_API_LUA_ONLY -{ - if (!lua_attr_active) { - return; - } - int line2 = -1, hl_id = 0; - colnr_T col2 = 0; - VirtText virt_text = KV_INITIAL_VALUE; - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("end_line", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "end_line is not an integer"); - goto error; - } - if (v->data.integer < 0) { - api_set_error(err, kErrorTypeValidation, - "end_line value outside range"); - goto error; - } - - line2 = (int)v->data.integer; - } else if (strequal("end_col", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "end_col is not an integer"); - goto error; - } - if (v->data.integer < 0 || v->data.integer > MAXCOL) { - api_set_error(err, kErrorTypeValidation, - "end_col value outside range"); - goto error; - } - - col2 = (colnr_T)v->data.integer; - } else if (strequal("hl_group", k.data)) { - String hl_group; - switch (v->type) { - case kObjectTypeString: - hl_group = v->data.string; - hl_id = syn_check_group( - (char_u *)(hl_group.data), - (int)hl_group.size); - break; - case kObjectTypeInteger: - hl_id = (int)v->data.integer; - break; - default: - api_set_error(err, kErrorTypeValidation, - "hl_group is not valid."); - goto error; - } - } else if (strequal("virt_text", k.data)) { - if (v->type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "virt_text is not an Array"); - goto error; - } - virt_text = parse_virt_text(v->data.array, err); - if (ERROR_SET(err)) { - goto error; - } - } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - goto error; - } - } - if (col2 && line2 < 0) { - line2 = (int)line; - } - - int attr = hl_id ? syn_id2attr((int)hl_id) : 0; - if (attr == 0 && !kv_size(virt_text)) { - return; - } - - VirtText *v = xmalloc(sizeof(*v)); - *v = virt_text; // LeakSanitizer be sad - decorations_add_luahl_attr(attr, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, v); -error: - return; -} - void nvim__screenshot(String path) FUNC_API_FAST { ui_call_screenshot(path); } -static void clear_luahl(bool force) +static DecorationProvider *get_provider(NS ns_id, bool force) { - if (luahl_active || force) { - api_free_luaref(luahl_start); - api_free_luaref(luahl_win); - api_free_luaref(luahl_line); - api_free_luaref(luahl_end); + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decoration_providers); i++) { + DecorationProvider *item = &kv_A(decoration_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } } - luahl_start = LUA_NOREF; - luahl_win = LUA_NOREF; - luahl_line = LUA_NOREF; - luahl_end = LUA_NOREF; - luahl_active = false; -} -/// Unstabilized interface for defining syntax hl in lua. -/// -/// This is not yet safe for general use, lua callbacks will need to -/// be restricted, like textlock and probably other stuff. -/// -/// The API on_line/nvim__put_attr is quite raw and not intended to be the -/// final shape. Ideally this should operate on chunks larger than a single -/// line to reduce interpreter overhead, and generate annotation objects -/// (bufhl/virttext) on the fly but using the same representation. -void nvim__set_luahl(DictionaryOf(LuaRef) opts, Error *err) - FUNC_API_LUA_ONLY -{ - redraw_later(NOT_VALID); - clear_luahl(false); + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decoration_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decoration_providers, (size_t)j+1); + kv_A(decoration_providers, (size_t)j+1) = kv_A(decoration_providers, j); + } + DecorationProvider *item = &kv_a(decoration_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +static void clear_provider(DecorationProvider *p) +{ + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +/// Set or change decoration provider for a namespace +/// +/// This is a very general purpose interface for having lua callbacks +/// being triggered during the redraw code. +/// +/// The expected usage is to set extmarks for the currently +/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks +/// on a per-window or per-lines basis. Use the `ephemeral` key to only +/// use the mark for the current screen redraw (the callback will be called +/// again for the next redraw ). +/// +/// Note: this function should not be called often. Rather, the callbacks +/// themselves can be used to throttle unneeded callbacks. the `on_start` +/// callback can return `false` to disable the provider until the next redraw. +/// Similarily, return `false` in `on_win` will skip the `on_lines` calls +/// for that window (but any extmarks set in `on_win` will still be used). +/// A plugin managing multiple sources of decorations should ideally only set +/// one provider, and merge the sources internally. You can use multiple `ns_id` +/// for the extmarks set/modified inside the callback anyway. +/// +/// Note: doing anything other than setting extmarks is considered experimental. +/// Doing things like changing options are not expliticly forbidden, but is +/// likely to have unexpected consequences (such as 100% CPU consumption). +/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious +/// for the moment. +/// +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param opts Callbacks invoked during redraw: +/// - on_start: called first on each screen redraw +/// ["start", tick] +/// - on_buf: called for each buffer being redrawn (before window +/// callbacks) +/// ["buf", bufnr, tick] +/// - on_win: called when starting to redraw a specific window. +/// ["win", winid, bufnr, topline, botline_guess] +/// - on_line: called for each buffer line being redrawn. (The +/// interation with fold lines is subject to change) +/// ["win", winid, bufnr, row] +/// - on_end: called at the end of a redraw cycle +/// ["end", tick] +void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, + Error *err) + FUNC_API_SINCE(7) FUNC_API_LUA_ONLY +{ + DecorationProvider *p = get_provider((NS)ns_id, true); + clear_provider(p); + + // regardless of what happens, it seems good idea to redraw + redraw_later(NOT_VALID); // TODO(bfredl): too soon? + + struct { + const char *name; + LuaRef *dest; + } cbs[] = { + { "on_start", &p->redraw_start }, + { "on_buf", &p->redraw_buf }, + { "on_win", &p->redraw_win }, + { "on_line", &p->redraw_line }, + { "on_end", &p->redraw_end }, + { NULL, NULL }, + }; for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (strequal("on_start", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - luahl_start = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_win", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - luahl_win = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (strequal("on_line", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; + size_t j; + for (j = 0; cbs[j].name; j++) { + if (strequal(cbs[j].name, k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[j].name); + goto error; + } + *(cbs[j].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; + break; } - luahl_line = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else { + } + if (!cbs[j].name) { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; } } - luahl_active = true; + + p->active = true; return; error: - clear_luahl(true); + clear_provider(p); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ea968d9592..5e5a20e8f2 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -544,6 +544,9 @@ struct file_buffer { long b_mod_xlines; // number of extra buffer lines inserted; // negative when lines were deleted wininfo_T *b_wininfo; // list of last used info for each window + int b_mod_tick_syn; // last display tick syntax was updated + int b_mod_tick_deco; // last display tick decoration providers + // where invoked long b_mtime; // last change time of original file long b_mtime_read; // last change time when reading diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index d394d4d806..101527ab4f 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -1,6 +1,7 @@ #ifndef NVIM_EXTMARK_H #define NVIM_EXTMARK_H +#include "nvim/pos.h" #include "nvim/buffer_defs.h" #include "nvim/extmark_defs.h" #include "nvim/marktree.h" @@ -98,6 +99,8 @@ typedef struct { VirtText *virt_text; } DecorationRedrawState; +EXTERN kvec_t(DecorationProvider) decoration_providers INIT(= KV_INITIAL_VALUE); +EXTERN win_T *redrawn_win INIT(= NULL); // used for ephemeral extmarks #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.h.generated.h" diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index 76804db848..f5ca0ebbb0 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_EXTMARK_DEFS_H #define NVIM_EXTMARK_DEFS_H -#include "nvim/pos.h" // for colnr_T +#include "nvim/types.h" #include "nvim/lib/kvec.h" typedef struct { @@ -42,4 +42,18 @@ typedef enum { kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable } ExtmarkOp; +typedef struct { + NS ns_id; + bool active; + LuaRef redraw_start; + LuaRef redraw_buf; + LuaRef redraw_win; + LuaRef redraw_line; + LuaRef redraw_end; +} DecorationProvider; + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorationProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF } + #endif // NVIM_EXTMARK_DEFS_H diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ddb69fc567..2db8689a56 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -125,8 +125,6 @@ typedef off_t off_T; EXTERN int mod_mask INIT(= 0x0); // current key modifiers -EXTERN bool lua_attr_active INIT(= false); - // Cmdline_row is the row where the command line starts, just below the // last window. // When the cmdline gets longer than the available space the screen gets @@ -405,12 +403,6 @@ EXTERN int sys_menu INIT(= false); // ('lines' and 'rows') must not be changed. EXTERN int updating_screen INIT(= 0); -EXTERN bool luahl_active INIT(= false); -EXTERN LuaRef luahl_start INIT(= LUA_NOREF); -EXTERN LuaRef luahl_win INIT(= LUA_NOREF); -EXTERN LuaRef luahl_line INIT(= LUA_NOREF); -EXTERN LuaRef luahl_end INIT(= LUA_NOREF); - // All windows are linked in a list. firstwin points to the first entry, // lastwin to the last entry (can be the same as firstwin) and curwin to the // currently active window. diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 6599b44584..1d7a15d9aa 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -24,6 +24,15 @@ EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); memcpy(&err_->msg[0], s, sizeof(s)); \ } while (0) +#define NLUA_CLEAR_REF(x) \ + do { \ + /* Take the address to avoid double evaluation. #1375 */ \ + if ((x) != LUA_NOREF) { \ + api_free_luaref(x); \ + (x) = LUA_NOREF; \ + } \ + } while (0) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif diff --git a/src/nvim/map.c b/src/nvim/map.c index 0c6bad7cb6..ca8ea76333 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -183,7 +183,6 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) #define EXTMARK_NS_INITIALIZER { 0, 0 } MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER) -#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } #define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL } MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 0c73e75b2e..8a1c564a6d 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,6 +2,7 @@ #define NVIM_MARKTREE_H #include <stdint.h> +#include "nvim/pos.h" #include "nvim/map.h" #include "nvim/garray.h" diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a75b146024..1191886888 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -119,10 +119,12 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/lua/executor.h" +#include "nvim/lib/kvec.h" #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ +typedef kvec_withinit_t(DecorationProvider *, 4) Providers; // temporary buffer for rendering a single screenline, so it can be // compared with previous contents to calculate smallest delta. @@ -156,13 +158,43 @@ static bool msg_grid_invalid = false; static bool resizing = false; -static bool do_luahl_line = false; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif #define SEARCH_HL_PRIORITY 0 +static char * provider_first_error = NULL; + +static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, + Array args, bool default_true) +{ + Error err = ERROR_INIT; + + textlock++; + Object ret = nlua_call_ref(ref, name, args, true, &err); + textlock--; + + if (!ERROR_SET(&err) + && api_is_truthy(ret, "provider %s retval", default_true, &err)) { + return true; + } + + if (ERROR_SET(&err)) { + const char *ns_name = describe_ns(ns_id); + ELOG("error in provider %s:%s: %s", ns_name, name, err.msg); + bool verbose_errs = true; // TODO(bfredl): + if (verbose_errs && provider_first_error == NULL) { + static char errbuf[IOSIZE]; + snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); + provider_first_error = xstrdup(errbuf); + } + } + + api_free_object(ret); + return false; +} + /* * Redraw the current window later, with update_screen(type). * Set must_redraw only if not already set to a higher value. @@ -446,6 +478,29 @@ int update_screen(int type) ui_comp_set_screen_valid(true); + Providers providers; + kvi_init(providers); + for (size_t i = 0; i < kv_size(decoration_providers); i++) { + DecorationProvider *p = &kv_A(decoration_providers, i); + if (!p->active) { + continue; + } + + bool active; + if (p->redraw_start != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 2); + args.items[0] = INTEGER_OBJ(display_tick); + args.items[1] = INTEGER_OBJ(type); + active = provider_invoke(p->ns_id, "start", p->redraw_start, args, true); + } else { + active = true; + } + + if (active) { + kvi_push(providers, p); + } + } + if (clear_cmdline) /* going to clear cmdline (done below) */ check_for_delay(FALSE); @@ -494,30 +549,24 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { update_window_hl(wp, type >= NOT_VALID); - if (wp->w_buffer->b_mod_set) { - win_T *wwp; - - // Check if we already did this buffer. - for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) { - if (wwp->w_buffer == wp->w_buffer) { - break; - } - } - if (wwp == wp && syntax_present(wp)) { - syn_stack_apply_changes(wp->w_buffer); - } - - buf_T *buf = wp->w_buffer; - if (luahl_active && luahl_start != LUA_NOREF) { - Error err = ERROR_INIT; - FIXED_TEMP_ARRAY(args, 2); - args.items[0] = BUFFER_OBJ(buf->handle); - args.items[1] = INTEGER_OBJ(display_tick); - nlua_call_ref(luahl_start, "start", args, false, &err); - if (ERROR_SET(&err)) { - ELOG("error in luahl start: %s", err.msg); - api_clear_error(&err); + buf_T *buf = wp->w_buffer; + if (buf->b_mod_set) { + if (buf->b_mod_tick_syn < display_tick + && syntax_present(wp)) { + syn_stack_apply_changes(buf); + buf->b_mod_tick_syn = display_tick; + } + + if (buf->b_mod_tick_deco < display_tick) { + for (size_t i = 0; i < kv_size(providers); i++) { + DecorationProvider *p = kv_A(providers, i); + if (p && p->redraw_buf != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = BUFFER_OBJ(buf->handle); + provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true); + } } + buf->b_mod_tick_deco = display_tick; } } } @@ -531,6 +580,8 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redrawn_win = wp; + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { grid_invalidate(&wp->w_grid); wp->w_redr_type = NOT_VALID; @@ -541,13 +592,15 @@ int update_screen(int type) did_one = TRUE; start_search_hl(); } - win_update(wp); + win_update(wp, &providers); } /* redraw status line after the window to minimize cursor movement */ if (wp->w_redr_status) { win_redr_status(wp); } + + redrawn_win = NULL; } end_search_hl(); @@ -578,6 +631,21 @@ int update_screen(int type) maybe_intro_message(); did_intro = TRUE; + for (size_t i = 0; i < kv_size(providers); i++) { + DecorationProvider *p = kv_A(providers, i); + if (!p->active) { + continue; + } + + if (p->redraw_end != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = INTEGER_OBJ(display_tick); + provider_invoke(p->ns_id, "end", p->redraw_end, args, true); + } + } + kvi_destroy(providers); + + // either cmdline is cleared, not drawn or mode is last drawn cmdline_was_last_drawn = false; return OK; @@ -635,15 +703,15 @@ bool win_cursorline_standout(const win_T *wp) } static DecorationRedrawState decorations; -bool decorations_active = false; -void decorations_add_luahl_attr(int attr_id, - int start_row, int start_col, - int end_row, int end_col, VirtText *virt_text) +void decorations_add_ephemeral(int attr_id, + int start_row, int start_col, + int end_row, int end_col, VirtText *virt_text) { kv_push(decorations.active, ((HlRange){ start_row, start_col, - end_row, end_col, attr_id, virt_text, true })); + end_row, end_col, + attr_id, virt_text, virt_text != NULL })); } /* @@ -673,7 +741,7 @@ void decorations_add_luahl_attr(int attr_id, * mid: from mid_start to mid_end (update inversion or changed text) * bot: from bot_start to last row (when scrolled up) */ -static void win_update(win_T *wp) +static void win_update(win_T *wp, Providers *providers) { buf_T *buf = wp->w_buffer; int type; @@ -1239,33 +1307,27 @@ static void win_update(win_T *wp) srow = 0; lnum = wp->w_topline; // first line shown in window - decorations_active = decorations_redraw_reset(buf, &decorations); + decorations_redraw_reset(buf, &decorations); - do_luahl_line = false; + Providers line_providers; + kvi_init(line_providers); - if (luahl_win != LUA_NOREF) { - Error err = ERROR_INIT; - FIXED_TEMP_ARRAY(args, 4); - linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) - ? wp->w_botline - : (wp->w_topline + wp->w_height_inner)); - args.items[0] = WINDOW_OBJ(wp->handle); - args.items[1] = BUFFER_OBJ(buf->handle); - // TODO(bfredl): we are not using this, but should be first drawn line? - args.items[2] = INTEGER_OBJ(wp->w_topline-1); - args.items[3] = INTEGER_OBJ(knownmax); - // TODO(bfredl): we could allow this callback to change mod_top, mod_bot. - // For now the "start" callback is expected to use nvim__buf_redraw_range. - Object ret = nlua_call_ref(luahl_win, "win", args, true, &err); - - if (!ERROR_SET(&err) && api_is_truthy(ret, "luahl_window retval", &err)) { - do_luahl_line = true; - decorations_active = true; - } + linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) + ? wp->w_botline + : (wp->w_topline + wp->w_height_inner)); - if (ERROR_SET(&err)) { - ELOG("error in luahl window: %s", err.msg); - api_clear_error(&err); + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorationProvider *p = kv_A(*providers, k); + if (p && p->redraw_win != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 4); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(buf->handle); + // TODO(bfredl): we are not using this, but should be first drawn line? + args.items[2] = INTEGER_OBJ(wp->w_topline-1); + args.items[3] = INTEGER_OBJ(knownmax); + if (provider_invoke(p->ns_id, "win", p->redraw_win, args, true)) { + kvi_push(line_providers, p); + } } } @@ -1483,7 +1545,7 @@ static void win_update(win_T *wp) // Display one line row = win_line(wp, lnum, srow, foldinfo.fi_lines ? srow : wp->w_grid.Rows, - mod_top == 0, false, foldinfo); + mod_top == 0, false, foldinfo, &line_providers); wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0; wp->w_lines[idx].wl_lastlnum = lnum; @@ -1519,7 +1581,8 @@ static void win_update(win_T *wp) // 'relativenumber' set: The text doesn't need to be drawn, but // the number column nearly always does. foldinfo_T info = fold_info(wp, lnum); - (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, info); + (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, + info, &line_providers); } // This line does not need to be drawn, advance to the next one. @@ -1615,6 +1678,8 @@ static void win_update(win_T *wp) HLF_EOB); } + kvi_destroy(line_providers); + if (wp->w_redr_type >= REDRAW_TOP) { draw_vsep_win(wp, 0); } @@ -1674,7 +1739,7 @@ static void win_update(win_T *wp) curbuf->b_mod_set = false; j = curbuf->b_mod_xlines; curbuf->b_mod_xlines = 0; - win_update(curwin); + win_update(curwin, providers); curbuf->b_mod_set = i; curbuf->b_mod_xlines = j; } @@ -1919,28 +1984,23 @@ fill_foldcolumn( return MAX(char_counter + (fdc-i), (size_t)fdc); } - /// Display line "lnum" of window 'wp' on the screen. -/// Start at row "startrow", stop when "endrow" is reached. /// wp->w_virtcol needs to be valid. /// -/// @param lnum line to display -/// @param endrow stop drawing once reaching this row -/// @param nochange not updating for changed text -/// @param number_only only update the number column -/// @param foldinfo fold info for this line +/// @param lnum line to display +/// @param startrow first row relative to window grid +/// @param endrow last grid row to be redrawn +/// @param nochange not updating for changed text +/// @param number_only only update the number column +/// @param foldinfo fold info for this line +/// @param[in, out] providers decoration providers active this line +/// items will be disables if they cause errors +/// or explicitly return `false`. /// -/// @return the number of last row the line occupies. -static int -win_line ( - win_T *wp, - linenr_T lnum, - int startrow, - int endrow, - bool nochange, - bool number_only, - foldinfo_T foldinfo -) +/// @return the number of last row the line occupies. +static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, + bool nochange, bool number_only, foldinfo_T foldinfo, + Providers *providers) { int c = 0; // init for GCC long vcol = 0; // virtual column (for tabs) @@ -2084,7 +2144,7 @@ win_line ( row = startrow; - char *luatext = NULL; + char *err_text = NULL; buf_T *buf = wp->w_buffer; @@ -2109,30 +2169,33 @@ win_line ( } } - if (decorations_active) { - if (do_luahl_line && luahl_line != LUA_NOREF) { - Error err = ERROR_INIT; + has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1, + &decorations); + + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorationProvider *p = kv_A(*providers, k); + if (p && p->redraw_line != LUA_NOREF) { FIXED_TEMP_ARRAY(args, 3); args.items[0] = WINDOW_OBJ(wp->handle); args.items[1] = BUFFER_OBJ(buf->handle); args.items[2] = INTEGER_OBJ(lnum-1); - lua_attr_active = true; - extra_check = true; - nlua_call_ref(luahl_line, "line", args, false, &err); - lua_attr_active = false; - - if (ERROR_SET(&err)) { - ELOG("error in luahl line: %s", err.msg); - luatext = err.msg; - do_virttext = true; + if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) { + has_decorations = true; + } else { + // return 'false' or error: skip rest of this window + kv_A(*providers, k) = NULL; } } + } - has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1, - &decorations); - if (has_decorations) { - extra_check = true; - } + if (has_decorations) { + extra_check = true; + } + + if (provider_first_error) { + err_text = provider_first_error; + provider_first_error = NULL; + do_virttext = true; } // Check for columns to display for 'colorcolumn'. @@ -3835,8 +3898,10 @@ win_line ( draw_color_col = advance_color_col(VCOL_HLC, &color_cols); VirtText virt_text = KV_INITIAL_VALUE; - if (luatext) { - kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); + if (err_text) { + int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg")); + kv_push(virt_text, ((VirtTextChunk){ .text = err_text, + .hl_id = hl_err })); do_virttext = true; } else if (has_decorations) { VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations); @@ -4256,7 +4321,7 @@ win_line ( } xfree(p_extra_free); - xfree(luatext); + xfree(err_text); return row; } diff --git a/src/nvim/types.h b/src/nvim/types.h index 91420c087f..a3d87f35ca 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -21,6 +21,8 @@ typedef int handle_T; // absent callback etc. typedef int LuaRef; +typedef uint64_t NS; + typedef struct expand expand_T; typedef enum { |