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 }