neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }