mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lua)!: register_keystroke_callback => on_key
Analogous to nodejs's `on('data', …)` interface, here on_key is the "add
listener" interface.
ref 3ccdbc570d #12536
BREAKING_CHANGE: vim.register_keystroke_callback() is now an error.
			
			
This commit is contained in:
		| @@ -1,4 +1,4 @@ | ||||
| .DONE: | ||||
| 	@echo "Please use GNU Make (gmake) to build neovim" | ||||
| 	@echo "Use GNU Make (gmake) to build neovim" | ||||
| .DEFAULT: | ||||
| 	@echo "Please use GNU Make (gmake) to build neovim" | ||||
| 	@echo "Use GNU Make (gmake) to build neovim" | ||||
|   | ||||
| @@ -14,6 +14,7 @@ updated. | ||||
|  | ||||
| API ~ | ||||
| *nvim_buf_clear_highlight()*	Use |nvim_buf_clear_namespace()| instead. | ||||
| *nvim_buf_set_virtual_text()*	Use |nvim_buf_set_extmark()| instead. | ||||
| *nvim_command_output()*		Use |nvim_exec()| instead. | ||||
| *nvim_execute_lua()*		Use |nvim_exec_lua()| instead. | ||||
|  | ||||
| @@ -54,6 +55,8 @@ Functions ~ | ||||
| 			without stopping the job. Use chanclose(id) to close | ||||
| 			any socket. | ||||
|  | ||||
| Lua ~ | ||||
| *vim.register_keystroke_callback()* Use |vim.on_key()| instead. | ||||
|  | ||||
| Modifiers ~ | ||||
| *cpo-<* | ||||
|   | ||||
| @@ -356,4 +356,11 @@ External UIs are expected to implement these common features: | ||||
|   published in this event. See also "mouse_on", "mouse_off". | ||||
|  | ||||
|  | ||||
| NAMING							*dev-naming* | ||||
|  | ||||
| Naming is very important. Consistent naming in the API and UI helps users | ||||
| discover and intuitively understand  related "families" of things.  It reduces | ||||
| cognitive burden. | ||||
|  | ||||
|  | ||||
|  vim:tw=78:ts=8:noet:ft=help:norl: | ||||
|   | ||||
| @@ -1243,37 +1243,6 @@ region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive})         *vim.region()* | ||||
|                 Return: ~ | ||||
|                     region lua table of the form {linenr = {startcol,endcol}} | ||||
|  | ||||
|                                            *vim.register_keystroke_callback()* | ||||
| register_keystroke_callback({fn}, {ns_id}) | ||||
|                 Register a lua {fn} with an {id} to be run after every | ||||
|                 keystroke. | ||||
|  | ||||
|                 If {fn} is nil, it removes the callback for the associated | ||||
|                 {ns_id} | ||||
|                 Note: | ||||
|                     {fn} will not be cleared from |nvim_buf_clear_namespace()| | ||||
|  | ||||
|                 Note: | ||||
|                     {fn} will receive the keystrokes after mappings have been | ||||
|                     evaluated | ||||
|  | ||||
|                 Parameters: ~ | ||||
|                     {fn}     function: Function to call. It should take one | ||||
|                           argument, which is a string. The string will contain | ||||
|                           the literal keys typed. See |i_CTRL-V| | ||||
|                     {ns_id}  number? Namespace ID. If not passed or 0, will | ||||
|                              generate and return a new namespace ID from | ||||
|                              |nvim_create_namesapce()| | ||||
|  | ||||
|                 Return: ~ | ||||
|                     number Namespace ID associated with {fn} | ||||
|  | ||||
|                 Note: | ||||
|                     {fn} will be automatically removed if an error occurs | ||||
|                     while calling. This is to prevent the annoying situation | ||||
|                     of every keystroke erroring while trying to remove a | ||||
|                     broken callback. | ||||
|  | ||||
| schedule_wrap({cb})                                      *vim.schedule_wrap()* | ||||
|                 Defers callback `cb` until the Nvim API is safe to call. | ||||
|  | ||||
|   | ||||
| @@ -329,12 +329,12 @@ argument. | ||||
| -w{number}	Set the 'window' option to {number}. | ||||
|  | ||||
| 							*-w* | ||||
| -w {scriptout}	All the characters that you type are recorded in the file | ||||
| 		"scriptout", until you exit Vim.  This is useful if you want | ||||
| 		to create a script file to be used with "vim -s" or | ||||
| 		":source!".  When the "scriptout" file already exists, new | ||||
| 		characters are appended.  See also |complex-repeat|. | ||||
| 		{scriptout} cannot start with a digit. | ||||
| -w {scriptout}	All keys that you type are recorded in the file "scriptout", | ||||
| 		until you exit Vim.  Useful to create a script file to be used | ||||
| 		with "vim -s" or ":source!".  Appends to the "scriptout" file | ||||
| 		if it already exists. {scriptout} cannot start with a digit. | ||||
| 		See also |vim.on_key()|. | ||||
| 		See also |complex-repeat|. | ||||
|  | ||||
| 							*-W* | ||||
| -W {scriptout}	Like -w, but do not append, overwrite an existing file. | ||||
|   | ||||
| @@ -1563,8 +1563,8 @@ int vgetc(void) | ||||
|    */ | ||||
|   may_garbage_collect = false; | ||||
|  | ||||
|   // Exec lua callbacks for on_keystroke | ||||
|   nlua_execute_log_keystroke(c); | ||||
|   // Execute Lua on_key callbacks. | ||||
|   nlua_execute_on_key(c); | ||||
|  | ||||
|   return c; | ||||
| } | ||||
|   | ||||
| @@ -1499,7 +1499,7 @@ int nlua_expand_pat(expand_T *xp, | ||||
|   lua_getfield(lstate, -1, "_expand_pat"); | ||||
|   luaL_checktype(lstate, -1, LUA_TFUNCTION); | ||||
|  | ||||
|   // [ vim, vim._log_keystroke, buf ] | ||||
|   // [ vim, vim._on_key, buf ] | ||||
|   lua_pushlstring(lstate, (const char *)pat, STRLEN(pat)); | ||||
|  | ||||
|   if (lua_pcall(lstate, 1, 2, 0) != 0) { | ||||
| @@ -1773,7 +1773,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) | ||||
|   return name; | ||||
| } | ||||
|  | ||||
| void nlua_execute_log_keystroke(int c) | ||||
| void nlua_execute_on_key(int c) | ||||
| { | ||||
|   char_u buf[NUMBUFLEN]; | ||||
|   size_t buf_len = special_to_buf(c, mod_mask, false, buf); | ||||
| @@ -1787,17 +1787,17 @@ void nlua_execute_log_keystroke(int c) | ||||
|   // [ vim ] | ||||
|   lua_getglobal(lstate, "vim"); | ||||
|  | ||||
|   // [ vim, vim._log_keystroke ] | ||||
|   lua_getfield(lstate, -1, "_log_keystroke"); | ||||
|   // [ vim, vim._on_key] | ||||
|   lua_getfield(lstate, -1, "_on_key"); | ||||
|   luaL_checktype(lstate, -1, LUA_TFUNCTION); | ||||
|  | ||||
|   // [ vim, vim._log_keystroke, buf ] | ||||
|   // [ vim, vim._on_key, buf ] | ||||
|   lua_pushlstring(lstate, (const char *)buf, buf_len); | ||||
|  | ||||
|   if (lua_pcall(lstate, 1, 0, 0)) { | ||||
|     nlua_error( | ||||
|         lstate, | ||||
|         _("Error executing vim.log_keystroke lua callback: %.*s")); | ||||
|         _("Error executing  vim.on_key Lua callback: %.*s")); | ||||
|   } | ||||
|  | ||||
|   // [ vim ] | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| --    1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the | ||||
| --       `inspect` and `lpeg` modules. | ||||
| --    2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests. | ||||
| --       (This will go away if we migrate to nvim as the test-runner.) | ||||
| --    3. src/nvim/lua/: Compiled-into Nvim itself. | ||||
| -- | ||||
| -- Guideline: "If in doubt, put it in the runtime". | ||||
| @@ -426,26 +427,35 @@ function vim.notify(msg, log_level, _opts) | ||||
| end | ||||
|  | ||||
|  | ||||
| local on_keystroke_callbacks = {} | ||||
| function vim.register_keystroke_callback() | ||||
|   error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key') | ||||
| end | ||||
|  | ||||
| --- Register a lua {fn} with an {id} to be run after every keystroke. | ||||
| local on_key_cbs = {} | ||||
|  | ||||
| --- Adds Lua function {fn} with namespace id {ns_id} as a listener to every, | ||||
| --- yes every, input key. | ||||
| --- | ||||
| ---@param fn function: Function to call. It should take one argument, which is a string. | ||||
| ---                   The string will contain the literal keys typed. | ||||
| ---                   See |i_CTRL-V| | ||||
| --- The Nvim command-line option |-w| is related but does not support callbacks | ||||
| --- and cannot be toggled dynamically. | ||||
| --- | ||||
| ---@param fn function: Callback function. It should take one string argument. | ||||
| ---                   On each key press, Nvim passes the key char to fn(). |i_CTRL-V| | ||||
| ---                   If {fn} is nil, it removes the callback for the associated {ns_id} | ||||
| ---@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new | ||||
| ---                    namespace ID from |nvim_create_namesapce()| | ||||
| ---@param ns_id number? Namespace ID. If nil or 0, generates and returns a new | ||||
| ---                    |nvim_create_namesapce()| id. | ||||
| --- | ||||
| ---@return number Namespace ID associated with {fn} | ||||
| ---@return number Namespace id associated with {fn}. Or count of all callbacks | ||||
| ---if on_key() is called without arguments. | ||||
| --- | ||||
| ---@note {fn} will be automatically removed if an error occurs while calling. | ||||
| ---     This is to prevent the annoying situation of every keystroke erroring | ||||
| ---     while trying to remove a broken callback. | ||||
| ---@note {fn} will not be cleared from |nvim_buf_clear_namespace()| | ||||
| ---@note {fn} will receive the keystrokes after mappings have been evaluated | ||||
| function vim.register_keystroke_callback(fn, ns_id) | ||||
| ---@note {fn} will be removed if an error occurs while calling. | ||||
| ---@note {fn} will not be cleared by |nvim_buf_clear_namespace()| | ||||
| ---@note {fn} will receive the keys after mappings have been evaluated | ||||
| function vim.on_key(fn, ns_id) | ||||
|   if fn == nil and ns_id == nil then | ||||
|     return #on_key_cbs | ||||
|   end | ||||
|  | ||||
|   vim.validate { | ||||
|     fn = { fn, 'c', true}, | ||||
|     ns_id = { ns_id, 'n', true } | ||||
| @@ -455,20 +465,19 @@ function vim.register_keystroke_callback(fn, ns_id) | ||||
|     ns_id = vim.api.nvim_create_namespace('') | ||||
|   end | ||||
|  | ||||
|   on_keystroke_callbacks[ns_id] = fn | ||||
|   on_key_cbs[ns_id] = fn | ||||
|   return ns_id | ||||
| end | ||||
|  | ||||
| --- Function that executes the keystroke callbacks. | ||||
| --- Executes the on_key callbacks. | ||||
| ---@private | ||||
| function vim._log_keystroke(char) | ||||
| function vim._on_key(char) | ||||
|   local failed_ns_ids = {} | ||||
|   local failed_messages = {} | ||||
|   for k, v in pairs(on_keystroke_callbacks) do | ||||
|   for k, v in pairs(on_key_cbs) do | ||||
|     local ok, err_msg = pcall(v, char) | ||||
|     if not ok then | ||||
|       vim.register_keystroke_callback(nil, k) | ||||
|  | ||||
|       vim.on_key(nil, k) | ||||
|       table.insert(failed_ns_ids, k) | ||||
|       table.insert(failed_messages, err_msg) | ||||
|     end | ||||
| @@ -476,7 +485,7 @@ function vim._log_keystroke(char) | ||||
|  | ||||
|   if failed_ns_ids[1] then | ||||
|     error(string.format( | ||||
|       "Error executing 'on_keystroke' with ns_ids of '%s'\n    With messages: %s", | ||||
|       "Error executing 'on_key' with ns_ids '%s'\n    Messages: %s", | ||||
|       table.concat(failed_ns_ids, ", "), | ||||
|       table.concat(failed_messages, "\n"))) | ||||
|   end | ||||
|   | ||||
| @@ -6,6 +6,7 @@ local funcs = helpers.funcs | ||||
| local meths = helpers.meths | ||||
| local dedent = helpers.dedent | ||||
| local command = helpers.command | ||||
| local insert = helpers.insert | ||||
| local clear = helpers.clear | ||||
| local eq = helpers.eq | ||||
| local ok = helpers.ok | ||||
| @@ -1881,7 +1882,7 @@ describe('lua stdlib', function() | ||||
|   end) | ||||
|  | ||||
|   it('vim.region', function() | ||||
|     helpers.insert(helpers.dedent( [[ | ||||
|     insert(helpers.dedent( [[ | ||||
|     text tααt tααt text | ||||
|     text tαxt txtα tex | ||||
|     text tαxt tαxt | ||||
| @@ -1889,65 +1890,67 @@ describe('lua stdlib', function() | ||||
|     eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) | ||||
|   end) | ||||
|  | ||||
|   describe('vim.execute_on_keystroke', function() | ||||
|     it('should keep track of keystrokes', function() | ||||
|       helpers.insert([[hello world ]]) | ||||
|   describe('vim.on_key', function() | ||||
|     it('tracks keystrokes', function() | ||||
|       insert([[hello world ]]) | ||||
|  | ||||
|       exec_lua [[ | ||||
|         KeysPressed = {} | ||||
|         keys = {} | ||||
|  | ||||
|         vim.register_keystroke_callback(function(buf) | ||||
|         vim.on_key(function(buf) | ||||
|           if buf:byte() == 27 then | ||||
|             buf = "<ESC>" | ||||
|           end | ||||
|  | ||||
|           table.insert(KeysPressed, buf) | ||||
|           table.insert(keys, buf) | ||||
|         end) | ||||
|       ]] | ||||
|  | ||||
|       helpers.insert([[next 🤦 lines å ]]) | ||||
|       insert([[next 🤦 lines å ]]) | ||||
|  | ||||
|       -- It has escape in the keys pressed | ||||
|       eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) | ||||
|       eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(keys, '')]]) | ||||
|     end) | ||||
|  | ||||
|     it('should allow removing trackers.', function() | ||||
|       helpers.insert([[hello world]]) | ||||
|     it('allows removing on_key listeners', function() | ||||
|       insert([[hello world]]) | ||||
|  | ||||
|       exec_lua [[ | ||||
|         KeysPressed = {} | ||||
|         keys = {} | ||||
|  | ||||
|         return vim.register_keystroke_callback(function(buf) | ||||
|         return vim.on_key(function(buf) | ||||
|           if buf:byte() == 27 then | ||||
|             buf = "<ESC>" | ||||
|           end | ||||
|  | ||||
|           table.insert(KeysPressed, buf) | ||||
|           table.insert(keys, buf) | ||||
|         end, vim.api.nvim_create_namespace("logger")) | ||||
|       ]] | ||||
|  | ||||
|       helpers.insert([[next lines]]) | ||||
|       insert([[next lines]]) | ||||
|  | ||||
|       exec_lua("vim.register_keystroke_callback(nil, vim.api.nvim_create_namespace('logger'))") | ||||
|       eq(1, exec_lua('return vim.on_key()')) | ||||
|       exec_lua("vim.on_key(nil, vim.api.nvim_create_namespace('logger'))") | ||||
|       eq(0, exec_lua('return vim.on_key()')) | ||||
|  | ||||
|       helpers.insert([[more lines]]) | ||||
|       insert([[more lines]]) | ||||
|  | ||||
|       -- It has escape in the keys pressed | ||||
|       eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) | ||||
|       eq('inext lines<ESC>', exec_lua [[return table.concat(keys, '')]]) | ||||
|     end) | ||||
|  | ||||
|     it('should not call functions that error again.', function() | ||||
|       helpers.insert([[hello world]]) | ||||
|     it('skips any function that caused an error', function() | ||||
|       insert([[hello world]]) | ||||
|  | ||||
|       exec_lua [[ | ||||
|         KeysPressed = {} | ||||
|         keys = {} | ||||
|  | ||||
|         return vim.register_keystroke_callback(function(buf) | ||||
|         return vim.on_key(function(buf) | ||||
|           if buf:byte() == 27 then | ||||
|             buf = "<ESC>" | ||||
|           end | ||||
|  | ||||
|           table.insert(KeysPressed, buf) | ||||
|           table.insert(keys, buf) | ||||
|  | ||||
|           if buf == 'l' then | ||||
|             error("Dumb Error") | ||||
| @@ -1955,35 +1958,30 @@ describe('lua stdlib', function() | ||||
|         end) | ||||
|       ]] | ||||
|  | ||||
|       helpers.insert([[next lines]]) | ||||
|       helpers.insert([[more lines]]) | ||||
|       insert([[next lines]]) | ||||
|       insert([[more lines]]) | ||||
|  | ||||
|       -- Only the first letter gets added. After that we remove the callback | ||||
|       eq('inext l', exec_lua [[ return table.concat(KeysPressed, '') ]]) | ||||
|       eq('inext l', exec_lua [[ return table.concat(keys, '') ]]) | ||||
|     end) | ||||
|  | ||||
|     it('should process mapped keys, not unmapped keys', function() | ||||
|     it('processes mapped keys, not unmapped keys', function() | ||||
|       exec_lua [[ | ||||
|         KeysPressed = {} | ||||
|         keys = {} | ||||
|  | ||||
|         vim.cmd("inoremap hello world") | ||||
|  | ||||
|         vim.register_keystroke_callback(function(buf) | ||||
|         vim.on_key(function(buf) | ||||
|           if buf:byte() == 27 then | ||||
|             buf = "<ESC>" | ||||
|           end | ||||
|  | ||||
|           table.insert(KeysPressed, buf) | ||||
|           table.insert(keys, buf) | ||||
|         end) | ||||
|       ]] | ||||
|       insert("hello") | ||||
|  | ||||
|       helpers.insert("hello") | ||||
|  | ||||
|       local next_status = exec_lua [[ | ||||
|         return table.concat(KeysPressed, '') | ||||
|       ]] | ||||
|  | ||||
|       eq("iworld<ESC>", next_status) | ||||
|       eq('iworld<ESC>', exec_lua[[return table.concat(keys, '')]]) | ||||
|     end) | ||||
|   end) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes