diff options
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 16 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 89 | ||||
-rw-r--r-- | src/nvim/buffer.c | 4 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/buffer_updates.c | 32 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 4 | ||||
-rw-r--r-- | src/nvim/fileio.c | 3 | ||||
-rw-r--r-- | test/functional/lua/buffer_updates_spec.lua | 51 |
9 files changed, 146 insertions, 62 deletions
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 70051636ec..3af66b134c 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -51,7 +51,11 @@ function M._create_parser(bufnr, lang, opts) self:_on_detach(...) end - a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true}) + local function reload_cb(_, ...) + self:_on_reload(...) + end + + a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, on_reload=reload_cb, preview=true}) self:parse() diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index a374974dca..28a87f26c7 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -46,11 +46,16 @@ function LanguageTree.new(source, lang, opts) end -- Invalidates this parser and all its children -function LanguageTree:invalidate() +function LanguageTree:invalidate(reload) self._valid = false + -- buffer was reloaded, reparse all trees + if reload then + self._trees = {} + end + for _, child in ipairs(self._children) do - child:invalidate() + child:invalidate(reload) end end @@ -398,8 +403,13 @@ function LanguageTree:_on_bytes(bufnr, changed_tick, new_row, new_col, new_byte) end +function LanguageTree:_on_reload() + self:invalidate(true) +end + + function LanguageTree:_on_detach(...) - self:invalidate() + self:invalidate(true) self:_do_callback('detach', ...) end diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index e2c0ebe5e0..bd336a2b3d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -119,6 +119,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - on_detach: Lua callback invoked on detach. Args: /// - the string "detach" /// - buffer handle +/// - on_reload: Lua callback invoked on reload. The entire buffer +/// content should be considered changed. Args: +/// - the string "detach" +/// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. /// - preview: also attach to command preview (i.e. 'inccommand') @@ -141,50 +145,57 @@ Boolean nvim_buf_attach(uint64_t channel_id, bool is_lua = (channel_id == LUA_INTERNAL_CALL); BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT; + struct { + const char *name; + LuaRef *dest; + } cbs[] = { + { "on_lines", &cb.on_lines }, + { "on_bytes", &cb.on_bytes }, + { "on_changedtick", &cb.on_changedtick }, + { "on_detach", &cb.on_detach }, + { "on_reload", &cb.on_reload }, + { NULL, NULL }, + }; + for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (is_lua && strequal("on_lines", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_lines = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_bytes", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_bytes = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_changedtick", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_changedtick = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_detach", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_detach = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("utf_sizes", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); - goto error; + bool key_used = false; + if (is_lua) { + for (size_t 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; + key_used = true; + break; + } } - cb.utf_sizes = v->data.boolean; - } else if (is_lua && strequal("preview", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "preview must be boolean"); - goto error; + + if (key_used) { + continue; + } else if (strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; + key_used = true; + } else if (strequal("preview", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "preview must be boolean"); + goto error; + } + cb.preview = v->data.boolean; + key_used = true; } - cb.preview = v->data.boolean; - } else { + } + + if (!key_used) { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 19ce02700f..139981b7e1 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -596,7 +596,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // Disable buffer-updates for the current buffer. // No need to check `unload_buf`: in that case the function returned above. - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); /* * Remove the buffer from the list. @@ -821,7 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); } /* diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 360616609c..a05bd6fcc7 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -488,11 +488,12 @@ typedef struct { LuaRef on_bytes; LuaRef on_changedtick; LuaRef on_detach; + LuaRef on_reload; bool utf_sizes; bool preview; } BufUpdateCallbacks; #define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, false, false } + LUA_NOREF, LUA_NOREF, false, false } EXTERN int curbuf_splice_pending INIT(= 0); diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 68e123896b..97562eace6 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -135,7 +135,7 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid) } } -void buf_updates_unregister_all(buf_T *buf) +void buf_updates_unload(buf_T *buf, bool can_reload) { size_t size = kv_size(buf->update_channels); if (size) { @@ -146,9 +146,20 @@ void buf_updates_unregister_all(buf_T *buf) kv_init(buf->update_channels); } + size_t j = 0; for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); - if (cb.on_detach != LUA_NOREF) { + LuaRef thecb = LUA_NOREF; + + bool keep = false; + if (can_reload && cb.on_reload != LUA_NOREF) { + keep = true; + thecb = cb.on_reload; + } else if (cb.on_detach != LUA_NOREF) { + thecb = cb.on_detach; + } + + if (thecb != LUA_NOREF) { Array args = ARRAY_DICT_INIT; Object items[1]; args.size = 1; @@ -158,15 +169,24 @@ void buf_updates_unregister_all(buf_T *buf) args.items[0] = BUFFER_OBJ(buf->handle); textlock++; - nlua_call_ref(cb.on_detach, "detach", args, false, NULL); + nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL); textlock--; } - free_update_callbacks(cb); + + if (keep) { + kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); + } else { + free_update_callbacks(cb); + } + } + kv_size(buf->update_callbacks) = j; + if (kv_size(buf->update_callbacks) == 0) { + kv_destroy(buf->update_callbacks); + kv_init(buf->update_callbacks); } - kv_destroy(buf->update_callbacks); - kv_init(buf->update_callbacks); } + void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index cd7ff9a00b..ab09284c9d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2537,13 +2537,13 @@ int do_ecmd( goto theend; } u_unchanged(curbuf); - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, BFA_KEEP_UNDO); // Tell readfile() not to clear or reload undo info. readfile_flags = READ_KEEP_UNDO; } else { - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, 0); // Free all things for buffer. } // If autocommands deleted the buffer we were going to re-edit, give diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b240e7b134..714bbb5780 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5076,7 +5076,8 @@ void buf_reload(buf_T *buf, int orig_mode) // Mark all undo states as changed. u_unchanged(curbuf); } - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, true); + curbuf->b_mod_set = true; } } xfree(ea.cmd); diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 6c2615eebd..167fe61e1a 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -1,5 +1,6 @@ -- Test suite for testing interactions with API bindings local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') local command = helpers.command local meths = helpers.meths @@ -9,8 +10,9 @@ local eq = helpers.eq local fail = helpers.fail local exec_lua = helpers.exec_lua local feed = helpers.feed -local deepcopy = helpers.deepcopy local expect_events = helpers.expect_events +local write_file = helpers.write_file +local dedent = helpers.dedent local origlines = {"original line 1", "original line 2", @@ -33,7 +35,7 @@ before_each(function () return true end end - local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes, preview=preview} + local opts = {[evname]=callback, on_detach=callback, on_reload=callback, utf_sizes=utf_sizes, preview=preview} if changedtick then opts.on_changedtick = callback end @@ -294,9 +296,12 @@ describe('lua: nvim_buf_attach on_bytes', function() -- nvim_buf_get_offset forces a flush of the memline). To be safe run the -- test both ways. local function setup_eventcheck(verify, start_txt) - meths.buf_set_lines(0, 0, -1, true, start_txt) - local shadow = deepcopy(start_txt) - local shadowbytes = table.concat(shadow, '\n') .. '\n' + if start_txt then + meths.buf_set_lines(0, 0, -1, true, start_txt) + else + start_txt = meths.buf_get_lines(0, 0, -1, true) + end + local shadowbytes = table.concat(start_txt, '\n') .. '\n' -- TODO: while we are brewing the real strong coffe, -- verify should check buf_get_offset after every check_events if verify then @@ -330,6 +335,8 @@ describe('lua: nvim_buf_attach on_bytes', function() local unknown = string.rep('\255', new_byte) local after = string.sub(shadowbytes, start_byte + old_byte + 1) shadowbytes = before .. unknown .. after + elseif event[1] == verify_name and event[2] == "reload" then + shadowbytes = table.concat(meths.buf_get_lines(0, 0, -1, true), '\n') .. '\n' end end @@ -522,8 +529,6 @@ describe('lua: nvim_buf_attach on_bytes', function() end) it('inccomand=nosplit and substitute', function() - if verify then pending("Verification can't be done when previewing") end - local check_events = setup_eventcheck(verify, {"abcde"}) meths.set_option('inccommand', 'nosplit') @@ -603,6 +608,38 @@ describe('lua: nvim_buf_attach on_bytes', function() "original line 5", "original line 6" }, meths.buf_get_lines(0, 0, -1, true)) end) + + it('checktime autoread', function() + write_file("Xtest-reload", dedent [[ + old line 1 + old line 2]]) + lfs.touch("Xtest-reload", os.time() - 10) + command "e Xtest-reload" + command "set autoread" + + local check_events = setup_eventcheck(verify, nil) + + write_file("Xtest-reload", dedent [[ + new line 1 + new line 2 + new line 3]]) + + command "checktime" + check_events { + { "test1", "reload", 1 }; + } + + feed 'ggJ' + check_events { + { "test1", "bytes", 1, 5, 0, 10, 10, 1, 0, 1, 0, 1, 1 }; + } + + eq({'new line 1 new line 2', 'new line 3'}, meths.buf_get_lines(0, 0, -1, true)) + end) + + teardown(function() + os.remove "Xtest-reload" + end) end describe('(with verify) handles', function() |