docs: OSC 133 shell config #32771

This commit is contained in:
Justin M. Keyes
2025-03-07 16:37:42 -08:00
committed by GitHub
parent b813075b8a
commit c38c88edfd
5 changed files with 69 additions and 42 deletions

View File

@@ -455,6 +455,8 @@ between Vi and Vim.
first column. When used after an operator, then also first column. When used after an operator, then also
stops below a "}" in the first column. |exclusive| stops below a "}" in the first column. |exclusive|
Note that |exclusive-linewise| often applies. Note that |exclusive-linewise| often applies.
In a :terminal buffer each shell prompt is treated as
a section. |terminal_]]|
*][* *][*
][ [count] |section|s forward or to the next '}' in the ][ [count] |section|s forward or to the next '}' in the
@@ -465,6 +467,8 @@ between Vi and Vim.
[[ [count] |section|s backward or to the previous "{" in [[ [count] |section|s backward or to the previous "{" in
the first column. |exclusive| the first column. |exclusive|
Note that |exclusive-linewise| often applies. Note that |exclusive-linewise| often applies.
In a :terminal buffer each shell prompt is treated as
a section. |terminal_]]|
*[]* *[]*
[] [count] |section|s backward or to the previous "}" in [] [count] |section|s backward or to the previous "}" in
@@ -498,6 +502,7 @@ A section begins after a form-feed (<C-L>) in the first column and at each of
a set of section macros, specified by the pairs of characters in the a set of section macros, specified by the pairs of characters in the
'sections' option. The default is "SHNHH HUnhsh", which defines a section to 'sections' option. The default is "SHNHH HUnhsh", which defines a section to
start at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh". start at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh".
In a :terminal buffer each shell prompt is treated as a section. |terminal_]]|
The "]]" and "[[" commands stop at the '{' in the first column. This is The "]]" and "[[" commands stop at the '{' in the first column. This is
useful to find the start of a function in a C program. To search for a '}' in useful to find the start of a function in a C program. To search for a '}' in

View File

@@ -187,26 +187,48 @@ event. The event is handled directly by Nvim and is not forwarded to plugins.
OSC 133: shell integration *terminal-osc133* OSC 133: shell integration *terminal-osc133*
Some shells will emit semantic escape sequences (OSC 133) to mark the Shells can emit semantic escape sequences (OSC 133) to mark where each prompt
beginning and end of a prompt. The start of a prompt is marked with the starts and ends. The start of a prompt is marked by sequence `OSC 133 ; A ST`,
sequence `OSC 133 ; A`. Nvim can be configured to create signs in terminal and the end by `OSC 133 ; B ST`.
buffers marking shell prompts. Example: >lua
local ns = vim.api.nvim_create_namespace('terminal_prompt_markers') *shell-prompt-config*
You can configure your shell "rc" (e.g. ~/.bashrc) to emit OSC 133 sequences,
or your terminal may attempt to do it for you (assuming your shell config
doesn't interfere).
- fish: https://fishshell.com/docs/current/relnotes.html#improved-terminal-support
- kitty: https://sw.kovidgoyal.net/kitty/shell-integration/
- vscode: https://code.visualstudio.com/docs/terminal/shell-integration
To configure bash to mark the start/end of each prompt, set $PROMPT_COMMAND
and $PS1 as follows: >bash
# Prompt start:
PROMPT_COMMAND='printf "\033]133;A\007"'
# Prompt end:
PS1="$PS1"'\033]133;B\007'
<
*terminal_]]* *terminal_[[*
The |]]| and |[[| motions jump to the next/previous prompts, if your shell
emits OSC 133 as described above.
*terminal-shell-prompt-signs*
To annotate each terminal prompt with a sign, call |nvim_buf_set_extmark()|
from a |TermRequest| handler: >lua
local ns = vim.api.nvim_create_namespace('my.terminal.prompt')
vim.api.nvim_create_autocmd('TermRequest', { vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args) callback = function(args)
if string.match(args.data.sequence, '^\027]133;A') then if string.match(args.data.sequence, '^\027]133;A') then
local lnum = args.data.cursor[1] local lnum = args.data.cursor[1]
vim.api.nvim_buf_set_extmark(args.buf, ns, lnum - 1, 0, { vim.api.nvim_buf_set_extmark(args.buf, ns, lnum - 1, 0, {
-- Replace with sign text and highlight group of choice
sign_text = '▶', sign_text = '▶',
sign_hl_group = 'SpecialChar', sign_hl_group = 'SpecialChar',
}) })
end end
end, end,
}) })
-- Enable signcolumn in terminal buffers.
-- Enable signcolumn in terminal buffers
vim.api.nvim_create_autocmd('TermOpen', { vim.api.nvim_create_autocmd('TermOpen', {
command = 'setlocal signcolumn=auto', command = 'setlocal signcolumn=auto',
}) })

View File

@@ -125,12 +125,11 @@ lua_State *get_global_lstate(void)
return global_lstate; return global_lstate;
} }
/// get error on top of stack as a string /// Gets the Lua error at top of stack as a string, possibly modifying it in-place (but doesn't
/// change stack height).
/// ///
/// Might alter the top value on stack in place (but doesn't change stack height) /// The returned string points to memory on the Lua stack. Use or duplicate it before using
/// /// `lstate` again.
/// "error" points to memory on the lua stack, use
/// or duplicate the string before using "lstate" again
/// ///
/// @param[out] len length of error (can be NULL) /// @param[out] len length of error (can be NULL)
static const char *nlua_get_error(lua_State *lstate, size_t *len) static const char *nlua_get_error(lua_State *lstate, size_t *len)
@@ -147,7 +146,7 @@ static const char *nlua_get_error(lua_State *lstate, size_t *len)
return lua_tolstring(lstate, -1, len); return lua_tolstring(lstate, -1, len);
} }
/// Convert lua error into a Vim error message /// Converts a Lua error into a Vim error message.
/// ///
/// @param lstate Lua interpreter state. /// @param lstate Lua interpreter state.
/// @param[in] msg Message base, must contain one `%.*s`. /// @param[in] msg Message base, must contain one `%.*s`.

View File

@@ -170,7 +170,8 @@ struct terminal {
struct { struct {
int row, col; int row, col;
int shape; int shape;
bool visible; bool visible; ///< Terminal wants to show cursor.
///< `TerminalState.cursor_visible` indicates whether it is actually shown.
bool blink; bool blink;
} cursor; } cursor;

View File

@@ -2526,7 +2526,7 @@ describe('LSP', function()
) )
end) end)
it('Supports file creation with CreateFile payload', function() it('supports file creation with CreateFile payload', function()
local tmpfile = tmpname(false) local tmpfile = tmpname(false)
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
local edit = { local edit = {
@@ -2544,7 +2544,7 @@ describe('LSP', function()
end) end)
it( it(
'Supports file creation in folder that needs to be created with CreateFile payload', 'supports file creation in folder that needs to be created with CreateFile payload',
function() function()
local tmpfile = tmpname(false) .. '/dummy/x/' local tmpfile = tmpname(false) .. '/dummy/x/'
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
@@ -2647,7 +2647,7 @@ describe('LSP', function()
describe('lsp.util.rename', function() describe('lsp.util.rename', function()
local pathsep = n.get_pathsep() local pathsep = n.get_pathsep()
it('Can rename an existing file', function() it('can rename an existing file', function()
local old = tmpname() local old = tmpname()
write_file(old, 'Test content') write_file(old, 'Test content')
local new = tmpname(false) local new = tmpname(false)
@@ -2668,7 +2668,7 @@ describe('LSP', function()
os.remove(new) os.remove(new)
end) end)
it('Can rename a directory', function() it('can rename a directory', function()
-- only reserve the name, file must not exist for the test scenario -- only reserve the name, file must not exist for the test scenario
local old_dir = tmpname(false) local old_dir = tmpname(false)
local new_dir = tmpname(false) local new_dir = tmpname(false)
@@ -2695,7 +2695,7 @@ describe('LSP', function()
os.remove(new_dir) os.remove(new_dir)
end) end)
it('Does not touch buffers that do not match path prefix', function() it('does not touch buffers that do not match path prefix', function()
local old = tmpname(false) local old = tmpname(false)
local new = tmpname(false) local new = tmpname(false)
n.mkdir_p(old) n.mkdir_p(old)
@@ -2730,7 +2730,7 @@ describe('LSP', function()
end) end)
it( it(
'Does not rename file if target exists and ignoreIfExists is set or overwrite is false', 'does not rename file if target exists and ignoreIfExists is set or overwrite is false',
function() function()
local old = tmpname() local old = tmpname()
write_file(old, 'Old File') write_file(old, 'Old File')
@@ -2753,7 +2753,7 @@ describe('LSP', function()
end end
) )
it('Maintains undo information for loaded buffer', function() it('maintains undo information for loaded buffer', function()
local old = tmpname() local old = tmpname()
write_file(old, 'line') write_file(old, 'line')
local new = tmpname(false) local new = tmpname(false)
@@ -2777,7 +2777,7 @@ describe('LSP', function()
eq(true, undo_kept) eq(true, undo_kept)
end) end)
it('Maintains undo information for unloaded buffer', function() it('maintains undo information for unloaded buffer', function()
local old = tmpname() local old = tmpname()
write_file(old, 'line') write_file(old, 'line')
local new = tmpname(false) local new = tmpname(false)
@@ -2798,7 +2798,7 @@ describe('LSP', function()
eq(true, undo_kept) eq(true, undo_kept)
end) end)
it('Does not rename file when it conflicts with a buffer without file', function() it('does not rename file when it conflicts with a buffer without file', function()
local old = tmpname() local old = tmpname()
write_file(old, 'Old File') write_file(old, 'Old File')
local new = tmpname(false) local new = tmpname(false)
@@ -2817,7 +2817,7 @@ describe('LSP', function()
eq('Old File', read_file(old)) eq('Old File', read_file(old))
end) end)
it('Does override target if overwrite is true', function() it('does override target if overwrite is true', function()
local old = tmpname() local old = tmpname()
write_file(old, 'Old file') write_file(old, 'Old file')
local new = tmpname() local new = tmpname()
@@ -2833,7 +2833,7 @@ describe('LSP', function()
end) end)
describe('lsp.util.locations_to_items', function() describe('lsp.util.locations_to_items', function()
it('Convert Location[] to items', function() it('convert Location[] to items', function()
local expected_template = { local expected_template = {
{ {
filename = '/fake/uri', filename = '/fake/uri',
@@ -2879,7 +2879,7 @@ describe('LSP', function()
end end
end) end)
it('Convert LocationLink[] to items', function() it('convert LocationLink[] to items', function()
local expected = { local expected = {
{ {
filename = '/fake/uri', filename = '/fake/uri',
@@ -2926,7 +2926,7 @@ describe('LSP', function()
describe('lsp.util.symbols_to_items', function() describe('lsp.util.symbols_to_items', function()
describe('convert DocumentSymbol[] to items', function() describe('convert DocumentSymbol[] to items', function()
it('DocumentSymbol has children', function() it('documentSymbol has children', function()
local expected = { local expected = {
{ {
col = 1, col = 1,
@@ -3047,7 +3047,7 @@ describe('LSP', function()
) )
end) end)
it('DocumentSymbol has no children', function() it('documentSymbol has no children', function()
local expected = { local expected = {
{ {
col = 1, col = 1,
@@ -4387,7 +4387,7 @@ describe('LSP', function()
end) end)
describe('vim.lsp.buf.code_action', function() describe('vim.lsp.buf.code_action', function()
it('Calls client side command if available', function() it('calls client side command if available', function()
local client --- @type vim.lsp.Client local client --- @type vim.lsp.Client
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
@@ -4431,7 +4431,7 @@ describe('LSP', function()
} }
end) end)
it('Calls workspace/executeCommand if no client side command', function() it('calls workspace/executeCommand if no client side command', function()
local client --- @type vim.lsp.Client local client --- @type vim.lsp.Client
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
@@ -4472,7 +4472,7 @@ describe('LSP', function()
}) })
end) end)
it('Filters and automatically applies action if requested', function() it('filters and automatically applies action if requested', function()
local client --- @type vim.lsp.Client local client --- @type vim.lsp.Client
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
@@ -4547,7 +4547,7 @@ describe('LSP', function()
} }
end) end)
it('Fallback to command execution on resolve error', function() it('fallback to command execution on resolve error', function()
clear() clear()
exec_lua(create_server_definition) exec_lua(create_server_definition)
local result = exec_lua(function() local result = exec_lua(function()
@@ -4592,7 +4592,7 @@ describe('LSP', function()
eq('command:1', result[5].params.command) eq('command:1', result[5].params.command)
end) end)
it('Resolves command property', function() it('resolves command property', function()
clear() clear()
exec_lua(create_server_definition) exec_lua(create_server_definition)
local result = exec_lua(function() local result = exec_lua(function()
@@ -4639,14 +4639,14 @@ describe('LSP', function()
end) end)
describe('vim.lsp.commands', function() describe('vim.lsp.commands', function()
it('Accepts only string keys', function() it('accepts only string keys', function()
matches( matches(
'.*The key for commands in `vim.lsp.commands` must be a string', '.*The key for commands in `vim.lsp.commands` must be a string',
pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end')
) )
end) end)
it('Accepts only function values', function() it('accepts only function values', function()
matches( matches(
'.*Command added to `vim.lsp.commands` must be a function', '.*Command added to `vim.lsp.commands` must be a function',
pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10') pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10')
@@ -4880,7 +4880,7 @@ describe('LSP', function()
end) end)
describe('vim.lsp.buf.format', function() describe('vim.lsp.buf.format', function()
it('Aborts with notify if no client matches filter', function() it('aborts with notify if no client matches filter', function()
local client --- @type vim.lsp.Client local client --- @type vim.lsp.Client
test_rpc_server { test_rpc_server {
test_name = 'basic_init', test_name = 'basic_init',
@@ -4906,7 +4906,7 @@ describe('LSP', function()
} }
end) end)
it('Sends textDocument/formatting request to format buffer', function() it('sends textDocument/formatting request to format buffer', function()
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
{ NIL, {}, { method = 'start', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } },
@@ -4940,7 +4940,7 @@ describe('LSP', function()
} }
end) end)
it('Sends textDocument/rangeFormatting request to format a range', function() it('sends textDocument/rangeFormatting request to format a range', function()
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
{ NIL, {}, { method = 'start', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } },
@@ -4981,7 +4981,7 @@ describe('LSP', function()
} }
end) end)
it('Sends textDocument/rangesFormatting request to format multiple ranges', function() it('sends textDocument/rangesFormatting request to format multiple ranges', function()
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
{ NIL, {}, { method = 'start', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } },
@@ -5028,7 +5028,7 @@ describe('LSP', function()
} }
end) end)
it('Can format async', function() it('can format async', function()
local expected_handlers = { local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'shutdown', client_id = 1 } },
{ NIL, {}, { method = 'start', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } },
@@ -5157,7 +5157,7 @@ describe('LSP', function()
eq(expected_range, result[5].params.range) eq(expected_range, result[5].params.range)
end) end)
it('Aborts with notify if no clients support requested method', function() it('aborts with notify if no clients support requested method', function()
exec_lua(create_server_definition) exec_lua(create_server_definition)
exec_lua(function() exec_lua(function()
vim.notify = function(msg, _) vim.notify = function(msg, _)