decoration_provider.c (11733B)
1 #include <assert.h> 2 #include <lauxlib.h> 3 #include <stdint.h> 4 #include <string.h> 5 6 #include "klib/kvec.h" 7 #include "nvim/api/extmark.h" 8 #include "nvim/api/private/defs.h" 9 #include "nvim/api/private/helpers.h" 10 #include "nvim/buffer_defs.h" 11 #include "nvim/decoration.h" 12 #include "nvim/decoration_defs.h" 13 #include "nvim/decoration_provider.h" 14 #include "nvim/globals.h" 15 #include "nvim/highlight.h" 16 #include "nvim/log.h" 17 #include "nvim/lua/executor.h" 18 #include "nvim/message.h" 19 #include "nvim/move.h" 20 #include "nvim/pos_defs.h" 21 22 #include "decoration_provider.c.generated.h" 23 24 static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; 25 26 static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg) 27 { 28 const char *ns = describe_ns(provider->ns_id, "(UNKNOWN PLUGIN)"); 29 ELOG("Error in decoration provider \"%s\" (ns=%s):\n%s", name, ns, msg); 30 msg_schedule_semsg_multiline("Decoration provider \"%s\" (ns=%s):\n%s", name, ns, msg); 31 } 32 33 // Note we pass in a provider index as this function may cause decor_providers providers to be 34 // reallocated so we need to be careful with DecorProvider pointers 35 static bool decor_provider_invoke(int provider_idx, const char *name, LuaRef ref, Array args, 36 bool default_true, Array *res) 37 { 38 Error err = ERROR_INIT; 39 40 textlock++; 41 Object ret = nlua_call_ref(ref, name, args, res ? kRetMulti : kRetNilBool, NULL, &err); 42 textlock--; 43 44 // We get the provider here via an index in case the above call to nlua_call_ref causes 45 // decor_providers to be reallocated. 46 DecorProvider *provider = &kv_A(decor_providers, provider_idx); 47 if (!ERROR_SET(&err)) { 48 provider->error_count = 0; 49 if (res) { 50 assert(ret.type == kObjectTypeArray); 51 *res = ret.data.array; 52 return true; 53 } else { 54 if (api_object_to_bool(ret, "provider %s retval", default_true, &err)) { 55 return true; 56 } 57 } 58 } 59 60 if (ERROR_SET(&err) && provider->error_count < CB_MAX_ERROR) { 61 decor_provider_error(provider, name, err.msg); 62 provider->error_count++; 63 64 if (provider->error_count >= CB_MAX_ERROR) { 65 provider->state = kDecorProviderDisabled; 66 } 67 } 68 69 api_clear_error(&err); 70 api_free_object(ret); // TODO(bfredl): wants to be on an arena 71 return false; 72 } 73 74 void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col) 75 { 76 for (size_t i = 0; i < kv_size(decor_providers); i++) { 77 DecorProvider *p = &kv_A(decor_providers, i); 78 if (p->state != kDecorProviderDisabled && p->spell_nav != LUA_NOREF) { 79 MAXSIZE_TEMP_ARRAY(args, 6); 80 ADD_C(args, INTEGER_OBJ(wp->handle)); 81 ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle)); 82 ADD_C(args, INTEGER_OBJ(start_row)); 83 ADD_C(args, INTEGER_OBJ(start_col)); 84 ADD_C(args, INTEGER_OBJ(end_row)); 85 ADD_C(args, INTEGER_OBJ(end_col)); 86 decor_provider_invoke((int)i, "spell", p->spell_nav, args, true, NULL); 87 } 88 } 89 } 90 91 /// @return whether a provider placed any marks in the callback. 92 bool decor_providers_invoke_conceal_line(win_T *wp, int row) 93 { 94 size_t keys = wp->w_buffer->b_marktree->n_keys; 95 for (size_t i = 0; i < kv_size(decor_providers); i++) { 96 DecorProvider *p = &kv_A(decor_providers, i); 97 if (p->state != kDecorProviderDisabled && p->conceal_line != LUA_NOREF) { 98 MAXSIZE_TEMP_ARRAY(args, 4); 99 ADD_C(args, INTEGER_OBJ(wp->handle)); 100 ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle)); 101 ADD_C(args, INTEGER_OBJ(row)); 102 decor_provider_invoke((int)i, "conceal_line", p->conceal_line, args, true, NULL); 103 } 104 } 105 return wp->w_buffer->b_marktree->n_keys > keys; 106 } 107 108 /// For each provider invoke the 'start' callback 109 /// 110 /// @param[out] providers Decoration providers 111 /// @param[out] err Provider err 112 void decor_providers_start(void) 113 { 114 for (size_t i = 0; i < kv_size(decor_providers); i++) { 115 DecorProvider *p = &kv_A(decor_providers, i); 116 if (p->state != kDecorProviderDisabled && p->redraw_start != LUA_NOREF) { 117 MAXSIZE_TEMP_ARRAY(args, 2); 118 ADD_C(args, INTEGER_OBJ((int)display_tick)); 119 bool active = decor_provider_invoke((int)i, "start", p->redraw_start, args, true, NULL); 120 kv_A(decor_providers, i).state = active ? kDecorProviderActive : kDecorProviderRedrawDisabled; 121 } else if (p->state != kDecorProviderDisabled) { 122 kv_A(decor_providers, i).state = kDecorProviderActive; 123 } 124 } 125 } 126 127 /// For each provider run 'win'. If result is not false, then collect the 128 /// 'on_line' callback to call inside win_line 129 /// 130 /// @param wp Window 131 /// @param providers Decoration providers 132 /// @param[out] line_providers Enabled line providers to invoke in win_line 133 /// @param[out] err Provider error 134 void decor_providers_invoke_win(win_T *wp) 135 { 136 // this might change in the future 137 // then we would need decor_state.running_decor_provider just like "on_line" below 138 assert(decor_state.current_end == 0 139 && decor_state.future_begin == (int)kv_size(decor_state.ranges_i)); 140 141 if (kv_size(decor_providers) > 0) { 142 validate_botline_win(wp); 143 } 144 linenr_T botline = MIN(wp->w_botline, wp->w_buffer->b_ml.ml_line_count); 145 146 for (size_t i = 0; i < kv_size(decor_providers); i++) { 147 DecorProvider *p = &kv_A(decor_providers, i); 148 if (p->state == kDecorProviderWinDisabled) { 149 p->state = kDecorProviderActive; 150 } 151 152 p->win_skip_row = 0; 153 p->win_skip_col = 0; 154 155 if (p->state == kDecorProviderActive && p->redraw_win != LUA_NOREF) { 156 MAXSIZE_TEMP_ARRAY(args, 4); 157 ADD_C(args, WINDOW_OBJ(wp->handle)); 158 ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle)); 159 // TODO(bfredl): we are not using this, but should be first drawn line? 160 ADD_C(args, INTEGER_OBJ(wp->w_topline - 1)); 161 ADD_C(args, INTEGER_OBJ(botline - 1)); 162 // TODO(bfredl): could skip a call if retval was interpreted like range? 163 if (!decor_provider_invoke((int)i, "win", p->redraw_win, args, true, NULL)) { 164 kv_A(decor_providers, i).state = kDecorProviderWinDisabled; 165 } 166 } 167 } 168 } 169 170 /// For each provider invoke the 'line' callback for a given window row. 171 /// 172 /// @param wp Window 173 /// @param providers Decoration providers 174 /// @param row Row to invoke line callback for 175 /// @param[out] has_decor Set when at least one provider invokes a line callback 176 /// @param[out] err Provider error 177 void decor_providers_invoke_line(win_T *wp, int row) 178 { 179 decor_state.running_decor_provider = true; 180 for (size_t i = 0; i < kv_size(decor_providers); i++) { 181 DecorProvider *p = &kv_A(decor_providers, i); 182 if (p->state == kDecorProviderActive && p->redraw_line != LUA_NOREF) { 183 MAXSIZE_TEMP_ARRAY(args, 3); 184 ADD_C(args, WINDOW_OBJ(wp->handle)); 185 ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle)); 186 ADD_C(args, INTEGER_OBJ(row)); 187 if (!decor_provider_invoke((int)i, "line", p->redraw_line, args, true, NULL)) { 188 // return 'false' or error: skip rest of this window 189 kv_A(decor_providers, i).state = kDecorProviderWinDisabled; 190 } 191 192 hl_check_ns(); 193 } 194 } 195 decor_state.running_decor_provider = false; 196 } 197 198 void decor_providers_invoke_range(win_T *wp, int start_row, int start_col, int end_row, int end_col) 199 { 200 decor_state.running_decor_provider = true; 201 for (size_t i = 0; i < kv_size(decor_providers); i++) { 202 DecorProvider *p = &kv_A(decor_providers, i); 203 if (p->state == kDecorProviderActive && p->redraw_range != LUA_NOREF) { 204 if (p->win_skip_row > end_row || (p->win_skip_row == end_row && p->win_skip_col >= end_col)) { 205 continue; 206 } 207 208 MAXSIZE_TEMP_ARRAY(args, 6); 209 ADD_C(args, WINDOW_OBJ(wp->handle)); 210 ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle)); 211 ADD_C(args, INTEGER_OBJ(start_row)); 212 ADD_C(args, INTEGER_OBJ(start_col)); 213 ADD_C(args, INTEGER_OBJ(end_row)); 214 ADD_C(args, INTEGER_OBJ(end_col)); 215 Array res = ARRAY_DICT_INIT; 216 bool status = decor_provider_invoke((int)i, "range", p->redraw_range, args, true, &res); 217 p = &kv_A(decor_providers, i); // lua call might have reallocated decor_providers 218 219 if (!status) { 220 // error: skip rest of this window 221 p->state = kDecorProviderWinDisabled; 222 } else if (res.size >= 1) { 223 Object first = res.items[0]; 224 if (first.type == kObjectTypeBoolean) { 225 if (first.data.boolean == false) { 226 p->state = kDecorProviderWinDisabled; 227 } 228 } else if (first.type == kObjectTypeInteger) { 229 Integer row = first.data.integer; 230 Integer col = 0; 231 if (res.size >= 2) { 232 Object second = res.items[1]; 233 if (second.type == kObjectTypeInteger) { 234 col = second.data.integer; 235 } 236 } 237 p->win_skip_row = (int)row; 238 p->win_skip_col = (int)col; 239 } 240 } 241 242 api_free_array(res); 243 244 hl_check_ns(); 245 } 246 } 247 decor_state.running_decor_provider = false; 248 } 249 250 /// For each provider invoke the 'buf' callback for a given buffer. 251 /// 252 /// @param buf Buffer 253 /// @param providers Decoration providers 254 /// @param[out] err Provider error 255 void decor_providers_invoke_buf(buf_T *buf) 256 { 257 for (size_t i = 0; i < kv_size(decor_providers); i++) { 258 DecorProvider *p = &kv_A(decor_providers, i); 259 if (p->state == kDecorProviderActive && p->redraw_buf != LUA_NOREF) { 260 MAXSIZE_TEMP_ARRAY(args, 2); 261 ADD_C(args, BUFFER_OBJ(buf->handle)); 262 ADD_C(args, INTEGER_OBJ((int64_t)display_tick)); 263 decor_provider_invoke((int)i, "buf", p->redraw_buf, args, true, NULL); 264 } 265 } 266 } 267 268 /// For each provider invoke the 'end' callback 269 /// 270 /// @param providers Decoration providers 271 /// @param displaytick Display tick 272 /// @param[out] err Provider error 273 void decor_providers_invoke_end(void) 274 { 275 for (size_t i = 0; i < kv_size(decor_providers); i++) { 276 DecorProvider *p = &kv_A(decor_providers, i); 277 if (p->state != kDecorProviderDisabled && p->redraw_end != LUA_NOREF) { 278 MAXSIZE_TEMP_ARRAY(args, 1); 279 ADD_C(args, INTEGER_OBJ((int)display_tick)); 280 decor_provider_invoke((int)i, "end", p->redraw_end, args, true, NULL); 281 } 282 } 283 decor_check_to_be_deleted(); 284 } 285 286 /// Mark all cached state of per-namespace highlights as invalid. Revalidate 287 /// current namespace. 288 /// 289 /// Expensive! Should on be called by an already throttled validity check 290 /// like highlight_changed() (throttled to the next redraw or mode change) 291 void decor_provider_invalidate_hl(void) 292 { 293 for (size_t i = 0; i < kv_size(decor_providers); i++) { 294 kv_A(decor_providers, i).hl_cached = false; 295 } 296 297 if (ns_hl_active) { 298 ns_hl_active = -1; 299 hl_check_ns(); 300 } 301 } 302 303 DecorProvider *get_decor_provider(NS ns_id, bool force) 304 { 305 assert(ns_id > 0); 306 size_t len = kv_size(decor_providers); 307 for (size_t i = 0; i < len; i++) { 308 DecorProvider *p = &kv_A(decor_providers, i); 309 if (p->ns_id == ns_id) { 310 return p; 311 } 312 } 313 314 if (!force) { 315 return NULL; 316 } 317 318 DecorProvider *item = &kv_a(decor_providers, len); 319 *item = DECORATION_PROVIDER_INIT(ns_id); 320 321 return item; 322 } 323 324 void decor_provider_clear(DecorProvider *p) 325 { 326 if (p == NULL) { 327 return; 328 } 329 NLUA_CLEAR_REF(p->redraw_start); 330 NLUA_CLEAR_REF(p->redraw_buf); 331 NLUA_CLEAR_REF(p->redraw_win); 332 NLUA_CLEAR_REF(p->redraw_line); 333 NLUA_CLEAR_REF(p->redraw_range); 334 NLUA_CLEAR_REF(p->redraw_end); 335 NLUA_CLEAR_REF(p->spell_nav); 336 NLUA_CLEAR_REF(p->conceal_line); 337 p->state = kDecorProviderDisabled; 338 } 339 340 void decor_free_all_mem(void) 341 { 342 for (size_t i = 0; i < kv_size(decor_providers); i++) { 343 decor_provider_clear(&kv_A(decor_providers, i)); 344 } 345 kv_destroy(decor_providers); 346 }