neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

defaults.lua (36174B)


      1 -- Default user-commands, autocmds, mappings, menus.
      2 
      3 --- Default user commands
      4 do
      5  vim.api.nvim_create_user_command('Inspect', function(cmd)
      6    if cmd.bang then
      7      vim.print(vim.inspect_pos())
      8    else
      9      vim.show_pos()
     10    end
     11  end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true })
     12 
     13  vim.api.nvim_create_user_command('InspectTree', function(cmd)
     14    local opts = { lang = cmd.fargs[1] }
     15 
     16    if cmd.mods ~= '' or cmd.count ~= 0 then
     17      local count = cmd.count ~= 0 and cmd.count or ''
     18      local new = cmd.mods ~= '' and 'new' or 'vnew'
     19 
     20      opts.command = ('%s %s%s'):format(cmd.mods, count, new)
     21    end
     22 
     23    vim.treesitter.inspect_tree(opts)
     24  end, { desc = 'Inspect treesitter language tree for buffer', count = true, nargs = '?' })
     25 
     26  vim.api.nvim_create_user_command('EditQuery', function(cmd)
     27    vim.treesitter.query.edit(cmd.fargs[1])
     28  end, {
     29    desc = 'Edit treesitter query',
     30    nargs = '?',
     31    complete = function()
     32      return vim.treesitter.language._complete()
     33    end,
     34  })
     35 
     36  vim.api.nvim_create_user_command('Open', function(cmd)
     37    vim.ui.open(assert(cmd.fargs[1]))
     38  end, {
     39    desc = 'Open file with system default handler. See :help vim.ui.open()',
     40    nargs = 1,
     41    complete = 'file',
     42  })
     43 end
     44 
     45 --- Default mappings
     46 do
     47  --- Default maps for * and # in visual mode.
     48  ---
     49  --- See |v_star-default| and |v_#-default|
     50  do
     51    local function _visual_search(forward)
     52      assert(forward == 0 or forward == 1)
     53      local pos = vim.fn.getpos('.')
     54      local vpos = vim.fn.getpos('v')
     55      local mode = vim.fn.mode()
     56      local chunks = vim.fn.getregion(pos, vpos, { type = mode })
     57      local esc_chunks = vim
     58        .iter(chunks)
     59        :map(function(v)
     60          return vim.fn.escape(v, [[\]])
     61        end)
     62        :totable()
     63      local esc_pat = table.concat(esc_chunks, [[\n]])
     64      if #esc_pat == 0 then
     65        vim.api.nvim_echo({ { 'E348: No string under cursor' } }, true, { err = true })
     66        return '<Esc>'
     67      end
     68      local search = [[\V]] .. esc_pat
     69 
     70      vim.fn.setreg('/', search)
     71      vim.fn.histadd('/', search)
     72      vim.v.searchforward = forward
     73 
     74      -- The count has to be adjusted when searching backwards and the cursor
     75      -- isn't positioned at the beginning of the selection
     76      local count = vim.v.count1
     77      if forward == 0 then
     78        local _, line, col, _ = unpack(pos)
     79        local _, vline, vcol, _ = unpack(vpos)
     80        if
     81          line > vline
     82          or mode == 'v' and line == vline and col > vcol
     83          or mode == 'V' and col ~= 1
     84          or mode == '\22' and col > vcol
     85        then
     86          count = count + 1
     87        end
     88      end
     89      return '<Esc>' .. count .. 'n'
     90    end
     91 
     92    vim.keymap.set('x', '*', function()
     93      return _visual_search(1)
     94    end, { desc = ':help v_star-default', expr = true })
     95    vim.keymap.set('x', '#', function()
     96      return _visual_search(0)
     97    end, { desc = ':help v_#-default', expr = true })
     98  end
     99 
    100  --- Map Y to y$. This mimics the behavior of D and C. See |Y-default|
    101  vim.keymap.set('n', 'Y', 'y$', { desc = ':help Y-default' })
    102 
    103  --- Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>. #17473
    104  ---
    105  --- See |CTRL-L-default|
    106  vim.keymap.set('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>', {
    107    desc = ':help CTRL-L-default',
    108  })
    109 
    110  --- Set undo points when deleting text in insert mode.
    111  ---
    112  --- See |i_CTRL-U-default| and |i_CTRL-W-default|
    113  vim.keymap.set('i', '<C-U>', '<C-G>u<C-U>', { desc = ':help i_CTRL-U-default' })
    114  vim.keymap.set('i', '<C-W>', '<C-G>u<C-W>', { desc = ':help i_CTRL-W-default' })
    115 
    116  --- Use the same flags as the previous substitution with &.
    117  ---
    118  --- Use : instead of <Cmd> so that ranges are supported. #19365
    119  ---
    120  --- See |&-default|
    121  vim.keymap.set('n', '&', ':&&<CR>', { desc = ':help &-default' })
    122 
    123  --- Use Q in Visual mode to execute a macro on each line of the selection. #21422
    124  --- This only make sense in linewise Visual mode. #28287
    125  ---
    126  --- Applies to @x and includes @@ too.
    127  vim.keymap.set(
    128    'x',
    129    'Q',
    130    "mode() ==# 'V' ? ':normal! @<C-R>=reg_recorded()<CR><CR>' : 'Q'",
    131    { silent = true, expr = true, desc = ':help v_Q-default' }
    132  )
    133  vim.keymap.set(
    134    'x',
    135    '@',
    136    "mode() ==# 'V' ? ':normal! @'.getcharstr().'<CR>' : '@'",
    137    { silent = true, expr = true, desc = ':help v_@-default' }
    138  )
    139 
    140  --- Map |gx| to call |vim.ui.open| on the `textDocument/documentLink` or <cfile> at cursor.
    141  do
    142    local function do_open(uri)
    143      local cmd, err = vim.ui.open(uri)
    144      local rv = cmd and cmd:wait(1000) or nil
    145      if cmd and rv and rv.code ~= 0 then
    146        err = ('vim.ui.open: command %s (%d): %s'):format(
    147          (rv.code == 124 and 'timeout' or 'failed'),
    148          rv.code,
    149          vim.inspect(cmd.cmd)
    150        )
    151      end
    152      return err
    153    end
    154 
    155    local gx_desc =
    156      'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
    157    vim.keymap.set({ 'n' }, 'gx', function()
    158      for _, url in ipairs(require('vim.ui')._get_urls()) do
    159        local err = do_open(url)
    160        if err then
    161          vim.notify(err, vim.log.levels.ERROR)
    162        end
    163      end
    164    end, { desc = gx_desc })
    165    vim.keymap.set({ 'x' }, 'gx', function()
    166      local lines =
    167        vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
    168      -- Trim whitespace on each line and concatenate.
    169      local err = do_open(table.concat(vim.iter(lines):map(vim.trim):totable()))
    170      if err then
    171        vim.notify(err, vim.log.levels.ERROR)
    172      end
    173    end, { desc = gx_desc })
    174  end
    175 
    176  --- Default maps for built-in commenting.
    177  ---
    178  --- See |gc-default| and |gcc-default|.
    179  do
    180    local operator_rhs = function()
    181      return require('vim._comment').operator()
    182    end
    183    vim.keymap.set({ 'n', 'x' }, 'gc', operator_rhs, { expr = true, desc = 'Toggle comment' })
    184 
    185    local line_rhs = function()
    186      return require('vim._comment').operator() .. '_'
    187    end
    188    vim.keymap.set('n', 'gcc', line_rhs, { expr = true, desc = 'Toggle comment line' })
    189 
    190    local textobject_rhs = function()
    191      require('vim._comment').textobject()
    192    end
    193    vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' })
    194  end
    195 
    196  --- Default maps for LSP functions.
    197  ---
    198  --- These are mapped unconditionally to avoid different behavior depending on whether an LSP
    199  --- client is attached. If no client is attached, or if a server does not support a capability, an
    200  --- error message is displayed rather than exhibiting different behavior.
    201  ---
    202  --- See |grr|, |grn|, |gra|, |gri|, |grt| |gO|, |i_CTRL-S|.
    203  do
    204    vim.keymap.set('n', 'grn', function()
    205      vim.lsp.buf.rename()
    206    end, { desc = 'vim.lsp.buf.rename()' })
    207 
    208    vim.keymap.set({ 'n', 'x' }, 'gra', function()
    209      vim.lsp.buf.code_action()
    210    end, { desc = 'vim.lsp.buf.code_action()' })
    211 
    212    vim.keymap.set('n', 'grr', function()
    213      vim.lsp.buf.references()
    214    end, { desc = 'vim.lsp.buf.references()' })
    215 
    216    vim.keymap.set('n', 'gri', function()
    217      vim.lsp.buf.implementation()
    218    end, { desc = 'vim.lsp.buf.implementation()' })
    219 
    220    vim.keymap.set('n', 'grt', function()
    221      vim.lsp.buf.type_definition()
    222    end, { desc = 'vim.lsp.buf.type_definition()' })
    223 
    224    vim.keymap.set({ 'x', 'o' }, 'an', function()
    225      vim.lsp.buf.selection_range(vim.v.count1)
    226    end, { desc = 'vim.lsp.buf.selection_range(vim.v.count1)' })
    227 
    228    vim.keymap.set({ 'x', 'o' }, 'in', function()
    229      vim.lsp.buf.selection_range(-vim.v.count1)
    230    end, { desc = 'vim.lsp.buf.selection_range(-vim.v.count1)' })
    231 
    232    vim.keymap.set('n', 'gO', function()
    233      vim.lsp.buf.document_symbol()
    234    end, { desc = 'vim.lsp.buf.document_symbol()' })
    235 
    236    vim.keymap.set({ 'i', 's' }, '<C-S>', function()
    237      vim.lsp.buf.signature_help()
    238    end, { desc = 'vim.lsp.buf.signature_help()' })
    239  end
    240 
    241  do
    242    ---@param direction vim.snippet.Direction
    243    ---@param key string
    244    local function set_snippet_jump(direction, key)
    245      vim.keymap.set({ 'i', 's' }, key, function()
    246        if vim.snippet.active({ direction = direction }) then
    247          return string.format('<Cmd>lua vim.snippet.jump(%d)<CR>', direction)
    248        else
    249          return key
    250        end
    251      end, {
    252        desc = 'vim.snippet.jump if active, otherwise ' .. key,
    253        expr = true,
    254        silent = true,
    255      })
    256    end
    257 
    258    set_snippet_jump(1, '<Tab>')
    259    set_snippet_jump(-1, '<S-Tab>')
    260  end
    261 
    262  --- Map [d and ]d to move to the previous/next diagnostic. Map <C-W>d to open a floating window
    263  --- for the diagnostic under the cursor.
    264  ---
    265  --- See |[d-default|, |]d-default|, and |CTRL-W_d-default|.
    266  do
    267    vim.keymap.set('n', ']d', function()
    268      vim.diagnostic.jump({ count = vim.v.count1 })
    269    end, { desc = 'Jump to the next diagnostic in the current buffer' })
    270 
    271    vim.keymap.set('n', '[d', function()
    272      vim.diagnostic.jump({ count = -vim.v.count1 })
    273    end, { desc = 'Jump to the previous diagnostic in the current buffer' })
    274 
    275    vim.keymap.set('n', ']D', function()
    276      vim.diagnostic.jump({ count = vim._maxint, wrap = false })
    277    end, { desc = 'Jump to the last diagnostic in the current buffer' })
    278 
    279    vim.keymap.set('n', '[D', function()
    280      vim.diagnostic.jump({ count = -vim._maxint, wrap = false })
    281    end, { desc = 'Jump to the first diagnostic in the current buffer' })
    282 
    283    vim.keymap.set('n', '<C-W>d', function()
    284      vim.diagnostic.open_float()
    285    end, { desc = 'Show diagnostics under the cursor' })
    286 
    287    vim.keymap.set(
    288      'n',
    289      '<C-W><C-D>',
    290      '<C-W>d',
    291      { remap = true, desc = 'Show diagnostics under the cursor' }
    292    )
    293  end
    294 
    295  --- vim-unimpaired style mappings. See: https://github.com/tpope/vim-unimpaired
    296  do
    297    --- Execute a command and print errors without a stacktrace.
    298    --- @param opts table Arguments to |nvim_cmd()|
    299    local function cmd(opts)
    300      local ok, err = pcall(vim.api.nvim_cmd, opts, {})
    301      if not ok then
    302        vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true })
    303      end
    304    end
    305 
    306    -- Quickfix mappings
    307    vim.keymap.set('n', '[q', function()
    308      cmd({ cmd = 'cprevious', count = vim.v.count1 })
    309    end, { desc = ':cprevious' })
    310 
    311    vim.keymap.set('n', ']q', function()
    312      cmd({ cmd = 'cnext', count = vim.v.count1 })
    313    end, { desc = ':cnext' })
    314 
    315    vim.keymap.set('n', '[Q', function()
    316      cmd({ cmd = 'crewind', count = vim.v.count ~= 0 and vim.v.count or nil })
    317    end, { desc = ':crewind' })
    318 
    319    vim.keymap.set('n', ']Q', function()
    320      cmd({ cmd = 'clast', count = vim.v.count ~= 0 and vim.v.count or nil })
    321    end, { desc = ':clast' })
    322 
    323    vim.keymap.set('n', '[<C-Q>', function()
    324      cmd({ cmd = 'cpfile', count = vim.v.count1 })
    325    end, { desc = ':cpfile' })
    326 
    327    vim.keymap.set('n', ']<C-Q>', function()
    328      cmd({ cmd = 'cnfile', count = vim.v.count1 })
    329    end, { desc = ':cnfile' })
    330 
    331    -- Location list mappings
    332    vim.keymap.set('n', '[l', function()
    333      cmd({ cmd = 'lprevious', count = vim.v.count1 })
    334    end, { desc = ':lprevious' })
    335 
    336    vim.keymap.set('n', ']l', function()
    337      cmd({ cmd = 'lnext', count = vim.v.count1 })
    338    end, { desc = ':lnext' })
    339 
    340    vim.keymap.set('n', '[L', function()
    341      cmd({ cmd = 'lrewind', count = vim.v.count ~= 0 and vim.v.count or nil })
    342    end, { desc = ':lrewind' })
    343 
    344    vim.keymap.set('n', ']L', function()
    345      cmd({ cmd = 'llast', count = vim.v.count ~= 0 and vim.v.count or nil })
    346    end, { desc = ':llast' })
    347 
    348    vim.keymap.set('n', '[<C-L>', function()
    349      cmd({ cmd = 'lpfile', count = vim.v.count1 })
    350    end, { desc = ':lpfile' })
    351 
    352    vim.keymap.set('n', ']<C-L>', function()
    353      cmd({ cmd = 'lnfile', count = vim.v.count1 })
    354    end, { desc = ':lnfile' })
    355 
    356    -- Argument list
    357    vim.keymap.set('n', '[a', function()
    358      cmd({ cmd = 'previous', count = vim.v.count1 })
    359    end, { desc = ':previous' })
    360 
    361    vim.keymap.set('n', ']a', function()
    362      -- count doesn't work with :next, must use range. See #30641.
    363      cmd({ cmd = 'next', range = { vim.v.count1 } })
    364    end, { desc = ':next' })
    365 
    366    vim.keymap.set('n', '[A', function()
    367      if vim.v.count ~= 0 then
    368        cmd({ cmd = 'argument', count = vim.v.count })
    369      else
    370        cmd({ cmd = 'rewind' })
    371      end
    372    end, { desc = ':rewind' })
    373 
    374    vim.keymap.set('n', ']A', function()
    375      if vim.v.count ~= 0 then
    376        cmd({ cmd = 'argument', count = vim.v.count })
    377      else
    378        cmd({ cmd = 'last' })
    379      end
    380    end, { desc = ':last' })
    381 
    382    -- Tags
    383    vim.keymap.set('n', '[t', function()
    384      -- count doesn't work with :tprevious, must use range. See #30641.
    385      cmd({ cmd = 'tprevious', range = { vim.v.count1 } })
    386    end, { desc = ':tprevious' })
    387 
    388    vim.keymap.set('n', ']t', function()
    389      -- count doesn't work with :tnext, must use range. See #30641.
    390      cmd({ cmd = 'tnext', range = { vim.v.count1 } })
    391    end, { desc = ':tnext' })
    392 
    393    vim.keymap.set('n', '[T', function()
    394      -- count doesn't work with :trewind, must use range. See #30641.
    395      cmd({ cmd = 'trewind', range = vim.v.count ~= 0 and { vim.v.count } or nil })
    396    end, { desc = ':trewind' })
    397 
    398    vim.keymap.set('n', ']T', function()
    399      -- :tlast does not accept a count, so use :trewind if count given
    400      if vim.v.count ~= 0 then
    401        cmd({ cmd = 'trewind', range = { vim.v.count } })
    402      else
    403        cmd({ cmd = 'tlast' })
    404      end
    405    end, { desc = ':tlast' })
    406 
    407    vim.keymap.set('n', '[<C-T>', function()
    408      -- count doesn't work with :ptprevious, must use range. See #30641.
    409      cmd({ cmd = 'ptprevious', range = { vim.v.count1 } })
    410    end, { desc = ':ptprevious' })
    411 
    412    vim.keymap.set('n', ']<C-T>', function()
    413      -- count doesn't work with :ptnext, must use range. See #30641.
    414      cmd({ cmd = 'ptnext', range = { vim.v.count1 } })
    415    end, { desc = ':ptnext' })
    416 
    417    -- Buffers
    418    vim.keymap.set('n', '[b', function()
    419      cmd({ cmd = 'bprevious', count = vim.v.count1 })
    420    end, { desc = ':bprevious' })
    421 
    422    vim.keymap.set('n', ']b', function()
    423      cmd({ cmd = 'bnext', count = vim.v.count1 })
    424    end, { desc = ':bnext' })
    425 
    426    vim.keymap.set('n', '[B', function()
    427      if vim.v.count ~= 0 then
    428        cmd({ cmd = 'buffer', count = vim.v.count })
    429      else
    430        cmd({ cmd = 'brewind' })
    431      end
    432    end, { desc = ':brewind' })
    433 
    434    vim.keymap.set('n', ']B', function()
    435      if vim.v.count ~= 0 then
    436        cmd({ cmd = 'buffer', count = vim.v.count })
    437      else
    438        cmd({ cmd = 'blast' })
    439      end
    440    end, { desc = ':blast' })
    441 
    442    -- Add empty lines
    443    vim.keymap.set('n', '[<Space>', function()
    444      -- TODO: update once it is possible to assign a Lua function to options #25672
    445      vim.go.operatorfunc = "v:lua.require'vim._core.util'.space_above"
    446      return 'g@l'
    447    end, { expr = true, desc = 'Add empty line above cursor' })
    448 
    449    vim.keymap.set('n', ']<Space>', function()
    450      -- TODO: update once it is possible to assign a Lua function to options #25672
    451      vim.go.operatorfunc = "v:lua.require'vim._core.util'.space_below"
    452      return 'g@l'
    453    end, { expr = true, desc = 'Add empty line below cursor' })
    454  end
    455 end
    456 
    457 --- Default menus
    458 do
    459  --- Right click popup menu
    460  vim.cmd([[
    461    amenu     PopUp.Open\ in\ web\ browser  gx
    462    anoremenu PopUp.Inspect                 <Cmd>Inspect<CR>
    463    anoremenu PopUp.Go\ to\ definition      <Cmd>lua vim.lsp.buf.definition()<CR>
    464    anoremenu PopUp.Show\ Diagnostics       <Cmd>lua vim.diagnostic.open_float()<CR>
    465    anoremenu PopUp.Show\ All\ Diagnostics  <Cmd>lua vim.diagnostic.setqflist()<CR>
    466    anoremenu PopUp.Configure\ Diagnostics  <Cmd>help vim.diagnostic.config()<CR>
    467    anoremenu PopUp.-1-                     <Nop>
    468    vnoremenu PopUp.Cut                     "+x
    469    vnoremenu PopUp.Copy                    "+y
    470    anoremenu PopUp.Paste                   "+gP
    471    vnoremenu PopUp.Paste                   "+P
    472    vnoremenu PopUp.Delete                  "_x
    473    nnoremenu PopUp.Select\ All             ggVG
    474    vnoremenu PopUp.Select\ All             gg0oG$
    475    inoremenu PopUp.Select\ All             <C-Home><C-O>VG
    476    anoremenu PopUp.-2-                     <Nop>
    477    anoremenu PopUp.How-to\ disable\ mouse  <Cmd>help disable-mouse<CR>
    478  ]])
    479 
    480  local function enable_ctx_menu()
    481    vim.cmd([[
    482      amenu disable PopUp.Go\ to\ definition
    483      amenu disable PopUp.Open\ in\ web\ browser
    484      amenu disable PopUp.Show\ Diagnostics
    485      amenu disable PopUp.Show\ All\ Diagnostics
    486      amenu disable PopUp.Configure\ Diagnostics
    487    ]])
    488 
    489    local url = require('vim.ui')._get_urls()[1]
    490    if url and vim.startswith(url, 'http') then
    491      vim.cmd([[amenu enable PopUp.Open\ in\ web\ browser]])
    492    elseif vim.lsp.get_clients({ bufnr = 0 })[1] then
    493      vim.cmd([[anoremenu enable PopUp.Go\ to\ definition]])
    494    end
    495 
    496    local lnum = vim.fn.getcurpos()[2] - 1 ---@type integer
    497    local diagnostic = false
    498    if next(vim.diagnostic.get(0, { lnum = lnum })) ~= nil then
    499      diagnostic = true
    500      vim.cmd([[anoremenu enable PopUp.Show\ Diagnostics]])
    501    end
    502 
    503    if diagnostic or next(vim.diagnostic.count(0)) ~= nil then
    504      vim.cmd([[
    505        anoremenu enable PopUp.Show\ All\ Diagnostics
    506        anoremenu enable PopUp.Configure\ Diagnostics
    507      ]])
    508    end
    509  end
    510 
    511  local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim.popupmenu', {})
    512  vim.api.nvim_create_autocmd('MenuPopup', {
    513    pattern = '*',
    514    group = nvim_popupmenu_augroup,
    515    desc = 'Mouse popup menu',
    516    -- nested = true,
    517    callback = function()
    518      enable_ctx_menu()
    519    end,
    520  })
    521 end
    522 
    523 --- Default autocommands. See |default-autocmds|
    524 do
    525  local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim.terminal', {})
    526  vim.api.nvim_create_autocmd('BufReadCmd', {
    527    pattern = 'term://*',
    528    group = nvim_terminal_augroup,
    529    desc = 'Treat term:// buffers as terminal buffers',
    530    nested = true,
    531    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, ''))})",
    532  })
    533 
    534  vim.api.nvim_create_autocmd({ 'TermClose' }, {
    535    group = nvim_terminal_augroup,
    536    nested = true,
    537    desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error',
    538    callback = function(args)
    539      if vim.v.event.status ~= 0 then
    540        return
    541      end
    542      local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel)
    543      local argv = info.argv or {}
    544      if table.concat(argv, ' ') == vim.o.shell then
    545        vim.api.nvim_buf_delete(args.buf, { force = true })
    546      end
    547    end,
    548  })
    549 
    550  vim.api.nvim_create_autocmd('TermRequest', {
    551    group = nvim_terminal_augroup,
    552    desc = 'Handles OSC foreground/background color requests',
    553    callback = function(args)
    554      --- @type integer
    555      local channel = vim.bo[args.buf].channel
    556      if channel == 0 then
    557        return
    558      end
    559      local fg_request = args.data.sequence == '\027]10;?'
    560      local bg_request = args.data.sequence == '\027]11;?'
    561      if fg_request or bg_request then
    562        -- WARN: This does not return the actual foreground/background color,
    563        -- but rather returns:
    564        --   - fg=white/bg=black when Nvim option 'background' is 'dark'
    565        --   - fg=black/bg=white when Nvim option 'background' is 'light'
    566        local red, green, blue = 0, 0, 0
    567        local bg_option_dark = vim.o.background == 'dark'
    568        if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then
    569          red, green, blue = 65535, 65535, 65535
    570        end
    571        local command = fg_request and 10 or 11
    572        local data = string.format(
    573          '\027]%d;rgb:%04x/%04x/%04x%s',
    574          command,
    575          red,
    576          green,
    577          blue,
    578          args.data.terminator
    579        )
    580        vim.api.nvim_chan_send(channel, data)
    581      end
    582    end,
    583  })
    584 
    585  local nvim_terminal_prompt_ns = vim.api.nvim_create_namespace('nvim.terminal.prompt')
    586  vim.api.nvim_create_autocmd('TermRequest', {
    587    group = nvim_terminal_augroup,
    588    desc = 'Mark shell prompts indicated by OSC 133 sequences for navigation',
    589    callback = function(args)
    590      if string.match(args.data.sequence, '^\027]133;A') then
    591        local lnum = args.data.cursor[1] ---@type integer
    592        if lnum >= 1 then
    593          vim.api.nvim_buf_set_extmark(
    594            args.buf,
    595            nvim_terminal_prompt_ns,
    596            lnum - 1,
    597            0,
    598            { right_gravity = false }
    599          )
    600        end
    601      end
    602    end,
    603  })
    604 
    605  ---@param ns integer
    606  ---@param buf integer
    607  ---@param count integer
    608  local function jump_to_prompt(ns, win, buf, count)
    609    local row, col = unpack(vim.api.nvim_win_get_cursor(win))
    610    local start = -1
    611    local end_ ---@type 0|-1
    612    if count > 0 then
    613      start = row
    614      end_ = -1
    615    elseif count < 0 then
    616      -- Subtract 2 because row is 1-based, but extmarks are 0-based
    617      start = row - 2
    618      end_ = 0
    619    end
    620 
    621    if start < 0 then
    622      return
    623    end
    624 
    625    local extmarks = vim.api.nvim_buf_get_extmarks(
    626      buf,
    627      ns,
    628      { start, col },
    629      end_,
    630      { limit = math.abs(count) }
    631    )
    632    if #extmarks > 0 then
    633      local extmark = assert(extmarks[math.min(#extmarks, math.abs(count))])
    634      vim.api.nvim_win_set_cursor(win, { extmark[2] + 1, extmark[3] })
    635    end
    636  end
    637 
    638  vim.api.nvim_create_autocmd('TermOpen', {
    639    group = nvim_terminal_augroup,
    640    desc = 'Default settings for :terminal buffers',
    641    callback = function(args)
    642      vim.bo[args.buf].modifiable = false
    643      vim.bo[args.buf].undolevels = -1
    644      vim.bo[args.buf].scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback)
    645      vim.bo[args.buf].textwidth = 0
    646      vim.wo[0][0].wrap = false
    647      vim.wo[0][0].list = false
    648      vim.wo[0][0].number = false
    649      vim.wo[0][0].relativenumber = false
    650      vim.wo[0][0].signcolumn = 'no'
    651      vim.wo[0][0].foldcolumn = '0'
    652 
    653      -- This is gross. Proper list options support when?
    654      local winhl = vim.o.winhighlight
    655      if winhl ~= '' then
    656        winhl = winhl .. ','
    657      end
    658      vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC'
    659 
    660      vim.keymap.set({ 'n', 'x', 'o' }, '[[', function()
    661        jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, -vim.v.count1)
    662      end, { buffer = args.buf, desc = 'Jump [count] shell prompts backward' })
    663      vim.keymap.set({ 'n', 'x', 'o' }, ']]', function()
    664        jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, vim.v.count1)
    665      end, { buffer = args.buf, desc = 'Jump [count] shell prompts forward' })
    666    end,
    667  })
    668 
    669  vim.api.nvim_create_autocmd('CmdwinEnter', {
    670    pattern = '[:>]',
    671    desc = 'Limit syntax sync to maxlines=1 in the command window',
    672    group = vim.api.nvim_create_augroup('nvim.cmdwin', {}),
    673    command = 'syntax sync minlines=1 maxlines=1',
    674  })
    675 
    676  vim.api.nvim_create_autocmd('SwapExists', {
    677    pattern = '*',
    678    desc = 'Skip the swapfile prompt when the swapfile is owned by a running Nvim process',
    679    group = vim.api.nvim_create_augroup('nvim.swapfile', {}),
    680    callback = function()
    681      local info = vim.fn.swapinfo(vim.v.swapname)
    682      local user = vim.uv.os_get_passwd().username
    683      local iswin = 1 == vim.fn.has('win32')
    684      if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then
    685        vim.v.swapchoice = '' -- Show the prompt.
    686        return
    687      end
    688      vim.v.swapchoice = 'e' -- Choose "(E)dit".
    689      vim.notify(
    690        ('W325: Ignoring swapfile from Nvim process %d'):format(info.pid),
    691        vim.log.levels.WARN
    692      )
    693    end,
    694  })
    695 
    696  -- Check if a TTY is attached
    697  local tty = nil
    698  for _, ui in ipairs(vim.api.nvim_list_uis()) do
    699    if ui.chan == 1 and ui.stdout_tty then
    700      tty = ui
    701      break
    702    end
    703  end
    704 
    705  if tty then
    706    local group = vim.api.nvim_create_augroup('nvim.tty', {})
    707 
    708    --- Set an option after startup (so that OptionSet is fired), but only if not
    709    --- already set by the user.
    710    ---
    711    --- @param option string Option name
    712    --- @param value any Option value
    713    --- @param force boolean? Always set the value, even if already set
    714    local function setoption(option, value, force)
    715      if not force and vim.api.nvim_get_option_info2(option, {}).was_set then
    716        -- Don't do anything if option is already set
    717        return
    718      end
    719 
    720      -- Wait until Nvim is finished starting to set the option to ensure the
    721      -- OptionSet event fires.
    722      if vim.v.vim_did_enter == 1 then
    723        --- @diagnostic disable-next-line:no-unknown
    724        vim.o[option] = value
    725      else
    726        vim.api.nvim_create_autocmd('VimEnter', {
    727          group = group,
    728          once = true,
    729          nested = true,
    730          callback = function()
    731            setoption(option, value, force)
    732          end,
    733        })
    734      end
    735    end
    736 
    737    --- Guess value of 'background' based on terminal color.
    738    ---
    739    --- We write Operating System Command (OSC) 11 to the terminal to request the
    740    --- terminal's background color. We then wait for a response. If the response
    741    --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
    742    --- compute the luminance[1] of the RGB color and classify it as light/dark
    743    --- accordingly. Note that the color components may have anywhere from one to
    744    --- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
    745    --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
    746    --- ignored in the calculations.
    747    ---
    748    --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
    749    do
    750      --- Parse a string of hex characters as a color.
    751      ---
    752      --- The string can contain 1 to 4 hex characters. The returned value is
    753      --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
    754      ---
    755      --- For instance, if only a single hex char "a" is used, then this function
    756      --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
    757      --- 256).
    758      ---
    759      --- @param c string Color as a string of hex chars
    760      --- @return number? Intensity of the color
    761      local function parsecolor(c)
    762        if #c == 0 or #c > 4 then
    763          return nil
    764        end
    765 
    766        local val = tonumber(c, 16)
    767        if not val then
    768          return nil
    769        end
    770 
    771        local max = assert(tonumber(string.rep('f', #c), 16))
    772        return val / max
    773      end
    774 
    775      --- Parse an OSC 11 response
    776      ---
    777      --- Either of the two formats below are accepted:
    778      ---
    779      ---   OSC 11 ; rgb:<red>/<green>/<blue>
    780      ---
    781      --- or
    782      ---
    783      ---   OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
    784      ---
    785      --- where
    786      ---
    787      ---   <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
    788      ---
    789      --- The alpha component is ignored, if present.
    790      ---
    791      --- @param resp string OSC 11 response
    792      --- @return string? Red component
    793      --- @return string? Green component
    794      --- @return string? Blue component
    795      local function parseosc11(resp)
    796        local r, g, b
    797        r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
    798        if not r and not g and not b then
    799          local a
    800          r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
    801          if not a or #a > 4 then
    802            return nil, nil, nil
    803          end
    804        end
    805 
    806        if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
    807          return r, g, b
    808        end
    809 
    810        return nil, nil, nil
    811      end
    812 
    813      -- This autocommand updates the value of 'background' anytime we receive
    814      -- an OSC 11 response from the terminal emulator. If the user has set
    815      -- 'background' explicitly then we will delete this autocommand,
    816      -- effectively disabling automatic background setting.
    817      local did_dsr_response = false
    818      local id = vim.api.nvim_create_autocmd('TermResponse', {
    819        group = group,
    820        nested = true,
    821        desc = "Update the value of 'background' automatically based on the terminal emulator's background color",
    822        callback = function(args)
    823          local resp = args.data.sequence ---@type string
    824 
    825          -- DSR response that should come after the OSC 11 response if the
    826          -- terminal supports it.
    827          if string.match(resp, '^\027%[0n$') then
    828            did_dsr_response = true
    829            -- Don't delete the autocmd because the bg response may come
    830            -- after the DSR response if the terminal handles requests out
    831            -- of sequence. In that case, the background will simply be set
    832            -- later in the startup sequence.
    833            return false
    834          end
    835 
    836          local r, g, b = parseosc11(resp)
    837          if r and g and b then
    838            local rr = parsecolor(r)
    839            local gg = parsecolor(g)
    840            local bb = parsecolor(b)
    841 
    842            if rr and gg and bb then
    843              local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
    844              local bg = luminance < 0.5 and 'dark' or 'light'
    845              vim.api.nvim_set_option_value('background', bg, {})
    846 
    847              -- Ensure OptionSet still triggers when we set the background during startup
    848              if vim.v.vim_did_enter == 0 then
    849                vim.api.nvim_create_autocmd('VimEnter', {
    850                  group = group,
    851                  once = true,
    852                  nested = true,
    853                  callback = function()
    854                    vim.api.nvim_exec_autocmds('OptionSet', {
    855                      pattern = 'background',
    856                    })
    857                  end,
    858                })
    859              end
    860            end
    861          end
    862        end,
    863      })
    864 
    865      vim.api.nvim_create_autocmd('VimEnter', {
    866        group = group,
    867        nested = true,
    868        once = true,
    869        callback = function()
    870          local optinfo = vim.api.nvim_get_option_info2('background', {})
    871          local sid_lua = -8
    872          if
    873            optinfo.was_set
    874            and optinfo.last_set_sid ~= sid_lua
    875            and next(vim.api.nvim_get_autocmds({ id = id })) ~= nil
    876          then
    877            vim.api.nvim_del_autocmd(id)
    878          end
    879        end,
    880      })
    881 
    882      -- Send OSC 11 query along with DSR sequence to determine whether
    883      -- terminal supports the query. If the DSR response comes first,
    884      -- the terminal most likely doesn't support the bg color query,
    885      -- and we don't have to keep waiting for a bg color response.
    886      -- #32109
    887      local osc11 = '\027]11;?\007'
    888      local dsr = '\027[5n'
    889      vim.api.nvim_ui_send(osc11 .. dsr)
    890 
    891      -- Wait until detection of OSC 11 capabilities is complete to
    892      -- ensure background is automatically set before user config.
    893      if
    894        not vim.wait(100, function()
    895          return did_dsr_response
    896        end, 1)
    897        -- Don't show the warning when running tests to avoid flakiness.
    898        and os.getenv('NVIM_TEST') == nil
    899      then
    900        vim.notify(
    901          'defaults.lua: Did not detect DSR response from terminal. This results in a slower startup time.',
    902          vim.log.levels.WARN
    903        )
    904      end
    905    end
    906 
    907    --- If the TUI (term_has_truecolor) was able to determine that the host
    908    --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
    909    --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
    910    --- response indicates that it does support truecolor enable 'termguicolors',
    911    --- but only if the user has not already disabled it.
    912    do
    913      local colorterm = os.getenv('COLORTERM')
    914      if tty.rgb or colorterm == 'truecolor' or colorterm == '24bit' then
    915        -- The TUI was able to determine truecolor support or $COLORTERM explicitly indicates
    916        -- truecolor support
    917        setoption('termguicolors', true)
    918      elseif colorterm == nil or colorterm == '' then
    919        -- Neither the TUI nor $COLORTERM indicate that truecolor is supported, so query the
    920        -- terminal
    921        local caps = {} ---@type table<string, boolean>
    922        require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
    923          if not found then
    924            return
    925          end
    926 
    927          caps[cap] = true
    928          if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
    929            setoption('termguicolors', true)
    930          end
    931        end)
    932 
    933        local timer = assert(vim.uv.new_timer())
    934 
    935        -- Arbitrary colors to set in the SGR sequence
    936        local r = 1
    937        local g = 2
    938        local b = 3
    939 
    940        local id = vim.api.nvim_create_autocmd('TermResponse', {
    941          group = group,
    942          nested = true,
    943          callback = function(args)
    944            local resp = args.data.sequence ---@type string
    945            local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
    946 
    947            if decrqss then
    948              -- The DECRQSS SGR response first contains attributes separated by
    949              -- semicolons, followed by the SGR itself with parameters separated
    950              -- by colons. Some terminals include "0" in the attribute list
    951              -- unconditionally; others do not. Our SGR sequence did not set any
    952              -- attributes, so there should be no attributes in the list.
    953              local attrs = vim.split(decrqss, ';')
    954              if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
    955                return false
    956              end
    957 
    958              -- The returned SGR sequence should begin with 48:2
    959              local sgr = assert(attrs[#attrs]):match('^48:2:([%d:]+)$')
    960              if not sgr then
    961                return false
    962              end
    963 
    964              -- The remaining elements of the SGR sequence should be the 3 colors
    965              -- we set. Some terminals also include an additional parameter
    966              -- (which can even be empty!), so handle those cases as well
    967              local params = vim.split(sgr, ':')
    968              if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
    969                return true
    970              end
    971 
    972              if
    973                tonumber(params[#params - 2]) == r
    974                and tonumber(params[#params - 1]) == g
    975                and tonumber(params[#params]) == b
    976              then
    977                setoption('termguicolors', true)
    978              end
    979 
    980              return true
    981            end
    982          end,
    983        })
    984 
    985        -- Write SGR followed by DECRQSS. This sets the background color then
    986        -- immediately asks the terminal what the background color is. If the
    987        -- terminal responds to the DECRQSS with the same SGR sequence that we
    988        -- sent then the terminal supports truecolor.
    989        local decrqss = '\027P$qm\027\\'
    990 
    991        -- Reset attributes first, as other code may have set attributes.
    992        vim.api.nvim_ui_send(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
    993 
    994        timer:start(1000, 0, function()
    995          -- Delete the autocommand if no response was received
    996          vim.schedule(function()
    997            -- Suppress error if autocommand has already been deleted
    998            pcall(vim.api.nvim_del_autocmd, id)
    999          end)
   1000 
   1001          if not timer:is_closing() then
   1002            timer:close()
   1003          end
   1004        end)
   1005      end
   1006    end
   1007  end
   1008 
   1009  if tty then
   1010    -- Show progress bars in supporting terminals
   1011    vim.api.nvim_create_autocmd('Progress', {
   1012      group = vim.api.nvim_create_augroup('nvim.progress', {}),
   1013      desc = 'Display native progress bars',
   1014      callback = function(ev)
   1015        if ev.data.status == 'running' then
   1016          vim.api.nvim_ui_send(string.format('\027]9;4;1;%d\027\\', ev.data.percent))
   1017        else
   1018          vim.api.nvim_ui_send('\027]9;4;0;0\027\\')
   1019        end
   1020      end,
   1021    })
   1022  end
   1023 end
   1024 
   1025 --- Default options
   1026 do
   1027  --- Default 'grepprg' to ripgrep if available.
   1028  if vim.fn.executable('rg') == 1 then
   1029    -- Use -uu to make ripgrep not check ignore files/skip dot-files
   1030    vim.o.grepprg = 'rg --vimgrep -uu '
   1031    vim.o.grepformat = '%f:%l:%c:%m'
   1032  end
   1033 end