diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2024-09-18 04:14:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-18 04:14:06 -0700 |
commit | ff85e54939b0aca34a779a2b6381d09db1858b29 (patch) | |
tree | eea30db8b69582af6c206b1a8ac0b4918fc61e20 | |
parent | 22553e1f38addd867ad659b2944d00129141a499 (diff) | |
download | rneovim-ff85e54939b0aca34a779a2b6381d09db1858b29.tar.gz rneovim-ff85e54939b0aca34a779a2b6381d09db1858b29.tar.bz2 rneovim-ff85e54939b0aca34a779a2b6381d09db1858b29.zip |
feat(tui): builtin UI (TUI) sets client info #30397
Problem:
The default builtin UI client does not declare its client info. This
reduces discoverability and makes it difficult for plugins to identify
the UI.
Solution:
- Call nvim_set_client_info after attaching, as recommended by `:help dev-ui`.
- Also set the "pid" field.
- Also change `ui_active()` to return a count. Not directly relevant to
this commit, but will be useful later.
-rw-r--r-- | runtime/doc/api.txt | 11 | ||||
-rw-r--r-- | runtime/doc/gui.txt | 28 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/doc/tui.txt | 41 | ||||
-rw-r--r-- | runtime/doc/ui.txt | 2 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 17 | ||||
-rw-r--r-- | src/nvim/main.c | 3 | ||||
-rw-r--r-- | src/nvim/tui/input.c | 2 | ||||
-rw-r--r-- | src/nvim/ui.c | 7 | ||||
-rw-r--r-- | src/nvim/ui_client.c | 43 | ||||
-rw-r--r-- | test/client/session.lua | 3 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 46 | ||||
-rw-r--r-- | test/functional/testnvim.lua | 2 |
13 files changed, 164 insertions, 44 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index a7b59f20bc..45c6168e3d 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -779,7 +779,7 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* • {chan} channel_id, or 0 for current channel Return: ~ - Dictionary describing a channel, with these keys: + Channel info dict with these keys: • "id" Channel id. • "argv" (optional) Job arguments list. • "stream" Stream underlying the channel. @@ -792,11 +792,11 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* • "terminal" |terminal| instance interprets ASCII sequences. • "rpc" |RPC| communication on the channel is active. • "pty" (optional) Name of pseudoterminal. On a POSIX system this is a - device path like "/dev/pts/1". If the name is unknown, the key will - still be present if a pty is used (e.g. for conpty on Windows). - • "buffer" (optional) Buffer with connected |terminal| instance. + device path like "/dev/pts/1". If unknown, the key will still be + present if a pty is used (e.g. for conpty on Windows). + • "buffer" (optional) Buffer connected to |terminal| instance. • "client" (optional) Info about the peer (client on the other end of - the RPC channel), if provided by it via |nvim_set_client_info()|. + the RPC channel), which it provided via |nvim_set_client_info()|. nvim_get_color_by_name({name}) *nvim_get_color_by_name()* Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or @@ -1285,6 +1285,7 @@ nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) inclusive. • {attributes} Arbitrary string:string map of informal client properties. Suggested keys: + • "pid": Process id. • "website": Client homepage URL (e.g. GitHub repository) • "license": License description ("Apache 2", "GPLv3", diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 69e251e657..f0f2334ff6 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -6,14 +6,38 @@ Nvim Graphical User Interface *gui* *GUI* +Any client that supports the Nvim |ui-protocol| can be used as a UI for Nvim. +And multiple UIs can connect to the same Nvim instance! The terms "UI" and +"GUI" are often used interchangeably because all Nvim UI clients have the same +potential capabilities; the "TUI" refers to a UI client that outputs to your +terminal, whereas a "GUI" outputs directly to the OS graphics system. + +Except where noted, this document describes UI capabilities available to both +TUI and GUI (assuming the UI supports the given feature). See |TUI| for notes +specific to the terminal UI. Help tags with the "gui-" prefix refer to UI +features, whereas help tags with the "ui-" prefix refer to the |ui-protocol|. + +Nvim provides a default, builtin UI (the |TUI|), but there are many other +(third-party) GUIs that you can use instead: + +- Firenvim (Nvim in your web browser!) https://github.com/glacambre/firenvim +- vscode-neovim (Nvim in VSCode!) https://github.com/vscode-neovim/vscode-neovim +- Neovide https://neovide.dev/ +- Goneovim https://github.com/akiyosi/goneovim +- Nvy https://github.com/RMichelsen/Nvy +- Neovim-Qt (Qt5) https://github.com/equalsraf/neovim-qt +- VimR (macOS) https://github.com/qvacua/vimr +- Others https://github.com/neovim/neovim/wiki/Related-projects#gui + Type |gO| to see the table of contents. ============================================================================== -Starting the GUI *gui-start* *E229* *E233* +Starting the GUI *gui-config* *gui-start* *ginit.vim* *gui-init* *gvimrc* *$MYGVIMRC* For GUI-specific configuration Nvim provides the |UIEnter| event. This -happens after other |initialization|s, like reading your vimrc file. +happens after other |initialization|s, or whenever a UI attaches (multiple UIs +can connect to any Nvim instance). Example: this sets "g:gui" to the value of the UI's "rgb" field: > :autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 515aef753d..1d95886356 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -189,6 +189,9 @@ TREESITTER TUI +• The builtin UI declares info |nvim_set_client_info()| on its channel. See + |startup-tui|. To see the current UI info, try this: > + :lua =vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan) • |log| messages written by the builtin UI client (TUI, |--remote-ui|) are now prefixed with "ui" instead of "?". diff --git a/runtime/doc/tui.txt b/runtime/doc/tui.txt index d2af878239..9493f91b1e 100644 --- a/runtime/doc/tui.txt +++ b/runtime/doc/tui.txt @@ -6,28 +6,45 @@ Terminal UI *TUI* *tui* -Nvim uses a list of terminal capabilities to display its user interface -(except in |--embed| and |--headless| modes). If that information is wrong, -the screen may be messed up or keys may not be recognized. +By default when you run `nvim` (without |--embed| or |--headless|) it starts +the builtin "terminal UI" (TUI). This default UI is optional: you can run Nvim +as a "headless" server, or you can use a |GUI|. Type |gO| to see the table of contents. ============================================================================== -Startup *startup-terminal* +Startup *startup-tui* *startup-terminal* + +Nvim has a client-server architecture: by default when you run `nvim`, this +starts the builtin UI client, which starts a `nvim --embed` server (child) +process that the UI client connects to. After attaching to the server, the UI +client calls |nvim_set_client_info()| (as recommended for all UIs |dev-ui|) +and sets these fields on its channel: > + + client = { + attributes = { + license = 'Apache 2', + pid = …, + website = 'https://neovim.io', + }, + name = 'nvim-tui', + type = 'ui', + version = { … }, + } Nvim guesses the terminal type when it starts (except in |--embed| and |--headless| modes). The |$TERM| environment variable is the primary hint that determines the terminal type. *terminfo* *E557* *E558* *E559* -The terminfo database is used if available. - -The Unibilium library (used by Nvim to read terminfo) allows you to override -the system terminfo with one in $HOME/.terminfo/ directory, in part or in -whole. - -Building your own terminfo is usually as simple as running this as -a non-superuser: +To display its user interface, Nvim reads a list of "terminal capabilities" +from the system terminfo database (or builtin defaults if terminfo is not +found). If that information is wrong, the screen may be messed up or keys may +not be recognized. + +The Unibilium library (used to read terminfo) allows you to override the +system terminfo with one in the "$HOME/.terminfo/" directory. Building your +own terminfo is usually as simple as running this: > curl -LO https://invisible-island.net/datafiles/current/terminfo.src.gz gunzip terminfo.src.gz diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 1f5132bd30..42dfcd2fe7 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -9,7 +9,7 @@ Nvim UI protocol *UI* *ui* Type |gO| to see the table of contents. ============================================================================== -UI Events *ui-events* +UI Events *ui-protocol* *ui-events* UIs can be implemented as external client processes communicating with Nvim over the RPC API. The default UI model is a terminal-like grid with a single, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index fe263d051d..d10ee91042 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1586,6 +1586,7 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena) /// /// @param attributes Arbitrary string:string map of informal client properties. /// Suggested keys: +/// - "pid": Process id. /// - "website": Client homepage URL (e.g. GitHub repository) /// - "license": License description ("Apache 2", "GPLv3", "MIT", …) /// - "logo": URI or path to image, preferably small logo or icon. @@ -1627,7 +1628,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, /// Gets information about a channel. /// /// @param chan channel_id, or 0 for current channel -/// @returns Dictionary describing a channel, with these keys: +/// @returns Channel info dict with these keys: /// - "id" Channel id. /// - "argv" (optional) Job arguments list. /// - "stream" Stream underlying the channel. @@ -1639,14 +1640,12 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, /// - "bytes" Send and receive raw bytes. /// - "terminal" |terminal| instance interprets ASCII sequences. /// - "rpc" |RPC| communication on the channel is active. -/// - "pty" (optional) Name of pseudoterminal. On a POSIX system this -/// is a device path like "/dev/pts/1". If the name is unknown, -/// the key will still be present if a pty is used (e.g. for -/// conpty on Windows). -/// - "buffer" (optional) Buffer with connected |terminal| instance. -/// - "client" (optional) Info about the peer (client on the other end of -/// the RPC channel), if provided by it via -/// |nvim_set_client_info()|. +/// - "pty" (optional) Name of pseudoterminal. On a POSIX system this is a device path like +/// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. +/// for conpty on Windows). +/// - "buffer" (optional) Buffer connected to |terminal| instance. +/// - "client" (optional) Info about the peer (client on the other end of the RPC channel), +/// which it provided via |nvim_set_client_info()|. /// Dictionary nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err) FUNC_API_SINCE(4) diff --git a/src/nvim/main.c b/src/nvim/main.c index 0b49ea13d7..e060d0dcaf 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -351,7 +351,6 @@ int main(int argc, char **argv) // NORETURN: Start builtin UI client. if (ui_client_channel_id) { - time_finish(); ui_client_run(remote_ui); // NORETURN } assert(!ui_client_channel_id && !use_builtin_ui); @@ -1514,7 +1513,7 @@ static void init_startuptime(mparm_T *paramp) } for (int i = 1; i < paramp->argc - 1; i++) { if (STRICMP(paramp->argv[i], "--startuptime") == 0) { - time_init(paramp->argv[i + 1], is_embed ? "Embedded" : "Primary/TUI"); + time_init(paramp->argv[i + 1], is_embed ? "Embedded" : "Primary (or UI client)"); time_start("--- NVIM STARTING ---"); break; } diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 3eb8d4ba2e..98dd7b4b45 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -464,7 +464,7 @@ static void tinput_timer_cb(uv_timer_t *handle) { TermInput *input = handle->data; // If the raw buffer is not empty, process the raw buffer first because it is - // processing an incomplete bracketed paster sequence. + // processing an incomplete bracketed paste sequence. size_t size = rstream_available(&input->read_stream); if (size) { size_t consumed = handle_raw_buffer(input, true, input->read_stream.read_pos, size); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 314d322a68..587124fab0 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -182,9 +182,10 @@ bool ui_override(void) return false; } -bool ui_active(void) +/// Gets the number of UIs connected to this server. +size_t ui_active(void) { - return ui_count > 0; + return ui_count; } void ui_refresh(void) @@ -197,7 +198,7 @@ void ui_refresh(void) int height = INT_MAX; bool ext_widgets[kUIExtCount]; bool inclusive = ui_override(); - memset(ext_widgets, ui_active(), ARRAY_SIZE(ext_widgets)); + memset(ext_widgets, !!ui_active(), ARRAY_SIZE(ext_widgets)); for (size_t i = 0; i < ui_count; i++) { RemoteUI *ui = uis[i]; diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index cfa79b9d8c..43ab238716 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -24,6 +24,7 @@ #include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" +#include "nvim/profile.h" #include "nvim/tui/tui.h" #include "nvim/tui/tui_defs.h" #include "nvim/ui.h" @@ -81,12 +82,15 @@ uint64_t ui_client_start_server(int argc, char **argv) return channel->id; } +/// Attaches this client to the UI channel, and sets its client info. void ui_client_attach(int width, int height, char *term, bool rgb) { + // + // nvim_ui_attach + // MAXSIZE_TEMP_ARRAY(args, 3); ADD_C(args, INTEGER_OBJ(width)); ADD_C(args, INTEGER_OBJ(height)); - MAXSIZE_TEMP_DICT(opts, 9); PUT_C(opts, "rgb", BOOLEAN_OBJ(rgb)); PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true)); @@ -94,7 +98,6 @@ void ui_client_attach(int width, int height, char *term, bool rgb) if (term) { PUT_C(opts, "term_name", CSTR_AS_OBJ(term)); } - PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors)); if (!ui_client_is_remote) { PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty)); @@ -108,6 +111,40 @@ void ui_client_attach(int width, int height, char *term, bool rgb) rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args); ui_client_attached = true; + + TIME_MSG("nvim_ui_attach"); + + // + // nvim_set_client_info + // + MAXSIZE_TEMP_ARRAY(args2, 5); + ADD_C(args2, CSTR_AS_OBJ("nvim-tui")); // name + Object m = api_metadata(); + Dictionary version = { 0 }; + assert(m.data.dictionary.size > 0); + for (size_t i = 0; i < m.data.dictionary.size; i++) { + if (strequal(m.data.dictionary.items[i].key.data, "version")) { + version = m.data.dictionary.items[i].value.data.dictionary; + break; + } else if (i + 1 == m.data.dictionary.size) { + abort(); + } + } + ADD_C(args2, DICTIONARY_OBJ(version)); // version + ADD_C(args2, CSTR_AS_OBJ("ui")); // type + // We don't send api_metadata.functions as the "methods" because: + // 1. it consumes memory. + // 2. it is unlikely to be useful, since the peer can just call `nvim_get_api`. + // 3. nvim_set_client_info expects a dict instead of an array. + ADD_C(args2, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); // methods + MAXSIZE_TEMP_DICT(info, 9); // attributes + PUT_C(info, "website", CSTR_AS_OBJ("https://neovim.io")); + PUT_C(info, "license", CSTR_AS_OBJ("Apache 2")); + PUT_C(info, "pid", INTEGER_OBJ(os_get_pid())); + ADD_C(args2, DICTIONARY_OBJ(info)); // attributes + rpc_send_event(ui_client_channel_id, "nvim_set_client_info", args2); + + TIME_MSG("nvim_set_client_info"); } void ui_client_detach(void) @@ -132,6 +169,8 @@ void ui_client_run(bool remote_ui) ELOG("test log message"); } + time_finish(); + // os_exit() will be invoked when the client channel detaches while (true) { LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1); diff --git a/test/client/session.lua b/test/client/session.lua index 38d81eff83..bfce4eedc7 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -96,8 +96,7 @@ end --- @param method string --- @param ... any ---- @return boolean ---- @return table +--- @return boolean, table function Session:request(method, ...) local args = { ... } local err, result diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 6375e6fbae..3e837e796d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -40,8 +40,8 @@ if t.skip(is_os('win')) then end describe('TUI', function() - local screen - local child_session + local screen --[[@type test.functional.ui.screen]] + local child_session --[[@type test.Session]] local child_exec_lua before_each(function() @@ -1651,12 +1651,13 @@ describe('TUI', function() ]]) end) - it('in nvim_list_uis()', function() + it('in nvim_list_uis(), sets nvim_set_client_info()', function() -- $TERM in :terminal. local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color' + local ui_chan = 1 local expected = { { - chan = 1, + chan = ui_chan, ext_cmdline = false, ext_hlstate = false, ext_linegrid = true, @@ -1679,6 +1680,43 @@ describe('TUI', function() } local _, rv = child_session:request('nvim_list_uis') eq(expected, rv) + + ---@type table + local expected_version = ({ + child_session:request('nvim_exec_lua', 'return vim.version()', {}), + })[2] + -- vim.version() returns `prerelease` string. Coerce it to boolean. + expected_version.prerelease = not not expected_version.prerelease + + local expected_chan_info = { + client = { + attributes = { + license = 'Apache 2', + -- pid = 5371, + website = 'https://neovim.io', + }, + methods = {}, + name = 'nvim-tui', + type = 'ui', + version = expected_version, + }, + id = ui_chan, + mode = 'rpc', + stream = 'stdio', + } + + local status, chan_info = child_session:request('nvim_get_chan_info', ui_chan) + ok(status) + local info = chan_info.client + ok(info.attributes.pid and info.attributes.pid > 0, 'PID', info.attributes.pid or 'nil') + ok(info.version.major >= 0) + ok(info.version.minor >= 0) + ok(info.version.patch >= 0) + + -- Delete variable fields so we can deep-compare. + info.attributes.pid = nil + + eq(expected_chan_info, chan_info) end) it('allows grid to assume wider ambiwidth chars than host terminal', function() diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index a66f0cd52c..ac2ec89271 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -458,7 +458,7 @@ end --- @param argv string[] --- @param merge boolean? --- @param env string[]? ---- @param keep boolean +--- @param keep boolean? --- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option --- @return test.Session function M.spawn(argv, merge, env, keep, io_extra) |