neovim

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

buffer_updates.c (12646B)


      1 #include <inttypes.h>
      2 #include <lauxlib.h>
      3 #include <stdbool.h>
      4 #include <stddef.h>
      5 
      6 #include "klib/kvec.h"
      7 #include "nvim/api/buffer.h"
      8 #include "nvim/api/private/defs.h"
      9 #include "nvim/api/private/helpers.h"
     10 #include "nvim/assert_defs.h"
     11 #include "nvim/buffer.h"
     12 #include "nvim/buffer_defs.h"
     13 #include "nvim/buffer_updates.h"
     14 #include "nvim/globals.h"
     15 #include "nvim/log.h"
     16 #include "nvim/lua/executor.h"
     17 #include "nvim/memline.h"
     18 #include "nvim/memory.h"
     19 #include "nvim/memory_defs.h"
     20 #include "nvim/msgpack_rpc/channel.h"
     21 #include "nvim/pos_defs.h"
     22 #include "nvim/types_defs.h"
     23 
     24 #include "buffer_updates.c.generated.h"  // IWYU pragma: keep
     25 
     26 // Register a channel. Return True if the channel was added, or already added.
     27 // Return False if the channel couldn't be added because the buffer is
     28 // unloaded.
     29 bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb, bool send_buffer)
     30 {
     31  // must fail if the buffer isn't loaded
     32  if (buf->b_ml.ml_mfp == NULL) {
     33    return false;
     34  }
     35 
     36  if (channel_id == LUA_INTERNAL_CALL) {
     37    kv_push(buf->update_callbacks, cb);
     38    if (cb.utf_sizes) {
     39      buf->update_need_codepoints = true;
     40    }
     41    return true;
     42  }
     43 
     44  // count how many channels are currently watching the buffer
     45  size_t size = kv_size(buf->update_channels);
     46  for (size_t i = 0; i < size; i++) {
     47    if (kv_A(buf->update_channels, i) == channel_id) {
     48      // buffer is already registered ... nothing to do
     49      return true;
     50    }
     51  }
     52 
     53  // append the channelid to the list
     54  kv_push(buf->update_channels, channel_id);
     55 
     56  if (send_buffer) {
     57    MAXSIZE_TEMP_ARRAY(args, 6);
     58 
     59    // the first argument is always the buffer handle
     60    ADD_C(args, BUFFER_OBJ(buf->handle));
     61    ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
     62    // the first line that changed (zero-indexed)
     63    ADD_C(args, INTEGER_OBJ(0));
     64    // the last line that was changed
     65    ADD_C(args, INTEGER_OBJ(-1));
     66 
     67    // collect buffer contents
     68 
     69    STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM");
     70    size_t line_count = (size_t)buf->b_ml.ml_line_count;
     71 
     72    Array linedata = ARRAY_DICT_INIT;
     73    Arena arena = ARENA_EMPTY;
     74    if (line_count > 0) {
     75      linedata = arena_array(&arena, line_count);
     76      buf_collect_lines(buf, line_count, 1, 0, true, &linedata, NULL, &arena);
     77    }
     78 
     79    ADD_C(args, ARRAY_OBJ(linedata));
     80    ADD_C(args, BOOLEAN_OBJ(false));
     81 
     82    rpc_send_event(channel_id, "nvim_buf_lines_event", args);
     83    arena_mem_free(arena_finish(&arena));
     84  } else {
     85    buf_updates_changedtick_single(buf, channel_id);
     86  }
     87 
     88  return true;
     89 }
     90 
     91 bool buf_updates_active(buf_T *buf)
     92  FUNC_ATTR_PURE
     93 {
     94  return kv_size(buf->update_channels) || kv_size(buf->update_callbacks);
     95 }
     96 
     97 void buf_updates_send_end(buf_T *buf, uint64_t channelid)
     98 {
     99  MAXSIZE_TEMP_ARRAY(args, 1);
    100  ADD_C(args, BUFFER_OBJ(buf->handle));
    101  rpc_send_event(channelid, "nvim_buf_detach_event", args);
    102 }
    103 
    104 void buf_updates_unregister(buf_T *buf, uint64_t channelid)
    105 {
    106  size_t size = kv_size(buf->update_channels);
    107  if (!size) {
    108    return;
    109  }
    110 
    111  // go through list backwards and remove the channel id each time it appears
    112  // (it should never appear more than once)
    113  size_t j = 0;
    114  size_t found = 0;
    115  for (size_t i = 0; i < size; i++) {
    116    if (kv_A(buf->update_channels, i) == channelid) {
    117      found++;
    118    } else {
    119      // copy item backwards into prior slot if needed
    120      if (i != j) {
    121        kv_A(buf->update_channels, j) = kv_A(buf->update_channels, i);
    122      }
    123      j++;
    124    }
    125  }
    126 
    127  if (found) {
    128    // remove X items from the end of the array
    129    buf->update_channels.size -= found;
    130 
    131    // make a new copy of the active array without the channelid in it
    132    buf_updates_send_end(buf, channelid);
    133 
    134    if (found == size) {
    135      kv_destroy(buf->update_channels);
    136      kv_init(buf->update_channels);
    137    }
    138  }
    139 }
    140 
    141 void buf_free_callbacks(buf_T *buf)
    142 {
    143  kv_destroy(buf->update_channels);
    144  for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
    145    buffer_update_callbacks_free(kv_A(buf->update_callbacks, i));
    146  }
    147  kv_destroy(buf->update_callbacks);
    148 }
    149 
    150 void buf_updates_unload(buf_T *buf, bool can_reload)
    151 {
    152  size_t size = kv_size(buf->update_channels);
    153  if (size) {
    154    for (size_t i = 0; i < size; i++) {
    155      buf_updates_send_end(buf, kv_A(buf->update_channels, i));
    156    }
    157    kv_destroy(buf->update_channels);
    158    kv_init(buf->update_channels);
    159  }
    160 
    161  size_t j = 0;
    162  for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
    163    BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
    164    LuaRef thecb = LUA_NOREF;
    165 
    166    bool keep = false;
    167    if (can_reload && cb.on_reload != LUA_NOREF) {
    168      keep = true;
    169      thecb = cb.on_reload;
    170    } else if (cb.on_detach != LUA_NOREF) {
    171      thecb = cb.on_detach;
    172    }
    173 
    174    if (thecb != LUA_NOREF) {
    175      MAXSIZE_TEMP_ARRAY(args, 1);
    176 
    177      // the first argument is always the buffer handle
    178      ADD_C(args, BUFFER_OBJ(buf->handle));
    179 
    180      TEXTLOCK_WRAP({
    181        nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL, NULL);
    182      });
    183    }
    184 
    185    if (keep) {
    186      kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
    187    } else {
    188      buffer_update_callbacks_free(cb);
    189    }
    190  }
    191  kv_size(buf->update_callbacks) = j;
    192  if (kv_size(buf->update_callbacks) == 0) {
    193    kv_destroy(buf->update_callbacks);
    194    kv_init(buf->update_callbacks);
    195  }
    196 }
    197 
    198 void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
    199                              int64_t num_removed)
    200 {
    201  size_t deleted_codepoints, deleted_codeunits;
    202  size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints,
    203                                                &deleted_codeunits);
    204 
    205  if (!buf_updates_active(buf)) {
    206    return;
    207  }
    208 
    209  // Don't send b:changedtick during 'inccommand' preview if "buf" is the current buffer.
    210  bool send_tick = !(cmdpreview && buf == curbuf);
    211 
    212  // if one the channels doesn't work, put its ID here so we can remove it later
    213  uint64_t badchannelid = 0;
    214 
    215  Arena arena = ARENA_EMPTY;
    216  Array linedata = ARRAY_DICT_INIT;
    217  if (num_added > 0 && kv_size(buf->update_channels)) {
    218    STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM");
    219    linedata = arena_array(&arena, (size_t)num_added);
    220    buf_collect_lines(buf, (size_t)num_added, firstline, 0, true, &linedata,
    221                      NULL, &arena);
    222  }
    223 
    224  // notify each of the active channels
    225  for (size_t i = 0; i < kv_size(buf->update_channels); i++) {
    226    uint64_t channelid = kv_A(buf->update_channels, i);
    227 
    228    // send through the changes now channel contents now
    229    MAXSIZE_TEMP_ARRAY(args, 6);
    230 
    231    // the first argument is always the buffer handle
    232    ADD_C(args, BUFFER_OBJ(buf->handle));
    233 
    234    // next argument is b:changedtick
    235    ADD_C(args, send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL);
    236 
    237    // the first line that changed (zero-indexed)
    238    ADD_C(args, INTEGER_OBJ(firstline - 1));
    239 
    240    // the last line that was changed
    241    ADD_C(args, INTEGER_OBJ(firstline - 1 + num_removed));
    242 
    243    // linedata of lines being swapped in
    244    ADD_C(args, ARRAY_OBJ(linedata));
    245    ADD_C(args, BOOLEAN_OBJ(false));
    246    if (!rpc_send_event(channelid, "nvim_buf_lines_event", args)) {
    247      // We can't unregister the channel while we're iterating over the
    248      // update_channels array, so we remember its ID to unregister it at
    249      // the end.
    250      badchannelid = channelid;
    251    }
    252  }
    253 
    254  // We can only ever remove one dead channel at a time. This is OK because the
    255  // change notifications are so frequent that many dead channels will be
    256  // cleared up quickly.
    257  if (badchannelid != 0) {
    258    ELOG("Disabling buffer updates for dead channel %" PRIu64, badchannelid);
    259    buf_updates_unregister(buf, badchannelid);
    260  }
    261 
    262  // callbacks don't use linedata
    263  arena_mem_free(arena_finish(&arena));
    264 
    265  // notify each of the active callbacks
    266  size_t j = 0;
    267  for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
    268    BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
    269    bool keep = true;
    270    if (cb.on_lines != LUA_NOREF && (cb.preview || !cmdpreview)) {
    271      MAXSIZE_TEMP_ARRAY(args, 8);  // 6 or 8 used
    272 
    273      // the first argument is always the buffer handle
    274      ADD_C(args, BUFFER_OBJ(buf->handle));
    275 
    276      // next argument is b:changedtick
    277      ADD_C(args, send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL);
    278 
    279      // the first line that changed (zero-indexed)
    280      ADD_C(args, INTEGER_OBJ(firstline - 1));
    281 
    282      // the last line that was changed
    283      ADD_C(args, INTEGER_OBJ(firstline - 1 + num_removed));
    284 
    285      // the last line in the updated range
    286      ADD_C(args, INTEGER_OBJ(firstline - 1 + num_added));
    287 
    288      // byte count of previous contents
    289      ADD_C(args, INTEGER_OBJ((Integer)deleted_bytes));
    290      if (cb.utf_sizes) {
    291        ADD_C(args, INTEGER_OBJ((Integer)deleted_codepoints));
    292        ADD_C(args, INTEGER_OBJ((Integer)deleted_codeunits));
    293      }
    294 
    295      Object res;
    296      TEXTLOCK_WRAP({
    297        res = nlua_call_ref(cb.on_lines, "lines", args, kRetNilBool, NULL, NULL);
    298      });
    299 
    300      if (LUARET_TRUTHY(res)) {
    301        buffer_update_callbacks_free(cb);
    302        keep = false;
    303      }
    304    }
    305    if (keep) {
    306      kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
    307    }
    308  }
    309  kv_size(buf->update_callbacks) = j;
    310 }
    311 
    312 void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcount_t start_byte,
    313                             int old_row, colnr_T old_col, bcount_t old_byte, int new_row,
    314                             colnr_T new_col, bcount_t new_byte)
    315 {
    316  if (!buf_updates_active(buf)
    317      || (old_byte == 0 && new_byte == 0)) {
    318    return;
    319  }
    320 
    321  // notify each of the active callbacks
    322  size_t j = 0;
    323  for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
    324    BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
    325    bool keep = true;
    326    if (cb.on_bytes != LUA_NOREF && (cb.preview || !cmdpreview)) {
    327      MAXSIZE_TEMP_ARRAY(args, 11);
    328 
    329      // the first argument is always the buffer handle
    330      ADD_C(args, BUFFER_OBJ(buf->handle));
    331 
    332      // next argument is b:changedtick
    333      ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
    334 
    335      ADD_C(args, INTEGER_OBJ(start_row));
    336      ADD_C(args, INTEGER_OBJ(start_col));
    337      ADD_C(args, INTEGER_OBJ(start_byte));
    338      ADD_C(args, INTEGER_OBJ(old_row));
    339      ADD_C(args, INTEGER_OBJ(old_col));
    340      ADD_C(args, INTEGER_OBJ(old_byte));
    341      ADD_C(args, INTEGER_OBJ(new_row));
    342      ADD_C(args, INTEGER_OBJ(new_col));
    343      ADD_C(args, INTEGER_OBJ(new_byte));
    344 
    345      Object res;
    346      TEXTLOCK_WRAP({
    347        res = nlua_call_ref(cb.on_bytes, "bytes", args, kRetNilBool, NULL, NULL);
    348      });
    349 
    350      if (LUARET_TRUTHY(res)) {
    351        buffer_update_callbacks_free(cb);
    352        keep = false;
    353      }
    354    }
    355    if (keep) {
    356      kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
    357    }
    358  }
    359  kv_size(buf->update_callbacks) = j;
    360 }
    361 void buf_updates_changedtick(buf_T *buf)
    362 {
    363  // notify each of the active channels
    364  for (size_t i = 0; i < kv_size(buf->update_channels); i++) {
    365    uint64_t channel_id = kv_A(buf->update_channels, i);
    366    buf_updates_changedtick_single(buf, channel_id);
    367  }
    368  size_t j = 0;
    369  for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
    370    BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
    371    bool keep = true;
    372    if (cb.on_changedtick != LUA_NOREF) {
    373      MAXSIZE_TEMP_ARRAY(args, 2);
    374 
    375      // the first argument is always the buffer handle
    376      ADD_C(args, BUFFER_OBJ(buf->handle));
    377 
    378      // next argument is b:changedtick
    379      ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
    380 
    381      Object res;
    382      TEXTLOCK_WRAP({
    383        res = nlua_call_ref(cb.on_changedtick, "changedtick", args, kRetNilBool, NULL, NULL);
    384      });
    385 
    386      if (LUARET_TRUTHY(res)) {
    387        buffer_update_callbacks_free(cb);
    388        keep = false;
    389      }
    390    }
    391    if (keep) {
    392      kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
    393    }
    394  }
    395  kv_size(buf->update_callbacks) = j;
    396 }
    397 
    398 void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
    399 {
    400  MAXSIZE_TEMP_ARRAY(args, 2);
    401 
    402  // the first argument is always the buffer handle
    403  ADD_C(args, BUFFER_OBJ(buf->handle));
    404 
    405  // next argument is b:changedtick
    406  ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
    407 
    408  // don't try and clean up dead channels here
    409  rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
    410 }
    411 
    412 void buffer_update_callbacks_free(BufUpdateCallbacks cb)
    413 {
    414  api_free_luaref(cb.on_lines);
    415  api_free_luaref(cb.on_bytes);
    416  api_free_luaref(cb.on_changedtick);
    417  api_free_luaref(cb.on_reload);
    418  api_free_luaref(cb.on_detach);
    419 }