123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816 |
- --- 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
- --- 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 == '?')
- local chunks =
- vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
- 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, replace_keycodes = false })
- vim.keymap.set('x', '#', function()
- return _visual_search('?')
- end, { desc = ':help v_#-default', expr = true, replace_keycodes = false })
- 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' })
- --- 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
- ---
- --- Applies to @x and includes @@ too.
- vim.keymap.set(
- 'x',
- 'Q',
- "mode() ==# 'V' ? ':normal! @<C-R>=reg_recorded()<CR><CR>' : 'Q'",
- { silent = true, expr = true, desc = ':help v_Q-default' }
- )
- vim.keymap.set(
- 'x',
- '@',
- "mode() ==# 'V' ? ':normal! @'.getcharstr().'<CR>' : '@'",
- { silent = true, expr = true, desc = ':help v_@-default' }
- )
- --- Map |gx| to call |vim.ui.open| on the <cfile> at cursor.
- do
- local function do_open(uri)
- local cmd, err = vim.ui.open(uri)
- local rv = cmd and cmd:wait(1000) or nil
- if cmd and rv and rv.code ~= 0 then
- err = ('vim.ui.open: command %s (%d): %s'):format(
- (rv.code == 124 and 'timeout' or 'failed'),
- rv.code,
- vim.inspect(cmd.cmd)
- )
- end
- return err
- end
- local gx_desc =
- 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
- vim.keymap.set({ 'n' }, 'gx', function()
- for _, url in ipairs(require('vim.ui')._get_urls()) do
- local err = do_open(url)
- if err then
- vim.notify(err, vim.log.levels.ERROR)
- end
- end
- end, { desc = gx_desc })
- vim.keymap.set({ 'x' }, 'gx', function()
- local lines =
- vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
- -- Trim whitespace on each line and concatenate.
- local err = do_open(table.concat(vim.iter(lines):map(vim.trim):totable()))
- if err then
- vim.notify(err, vim.log.levels.ERROR)
- end
- end, { desc = gx_desc })
- end
- --- Default maps for built-in commenting.
- ---
- --- See |gc-default| and |gcc-default|.
- 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
- --- Default maps for LSP functions.
- ---
- --- These are mapped unconditionally to avoid different behavior depending on whether an LSP
- --- client is attached. If no client is attached, or if a server does not support a capability, an
- --- error message is displayed rather than exhibiting different behavior.
- ---
- --- See |grr|, |grn|, |gra|, |gri|, |gO|, |i_CTRL-S|.
- do
- vim.keymap.set('n', 'grn', function()
- vim.lsp.buf.rename()
- end, { desc = 'vim.lsp.buf.rename()' })
- vim.keymap.set({ 'n', 'x' }, 'gra', function()
- vim.lsp.buf.code_action()
- end, { desc = 'vim.lsp.buf.code_action()' })
- vim.keymap.set('n', 'grr', function()
- vim.lsp.buf.references()
- end, { desc = 'vim.lsp.buf.references()' })
- vim.keymap.set('n', 'gri', function()
- vim.lsp.buf.implementation()
- end, { desc = 'vim.lsp.buf.implementation()' })
- vim.keymap.set('n', 'gO', function()
- vim.lsp.buf.document_symbol()
- end, { desc = 'vim.lsp.buf.document_symbol()' })
- vim.keymap.set({ 'i', 's' }, '<C-S>', function()
- vim.lsp.buf.signature_help()
- end, { desc = 'vim.lsp.buf.signature_help()' })
- end
- --- 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.jump({ count = vim.v.count1 })
- end, { desc = 'Jump to the next diagnostic in the current buffer' })
- vim.keymap.set('n', '[d', function()
- vim.diagnostic.jump({ count = -vim.v.count1 })
- end, { desc = 'Jump to the previous diagnostic in the current buffer' })
- vim.keymap.set('n', ']D', function()
- vim.diagnostic.jump({ count = math.huge, wrap = false })
- end, { desc = 'Jump to the last diagnostic in the current buffer' })
- vim.keymap.set('n', '[D', function()
- vim.diagnostic.jump({ count = -math.huge, wrap = false })
- end, { desc = 'Jump to the first diagnostic in the current buffer' })
- vim.keymap.set('n', '<C-W>d', function()
- vim.diagnostic.open_float()
- end, { desc = 'Show diagnostics under the cursor' })
- vim.keymap.set(
- 'n',
- '<C-W><C-D>',
- '<C-W>d',
- { remap = true, desc = 'Show diagnostics under the cursor' }
- )
- end
- --- vim-unimpaired style mappings. See: https://github.com/tpope/vim-unimpaired
- do
- --- Execute a command and print errors without a stacktrace.
- --- @param opts table Arguments to |nvim_cmd()|
- local function cmd(opts)
- local ok, err = pcall(vim.api.nvim_cmd, opts, {})
- if not ok then
- vim.api.nvim_err_writeln(err:sub(#'Vim:' + 1))
- end
- end
- -- Quickfix mappings
- vim.keymap.set('n', '[q', function()
- cmd({ cmd = 'cprevious', count = vim.v.count1 })
- end, { desc = ':cprevious' })
- vim.keymap.set('n', ']q', function()
- cmd({ cmd = 'cnext', count = vim.v.count1 })
- end, { desc = ':cnext' })
- vim.keymap.set('n', '[Q', function()
- cmd({ cmd = 'crewind', count = vim.v.count ~= 0 and vim.v.count or nil })
- end, { desc = ':crewind' })
- vim.keymap.set('n', ']Q', function()
- cmd({ cmd = 'clast', count = vim.v.count ~= 0 and vim.v.count or nil })
- end, { desc = ':clast' })
- vim.keymap.set('n', '[<C-Q>', function()
- cmd({ cmd = 'cpfile', count = vim.v.count1 })
- end, { desc = ':cpfile' })
- vim.keymap.set('n', ']<C-Q>', function()
- cmd({ cmd = 'cnfile', count = vim.v.count1 })
- end, { desc = ':cnfile' })
- -- Location list mappings
- vim.keymap.set('n', '[l', function()
- cmd({ cmd = 'lprevious', count = vim.v.count1 })
- end, { desc = ':lprevious' })
- vim.keymap.set('n', ']l', function()
- cmd({ cmd = 'lnext', count = vim.v.count1 })
- end, { desc = ':lnext' })
- vim.keymap.set('n', '[L', function()
- cmd({ cmd = 'lrewind', count = vim.v.count ~= 0 and vim.v.count or nil })
- end, { desc = ':lrewind' })
- vim.keymap.set('n', ']L', function()
- cmd({ cmd = 'llast', count = vim.v.count ~= 0 and vim.v.count or nil })
- end, { desc = ':llast' })
- vim.keymap.set('n', '[<C-L>', function()
- cmd({ cmd = 'lpfile', count = vim.v.count1 })
- end, { desc = ':lpfile' })
- vim.keymap.set('n', ']<C-L>', function()
- cmd({ cmd = 'lnfile', count = vim.v.count1 })
- end, { desc = ':lnfile' })
- -- Argument list
- vim.keymap.set('n', '[a', function()
- cmd({ cmd = 'previous', count = vim.v.count1 })
- end, { desc = ':previous' })
- vim.keymap.set('n', ']a', function()
- -- count doesn't work with :next, must use range. See #30641.
- cmd({ cmd = 'next', range = { vim.v.count1 } })
- end, { desc = ':next' })
- vim.keymap.set('n', '[A', function()
- if vim.v.count ~= 0 then
- cmd({ cmd = 'argument', count = vim.v.count })
- else
- cmd({ cmd = 'rewind' })
- end
- end, { desc = ':rewind' })
- vim.keymap.set('n', ']A', function()
- if vim.v.count ~= 0 then
- cmd({ cmd = 'argument', count = vim.v.count })
- else
- cmd({ cmd = 'last' })
- end
- end, { desc = ':last' })
- -- Tags
- vim.keymap.set('n', '[t', function()
- -- count doesn't work with :tprevious, must use range. See #30641.
- cmd({ cmd = 'tprevious', range = { vim.v.count1 } })
- end, { desc = ':tprevious' })
- vim.keymap.set('n', ']t', function()
- -- count doesn't work with :tnext, must use range. See #30641.
- cmd({ cmd = 'tnext', range = { vim.v.count1 } })
- end, { desc = ':tnext' })
- vim.keymap.set('n', '[T', function()
- -- count doesn't work with :trewind, must use range. See #30641.
- cmd({ cmd = 'trewind', range = vim.v.count ~= 0 and { vim.v.count } or nil })
- end, { desc = ':trewind' })
- vim.keymap.set('n', ']T', function()
- -- :tlast does not accept a count, so use :trewind if count given
- if vim.v.count ~= 0 then
- cmd({ cmd = 'trewind', range = { vim.v.count } })
- else
- cmd({ cmd = 'tlast' })
- end
- end, { desc = ':tlast' })
- vim.keymap.set('n', '[<C-T>', function()
- -- count doesn't work with :ptprevious, must use range. See #30641.
- cmd({ cmd = 'ptprevious', range = { vim.v.count1 } })
- end, { desc = ' :ptprevious' })
- vim.keymap.set('n', ']<C-T>', function()
- -- count doesn't work with :ptnext, must use range. See #30641.
- cmd({ cmd = 'ptnext', range = { vim.v.count1 } })
- end, { desc = ':ptnext' })
- -- Buffers
- vim.keymap.set('n', '[b', function()
- cmd({ cmd = 'bprevious', count = vim.v.count1 })
- end, { desc = ':bprevious' })
- vim.keymap.set('n', ']b', function()
- cmd({ cmd = 'bnext', count = vim.v.count1 })
- end, { desc = ':bnext' })
- vim.keymap.set('n', '[B', function()
- if vim.v.count ~= 0 then
- cmd({ cmd = 'buffer', count = vim.v.count })
- else
- cmd({ cmd = 'brewind' })
- end
- end, { desc = ':brewind' })
- vim.keymap.set('n', ']B', function()
- if vim.v.count ~= 0 then
- cmd({ cmd = 'buffer', count = vim.v.count })
- else
- cmd({ cmd = 'blast' })
- end
- end, { desc = ':blast' })
- -- Add empty lines
- vim.keymap.set('n', '[<Space>', function()
- -- TODO: update once it is possible to assign a Lua function to options #25672
- vim.go.operatorfunc = "v:lua.require'vim._buf'.space_above"
- return 'g@l'
- end, { expr = true, desc = 'Add empty line above cursor' })
- vim.keymap.set('n', ']<Space>', function()
- -- TODO: update once it is possible to assign a Lua function to options #25672
- vim.go.operatorfunc = "v:lua.require'vim._buf'.space_below"
- return 'g@l'
- end, { expr = true, desc = 'Add empty line below cursor' })
- end
- end
- --- Default menus
- do
- --- Right click popup menu
- vim.cmd([[
- anoremenu PopUp.Go\ to\ definition <Cmd>lua vim.lsp.buf.definition()<CR>
- amenu PopUp.Open\ in\ web\ browser gx
- anoremenu PopUp.Inspect <Cmd>Inspect<CR>
- anoremenu PopUp.-1- <Nop>
- 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
- anoremenu PopUp.-2- <Nop>
- anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
- ]])
- local function enable_ctx_menu(ctx)
- vim.cmd([[
- amenu disable PopUp.Go\ to\ definition
- amenu disable PopUp.Open\ in\ web\ browser
- ]])
- if ctx == 'url' then
- vim.cmd([[amenu enable PopUp.Open\ in\ web\ browser]])
- elseif ctx == 'lsp' then
- vim.cmd([[anoremenu enable PopUp.Go\ to\ definition]])
- end
- end
- local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim_popupmenu', {})
- vim.api.nvim_create_autocmd('MenuPopup', {
- pattern = '*',
- group = nvim_popupmenu_augroup,
- desc = 'Mouse popup menu',
- -- nested = true,
- callback = function()
- local urls = require('vim.ui')._get_urls()
- local url = vim.startswith(urls[1], 'http')
- local ctx = url and 'url' or (vim.lsp.get_clients({ bufnr = 0 })[1] and 'lsp' or nil)
- enable_ctx_menu(ctx)
- end,
- })
- 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 jobstart(matchstr(expand(\"<amatch>\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'term': v:true, 'cwd': expand(get(matchlist(expand(\"<amatch>\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})",
- })
- vim.api.nvim_create_autocmd({ 'TermClose' }, {
- group = nvim_terminal_augroup,
- nested = true,
- desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error',
- callback = function(args)
- 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 {}
- if table.concat(argv, ' ') == vim.o.shell then
- vim.api.nvim_buf_delete(args.buf, { force = true })
- end
- end,
- })
- vim.api.nvim_create_autocmd('TermRequest', {
- group = nvim_terminal_augroup,
- desc = 'Handles OSC foreground/background color requests',
- callback = function(args)
- --- @type integer
- local channel = vim.bo[args.buf].channel
- if channel == 0 then
- return
- end
- 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,
- })
- vim.api.nvim_create_autocmd('TermOpen', {
- group = nvim_terminal_augroup,
- desc = 'Default settings for :terminal buffers',
- callback = function()
- vim.bo.modifiable = false
- vim.bo.undolevels = -1
- vim.bo.scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback)
- vim.bo.textwidth = 0
- vim.wo[0][0].wrap = false
- vim.wo[0][0].list = false
- vim.wo[0][0].number = false
- vim.wo[0][0].relativenumber = false
- vim.wo[0][0].signcolumn = 'no'
- vim.wo[0][0].foldcolumn = '0'
- -- This is gross. Proper list options support when?
- local winhl = vim.o.winhighlight
- if winhl ~= '' then
- winhl = winhl .. ','
- end
- vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC'
- end,
- })
- 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),
- vim.log.levels.WARN
- )
- end,
- })
- -- 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
- end
- end
- 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.
- ---
- --- @param option string Option name
- --- @param value any Option value
- --- @param force boolean? Always set the value, even if already set
- local function setoption(option, value, force)
- if not force and vim.api.nvim_get_option_info2(option, {}).was_set then
- -- Don't do anything if option is already set
- return
- end
- -- 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, force)
- end,
- })
- end
- end
- --- Guess value of 'background' based on terminal color.
- ---
- --- 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.
- ---
- --- [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
- end
- local val = tonumber(c, 16)
- if not val then
- return nil
- end
- local max = tonumber(string.rep('f', #c), 16)
- return val / max
- end
- --- 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
- end
- if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
- return r, g, b
- end
- return nil, nil, nil
- end
- -- This autocommand updates the value of 'background' anytime we receive
- -- an OSC 11 response from the terminal emulator. If the user has set
- -- 'background' explictly then we will delete this autocommand,
- -- effectively disabling automatic background setting.
- local force = false
- local id = vim.api.nvim_create_autocmd('TermResponse', {
- group = group,
- nested = true,
- desc = "Update the value of 'background' automatically based on the terminal emulator's background color",
- callback = function(args)
- local resp = args.data ---@type string
- 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, force)
- -- On the first query response, don't force setting the option in
- -- case the user has already set it manually. If they have, then
- -- this autocommand will be deleted. If they haven't, then we do
- -- want to force setting the option to override the value set by
- -- this autocommand.
- if not force then
- force = true
- end
- end
- end
- end,
- })
- vim.api.nvim_create_autocmd('VimEnter', {
- group = group,
- nested = true,
- once = true,
- callback = function()
- if vim.api.nvim_get_option_info2('background', {}).was_set then
- vim.api.nvim_del_autocmd(id)
- end
- end,
- })
- io.stdout:write('\027]11;?\007')
- end
- --- 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
- local colorterm = os.getenv('COLORTERM')
- if tty.rgb or colorterm == 'truecolor' or colorterm == '24bit' then
- -- The TUI was able to determine truecolor support or $COLORTERM explicitly indicates
- -- truecolor support
- setoption('termguicolors', true)
- elseif colorterm == nil or colorterm == '' then
- -- Neither the TUI nor $COLORTERM indicate that truecolor is supported, so query the
- -- terminal
- 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
- end
- end
- --- Default options
- do
- --- Default 'grepprg' to ripgrep if available.
- if vim.fn.executable('rg') == 1 then
- -- Use -uu to make ripgrep not check ignore files/skip dot-files
- vim.o.grepprg = 'rg --vimgrep -uu '
- vim.o.grepformat = '%f:%l:%c:%m'
- end
- end
|