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