2024-04-05 03:08:54 -07:00
|
|
|
--- Default user commands
|
|
|
|
do
|
|
|
|
vim.api.nvim_create_user_command('Inspect', function(cmd)
|
|
|
|
if cmd.bang then
|
|
|
|
vim.print(vim.inspect_pos())
|
|
|
|
else
|
|
|
|
vim.show_pos()
|
|
|
|
end
|
|
|
|
end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true })
|
|
|
|
|
|
|
|
vim.api.nvim_create_user_command('InspectTree', function(cmd)
|
|
|
|
if cmd.mods ~= '' or cmd.count ~= 0 then
|
|
|
|
local count = cmd.count ~= 0 and cmd.count or ''
|
|
|
|
local new = cmd.mods ~= '' and 'new' or 'vnew'
|
|
|
|
|
|
|
|
vim.treesitter.inspect_tree({
|
|
|
|
command = ('%s %s%s'):format(cmd.mods, count, new),
|
|
|
|
})
|
|
|
|
else
|
|
|
|
vim.treesitter.inspect_tree()
|
|
|
|
end
|
|
|
|
end, { desc = 'Inspect treesitter language tree for buffer', count = true })
|
|
|
|
|
|
|
|
vim.api.nvim_create_user_command('EditQuery', function(cmd)
|
|
|
|
vim.treesitter.query.edit(cmd.fargs[1])
|
|
|
|
end, { desc = 'Edit treesitter query', nargs = '?' })
|
|
|
|
end
|
|
|
|
|
2023-11-08 08:33:37 -07:00
|
|
|
--- Default mappings
|
|
|
|
do
|
|
|
|
--- Default maps for * and # in visual mode.
|
|
|
|
---
|
|
|
|
--- See |v_star-default| and |v_#-default|
|
|
|
|
do
|
|
|
|
local function _visual_search(cmd)
|
|
|
|
assert(cmd == '/' or cmd == '?')
|
vim-patch:9.1.0142: getregion() can be improved (#27662)
Problem: getregion() can be improved (after v9.1.120)
Solution: change getregion() implementation to use pos as lists and
one optional {opt} dictionary (Shougo Matsushita)
Note: The following is a breaking change!
Currently, the getregion() function (included as of patch v9.1.120) takes
3 arguments: the first 2 arguments are strings, describing a position,
arg3 is the type string.
However, that is slightly inflexible, there is no way to specify
additional arguments. So let's instead change the function signature to:
getregion(pos1, pos2 [, {Dict}]) where both pos1 and pos2 are lists.
This is slightly cleaner, and gives us the flexibility to specify
additional arguments as key/value pairs to the optional Dict arg.
Now it supports the "type" key to specify the selection type
(characterwise, blockwise or linewise) and now in addition one can also
define the selection type, independently of what the 'selection' option
actually is.
Technically, this is a breaking change, but since the getregion()
Vimscript function is still quite new, this should be fine.
closes: vim/vim#14090
https://github.com/vim/vim/commit/19b718828d8d5fab52d94c6cdba694641879ab38
Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
2024-02-28 16:19:26 -07:00
|
|
|
local chunks =
|
|
|
|
vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
|
2023-11-08 08:33:37 -07:00
|
|
|
local esc_chunks = vim
|
|
|
|
.iter(chunks)
|
|
|
|
:map(function(v)
|
|
|
|
return vim.fn.escape(v, cmd == '/' and [[/\]] or [[?\]])
|
|
|
|
end)
|
|
|
|
:totable()
|
|
|
|
local esc_pat = table.concat(esc_chunks, [[\n]])
|
|
|
|
local search_cmd = ([[%s\V%s%s]]):format(cmd, esc_pat, '\n')
|
|
|
|
return '\27' .. search_cmd
|
|
|
|
end
|
|
|
|
|
|
|
|
vim.keymap.set('x', '*', function()
|
|
|
|
return _visual_search('/')
|
|
|
|
end, { desc = ':help v_star-default', expr = true, silent = true })
|
|
|
|
vim.keymap.set('x', '#', function()
|
|
|
|
return _visual_search('?')
|
|
|
|
end, { desc = ':help v_#-default', expr = true, silent = true })
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Map Y to y$. This mimics the behavior of D and C. See |Y-default|
|
|
|
|
vim.keymap.set('n', 'Y', 'y$', { desc = ':help Y-default' })
|
|
|
|
|
|
|
|
--- Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>. #17473
|
|
|
|
---
|
|
|
|
--- See |CTRL-L-default|
|
|
|
|
vim.keymap.set('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>', {
|
|
|
|
desc = ':help CTRL-L-default',
|
|
|
|
})
|
|
|
|
|
|
|
|
--- Set undo points when deleting text in insert mode.
|
|
|
|
---
|
|
|
|
--- See |i_CTRL-U-default| and |i_CTRL-W-default|
|
|
|
|
vim.keymap.set('i', '<C-U>', '<C-G>u<C-U>', { desc = ':help i_CTRL-U-default' })
|
|
|
|
vim.keymap.set('i', '<C-W>', '<C-G>u<C-W>', { desc = ':help i_CTRL-W-default' })
|
|
|
|
|
|
|
|
--- Use the same flags as the previous substitution with &.
|
|
|
|
---
|
|
|
|
--- Use : instead of <Cmd> so that ranges are supported. #19365
|
|
|
|
---
|
|
|
|
--- See |&-default|
|
|
|
|
vim.keymap.set('n', '&', ':&&<CR>', { desc = ':help &-default' })
|
|
|
|
|
2024-04-14 12:43:33 -07:00
|
|
|
--- Use Q in Visual mode to execute a macro on each line of the selection. #21422
|
|
|
|
--- This only make sense in linewise Visual mode. #28287
|
2023-12-26 16:26:18 -07:00
|
|
|
---
|
|
|
|
--- Applies to @x and includes @@ too.
|
|
|
|
vim.keymap.set(
|
|
|
|
'x',
|
|
|
|
'Q',
|
2024-04-14 12:43:33 -07:00
|
|
|
"mode() == 'V' ? ':normal! @<C-R>=reg_recorded()<CR><CR>' : 'Q'",
|
|
|
|
{ silent = true, expr = true, desc = ':help v_Q-default' }
|
2023-12-26 16:26:18 -07:00
|
|
|
)
|
|
|
|
vim.keymap.set(
|
|
|
|
'x',
|
|
|
|
'@',
|
2024-04-14 12:43:33 -07:00
|
|
|
"mode() == 'V' ? ':normal! @'.getcharstr().'<CR>' : '@'",
|
2023-12-26 16:26:18 -07:00
|
|
|
{ silent = true, expr = true, desc = ':help v_@-default' }
|
|
|
|
)
|
2024-04-11 18:16:13 -07:00
|
|
|
|
2024-04-15 04:33:09 -07:00
|
|
|
--- Map |gx| to call |vim.ui.open| on the <cfile> at cursor.
|
2023-11-08 08:33:37 -07:00
|
|
|
do
|
|
|
|
local function do_open(uri)
|
2024-04-25 04:15:58 -07:00
|
|
|
local ok, cmd_or_err = vim.ui.open(uri)
|
|
|
|
local rv = ok and (cmd_or_err --[[@as vim.SystemObj]]):wait(1000) or nil
|
|
|
|
if rv and rv.code ~= 0 then
|
|
|
|
ok = false
|
|
|
|
cmd_or_err = ('vim.ui.open: command %s (%d): %s'):format(
|
2024-04-15 04:33:09 -07:00
|
|
|
(rv.code == 124 and 'timeout' or 'failed'),
|
|
|
|
rv.code,
|
2024-04-25 04:15:58 -07:00
|
|
|
vim.inspect(cmd_or_err.cmd)
|
2024-04-15 04:33:09 -07:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2024-04-25 04:15:58 -07:00
|
|
|
if not ok then
|
|
|
|
vim.notify(cmd_or_err --[[@as string]], vim.log.levels.ERROR)
|
2023-11-08 08:33:37 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local gx_desc =
|
|
|
|
'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
|
|
|
|
vim.keymap.set({ 'n' }, 'gx', function()
|
|
|
|
do_open(vim.fn.expand('<cfile>'))
|
|
|
|
end, { desc = gx_desc })
|
|
|
|
vim.keymap.set({ 'x' }, 'gx', function()
|
2024-02-28 17:21:13 -07:00
|
|
|
local lines =
|
|
|
|
vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
|
|
|
|
-- Trim whitespace on each line and concatenate.
|
|
|
|
do_open(table.concat(vim.iter(lines):map(vim.trim):totable()))
|
2023-11-08 08:33:37 -07:00
|
|
|
end, { desc = gx_desc })
|
|
|
|
end
|
feat(comment): add built-in commenting
Design
- Enable commenting support only through `gc` mappings for simplicity.
No ability to configure, no Lua module, no user commands. Yet.
- Overall implementation is a simplified version of 'mini.comment'
module of 'echasnovski/mini.nvim' adapted to be a better suit for
core. It basically means reducing code paths which use only specific
fixed set of plugin config.
All used options are default except `pad_comment_parts = false`. This
means that 'commentstring' option is used as is without forcing single
space inner padding.
As 'tpope/vim-commentary' was considered for inclusion earlier, here is
a quick summary of how this commit differs from it:
- **User-facing features**. Both implement similar user-facing mappings.
This commit does not include `gcu` which is essentially a `gcgc`.
There are no commands, events, or configuration in this commit.
- **Size**. Both have reasonably comparable number of lines of code,
while this commit has more comments in tricky areas.
- **Maintainability**. This commit has (purely subjectively) better
readability, tests, and Lua types.
- **Configurability**. This commit has no user configuration, while
'vim-commentary' has some (partially as a counter-measure to possibly
modifying 'commentstring' option).
- **Extra features**:
- This commit supports tree-sitter by computing `'commentstring'`
option under cursor, which can matter in presence of tree-sitter
injected languages.
- This commit comments blank lines while 'tpope/vim-commentary' does
not. At the same time, blank lines are not taken into account when
deciding the toggle action.
- This commit has much better speed on larger chunks of lines (like
above 1000). This is thanks to using `nvim_buf_set_lines()` to set
all new lines at once, and not with `vim.fn.setline()`.
2024-04-04 08:10:12 -07:00
|
|
|
|
2024-04-26 11:16:12 -07:00
|
|
|
--- Default maps for built-in commenting.
|
|
|
|
---
|
|
|
|
--- See |gc-default| and |gcc-default|.
|
feat(comment): add built-in commenting
Design
- Enable commenting support only through `gc` mappings for simplicity.
No ability to configure, no Lua module, no user commands. Yet.
- Overall implementation is a simplified version of 'mini.comment'
module of 'echasnovski/mini.nvim' adapted to be a better suit for
core. It basically means reducing code paths which use only specific
fixed set of plugin config.
All used options are default except `pad_comment_parts = false`. This
means that 'commentstring' option is used as is without forcing single
space inner padding.
As 'tpope/vim-commentary' was considered for inclusion earlier, here is
a quick summary of how this commit differs from it:
- **User-facing features**. Both implement similar user-facing mappings.
This commit does not include `gcu` which is essentially a `gcgc`.
There are no commands, events, or configuration in this commit.
- **Size**. Both have reasonably comparable number of lines of code,
while this commit has more comments in tricky areas.
- **Maintainability**. This commit has (purely subjectively) better
readability, tests, and Lua types.
- **Configurability**. This commit has no user configuration, while
'vim-commentary' has some (partially as a counter-measure to possibly
modifying 'commentstring' option).
- **Extra features**:
- This commit supports tree-sitter by computing `'commentstring'`
option under cursor, which can matter in presence of tree-sitter
injected languages.
- This commit comments blank lines while 'tpope/vim-commentary' does
not. At the same time, blank lines are not taken into account when
deciding the toggle action.
- This commit has much better speed on larger chunks of lines (like
above 1000). This is thanks to using `nvim_buf_set_lines()` to set
all new lines at once, and not with `vim.fn.setline()`.
2024-04-04 08:10:12 -07:00
|
|
|
do
|
|
|
|
local operator_rhs = function()
|
|
|
|
return require('vim._comment').operator()
|
|
|
|
end
|
|
|
|
vim.keymap.set({ 'n', 'x' }, 'gc', operator_rhs, { expr = true, desc = 'Toggle comment' })
|
|
|
|
|
|
|
|
local line_rhs = function()
|
|
|
|
return require('vim._comment').operator() .. '_'
|
|
|
|
end
|
|
|
|
vim.keymap.set('n', 'gcc', line_rhs, { expr = true, desc = 'Toggle comment line' })
|
|
|
|
|
|
|
|
local textobject_rhs = function()
|
|
|
|
require('vim._comment').textobject()
|
|
|
|
end
|
|
|
|
vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' })
|
|
|
|
end
|
2024-04-26 09:12:49 -07:00
|
|
|
|
|
|
|
--- Default maps for LSP functions.
|
|
|
|
---
|
|
|
|
--- These are mapped unconditionally to avoid confusion. If no server is attached, or if a server
|
|
|
|
--- does not support a capability, an error message is displayed rather than exhibiting different
|
|
|
|
--- behavior.
|
|
|
|
---
|
|
|
|
--- See |gr-default|, |crn|, |crr|, |i_CTRL-S|.
|
|
|
|
do
|
|
|
|
vim.keymap.set('n', 'crn', function()
|
|
|
|
vim.lsp.buf.rename()
|
|
|
|
end, { desc = 'vim.lsp.buf.rename()' })
|
|
|
|
|
|
|
|
vim.keymap.set({ 'n', 'v' }, 'crr', function()
|
|
|
|
vim.lsp.buf.code_action()
|
|
|
|
end, { desc = 'vim.lsp.buf.code_action()' })
|
|
|
|
|
|
|
|
vim.keymap.set('n', 'gr', function()
|
|
|
|
vim.lsp.buf.references()
|
|
|
|
end, { desc = 'vim.lsp.buf.references()' })
|
|
|
|
|
|
|
|
vim.keymap.set('i', '<C-S>', function()
|
|
|
|
vim.lsp.buf.signature_help()
|
|
|
|
end, { desc = 'vim.lsp.buf.signature_help()' })
|
|
|
|
end
|
2024-04-26 11:16:12 -07:00
|
|
|
|
|
|
|
--- Map [d and ]d to move to the previous/next diagnostic. Map <C-W>d to open a floating window
|
|
|
|
--- for the diagnostic under the cursor.
|
|
|
|
---
|
|
|
|
--- See |[d-default|, |]d-default|, and |CTRL-W_d-default|.
|
|
|
|
do
|
|
|
|
vim.keymap.set('n', ']d', function()
|
|
|
|
vim.diagnostic.goto_next({ float = false })
|
|
|
|
end, {
|
|
|
|
desc = 'Jump to the next diagnostic with the highest severity',
|
|
|
|
})
|
|
|
|
|
|
|
|
vim.keymap.set('n', '[d', function()
|
|
|
|
vim.diagnostic.goto_prev({ float = false })
|
|
|
|
end, {
|
|
|
|
desc = 'Jump to the previous diagnostic with the highest severity',
|
|
|
|
})
|
|
|
|
|
|
|
|
vim.keymap.set('n', '<C-W>d', function()
|
2024-04-28 08:15:10 -07:00
|
|
|
vim.diagnostic.open_float()
|
2024-04-26 11:16:12 -07:00
|
|
|
end, {
|
|
|
|
desc = 'Open a floating window showing diagnostics under the cursor',
|
|
|
|
})
|
|
|
|
|
|
|
|
vim.keymap.set('n', '<C-W><C-D>', '<C-W>d', {
|
|
|
|
remap = true,
|
|
|
|
desc = 'Open a floating window showing diagnostics under the cursor',
|
|
|
|
})
|
|
|
|
end
|
2023-11-08 08:33:37 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
--- Default menus
|
|
|
|
do
|
|
|
|
--- Right click popup menu
|
|
|
|
-- TODO VimScript, no l10n
|
|
|
|
vim.cmd([[
|
|
|
|
vnoremenu PopUp.Cut "+x
|
|
|
|
vnoremenu PopUp.Copy "+y
|
|
|
|
anoremenu PopUp.Paste "+gP
|
|
|
|
vnoremenu PopUp.Paste "+P
|
|
|
|
vnoremenu PopUp.Delete "_x
|
|
|
|
nnoremenu PopUp.Select\ All ggVG
|
|
|
|
vnoremenu PopUp.Select\ All gg0oG$
|
|
|
|
inoremenu PopUp.Select\ All <C-Home><C-O>VG
|
2024-04-05 03:08:54 -07:00
|
|
|
anoremenu PopUp.Inspect <Cmd>Inspect<CR>
|
2023-11-08 08:33:37 -07:00
|
|
|
anoremenu PopUp.-1- <Nop>
|
|
|
|
anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
|
|
|
|
]])
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Default autocommands. See |default-autocmds|
|
|
|
|
do
|
|
|
|
local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim_terminal', {})
|
|
|
|
vim.api.nvim_create_autocmd('BufReadCmd', {
|
|
|
|
pattern = 'term://*',
|
|
|
|
group = nvim_terminal_augroup,
|
|
|
|
desc = 'Treat term:// buffers as terminal buffers',
|
|
|
|
nested = true,
|
|
|
|
command = "if !exists('b:term_title')|call termopen(matchstr(expand(\"<amatch>\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'cwd': expand(get(matchlist(expand(\"<amatch>\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})",
|
|
|
|
})
|
|
|
|
|
|
|
|
vim.api.nvim_create_autocmd({ 'TermClose' }, {
|
|
|
|
group = nvim_terminal_augroup,
|
2024-02-22 12:54:21 -07:00
|
|
|
nested = true,
|
2023-11-08 08:33:37 -07:00
|
|
|
desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error',
|
|
|
|
callback = function(args)
|
2024-02-22 12:54:21 -07:00
|
|
|
if vim.v.event.status ~= 0 then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel)
|
|
|
|
local argv = info.argv or {}
|
2024-04-13 04:41:59 -07:00
|
|
|
if table.concat(argv, ' ') == vim.o.shell then
|
2024-02-22 12:54:21 -07:00
|
|
|
vim.api.nvim_buf_delete(args.buf, { force = true })
|
2023-11-08 08:33:37 -07:00
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2024-01-15 09:12:07 -07:00
|
|
|
vim.api.nvim_create_autocmd('TermRequest', {
|
|
|
|
group = nvim_terminal_augroup,
|
|
|
|
desc = 'Respond to OSC foreground/background color requests',
|
|
|
|
callback = function(args)
|
2024-04-26 06:07:47 -07:00
|
|
|
--- @type integer
|
2024-02-26 10:33:16 -07:00
|
|
|
local channel = vim.bo[args.buf].channel
|
|
|
|
if channel == 0 then
|
|
|
|
return
|
|
|
|
end
|
2024-01-15 09:12:07 -07:00
|
|
|
local fg_request = args.data == '\027]10;?'
|
|
|
|
local bg_request = args.data == '\027]11;?'
|
|
|
|
if fg_request or bg_request then
|
|
|
|
-- WARN: This does not return the actual foreground/background color,
|
|
|
|
-- but rather returns:
|
|
|
|
-- - fg=white/bg=black when Nvim option 'background' is 'dark'
|
|
|
|
-- - fg=black/bg=white when Nvim option 'background' is 'light'
|
|
|
|
local red, green, blue = 0, 0, 0
|
|
|
|
local bg_option_dark = vim.o.background == 'dark'
|
|
|
|
if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then
|
|
|
|
red, green, blue = 65535, 65535, 65535
|
|
|
|
end
|
|
|
|
local command = fg_request and 10 or 11
|
|
|
|
local data = string.format('\027]%d;rgb:%04x/%04x/%04x\007', command, red, green, blue)
|
|
|
|
vim.api.nvim_chan_send(channel, data)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2023-11-08 08:33:37 -07:00
|
|
|
vim.api.nvim_create_autocmd('CmdwinEnter', {
|
|
|
|
pattern = '[:>]',
|
|
|
|
desc = 'Limit syntax sync to maxlines=1 in the command window',
|
|
|
|
group = vim.api.nvim_create_augroup('nvim_cmdwin', {}),
|
|
|
|
command = 'syntax sync minlines=1 maxlines=1',
|
|
|
|
})
|
|
|
|
|
|
|
|
vim.api.nvim_create_autocmd('SwapExists', {
|
|
|
|
pattern = '*',
|
|
|
|
desc = 'Skip the swapfile prompt when the swapfile is owned by a running Nvim process',
|
|
|
|
group = vim.api.nvim_create_augroup('nvim_swapfile', {}),
|
|
|
|
callback = function()
|
|
|
|
local info = vim.fn.swapinfo(vim.v.swapname)
|
|
|
|
local user = vim.uv.os_get_passwd().username
|
|
|
|
local iswin = 1 == vim.fn.has('win32')
|
|
|
|
if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then
|
|
|
|
vim.v.swapchoice = '' -- Show the prompt.
|
|
|
|
return
|
|
|
|
end
|
|
|
|
vim.v.swapchoice = 'e' -- Choose "(E)dit".
|
|
|
|
vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid))
|
|
|
|
end,
|
|
|
|
})
|
2024-01-06 17:08:29 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
-- Only do the following when the TUI is attached
|
|
|
|
local tty = nil
|
|
|
|
for _, ui in ipairs(vim.api.nvim_list_uis()) do
|
|
|
|
if ui.chan == 1 and ui.stdout_tty then
|
|
|
|
tty = ui
|
|
|
|
break
|
2023-12-14 10:11:46 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
if tty then
|
|
|
|
local group = vim.api.nvim_create_augroup('nvim_tty', {})
|
|
|
|
|
|
|
|
--- Set an option after startup (so that OptionSet is fired), but only if not
|
|
|
|
--- already set by the user.
|
2023-12-05 11:01:32 -07:00
|
|
|
---
|
2024-04-26 09:12:49 -07:00
|
|
|
--- @param option string Option name
|
|
|
|
--- @param value any Option value
|
|
|
|
local function setoption(option, value)
|
|
|
|
if vim.api.nvim_get_option_info2(option, {}).was_set then
|
|
|
|
-- Don't do anything if option is already set
|
|
|
|
return
|
2023-11-06 14:46:44 -07:00
|
|
|
end
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
-- Wait until Nvim is finished starting to set the option to ensure the
|
|
|
|
-- OptionSet event fires.
|
|
|
|
if vim.v.vim_did_enter == 1 then
|
|
|
|
--- @diagnostic disable-next-line:no-unknown
|
|
|
|
vim.o[option] = value
|
|
|
|
else
|
|
|
|
vim.api.nvim_create_autocmd('VimEnter', {
|
|
|
|
group = group,
|
|
|
|
once = true,
|
|
|
|
nested = true,
|
|
|
|
callback = function()
|
|
|
|
setoption(option, value)
|
|
|
|
end,
|
|
|
|
})
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
2023-11-06 14:46:44 -07:00
|
|
|
end
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
--- Guess value of 'background' based on terminal color.
|
2023-12-05 11:01:32 -07:00
|
|
|
---
|
2024-04-26 09:12:49 -07:00
|
|
|
--- We write Operating System Command (OSC) 11 to the terminal to request the
|
|
|
|
--- terminal's background color. We then wait for a response. If the response
|
|
|
|
--- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
|
|
|
|
--- compute the luminance[1] of the RGB color and classify it as light/dark
|
|
|
|
--- accordingly. Note that the color components may have anywhere from one to
|
|
|
|
--- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
|
|
|
|
--- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
|
|
|
|
--- ignored in the calculations.
|
2023-12-05 11:01:32 -07:00
|
|
|
---
|
2024-04-26 09:12:49 -07:00
|
|
|
--- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
|
|
|
|
do
|
|
|
|
--- Parse a string of hex characters as a color.
|
|
|
|
---
|
|
|
|
--- The string can contain 1 to 4 hex characters. The returned value is
|
|
|
|
--- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
|
|
|
|
---
|
|
|
|
--- For instance, if only a single hex char "a" is used, then this function
|
|
|
|
--- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
|
|
|
|
--- 256).
|
|
|
|
---
|
|
|
|
--- @param c string Color as a string of hex chars
|
|
|
|
--- @return number? Intensity of the color
|
|
|
|
local function parsecolor(c)
|
|
|
|
if #c == 0 or #c > 4 then
|
|
|
|
return nil
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
2023-11-06 14:46:44 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
local val = tonumber(c, 16)
|
|
|
|
if not val then
|
|
|
|
return nil
|
2023-11-06 14:46:44 -07:00
|
|
|
end
|
2023-11-14 16:02:57 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
local max = tonumber(string.rep('f', #c), 16)
|
|
|
|
return val / max
|
2023-11-14 16:02:57 -07:00
|
|
|
end
|
2023-12-05 11:01:32 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
--- Parse an OSC 11 response
|
|
|
|
---
|
|
|
|
--- Either of the two formats below are accepted:
|
|
|
|
---
|
|
|
|
--- OSC 11 ; rgb:<red>/<green>/<blue>
|
|
|
|
---
|
|
|
|
--- or
|
|
|
|
---
|
|
|
|
--- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
|
|
|
|
---
|
|
|
|
--- where
|
|
|
|
---
|
|
|
|
--- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
|
|
|
|
---
|
|
|
|
--- The alpha component is ignored, if present.
|
|
|
|
---
|
|
|
|
--- @param resp string OSC 11 response
|
|
|
|
--- @return string? Red component
|
|
|
|
--- @return string? Green component
|
|
|
|
--- @return string? Blue component
|
|
|
|
local function parseosc11(resp)
|
|
|
|
local r, g, b
|
|
|
|
r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
|
|
|
|
if not r and not g and not b then
|
|
|
|
local a
|
|
|
|
r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
|
|
|
|
if not a or #a > 4 then
|
|
|
|
return nil, nil, nil
|
|
|
|
end
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
|
|
|
|
return r, g, b
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
return nil, nil, nil
|
|
|
|
end
|
2023-12-05 11:01:32 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
local timer = assert(vim.uv.new_timer())
|
2023-12-05 11:01:32 -07:00
|
|
|
|
|
|
|
local id = vim.api.nvim_create_autocmd('TermResponse', {
|
2024-01-06 17:08:29 -07:00
|
|
|
group = group,
|
2023-12-05 11:01:32 -07:00
|
|
|
nested = true,
|
|
|
|
callback = function(args)
|
|
|
|
local resp = args.data ---@type string
|
2024-04-26 09:12:49 -07:00
|
|
|
local r, g, b = parseosc11(resp)
|
|
|
|
if r and g and b then
|
|
|
|
local rr = parsecolor(r)
|
|
|
|
local gg = parsecolor(g)
|
|
|
|
local bb = parsecolor(b)
|
|
|
|
|
|
|
|
if rr and gg and bb then
|
|
|
|
local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
|
|
|
|
local bg = luminance < 0.5 and 'dark' or 'light'
|
|
|
|
setoption('background', bg)
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
io.stdout:write('\027]11;?\007')
|
2023-12-05 11:01:32 -07:00
|
|
|
|
|
|
|
timer:start(1000, 0, function()
|
|
|
|
-- Delete the autocommand if no response was received
|
|
|
|
vim.schedule(function()
|
|
|
|
-- Suppress error if autocommand has already been deleted
|
|
|
|
pcall(vim.api.nvim_del_autocmd, id)
|
|
|
|
end)
|
|
|
|
|
|
|
|
if not timer:is_closing() then
|
|
|
|
timer:close()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
2024-04-26 09:12:49 -07:00
|
|
|
|
|
|
|
--- If the TUI (term_has_truecolor) was able to determine that the host
|
|
|
|
--- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
|
|
|
|
--- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
|
|
|
|
--- response indicates that it does support truecolor enable 'termguicolors',
|
|
|
|
--- but only if the user has not already disabled it.
|
|
|
|
do
|
|
|
|
if tty.rgb then
|
|
|
|
-- The TUI was able to determine truecolor support
|
|
|
|
setoption('termguicolors', true)
|
|
|
|
else
|
|
|
|
local caps = {} ---@type table<string, boolean>
|
|
|
|
require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
|
|
|
|
if not found then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
caps[cap] = true
|
|
|
|
if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
|
|
|
|
setoption('termguicolors', true)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
local timer = assert(vim.uv.new_timer())
|
|
|
|
|
|
|
|
-- Arbitrary colors to set in the SGR sequence
|
|
|
|
local r = 1
|
|
|
|
local g = 2
|
|
|
|
local b = 3
|
|
|
|
|
|
|
|
local id = vim.api.nvim_create_autocmd('TermResponse', {
|
|
|
|
group = group,
|
|
|
|
nested = true,
|
|
|
|
callback = function(args)
|
|
|
|
local resp = args.data ---@type string
|
|
|
|
local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
|
|
|
|
|
|
|
|
if decrqss then
|
|
|
|
-- The DECRQSS SGR response first contains attributes separated by
|
|
|
|
-- semicolons, followed by the SGR itself with parameters separated
|
|
|
|
-- by colons. Some terminals include "0" in the attribute list
|
|
|
|
-- unconditionally; others do not. Our SGR sequence did not set any
|
|
|
|
-- attributes, so there should be no attributes in the list.
|
|
|
|
local attrs = vim.split(decrqss, ';')
|
|
|
|
if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- The returned SGR sequence should begin with 48:2
|
|
|
|
local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$')
|
|
|
|
if not sgr then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- The remaining elements of the SGR sequence should be the 3 colors
|
|
|
|
-- we set. Some terminals also include an additional parameter
|
|
|
|
-- (which can even be empty!), so handle those cases as well
|
|
|
|
local params = vim.split(sgr, ':')
|
|
|
|
if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
if
|
|
|
|
tonumber(params[#params - 2]) == r
|
|
|
|
and tonumber(params[#params - 1]) == g
|
|
|
|
and tonumber(params[#params]) == b
|
|
|
|
then
|
|
|
|
setoption('termguicolors', true)
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
|
|
|
-- Write SGR followed by DECRQSS. This sets the background color then
|
|
|
|
-- immediately asks the terminal what the background color is. If the
|
|
|
|
-- terminal responds to the DECRQSS with the same SGR sequence that we
|
|
|
|
-- sent then the terminal supports truecolor.
|
|
|
|
local decrqss = '\027P$qm\027\\'
|
|
|
|
if os.getenv('TMUX') then
|
|
|
|
decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027'))
|
|
|
|
end
|
|
|
|
-- Reset attributes first, as other code may have set attributes.
|
|
|
|
io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
|
|
|
|
|
|
|
|
timer:start(1000, 0, function()
|
|
|
|
-- Delete the autocommand if no response was received
|
|
|
|
vim.schedule(function()
|
|
|
|
-- Suppress error if autocommand has already been deleted
|
|
|
|
pcall(vim.api.nvim_del_autocmd, id)
|
|
|
|
end)
|
|
|
|
|
|
|
|
if not timer:is_closing() then
|
|
|
|
timer:close()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
2023-12-05 11:01:32 -07:00
|
|
|
end
|
2023-11-06 14:46:44 -07:00
|
|
|
end
|
2024-04-14 03:54:10 -07:00
|
|
|
|
2024-04-26 09:12:49 -07:00
|
|
|
--- Default options
|
|
|
|
do
|
|
|
|
--- Default 'grepprg' to ripgrep if available.
|
|
|
|
if vim.fn.executable('rg') == 1 then
|
|
|
|
-- Match :grep default, otherwise rg searches cwd by default
|
|
|
|
-- Use -uuu to make ripgrep not do its default filtering
|
|
|
|
vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul')
|
|
|
|
vim.o.grepformat = '%f:%l:%c:%m'
|
|
|
|
end
|
2024-04-14 03:54:10 -07:00
|
|
|
end
|