_defaults.lua 30 KB

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