1-- Keybindings (qwerty).
2
3-- DESCRIPTION:
4-- All mappings are defined here.
5
6-- Sections:
7-- ## Base bindings
8-- -> icons displayed on which-key.nvim
9-- -> standard operations
10-- -> clipboard
11-- -> search highlighting
12-- -> improved tabulation
13-- -> improved gg
14-- -> packages
15-- -> buffers/tabs [buffers]
16-- -> ui toggles [ui]
17-- -> shifted movement keys
18-- -> cmdline autocompletion
19-- -> special cases
20
21-- ## Plugin bindings
22-- -> alpha-nvim
23-- -> git [git]
24-- -> file browsers
25-- -> session manager
26-- -> smart-splits.nvim
27-- -> aerial.nvim
28-- -> litee-calltree.nvim
29-- -> telescope.nivm [find]
30-- -> toggleterm.nvim
31-- -> dap.nvim [debugger]
32-- -> tests [tests]
33-- -> nvim-ufo
34-- -> code documentation [docs]
35-- -> ask chatgpt [neural]
36-- -> hop.nvim
37-- -> mason-lspconfig.nvim [lsp]
38
39--
40-- KEYBINDINGS REFERENCE
41-- -------------------------------------------------------------------
42-- | Mode | Norm | Ins | Cmd | Vis | Sel | Opr | Term | Lang |
43-- Command +------+-----+-----+-----+-----+-----+------+------+
44-- [nore]map | yes | - | - | yes | yes | yes | - | - |
45-- n[nore]map | yes | - | - | - | - | - | - | - |
46-- [nore]map! | - | yes | yes | - | - | - | - | - |
47-- i[nore]map | - | yes | - | - | - | - | - | - |
48-- c[nore]map | - | - | yes | - | - | - | - | - |
49-- v[nore]map | - | - | - | yes | yes | - | - | - |
50-- x[nore]map | - | - | - | yes | - | - | - | - |
51-- s[nore]map | - | - | - | - | yes | - | - | - |
52-- o[nore]map | - | - | - | - | - | yes | - | - |
53-- t[nore]map | - | - | - | - | - | - | yes | - |
54-- l[nore]map | - | yes | yes | - | - | - | - | yes |
55-- -------------------------------------------------------------------
56
57local M = {}
58local utils = require "base.utils"
59local get_icon = utils.get_icon
60local is_available = utils.is_available
61local ui = require "base.utils.ui"
62local maps = require("base.utils").get_mappings_template()
63local is_android = vim.fn.isdirectory('/data') == 1 -- true if on android
64
65-- -------------------------------------------------------------------------
66--
67-- ## Base bindings ########################################################
68--
69-- -------------------------------------------------------------------------
70
71-- icons displayed on which-key.nvim ---------------------------------------
72local icons = {
73 f = { desc = get_icon("Search", 1, true) .. "Find" },
74 p = { desc = get_icon("Package", 1, true) .. "Packages" },
75 l = { desc = get_icon("ActiveLSP", 1, true) .. "LSP" },
76 u = { desc = get_icon("Window", 1, true) .. "UI" },
77 b = { desc = get_icon("Tab", 1, true) .. "Buffers" },
78 bs = { desc = get_icon("Sort", 1, true) .. "Sort Buffers" },
79 c = { desc = get_icon("Run", 1, true) .. "Compiler" },
80 d = { desc = get_icon("Debugger", 1, true) .. "Debugger" },
81 tt = { desc = get_icon("Test", 1, true) .. "Test" },
82 dc = { desc = get_icon("Docs", 1, true) .. "Docs" },
83 g = { desc = get_icon("Git", 1, true) .. "Git" },
84 S = { desc = get_icon("Session", 1, true) .. "Session" },
85 t = { desc = get_icon("Terminal", 1, true) .. "Terminal" },
86}
87
88-- standard Operations -----------------------------------------------------
89maps.n["j"] =
90{ "v:count == 0 ? 'gj' : 'j'", expr = true, desc = "Move cursor down" }
91maps.n["k"] =
92{ "v:count == 0 ? 'gk' : 'k'", expr = true, desc = "Move cursor up" }
93maps.n["<leader>w"] = { "<cmd>w<cr>", desc = "Save" }
94maps.n["<leader>W"] =
95{ function() vim.cmd "SudaWrite" end, desc = "Save as sudo" }
96maps.n["<leader>n"] = { "<cmd>enew<cr>", desc = "New file" }
97maps.n["<Leader>/"] = { "gcc", remap = true, desc = "Toggle comment line" }
98maps.x["<Leader>/"] = { "gc", remap = true, desc = "Toggle comment" }
99maps.n["gx"] =
100{ utils.open_with_program, desc = "Open the file under cursor with a program" }
101maps.n["<C-s>"] = { "<cmd>w!<cr>", desc = "Force write" }
102maps.n["|"] = { "<cmd>vsplit<cr>", desc = "Vertical Split" }
103maps.n["\\"] = { "<cmd>split<cr>", desc = "Horizontal Split" }
104maps.i["<C-BS>"] = { "<C-W>", desc = "Enable CTRL+backsace to delete." }
105maps.n["0"] =
106{ "^", desc = "Go to the fist character of the line (aliases 0 to ^)" }
107maps.n["<leader>q"] = { "<cmd>confirm q<cr>", desc = "Quit" }
108maps.n["<leader>q"] = {
109 function()
110 -- Ask user for confirmation
111 local choice = vim.fn.confirm("Do you really want to exit nvim?", "&Yes\n&No", 2)
112 if choice == 1 then
113 -- If user confirms, but there are still files to be saved: Ask
114 vim.cmd('confirm quit')
115 end
116 end,
117 desc = "Quit",
118}
119maps.n["<Tab>"] = {
120 "<Tab>",
121 noremap = true,
122 silent = true,
123 expr = false,
124 desc = "FIX: Prevent TAB from behaving like <C-i>, as they share the same internal code",
125}
126
127-- clipboard ---------------------------------------------------------------
128
129-- BUG: We disable these mappings on termux by default because <C-y>
130-- is the keycode for scrolling, and remapping it would break it.
131if not is_android then
132 -- only useful when the option clipboard is commented on ./1-options.lua
133 maps.n["<C-y>"] = { '"+y<esc>', desc = "Copy to cliboard" }
134 maps.x["<C-y>"] = { '"+y<esc>', desc = "Copy to cliboard" }
135 maps.n["<C-d>"] = { '"+y<esc>dd', desc = "Copy to clipboard and delete line" }
136 maps.x["<C-d>"] = { '"+y<esc>dd', desc = "Copy to clipboard and delete line" }
137 maps.n["<C-p>"] = { '"+p<esc>', desc = "Paste from clipboard" }
138end
139
140-- Make 'c' key not copy to clipboard when changing a character.
141maps.n["c"] = { '"_c', desc = "Change without yanking" }
142maps.n["C"] = { '"_C', desc = "Change without yanking" }
143maps.x["c"] = { '"_c', desc = "Change without yanking" }
144maps.x["C"] = { '"_C', desc = "Change without yanking" }
145
146-- Make 'x' key not copy to clipboard when deleting a character.
147maps.n["x"] = {
148 -- Also let's allow 'x' key to delete blank lines in normal mode.
149 function()
150 if vim.fn.col "." == 1 then
151 local line = vim.fn.getline "."
152 if line:match "^%s*$" then
153 vim.api.nvim_feedkeys('"_dd', "n", false)
154 vim.api.nvim_feedkeys("$", "n", false)
155 else
156 vim.api.nvim_feedkeys('"_x', "n", false)
157 end
158 else
159 vim.api.nvim_feedkeys('"_x', "n", false)
160 end
161 end,
162 desc = "Delete character without yanking it",
163}
164maps.x["x"] = { '"_x', desc = "Delete all characters in line" }
165
166-- Same for shifted X
167maps.n["X"] = {
168 -- Also let's allow 'x' key to delete blank lines in normal mode.
169 function()
170 if vim.fn.col "." == 1 then
171 local line = vim.fn.getline "."
172 if line:match "^%s*$" then
173 vim.api.nvim_feedkeys('"_dd', "n", false)
174 vim.api.nvim_feedkeys("$", "n", false)
175 else
176 vim.api.nvim_feedkeys('"_X', "n", false)
177 end
178 else
179 vim.api.nvim_feedkeys('"_X', "n", false)
180 end
181 end,
182 desc = "Delete before character without yanking it",
183}
184maps.x["X"] = { '"_X', desc = "Delete all characters in line" }
185
186-- Override nvim default behavior so it doesn't auto-yank when pasting on visual mode.
187maps.x["p"] = { "P", desc = "Paste content you've previourly yanked" }
188maps.x["P"] = { "p", desc = "Yank what you are going to override, then paste" }
189
190-- search highlighting ------------------------------------------------------
191-- use ESC to clear hlsearch, while preserving its original functionality.
192--
193-- TIP: If you prefer, use <leader>ENTER instead of <ESC>
194-- to avoid triggering it by accident.
195maps.n["<ESC>"] = {
196 function()
197 if vim.fn.hlexists "Search" then
198 vim.cmd "nohlsearch"
199 else
200 vim.api.nvim_feedkeys(
201 vim.api.nvim_replace_termcodes("<ESC>", true, true, true),
202 "n",
203 true
204 )
205 end
206 end,
207}
208
209-- Improved tabulation ------------------------------------------------------
210maps.x["<S-Tab>"] = { "<gv", desc = "unindent line" }
211maps.x["<Tab>"] = { ">gv", desc = "indent line" }
212maps.x["<"] = { "<gv", desc = "unindent line" }
213maps.x[">"] = { ">gv", desc = "indent line" }
214
215-- improved gg --------------------------------------------------------------
216maps.n["gg"] = {
217 function()
218 vim.g.minianimate_disable = true
219 if vim.v.count > 0 then
220 vim.cmd("normal! " .. vim.v.count .. "gg")
221 else
222 vim.cmd "normal! gg0"
223 end
224 vim.g.minianimate_disable = false
225 end,
226 desc = "gg and go to the first position",
227}
228maps.n["G"] = {
229 function()
230 vim.g.minianimate_disable = true
231 vim.cmd "normal! G$"
232 vim.g.minianimate_disable = false
233 end,
234 desc = "G and go to the last position",
235}
236maps.x["gg"] = {
237 function()
238 vim.g.minianimate_disable = true
239 if vim.v.count > 0 then
240 vim.cmd("normal! " .. vim.v.count .. "gg")
241 else
242 vim.cmd "normal! gg0"
243 end
244 vim.g.minianimate_disable = false
245 end,
246 desc = "gg and go to the first position (visual)",
247}
248maps.x["G"] = {
249 function()
250 vim.g.minianimate_disable = true
251 vim.cmd "normal! G$"
252 vim.g.minianimate_disable = false
253 end,
254 desc = "G and go to the last position (visual)",
255}
256maps.n["<C-a>"] = { -- to move to the previous position press ctrl + oo
257 function()
258 vim.g.minianimate_disable = true
259 vim.cmd "normal! gg0vG$"
260 vim.g.minianimate_disable = false
261 end,
262 desc = "Visually select all",
263}
264
265-- packages -----------------------------------------------------------------
266-- lazy
267maps.n["<leader>p"] = icons.p
268maps.n["<leader>pu"] =
269{ function() require("lazy").check() end, desc = "Lazy open" }
270maps.n["<leader>pU"] =
271{ function() require("lazy").update() end, desc = "Lazy update" }
272
273-- mason
274if is_available "mason.nvim" then
275 maps.n["<leader>pm"] = { "<cmd>Mason<cr>", desc = "Mason open" }
276 maps.n["<leader>pM"] = { "<cmd>MasonUpdateAll<cr>", desc = "Mason update" }
277end
278
279-- treesitter
280if is_available "nvim-treesitter" then
281 maps.n["<leader>pT"] = { "<cmd>TSUpdate<cr>", desc = "Treesitter update" }
282 maps.n["<leader>pt"] = { "<cmd>TSInstallInfo<cr>", desc = "Treesitter open" }
283end
284
285-- nvim updater
286maps.n["<leader>pD"] = { "<cmd>DistroUpdate<cr>", desc = "Distro update" }
287maps.n["<leader>pv"] = { "<cmd>DistroReadVersion<cr>", desc = "Distro version" }
288maps.n["<leader>pc"] = { "<cmd>DistroReadChangelog<cr>", desc = "Distro changelog" }
289
290-- buffers/tabs [buffers ]--------------------------------------------------
291maps.n["<leader>c"] = { -- Close window and buffer at the same time.
292 function() require("heirline-components.buffer").wipe() end,
293 desc = "Wipe buffer",
294}
295maps.n["<leader>C"] = { -- Close buffer keeping the window.
296 function() require("heirline-components.buffer").close() end,
297 desc = "Close buffer",
298}
299-- Close buffer keeping the window → Without confirmation.
300-- maps.n["<leader>X"] = {
301-- function() require("heirline-components.buffer").close(0, true) end,
302-- desc = "Force close buffer",
303--
304maps.n["<leader>ba"] = {
305 function() vim.cmd "wa" end,
306 desc = "Write all changed buffers",
307}
308maps.n["]b"] = {
309 function()
310 require("heirline-components.buffer").nav(vim.v.count > 0 and vim.v.count or 1)
311 end,
312 desc = "Next buffer",
313}
314maps.n["[b"] = {
315 function()
316 require("heirline-components.buffer").nav(-(vim.v.count > 0 and vim.v.count or 1))
317 end,
318 desc = "Previous buffer",
319}
320maps.n[">b"] = {
321 function()
322 require("heirline-components.buffer").move(vim.v.count > 0 and vim.v.count or 1)
323 end,
324 desc = "Move buffer tab right",
325}
326maps.n["<b"] = {
327 function()
328 require("heirline-components.buffer").move(-(vim.v.count > 0 and vim.v.count or 1))
329 end,
330 desc = "Move buffer tab left",
331}
332
333maps.n["<leader>b"] = icons.b
334maps.n["<leader>bc"] = {
335 function() require("heirline-components.buffer").close_all(true) end,
336 desc = "Close all buffers except current",
337}
338maps.n["<leader>bC"] = {
339 function() require("heirline-components.buffer").close_all() end,
340 desc = "Close all buffers",
341}
342maps.n["<leader>bb"] = {
343 function()
344 require("heirline-components.all").heirline.buffer_picker(
345 function(bufnr) vim.api.nvim_win_set_buf(0, bufnr) end
346 )
347 end,
348 desc = "Select buffer from tabline",
349}
350maps.n["<leader>bd"] = {
351 function()
352 require("heirline-components.all").heirline.buffer_picker(
353 function(bufnr) require("heirline-components.buffer").close(bufnr) end
354 )
355 end,
356 desc = "Delete buffer from tabline",
357}
358maps.n["<leader>bl"] = {
359 function() require("heirline-components.buffer").close_left() end,
360 desc = "Close all buffers to the left",
361}
362maps.n["<leader>br"] = {
363 function() require("heirline-components.buffer").close_right() end,
364 desc = "Close all buffers to the right",
365}
366maps.n["<leader>bs"] = icons.bs
367maps.n["<leader>bse"] = {
368 function() require("heirline-components.buffer").sort "extension" end,
369 desc = "Sort by extension (buffers)",
370}
371maps.n["<leader>bsr"] = {
372 function() require("heirline-components.buffer").sort "unique_path" end,
373 desc = "Sort by relative path (buffers)",
374}
375maps.n["<leader>bsp"] = {
376 function() require("heirline-components.buffer").sort "full_path" end,
377 desc = "Sort by full path (buffers)",
378}
379maps.n["<leader>bsi"] = {
380 function() require("heirline-components.buffer").sort "bufnr" end,
381 desc = "Sort by buffer number (buffers)",
382}
383maps.n["<leader>bsm"] = {
384 function() require("heirline-components.buffer").sort "modified" end,
385 desc = "Sort by modification (buffers)",
386}
387maps.n["<leader>b\\"] = {
388 function()
389 require("heirline-components.all").heirline.buffer_picker(function(bufnr)
390 vim.cmd.split()
391 vim.api.nvim_win_set_buf(0, bufnr)
392 end)
393 end,
394 desc = "Horizontal split buffer from tabline",
395}
396maps.n["<leader>b|"] = {
397 function()
398 require("heirline-components.all").heirline.buffer_picker(function(bufnr)
399 vim.cmd.vsplit()
400 vim.api.nvim_win_set_buf(0, bufnr)
401 end)
402 end,
403 desc = "Vertical split buffer from tabline",
404}
405
406-- quick movement aliases
407maps.n["<C-k>"] = {
408 function()
409 require("heirline-components.buffer").nav(vim.v.count > 0 and vim.v.count or 1)
410 end,
411 desc = "Next buffer",
412}
413maps.n["<C-j>"] = {
414 function()
415 require("heirline-components.buffer").nav(-(vim.v.count > 0 and vim.v.count or 1))
416 end,
417 desc = "Previous buffer",
418}
419maps.n["<S-Down>"] = {
420 function() vim.api.nvim_feedkeys("5j", "n", true) end,
421 desc = "Fast move down",
422}
423maps.n["<S-Up>"] = {
424 function() vim.api.nvim_feedkeys("5k", "n", true) end,
425 desc = "Fast move up",
426}
427
428-- tabs
429maps.n["]t"] = { function() vim.cmd.tabnext() end, desc = "Next tab" }
430maps.n["[t"] = { function() vim.cmd.tabprevious() end, desc = "Previous tab" }
431
432-- zen mode
433if is_available "zen-mode.nvim" then
434 maps.n["<leader>uz"] =
435 { function() ui.toggle_zen_mode() end, desc = "Zen mode" }
436end
437
438-- ui toggles [ui] ---------------------------------------------------------
439maps.n["<leader>u"] = icons.u
440if is_available "nvim-autopairs" then
441 maps.n["<leader>ua"] = { ui.toggle_autopairs, desc = "Autopairs" }
442end
443maps.n["<leader>ub"] = { ui.toggle_background, desc = "Background" }
444if is_available "nvim-cmp" then
445 maps.n["<leader>uc"] = { ui.toggle_cmp, desc = "Autocompletion" }
446end
447if is_available "nvim-colorizer.lua" then
448 maps.n["<leader>uC"] =
449 { "<cmd>ColorizerToggle<cr>", desc = "color highlight" }
450end
451maps.n["<leader>ud"] = { ui.toggle_diagnostics, desc = "Diagnostics" }
452maps.n["<leader>uD"] = { ui.set_indent, desc = "Change indent setting" }
453maps.n["<leader>ug"] = { ui.toggle_signcolumn, desc = "Signcolumn" }
454maps.n["<leader>ul"] = { ui.toggle_statusline, desc = "Statusline" }
455maps.n["<leader>un"] = { ui.change_number, desc = "Change line numbering" }
456maps.n["<leader>uP"] = { ui.toggle_paste, desc = "Paste mode" }
457maps.n["<leader>us"] = { ui.toggle_spell, desc = "Spellcheck" }
458maps.n["<leader>uS"] = { ui.toggle_conceal, desc = "Conceal" }
459maps.n["<leader>ut"] = { ui.toggle_tabline, desc = "Tabline" }
460maps.n["<leader>uu"] = { ui.toggle_url_effect, desc = "URL highlight" }
461maps.n["<leader>uw"] = { ui.toggle_wrap, desc = "Wrap" }
462maps.n["<leader>uy"] = { ui.toggle_buffer_syntax, desc = "Syntax highlight (buffer)" }
463maps.n["<leader>uh"] = { ui.toggle_foldcolumn, desc = "Foldcolumn" }
464maps.n["<leader>uN"] =
465{ ui.toggle_ui_notifications, desc = "UI notifications" }
466if is_available "lsp_signature.nvim" then
467 maps.n["<leader>up"] = { ui.toggle_lsp_signature, desc = "LSP signature" }
468end
469if is_available "mini.animate" then
470 maps.n["<leader>uA"] = { ui.toggle_animations, desc = "Animations" }
471end
472
473-- shifted movement keys ----------------------------------------------------
474maps.n["<S-Down>"] = {
475 function() vim.api.nvim_feedkeys("7j", "n", true) end,
476 desc = "Fast move down",
477}
478maps.n["<S-Up>"] = {
479 function() vim.api.nvim_feedkeys("7k", "n", true) end,
480 desc = "Fast move up",
481}
482maps.n["<S-PageDown>"] = {
483 function()
484 local current_line = vim.fn.line "."
485 local total_lines = vim.fn.line "$"
486 local target_line = current_line + 1 + math.floor(total_lines * 0.20)
487 if target_line > total_lines then target_line = total_lines end
488 vim.api.nvim_win_set_cursor(0, { target_line, 0 })
489 vim.cmd "normal! zz"
490 end,
491 desc = "Page down exactly a 20% of the total size of the buffer",
492}
493maps.n["<S-PageUp>"] = {
494 function()
495 local current_line = vim.fn.line "."
496 local target_line = current_line - 1 - math.floor(vim.fn.line "$" * 0.20)
497 if target_line < 1 then target_line = 1 end
498 vim.api.nvim_win_set_cursor(0, { target_line, 0 })
499 vim.cmd "normal! zz"
500 end,
501 desc = "Page up exactly 20% of the total size of the buffer",
502}
503
504-- cmdline autocompletion ---------------------------------------------------
505maps.c["<Up>"] = {
506 function() return vim.fn.wildmenumode() == 1 and "<Left>" or "<Up>" end,
507 noremap = true,
508 expr = true,
509 desc = "Wildmenu fix for neovim bug #9953",
510}
511maps.c["<Down>"] = {
512 function() return vim.fn.wildmenumode() == 1 and "<Right>" or "<Down>" end,
513 noremap = true,
514 expr = true,
515 desc = "Wildmenu fix for neovim bug #9953",
516}
517maps.c["<Left>"] = {
518 function() return vim.fn.wildmenumode() == 1 and "<Up>" or "<Left>" end,
519 noremap = true,
520 expr = true,
521 desc = "Wildmenu fix for neovim bug #9953",
522}
523maps.c["<Right>"] = {
524 function() return vim.fn.wildmenumode() == 1 and "<Down>" or "<Right>" end,
525 noremap = true,
526 expr = true,
527 desc = "Wildmenu fix for neovim bug #9953",
528}
529
530-- special cases ------------------------------------------------------------
531vim.api.nvim_create_autocmd("BufWinEnter", {
532 desc = "Make q close help, man, quickfix, dap floats",
533 callback = function(args)
534 local buftype =
535 vim.api.nvim_get_option_value("buftype", { buf = args.buf })
536 if vim.tbl_contains({ "help", "nofile", "quickfix" }, buftype) then
537 vim.keymap.set(
538 "n", "q", "<cmd>close<cr>",
539 { buffer = args.buf, silent = true, nowait = true }
540 )
541 end
542 end,
543})
544vim.api.nvim_create_autocmd("CmdwinEnter", {
545 desc = "Make q close command history (q: and q?)",
546 callback = function(args)
547 vim.keymap.set(
548 "n", "q", "<cmd>close<cr>",
549 { buffer = args.buf, silent = true, nowait = true }
550 )
551 end,
552})
553
554-- -------------------------------------------------------------------------
555--
556-- ## Plugin bindings
557--
558-- -------------------------------------------------------------------------
559
560-- alpha-nvim --------------------------------------------------------------
561if is_available "alpha-nvim" then
562 maps.n["<leader>h"] = {
563 function()
564 local wins = vim.api.nvim_tabpage_list_wins(0)
565 if #wins > 1
566 and vim.api.nvim_get_option_value("filetype", { win = wins[1] })
567 == "neo-tree"
568 then
569 vim.fn.win_gotoid(wins[2]) -- go to non-neo-tree window to toggle alpha
570 end
571 require("alpha").start(false, require("alpha").default_config)
572 vim.b.miniindentscope_disable = true
573 end,
574 desc = "Home screen",
575 }
576end
577
578-- [git] -----------------------------------------------------------
579-- gitsigns.nvim
580maps.n["<leader>g"] = icons.g
581if is_available "gitsigns.nvim" then
582 maps.n["<leader>g"] = icons.g
583 maps.n["]g"] =
584 { function() require("gitsigns").next_hunk() end, desc = "Next Git hunk" }
585 maps.n["[g"] = {
586 function() require("gitsigns").prev_hunk() end,
587 desc = "Previous Git hunk",
588 }
589 maps.n["<leader>gl"] = {
590 function() require("gitsigns").blame_line() end,
591 desc = "View Git blame",
592 }
593 maps.n["<leader>gL"] = {
594 function() require("gitsigns").blame_line { full = true } end,
595 desc = "View full Git blame",
596 }
597 maps.n["<leader>gp"] = {
598 function() require("gitsigns").preview_hunk() end,
599 desc = "Preview Git hunk",
600 }
601 maps.n["<leader>gh"] = {
602 function() require("gitsigns").reset_hunk() end,
603 desc = "Reset Git hunk",
604 }
605 maps.n["<leader>gr"] = {
606 function() require("gitsigns").reset_buffer() end,
607 desc = "Reset Git buffer",
608 }
609 maps.n["<leader>gs"] = {
610 function() require("gitsigns").stage_hunk() end,
611 desc = "Stage Git hunk",
612 }
613 maps.n["<leader>gS"] = {
614 function() require("gitsigns").stage_buffer() end,
615 desc = "Stage Git buffer",
616 }
617 maps.n["<leader>gu"] = {
618 function() require("gitsigns").undo_stage_hunk() end,
619 desc = "Unstage Git hunk",
620 }
621 maps.n["<leader>gd"] = {
622 function() require("gitsigns").diffthis() end,
623 desc = "View Git diff",
624 }
625end
626-- git fugitive
627if is_available "vim-fugitive" then
628 maps.n["<leader>gP"] = {
629 function() vim.cmd ":GBrowse" end,
630 desc = "Open in github ",
631 }
632end
633-- git client
634if vim.fn.executable "lazygit" == 1 then -- if lazygit exists, show it
635 maps.n["<leader>gg"] = {
636 function()
637 local git_dir = vim.fn.finddir(".git", vim.fn.getcwd() .. ";")
638 if git_dir ~= "" then
639 vim.cmd "TermExec cmd='lazygit && exit'"
640 else
641 utils.notify("Not a git repository", vim.log.levels.WARN)
642 end
643 end,
644 desc = "ToggleTerm lazygit",
645 }
646end
647if vim.fn.executable "gitui" == 1 then -- if gitui exists, show it
648 maps.n["<leader>gg"] = {
649 function()
650 local git_dir = vim.fn.finddir(".git", vim.fn.getcwd() .. ";")
651 if git_dir ~= "" then
652 if vim.fn.executable "keychain" == 1 then
653 vim.cmd 'TermExec cmd="eval `keychain --eval ~/.ssh/github.key` && gitui && exit"'
654 else
655 vim.cmd "TermExec cmd='gitui && exit'"
656 end
657 else
658 utils.notify("Not a git repository", vim.log.levels.WARN)
659 end
660 end,
661 desc = "ToggleTerm gitui",
662 }
663end
664
665-- file browsers ------------------------------------
666-- ranger
667if is_available "rnvimr" then
668 maps.n["<leader>r"] = { "<cmd>RnvimrToggle<cr>", desc = "Ranger" }
669end
670
671-- neotree
672if is_available "neo-tree.nvim" then
673 maps.n["<leader>e"] = { "<cmd>Neotree toggle<cr>", desc = "Neotree" }
674end
675
676-- session manager ---------------------------------------------------------
677if is_available "neovim-session-manager" then
678 maps.n["<leader>S"] = icons.S
679 maps.n["<leader>Sl"] = {
680 "<cmd>SessionManager! load_last_session<cr>",
681 desc = "Load last session",
682 }
683 maps.n["<leader>Ss"] = {
684 "<cmd>SessionManager! save_current_session<cr>",
685 desc = "Save this session",
686 }
687 maps.n["<leader>Sd"] =
688 { "<cmd>SessionManager! delete_session<cr>", desc = "Delete session" }
689 maps.n["<leader>Sf"] =
690 { "<cmd>SessionManager! load_session<cr>", desc = "Search sessions" }
691 maps.n["<leader>S."] = {
692 "<cmd>SessionManager! load_current_dir_session<cr>",
693 desc = "Load current directory session",
694 }
695end
696if is_available "resession.nvim" then
697 maps.n["<leader>S"] = icons.S
698 maps.n["<leader>Sl"] = {
699 function() require("resession").load "Last Session" end,
700 desc = "Load last session",
701 }
702 maps.n["<leader>Ss"] =
703 { function() require("resession").save() end, desc = "Save this session" }
704 maps.n["<leader>St"] = {
705 function() require("resession").save_tab() end,
706 desc = "Save this tab's session",
707 }
708 maps.n["<leader>Sd"] =
709 { function() require("resession").delete() end, desc = "Delete a session" }
710 maps.n["<leader>Sf"] =
711 { function() require("resession").load() end, desc = "Load a session" }
712 maps.n["<leader>S."] = {
713 function()
714 require("resession").load(vim.fn.getcwd(), { dir = "dirsession" })
715 end,
716 desc = "Load current directory session",
717 }
718end
719
720-- smart-splits.nivm
721if is_available "smart-splits.nvim" then
722 maps.n["<C-h>"] = {
723 function() require("smart-splits").move_cursor_left() end,
724 desc = "Move to left split",
725 }
726 maps.n["<C-j>"] = {
727 function() require("smart-splits").move_cursor_down() end,
728 desc = "Move to below split",
729 }
730 maps.n["<C-k>"] = {
731 function() require("smart-splits").move_cursor_up() end,
732 desc = "Move to above split",
733 }
734 maps.n["<C-l>"] = {
735 function() require("smart-splits").move_cursor_right() end,
736 desc = "Move to right split",
737 }
738 maps.n["<C-Up>"] = {
739 function() require("smart-splits").resize_up() end,
740 desc = "Resize split up",
741 }
742 maps.n["<C-Down>"] = {
743 function() require("smart-splits").resize_down() end,
744 desc = "Resize split down",
745 }
746 maps.n["<C-Left>"] = {
747 function() require("smart-splits").resize_left() end,
748 desc = "Resize split left",
749 }
750 maps.n["<C-Right>"] = {
751 function() require("smart-splits").resize_right() end,
752 desc = "Resize split right",
753 }
754else
755 maps.n["<C-h>"] = { "<C-w>h", desc = "Move to left split" }
756 maps.n["<C-j>"] = { "<C-w>j", desc = "Move to below split" }
757 maps.n["<C-k>"] = { "<C-w>k", desc = "Move to above split" }
758 maps.n["<C-l>"] = { "<C-w>l", desc = "Move to right split" }
759 maps.n["<C-Up>"] = { "<cmd>resize -2<CR>", desc = "Resize split up" }
760 maps.n["<C-Down>"] = { "<cmd>resize +2<CR>", desc = "Resize split down" }
761 maps.n["<C-Left>"] =
762 { "<cmd>vertical resize -2<CR>", desc = "Resize split left" }
763 maps.n["<C-Right>"] =
764 { "<cmd>vertical resize +2<CR>", desc = "Resize split right" }
765end
766
767-- aerial.nvimm ------------------------------------------------------------
768if is_available "aerial.nvim" then
769 maps.n["<leader>i"] =
770 { function() require("aerial").toggle() end, desc = "Aerial" }
771end
772
773-- letee-calltree.nvimm ------------------------------------------------------------
774if is_available "litee-calltree.nvim" then
775 -- For every buffer, look for the one with filetype "calltree" and focus it.
776 local calltree_delay = 1500 -- first run? wait a bit longer.
777 local function focus_calltree()
778 -- Note: No go to the previous cursor position, press ctrl+i / ctrl+o
779 vim.defer_fn(function()
780 for _, win in ipairs(vim.api.nvim_list_wins()) do
781 local buf = vim.api.nvim_win_get_buf(win)
782 local ft = vim.api.nvim_get_option_value('filetype', { buf = buf })
783
784 if ft == "calltree" then
785 vim.api.nvim_set_current_win(win)
786 return true
787 end
788 end
789 end, calltree_delay)
790 calltree_delay = 100
791 end
792 maps.n["gj"] = {
793 function()
794 vim.lsp.buf.incoming_calls()
795 focus_calltree()
796 end,
797 desc = "Call tree (incoming)"
798 }
799 maps.n["gJ"] =
800 {
801 function()
802 vim.lsp.buf.outgoing_calls()
803 focus_calltree()
804 end,
805 desc = "Call tree (outgoing)"
806 }
807end
808
809-- telescope.nvim [find] ----------------------------------------------------
810if is_available "telescope.nvim" then
811 maps.n["<leader>f"] = icons.f
812 maps.n["<leader>gb"] = {
813 function() require("telescope.builtin").git_branches() end,
814 desc = "Git branches",
815 }
816 maps.n["<leader>gc"] = {
817 function()
818 require("telescope.builtin").git_commits()
819 end,
820 desc = "Git commits (repository)"
821 }
822 maps.n["<leader>gC"] = {
823 function()
824 require("telescope.builtin").git_bcommits()
825 end,
826 desc = "Git commits (current file)"
827 }
828 maps.n["<leader>gt"] = {
829 function() require("telescope.builtin").git_status() end,
830 desc = "Git status",
831 }
832 maps.n["<leader>f<CR>"] = {
833 function() require("telescope.builtin").resume() end,
834 desc = "Resume previous search",
835 }
836 maps.n["<leader>f'"] = {
837 function() require("telescope.builtin").marks() end,
838 desc = "Find marks",
839 }
840 maps.n["<leader>fa"] = {
841 function()
842 local cwd = vim.fn.stdpath "config" .. "/.."
843 local search_dirs = { vim.fn.stdpath "config" }
844 if #search_dirs == 1 then cwd = search_dirs[1] end -- if only one directory, focus cwd
845 require("telescope.builtin").find_files {
846 prompt_title = "Config Files",
847 search_dirs = search_dirs,
848 cwd = cwd,
849 follow = true,
850 } -- call telescope
851 end,
852 desc = "Find nvim config files",
853 }
854 maps.n["<leader>fB"] = {
855 function() require("telescope.builtin").buffers() end,
856 desc = "Find buffers",
857 }
858 maps.n["<leader>fw"] = {
859 function() require("telescope.builtin").grep_string() end,
860 desc = "Find word under cursor in project",
861 }
862 maps.n["<leader>fC"] = {
863 function() require("telescope.builtin").commands() end,
864 desc = "Find commands",
865 }
866 -- Let's disable this. It is way too imprecise. Use rnvimr instead.
867 -- maps.n["<leader>ff"] = {
868 -- function()
869 -- require("telescope.builtin").find_files { hidden = true, no_ignore = true }
870 -- end,
871 -- desc = "Find all files",
872 -- }
873 -- maps.n["<leader>fF"] = {
874 -- function() require("telescope.builtin").find_files() end,
875 -- desc = "Find files (no hidden)",
876 -- }
877 maps.n["<leader>fh"] = {
878 function() require("telescope.builtin").help_tags() end,
879 desc = "Find help",
880 }
881 maps.n["<leader>fk"] = {
882 function() require("telescope.builtin").keymaps() end,
883 desc = "Find keymaps",
884 }
885 maps.n["<leader>fm"] = {
886 function() require("telescope.builtin").man_pages() end,
887 desc = "Find man",
888 }
889 if is_available "nvim-notify" then
890 maps.n["<leader>fn"] = {
891 function() require("telescope").extensions.notify.notify() end,
892 desc = "Find notifications",
893 }
894 end
895 maps.n["<leader>fo"] = {
896 function() require("telescope.builtin").oldfiles() end,
897 desc = "Find recent",
898 }
899 maps.n["<leader>fv"] = {
900 function() require("telescope.builtin").registers() end,
901 desc = "Find vim registers",
902 }
903 maps.n["<leader>ft"] = {
904 function()
905 -- load color schemes before listing them
906 pcall(vim.api.nvim_command, "doautocmd User LoadColorSchemes")
907
908 -- Open telescope
909 pcall(require("telescope.builtin").colorscheme, {
910 enable_preview = true,
911 ignore_builtins = true
912 })
913 end,
914 desc = "Find themes",
915 }
916 maps.n["<leader>ff"] = {
917 function()
918 require("telescope.builtin").live_grep {
919 additional_args = function(args)
920 return vim.list_extend(args, { "--hidden", "--no-ignore" })
921 end,
922 }
923 end,
924 desc = "Find words in project",
925 }
926 maps.n["<leader>fF"] = {
927 function() require("telescope.builtin").live_grep() end,
928 desc = "Find words in project (no hidden)",
929 }
930 maps.n["<leader>f/"] = {
931 function() require("telescope.builtin").current_buffer_fuzzy_find() end,
932 desc = "Find words in current buffer"
933 }
934
935 -- Some lsp keymappings are here because they depend on telescope
936 maps.n["<leader>l"] = icons.l
937 maps.n["<leader>ls"] = {
938 function()
939 local aerial_avail, _ = pcall(require, "aerial")
940 if aerial_avail then
941 require("telescope").extensions.aerial.aerial()
942 else
943 require("telescope.builtin").lsp_document_symbols()
944 end
945 end,
946 desc = "Search symbol in buffer", -- Useful to find every time a variable is assigned.
947 }
948 maps.n["gs"] = {
949 function()
950 local aerial_avail, _ = pcall(require, "aerial")
951 if aerial_avail then
952 require("telescope").extensions.aerial.aerial()
953 else
954 require("telescope.builtin").lsp_document_symbols()
955 end
956 end,
957 desc = "Search symbol in buffer", -- Useful to find every time a variable is assigned.
958 }
959
960 -- extra - project.nvim
961 if is_available "project.nvim" then
962 maps.n["<leader>fp"] = {
963 function() vim.cmd "Telescope projects" end,
964 desc = "Find project",
965 }
966 end
967
968 -- extra - spectre.nvim (search and replace in project)
969 if is_available "nvim-spectre" then
970 maps.n["<leader>fr"] = {
971 function() require("spectre").toggle() end,
972 desc = "Find and replace word in project",
973 }
974 maps.n["<leader>fb"] = {
975 function() require("spectre").toggle { path = vim.fn.expand "%:t:p" } end,
976 desc = "Find and replace word in buffer",
977 }
978 end
979
980 -- extra - luasnip
981 if is_available "LuaSnip" and is_available "telescope-luasnip.nvim" then
982 maps.n["<leader>fs"] = {
983 function() require("telescope").extensions.luasnip.luasnip {} end,
984 desc = "Find snippets",
985 }
986 end
987
988 -- extra - nvim-neoclip (neovim internal clipboard)
989 -- Specially useful if you disable the shared clipboard in options.
990 if is_available "nvim-neoclip.lua" then
991 maps.n["<leader>fy"] = {
992 function() require("telescope").extensions.neoclip.default() end,
993 desc = "Find yank history",
994 }
995 maps.n["<leader>fq"] = {
996 function() require("telescope").extensions.macroscope.default() end,
997 desc = "Find macro history",
998 }
999 end
1000
1001 -- extra - undotree
1002 if is_available "telescope-undo.nvim" then
1003 maps.n["<leader>fu"] = {
1004 function() require("telescope").extensions.undo.undo() end,
1005 desc = "Find in undo tree",
1006 }
1007 end
1008
1009 -- extra - compiler
1010 if is_available "compiler.nvim" and is_available "overseer.nvim" then
1011 maps.n["<leader>m"] = icons.c
1012 maps.n["<leader>mm"] = {
1013 function() vim.cmd "CompilerOpen" end,
1014 desc = "Open compiler",
1015 }
1016 maps.n["<leader>mr"] = {
1017 function() vim.cmd "CompilerRedo" end,
1018 desc = "Compiler redo",
1019 }
1020 maps.n["<leader>mt"] = {
1021 function() vim.cmd "CompilerToggleResults" end,
1022 desc = "compiler results",
1023 }
1024 maps.n["<F6>"] = {
1025 function() vim.cmd "CompilerOpen" end,
1026 desc = "Open compiler",
1027 }
1028 maps.n["<S-F6>"] = {
1029 function() vim.cmd "CompilerRedo" end,
1030 desc = "Compiler redo",
1031 }
1032 maps.n["<S-F7>"] = {
1033 function() vim.cmd "CompilerToggleResults" end,
1034 desc = "compiler resume",
1035 }
1036 end
1037end
1038
1039-- toggleterm.nvim ----------------------------------------------------------
1040if is_available "toggleterm.nvim" then
1041 maps.n["<leader>t"] = icons.t
1042 maps.n["<leader>tt"] =
1043 { "<cmd>ToggleTerm direction=float<cr>", desc = "ToggleTerm float" }
1044 maps.n["<leader>th"] = {
1045 "<cmd>ToggleTerm size=10 direction=horizontal<cr>",
1046 desc = "Toggleterm horizontal split",
1047 }
1048 maps.n["<leader>tv"] = {
1049 "<cmd>ToggleTerm size=80 direction=vertical<cr>",
1050 desc = "Toggleterm vertical split",
1051 }
1052 maps.n["<F7>"] = { "<cmd>ToggleTerm<cr>", desc = "terminal" }
1053 maps.t["<F7>"] = maps.n["<F7>"]
1054 maps.n["<C-'>"] = maps.n["<F7>"] -- requires terminal that supports binding <C-'>
1055 maps.t["<C-'>"] = maps.n["<F7>"] -- requires terminal that supports binding <C-'>
1056end
1057
1058-- extra - improved terminal navigation
1059maps.t["<C-h>"] =
1060{ "<cmd>wincmd h<cr>", desc = "Terminal left window navigation" }
1061maps.t["<C-j>"] =
1062{ "<cmd>wincmd j<cr>", desc = "Terminal down window navigation" }
1063maps.t["<C-k>"] =
1064{ "<cmd>wincmd k<cr>", desc = "Terminal up window navigation" }
1065maps.t["<C-l>"] =
1066{ "<cmd>wincmd l<cr>", desc = "Terminal right window navigation" }
1067
1068-- dap.nvim [debugger] -----------------------------------------------------
1069-- Depending your terminal some F keys may not work. To fix it:
1070-- modified function keys found with `showkey -a` in the terminal to get key code
1071-- run `nvim -V3log +quit` and search through the "Terminal info" in the `log` file for the correct keyname
1072if is_available "nvim-dap" then
1073 maps.n["<leader>d"] = icons.d
1074 maps.x["<leader>d"] = icons.d
1075
1076 -- F keys
1077 maps.n["<F5>"] = {
1078 function()
1079 require("dap").continue()
1080 end,
1081 desc = "Debugger: Start"
1082 }
1083 maps.n["<S-F5>"] =
1084 { function() require("dap").terminate() end, desc = "Debugger: Stop" }
1085 maps.n["<C-F5>"] = {
1086 function() require("dap").restart_frame() end, desc = "Debugger: Restart" }
1087 maps.n["<F9>"] = {
1088 function() require("dap").toggle_breakpoint() end, desc = "Debugger: Toggle Breakpoint" }
1089 maps.n["<S-F9>"] = {
1090 function()
1091 vim.ui.input({ prompt = "Condition: " }, function(condition)
1092 if condition then require("dap").set_breakpoint(condition) end
1093 end)
1094 end,
1095 desc = "Debugger: Conditional Breakpoint",
1096 }
1097 maps.n["<F10>"] =
1098 { function() require("dap").step_over() end, desc = "Debugger: Step Over" }
1099 maps.n["<S-F10>"] =
1100 { function() require("dap").step_back() end, desc = "Debugger: Step Back" }
1101 maps.n["<F11>"] =
1102 { function() require("dap").step_into() end, desc = "Debugger: Step Into" }
1103 maps.n["<S-11>"] =
1104 { function() require("dap").step_out() end, desc = "Debugger: Step Out" }
1105
1106 -- Space + d
1107 maps.n["<leader>db"] = {
1108 function() require("dap").toggle_breakpoint() end,
1109 desc = "Breakpoint (F9)",
1110 }
1111 maps.n["<leader>dB"] = {
1112 function() require("dap").clear_breakpoints() end,
1113 desc = "Clear Breakpoints",
1114 }
1115 maps.n["<leader>dc"] =
1116 { function() require("dap").continue() end, desc = "Start/Continue (F5)" }
1117 maps.n["<leader>dC"] = {
1118 function()
1119 vim.ui.input({ prompt = "Condition: " }, function(condition)
1120 if condition then require("dap").set_breakpoint(condition) end
1121 end)
1122 end,
1123 desc = "Conditional Breakpoint (S-F9)",
1124 }
1125 maps.n["<leader>do"] =
1126 { function() require("dap").step_over() end, desc = "Step Over (F10)" }
1127 maps.n["<leader>do"] =
1128 { function() require("dap").step_back() end, desc = "Step Back (S-F10)" }
1129 maps.n["<leader>db"] =
1130 { function() require("dap").step_into() end, desc = "Step Into (F11)" }
1131 maps.n["<leader>dO"] =
1132 { function() require("dap").step_out() end, desc = "Step Out (S-F11)" }
1133 maps.n["<leader>dq"] =
1134 { function() require("dap").close() end, desc = "Close Session" }
1135 maps.n["<leader>dQ"] = {
1136 function() require("dap").terminate() end,
1137 desc = "Terminate Session (S-F5)",
1138 }
1139 maps.n["<leader>dp"] =
1140 { function() require("dap").pause() end, desc = "Pause" }
1141 maps.n["<leader>dr"] =
1142 { function() require("dap").restart_frame() end, desc = "Restart (C-F5)" }
1143 maps.n["<leader>dR"] =
1144 { function() require("dap").repl.toggle() end, desc = "REPL" }
1145 maps.n["<leader>ds"] =
1146 { function() require("dap").run_to_cursor() end, desc = "Run To Cursor" }
1147
1148 if is_available "nvim-dap-ui" then
1149 maps.n["<leader>dE"] = {
1150 function()
1151 vim.ui.input({ prompt = "Expression: " }, function(expr)
1152 if expr then require("dapui").eval(expr, { enter = true }) end
1153 end)
1154 end,
1155 desc = "Evaluate Input",
1156 }
1157 maps.x["<leader>dE"] =
1158 { function() require("dapui").eval() end, desc = "Evaluate Input" }
1159 maps.n["<leader>du"] =
1160 { function() require("dapui").toggle() end, desc = "Debugger UI" }
1161 maps.n["<leader>dh"] = {
1162 function() require("dap.ui.widgets").hover() end,
1163 desc = "Debugger Hover",
1164 }
1165 end
1166end
1167
1168-- testing [tests] -------------------------------------------------
1169-- neotest
1170maps.n["<leader>T"] = icons.tt
1171maps.x["<leader>T"] = icons.tt
1172if is_available "neotest" then
1173 maps.n["<leader>Tu"] = {
1174 function() require("neotest").run.run() end,
1175 desc = "Unit",
1176 }
1177 maps.n["<leader>Ts"] = {
1178 function() require("neotest").run.stop() end,
1179 desc = "Stop unit",
1180 }
1181 maps.n["<leader>Tf"] = {
1182 function() require("neotest").run.run(vim.fn.expand "%") end,
1183 desc = "File",
1184 }
1185 maps.n["<leader>Td"] = {
1186 function() require("neotest").run.run { strategy = "dap" } end,
1187 desc = "Unit in debugger",
1188 }
1189 maps.n["<leader>Tt"] = {
1190 function() require("neotest").summary.toggle() end,
1191 desc = "Neotest summary",
1192 }
1193 maps.n["<leader>TT"] = {
1194 function() require("neotest").output_panel.toggle() end,
1195 desc = "Output panel",
1196 }
1197end
1198
1199-- Extra - nvim-coverage
1200-- Your project must generate coverage/lcov.info for this to work.
1201--
1202-- On jest, make sure your packages.json file has this:
1203-- "test": "jest --coverage"
1204--
1205-- If you use other framework or language, refer to nvim-coverage docs:
1206-- https://github.com/andythigpen/nvim-coverage/blob/main/doc/nvim-coverage.txt
1207if is_available "nvim-coverage" then
1208 maps.n["<leader>Tc"] = {
1209 function()
1210 require("coverage").load(false)
1211 require("coverage").summary()
1212 end,
1213 desc = "Coverage",
1214 }
1215 maps.n["<leader>TC"] = {
1216 function()
1217 ui.toggle_coverage_signs()
1218 end,
1219 desc = "Coverage signs (toggle)",
1220 }
1221end
1222
1223-- Extra - nodejs testing commands
1224maps.n["<leader>Ta"] = {
1225 function() vim.cmd "TestNodejs" end,
1226 desc = "All",
1227}
1228maps.n["<leader>Te"] = {
1229 function() vim.cmd "TestNodejsE2e" end,
1230 desc = "E2e",
1231}
1232
1233-- nvim-ufo [code folding] --------------------------------------------------
1234if is_available "nvim-ufo" then
1235 maps.n["zR"] =
1236 { function() require("ufo").openAllFolds() end, desc = "Open all folds" }
1237 maps.n["zM"] =
1238 { function() require("ufo").closeAllFolds() end, desc = "Close all folds" }
1239 maps.n["zr"] = {
1240 function() require("ufo").openFoldsExceptKinds() end,
1241 desc = "Fold less",
1242 }
1243 maps.n["zm"] =
1244 { function() require("ufo").closeFoldsWith() end, desc = "Fold more" }
1245 maps.n["zp"] = {
1246 function() require("ufo").peekFoldedLinesUnderCursor() end,
1247 desc = "Peek fold",
1248 }
1249 maps.n["zn"] =
1250 {
1251 function() require("ufo").openFoldsExceptKinds({ 'comment' }) end,
1252 desc = "Fold comments"
1253 }
1254 maps.n["zN"] =
1255 {
1256 function() require("ufo").openFoldsExceptKinds({ 'region' }) end,
1257 desc = "Fold region"
1258 }
1259end
1260
1261-- code docmentation [docs] -------------------------------------------------
1262
1263if is_available "markdown-preview.nivm" or is_available "markmap.nvim" or is_available "dooku.nvim" then
1264 maps.n["<leader>D"] = icons.dc
1265
1266 -- Markdown preview
1267 if is_available "markdown-preview.nvim" then
1268 maps.n["<leader>Dp"] = {
1269 function() vim.cmd "MarkdownPreview" end,
1270 desc = "Markdown preview",
1271 }
1272 end
1273
1274 -- Markdown Mindmap
1275 if is_available "markmap.nvim" then
1276 maps.n["<leader>Dm"] = {
1277 function()
1278 if is_android then
1279 vim.cmd "MarkmapWatch"
1280 else
1281 vim.cmd "MarkmapOpen"
1282 end
1283 end,
1284 desc = "Markmap",
1285 }
1286 end
1287
1288 if is_available "dooku.nvim" then
1289 maps.n["<leader>Dd"] = {
1290 function() vim.cmd ":DookuGenerate" end,
1291 desc = "Open documentation",
1292 }
1293 end
1294end
1295
1296-- [neural] -----------------------------------------------------------------
1297if is_available "neural" or is_available "copilot" then
1298 maps.n["<leader>a"] = {
1299 function() require("neural").prompt() end,
1300 desc = "Ask chatgpt",
1301 }
1302end
1303
1304-- hop.nivm ----------------------------------------------------------------
1305if is_available "hop.nvim" then
1306 -- Note that Even though we are using ENTER for hop, you can still select items
1307 -- from special menus like 'quickfix', 'q?' and 'q:' with <C+ENTER>.
1308
1309 maps.n["<C-m>"] = { -- The terminal undersand C-m and ENTER as the same key.
1310 function()
1311 require("hop")
1312 vim.cmd("silent! HopWord")
1313 end,
1314 desc = "Hop to word",
1315 }
1316 maps.x["<C-m>"] = { -- The terminal undersand C-m and ENTER as the same key.
1317 function()
1318 require("hop")
1319 vim.cmd("silent! HopWord")
1320 end,
1321 desc = "Hop to word",
1322 }
1323end
1324
1325-- mason-lspconfig.nvim [lsp] -------------------------------------------------
1326-- WARNING: Don't delete this section, or you won't have LSP keymappings.
1327
1328--A function we call from the script to start lsp.
1329--@return table lsp_mappings
1330function M.lsp_mappings(client, bufnr)
1331 -- Helper function to check if any active LSP clients
1332 -- given a filter provide a specific capability.
1333 -- @param capability string The server capability to check for (example: "documentFormattingProvider").
1334 -- @param filter vim.lsp.get_clients.filter|nil A valid get_clients filter (see function docs).
1335 -- @return boolean # `true` if any of the clients provide the capability.
1336 local function has_capability(capability, filter)
1337 for _, lsp_client in ipairs(vim.lsp.get_clients(filter)) do
1338 if lsp_client.supports_method(capability) then return true end
1339 end
1340 return false
1341 end
1342
1343 local lsp_mappings = require("base.utils").get_mappings_template()
1344
1345 -- Diagnostics
1346 lsp_mappings.n["<leader>ld"] = { function() vim.diagnostic.open_float() end, desc = "Hover diagnostics" }
1347 lsp_mappings.n["[d"] = { function()
1348 -- TODO: Delete after dropping nvim 0.10 support.
1349 if vim.fn.has('nvim-0.11') == 1 then vim.diagnostic.jump({ count = -1 })
1350 else vim.diagnostic.goto_prev() end end, desc = "Previous diagnostic"
1351 }
1352 lsp_mappings.n["]d"] = { function()
1353 -- TODO: Delete after dropping nvim 0.10 support.
1354 if vim.fn.has('nvim-0.11') == 1 then vim.diagnostic.jump({ count = 1 })
1355 else vim.diagnostic.goto_next() end end, desc = "Next diagnostic" }
1356
1357 -- Diagnostics
1358 lsp_mappings.n["gl"] = { function() vim.diagnostic.open_float() end, desc = "Hover diagnostics" }
1359 if is_available "telescope.nvim" then
1360 lsp_mappings.n["<leader>lD"] =
1361 { function() require("telescope.builtin").diagnostics() end, desc = "Diagnostics" }
1362 end
1363
1364 -- LSP info
1365 if is_available "mason-lspconfig.nvim" then
1366 lsp_mappings.n["<leader>li"] = { "<cmd>LspInfo<cr>", desc = "LSP information" }
1367 end
1368
1369 if is_available "none-ls.nvim" then
1370 lsp_mappings.n["<leader>lI"] = { "<cmd>NullLsInfo<cr>", desc = "Null-ls information" }
1371 end
1372
1373 -- Code actions
1374 lsp_mappings.n["<leader>la"] = {
1375 function() vim.lsp.buf.code_action() end,
1376 desc = "LSP code action",
1377 }
1378 lsp_mappings.v["<leader>la"] = lsp_mappings.n["<leader>la"]
1379
1380 -- Codelens
1381 utils.add_autocmds_to_buffer("lsp_codelens_refresh", bufnr, {
1382 events = { "InsertLeave" },
1383 desc = "Refresh codelens",
1384 callback = function(args)
1385 if client.supports_method "textDocument/codeLens" then
1386 if vim.g.codelens_enabled then vim.lsp.codelens.refresh({ bufnr = args.buf }) end
1387 end
1388 end,
1389 })
1390 if client.supports_method "textDocument/codeLens" then -- on LspAttach
1391 if vim.g.codelens_enabled then vim.lsp.codelens.refresh({ bufnr = 0 }) end
1392 end
1393
1394 lsp_mappings.n["<leader>ll"] = {
1395 function()
1396 vim.lsp.codelens.run()
1397 vim.lsp.codelens.refresh({ bufnr = 0 })
1398 end,
1399 desc = "LSP CodeLens run",
1400 }
1401 lsp_mappings.n["<leader>uL"] = {
1402 function() ui.toggle_codelens() end,
1403 desc = "CodeLens",
1404 }
1405
1406 -- Formatting
1407 local formatting = require("base.utils.lsp").formatting
1408 lsp_mappings.n["<leader>lf"] = {
1409 function()
1410 vim.lsp.buf.format(M.format_opts)
1411 vim.cmd('checktime') -- update buffer to reflect changes.
1412 end,
1413 desc = "Format buffer",
1414 }
1415 lsp_mappings.v["<leader>lf"] = lsp_mappings.n["<leader>lf"]
1416
1417 vim.api.nvim_buf_create_user_command(
1418 bufnr,
1419 "Format",
1420 function() vim.lsp.buf.format(M.format_opts) end,
1421 { desc = "Format file with LSP" }
1422 )
1423 local autoformat = formatting.format_on_save
1424 local filetype = vim.api.nvim_get_option_value("filetype", { buf = bufnr })
1425 if
1426 autoformat.enabled
1427 and (vim.tbl_isempty(autoformat.allow_filetypes or {}) or vim.tbl_contains(autoformat.allow_filetypes, filetype))
1428 and (vim.tbl_isempty(autoformat.ignore_filetypes or {}) or not vim.tbl_contains(autoformat.ignore_filetypes, filetype))
1429 then
1430 utils.add_autocmds_to_buffer("lsp_auto_format", bufnr, {
1431 events = "BufWritePre",
1432 desc = "Autoformat on save",
1433 callback = function()
1434 if not has_capability("textDocument/formatting", { bufnr = bufnr }) then
1435 utils.del_autocmds_from_buffer("lsp_auto_format", bufnr)
1436 return
1437 end
1438 local autoformat_enabled = vim.b.autoformat_enabled
1439 if autoformat_enabled == nil then autoformat_enabled = vim.g.autoformat_enabled end
1440 if autoformat_enabled and ((not autoformat.filter) or autoformat.filter(bufnr)) then
1441 vim.lsp.buf.format(vim.tbl_deep_extend("force", M.format_opts, { bufnr = bufnr }))
1442 end
1443 end,
1444 })
1445 lsp_mappings.n["<leader>uf"] = {
1446 function() require("base.utils.ui").toggle_buffer_autoformat() end,
1447 desc = "Autoformatting (buffer)",
1448 }
1449 lsp_mappings.n["<leader>uF"] = {
1450 function() require("base.utils.ui").toggle_autoformat() end,
1451 desc = "Autoformatting (global)",
1452 }
1453 end
1454
1455 -- Highlight references when cursor holds
1456 utils.add_autocmds_to_buffer("lsp_document_highlight", bufnr, {
1457 {
1458 events = { "CursorHold", "CursorHoldI" },
1459 desc = "highlight references when cursor holds",
1460 callback = function()
1461 if has_capability("textDocument/documentHighlight", { bufnr = bufnr }) then
1462 vim.lsp.buf.document_highlight()
1463 end
1464 end,
1465 },
1466 {
1467 events = { "CursorMoved", "CursorMovedI", "BufLeave" },
1468 desc = "clear references when cursor moves",
1469 callback = function() vim.lsp.buf.clear_references() end,
1470 },
1471 })
1472
1473 -- Other LSP mappings
1474 lsp_mappings.n["<leader>lL"] = {
1475 function() vim.api.nvim_command(':LspRestart') end,
1476 desc = "LSP refresh",
1477 }
1478
1479 -- Goto definition / declaration
1480 lsp_mappings.n["gd"] = {
1481 function() vim.lsp.buf.definition() end,
1482 desc = "Goto definition of current symbol",
1483 }
1484 lsp_mappings.n["gD"] = {
1485 function() vim.lsp.buf.declaration() end,
1486 desc = "Goto declaration of current symbol",
1487 }
1488
1489 -- Goto implementation
1490 lsp_mappings.n["gI"] = {
1491 function() vim.lsp.buf.implementation() end,
1492 desc = "Goto implementation of current symbol",
1493 }
1494
1495 -- Goto type definition
1496 lsp_mappings.n["gT"] = {
1497 function() vim.lsp.buf.type_definition() end,
1498 desc = "Goto definition of current type",
1499 }
1500
1501 -- Goto references
1502 lsp_mappings.n["<leader>lR"] = {
1503 function() vim.lsp.buf.references() end,
1504 desc = "Hover references",
1505 }
1506 lsp_mappings.n["gr"] = {
1507 function() vim.lsp.buf.references() end,
1508 desc = "References of current symbol",
1509 }
1510
1511 -- Goto help
1512 lsp_mappings.n["gh"] = {
1513 function() vim.lsp.buf.hover() end,
1514 desc = "Hover help",
1515 }
1516 lsp_mappings.n["gH"] = {
1517 function() vim.lsp.buf.signature_help() end,
1518 desc = "Signature help",
1519 }
1520
1521 lsp_mappings.n["<leader>lh"] = {
1522 function() vim.lsp.buf.hover() end,
1523 desc = "Hover help",
1524 }
1525 lsp_mappings.n["<leader>lH"] = {
1526 function() vim.lsp.buf.signature_help() end,
1527 desc = "Signature help",
1528 }
1529
1530 -- Goto man
1531 lsp_mappings.n["gm"] = {
1532 function() vim.api.nvim_feedkeys("K", "n", false) end,
1533 desc = "Hover man",
1534 }
1535 lsp_mappings.n["<leader>lm"] = {
1536 function() vim.api.nvim_feedkeys("K", "n", false) end,
1537 desc = "Hover man",
1538 }
1539
1540 -- Rename symbol
1541 lsp_mappings.n["<leader>lr"] = {
1542 function() vim.lsp.buf.rename() end,
1543 desc = "Rename current symbol",
1544 }
1545
1546 -- Toggle inlay hints
1547 if vim.b.inlay_hints_enabled == nil then vim.b.inlay_hints_enabled = vim.g.inlay_hints_enabled end
1548 if vim.b.inlay_hints_enabled then vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) end
1549 lsp_mappings.n["<leader>uH"] = {
1550 function() require("base.utils.ui").toggle_buffer_inlay_hints(bufnr) end,
1551 desc = "LSP inlay hints (buffer)",
1552 }
1553
1554 -- Toggle semantic tokens
1555 if vim.g.semantic_tokens_enabled then
1556 vim.b[bufnr].semantic_tokens_enabled = true
1557 lsp_mappings.n["<leader>uY"] = {
1558 function() require("base.utils.ui").toggle_buffer_semantic_tokens(bufnr) end,
1559 desc = "LSP semantic highlight (buffer)",
1560 }
1561 else
1562 client.server_capabilities.semanticTokensProvider = nil
1563 end
1564
1565 -- LSP based search
1566 lsp_mappings.n["<leader>lS"] = { function() vim.lsp.buf.workspace_symbol() end, desc = "Search symbol in workspace" }
1567 lsp_mappings.n["gS"] = { function() vim.lsp.buf.workspace_symbol() end, desc = "Search symbol in workspace" }
1568
1569 -- LSP telescope
1570 if is_available "telescope.nvim" then -- setup telescope mappings if available
1571 if lsp_mappings.n.gd then lsp_mappings.n.gd[1] = function() require("telescope.builtin").lsp_definitions() end end
1572 if lsp_mappings.n.gI then
1573 lsp_mappings.n.gI[1] = function() require("telescope.builtin").lsp_implementations() end
1574 end
1575 if lsp_mappings.n.gr then lsp_mappings.n.gr[1] = function() require("telescope.builtin").lsp_references() end end
1576 if lsp_mappings.n["<leader>lR"] then
1577 lsp_mappings.n["<leader>lR"][1] = function() require("telescope.builtin").lsp_references() end
1578 end
1579 if lsp_mappings.n.gy then
1580 lsp_mappings.n.gy[1] = function() require("telescope.builtin").lsp_type_definitions() end
1581 end
1582 if lsp_mappings.n["<leader>lS"] then
1583 lsp_mappings.n["<leader>lS"][1] = function()
1584 vim.ui.input({ prompt = "Symbol Query: (leave empty for word under cursor)" }, function(query)
1585 if query then
1586 -- word under cursor if given query is empty
1587 if query == "" then query = vim.fn.expand "<cword>" end
1588 require("telescope.builtin").lsp_workspace_symbols {
1589 query = query,
1590 prompt_title = ("Find word (%s)"):format(query),
1591 }
1592 end
1593 end)
1594 end
1595 end
1596 if lsp_mappings.n["gS"] then
1597 lsp_mappings.n["gS"][1] = function()
1598 vim.ui.input({ prompt = "Symbol Query: (leave empty for word under cursor)" }, function(query)
1599 if query then
1600 -- word under cursor if given query is empty
1601 if query == "" then query = vim.fn.expand "<cword>" end
1602 require("telescope.builtin").lsp_workspace_symbols {
1603 query = query,
1604 prompt_title = ("Find word (%s)"):format(query),
1605 }
1606 end
1607 end)
1608 end
1609 end
1610 end
1611
1612 return lsp_mappings
1613end
1614
1615utils.set_mappings(maps)
1616return M