1-- Core behaviors
2-- Things that add new behaviors.
3
4-- Sections:
5-- -> ranger file browser [ranger]
6-- -> project.nvim [project search + auto cd]
7-- -> trim.nvim [auto trim spaces]
8-- -> stickybuf.nvim [lock special buffers]
9-- -> mini.bufremove [smart bufdelete]
10-- -> smart-splits [move and resize buffers]
11-- -> better-scape.nvim [esc]
12-- -> toggleterm.nvim [term]
13-- -> session-manager [session]
14-- -> spectre.nvim [search and replace in project]
15-- -> neotree file browser [neotree]
16-- -> nvim-ufo [folding mod]
17-- -> nvim-neoclip [nvim clipboard]
18-- -> zen-mode.nvim [distraction free mode]
19-- -> suda.vim [write as sudo]
20-- -> vim-matchup [Improved % motion]
21-- -> hop.nvim [go to word visually]
22-- -> nvim-autopairs [auto close brackets]
23-- -> lsp_signature.nvim [auto params help]
24-- -> nvim-lightbulb [lightbulb for code actions]
25-- -> distroupdate.nvim [distro update]
26
27local is_windows = vim.fn.has('win32') == 1 -- true if on windows
28local is_android = vim.fn.isdirectory('/data') == 1 -- true if on android
29
30return {
31 -- [ranger] file browser
32 -- https://github.com/kevinhwang91/rnvimr
33 -- This is NormalNvim file browser, which is only for Linux.
34 --
35 -- If you are on Windows, you have 3 options:
36 -- * Use neotree instead (<space>+e).
37 -- * Delete rnvimr and install some other file browser you like.
38 -- * Or enable WLS on Windows and launch neovim from there.
39 -- This way you can install and use 'ranger' and its dependency 'pynvim'.
40 {
41 "kevinhwang91/rnvimr",
42 event = "User BaseDefered",
43 cmd = { "RnvimrToggle" },
44 enabled = not is_windows,
45 config = function()
46 -- vim.g.rnvimr_vanilla = 1 -- Often solves issues in your ranger config.
47 vim.g.rnvimr_enable_picker = 1 -- Close rnvimr after choosing a file.
48 vim.g.rnvimr_ranger_cmd = { "ranger" } -- By passing a script like TERM=foot ranger "$@" you can open terminals inside ranger.
49 if is_android then -- Open on full screenn
50 vim.g.rnvimr_layout = {
51 relative = "editor",
52 width = 200,
53 height = 100,
54 col = 0,
55 row = 0,
56 style = "minimal",
57 }
58 end
59 end,
60 },
61
62 -- project.nvim [project search + auto cd]
63 -- https://github.com/ahmedkhalf/project.nvim
64 {
65 "zeioth/project.nvim",
66 event = "User BaseDefered",
67 cmd = "ProjectRoot",
68 opts = {
69 -- How to find root directory
70 patterns = {
71 ".git",
72 "_darcs",
73 ".hg",
74 ".bzr",
75 ".svn",
76 "Makefile",
77 "package.json",
78 ".solution",
79 ".solution.toml"
80 },
81 -- Don't list the next projects
82 exclude_dirs = {
83 "~/"
84 },
85 silent_chdir = true,
86 manual_mode = false,
87
88 -- Don't chdir for certain buffers
89 exclude_chdir = {
90 filetype = {"", "OverseerList", "alpha"},
91 buftype = {"nofile", "terminal"},
92 },
93
94 --ignore_lsp = { "lua_ls" },
95 },
96 config = function(_, opts) require("project_nvim").setup(opts) end,
97 },
98
99 -- trim.nvim [auto trim spaces]
100 -- https://github.com/cappyzawa/trim.nvim
101 {
102 "cappyzawa/trim.nvim",
103 event = "BufWrite",
104 opts = {
105 trim_on_write = true,
106 trim_trailing = true,
107 trim_last_line = false,
108 trim_first_line = false,
109 -- ft_blocklist = { "markdown", "text", "org", "tex", "asciidoc", "rst" },
110 -- patterns = {[[%s/\(\n\n\)\n\+/\1/]]}, -- Only one consecutive bl
111 },
112 },
113
114 -- stickybuf.nvim [lock special buffers]
115 -- https://github.com/arnamak/stay-centered.nvim
116 -- By default it support neovim/aerial and others.
117 {
118 "stevearc/stickybuf.nvim",
119 event = "User BaseDefered",
120 config = function() require("stickybuf").setup() end
121 },
122
123 -- mini.bufremove [smart bufdelete]
124 -- https://github.com/echasnovski/mini.bufremove
125 -- Defines what tab to go on :bufdelete
126 {
127 "echasnovski/mini.bufremove",
128 event = "User BaseFile"
129 },
130
131 -- smart-splits [move and resize buffers]
132 -- https://github.com/mrjones2014/smart-splits.nvim
133 {
134 "mrjones2014/smart-splits.nvim",
135 event = "User BaseFile",
136 opts = {
137 ignored_filetypes = { "nofile", "quickfix", "qf", "prompt" },
138 ignored_buftypes = { "nofile" },
139 },
140 },
141
142 -- better-scape.nvim [esc]
143 -- https://github.com/max397574/better-escape.nvim
144 {
145 "max397574/better-escape.nvim",
146 event = "InsertCharPre",
147 opts = {
148 mapping = {},
149 timeout = 300,
150 },
151 },
152
153 -- Toggle floating terminal on <F7> [term]
154 -- https://github.com/akinsho/toggleterm.nvim
155 -- neovim bug → https://github.com/neovim/neovim/issues/21106
156 -- workarounds → https://github.com/akinsho/toggleterm.nvim/wiki/Mouse-support
157 {
158 "akinsho/toggleterm.nvim",
159 cmd = { "ToggleTerm", "TermExec" },
160 opts = {
161 highlights = {
162 Normal = { link = "Normal" },
163 NormalNC = { link = "NormalNC" },
164 NormalFloat = { link = "Normal" },
165 FloatBorder = { link = "FloatBorder" },
166 StatusLine = { link = "StatusLine" },
167 StatusLineNC = { link = "StatusLineNC" },
168 WinBar = { link = "WinBar" },
169 WinBarNC = { link = "WinBarNC" },
170 },
171 size = 10,
172 open_mapping = [[<F7>]],
173 shading_factor = 2,
174 direction = "float",
175 float_opts = {
176 border = "rounded",
177 highlights = { border = "Normal", background = "Normal" },
178 },
179 },
180 },
181
182 -- session-manager [session]
183 -- https://github.com/Shatur/neovim-session-manager
184 {
185 "Shatur/neovim-session-manager",
186 event = "User BaseDefered",
187 cmd = "SessionManager",
188 opts = function()
189 local config = require('session_manager.config')
190 return {
191 autoload_mode = config.AutoloadMode.Disabled,
192 autosave_last_session = false,
193 autosave_only_in_session = false,
194 }
195 end,
196 config = function(_, opts)
197 local session_manager = require('session_manager')
198 session_manager.setup(opts)
199
200 -- Auto save session
201 -- BUG: This feature will auto-close anything nofile before saving.
202 -- This include neotree, aerial, mergetool, among others.
203 -- Consider commenting the next block if this is important for you.
204 --
205 -- This won't be necessary once neovim fixes:
206 -- https://github.com/neovim/neovim/issues/12242
207 -- vim.api.nvim_create_autocmd({ 'BufWritePre' }, {
208 -- callback = function ()
209 -- session_manager.save_current_session()
210 -- end
211 -- })
212 end
213 },
214
215 -- spectre.nvim [search and replace in project]
216 -- https://github.com/nvim-pack/nvim-spectre
217 -- INSTRUCTIONS:
218 -- To see the instructions press '?'
219 -- To start the search press <ESC>.
220 -- It doesn't have ctrl-z so please always commit before using it.
221 {
222 "nvim-pack/nvim-spectre",
223 cmd = "Spectre",
224 opts = {
225 default = {
226 find = {
227 -- pick one of item in find_engine [ fd, rg ]
228 cmd = "fd",
229 options = {}
230 },
231 replace = {
232 -- pick one of item in [ sed, oxi ]
233 cmd = "sed"
234 },
235 },
236 is_insert_mode = true, -- start open panel on is_insert_mode
237 is_block_ui_break = true, -- prevent the UI from breaking
238 mapping = {
239 ["toggle_line"] = {
240 map = "d",
241 cmd = "<cmd>lua require('spectre').toggle_line()<CR>",
242 desc = "toggle item.",
243 },
244 ["enter_file"] = {
245 map = "<cr>",
246 cmd = "<cmd>lua require('spectre.actions').select_entry()<CR>",
247 desc = "open file.",
248 },
249 ["send_to_qf"] = {
250 map = "sqf",
251 cmd = "<cmd>lua require('spectre.actions').send_to_qf()<CR>",
252 desc = "send all items to quickfix.",
253 },
254 ["replace_cmd"] = {
255 map = "src",
256 cmd = "<cmd>lua require('spectre.actions').replace_cmd()<CR>",
257 desc = "replace command.",
258 },
259 ["show_option_menu"] = {
260 map = "so",
261 cmd = "<cmd>lua require('spectre').show_options()<CR>",
262 desc = "show options.",
263 },
264 ["run_current_replace"] = {
265 map = "c",
266 cmd = "<cmd>lua require('spectre.actions').run_current_replace()<CR>",
267 desc = "confirm item.",
268 },
269 ["run_replace"] = {
270 map = "R",
271 cmd = "<cmd>lua require('spectre.actions').run_replace()<CR>",
272 desc = "replace all.",
273 },
274 ["change_view_mode"] = {
275 map = "sv",
276 cmd = "<cmd>lua require('spectre').change_view()<CR>",
277 desc = "results view mode.",
278 },
279 ["change_replace_sed"] = {
280 map = "srs",
281 cmd = "<cmd>lua require('spectre').change_engine_replace('sed')<CR>",
282 desc = "use sed to replace.",
283 },
284 ["change_replace_oxi"] = {
285 map = "sro",
286 cmd = "<cmd>lua require('spectre').change_engine_replace('oxi')<CR>",
287 desc = "use oxi to replace.",
288 },
289 ["toggle_live_update"] = {
290 map = "sar",
291 cmd = "<cmd>lua require('spectre').toggle_live_update()<CR>",
292 desc = "auto refresh changes when nvim writes a file.",
293 },
294 ["resume_last_search"] = {
295 map = "sl",
296 cmd = "<cmd>lua require('spectre').resume_last_search()<CR>",
297 desc = "repeat last search.",
298 },
299 ["insert_qwerty"] = {
300 map = "i",
301 cmd = "<cmd>startinsert<CR>",
302 desc = "insert (qwerty).",
303 },
304 ["insert_colemak"] = {
305 map = "o",
306 cmd = "<cmd>startinsert<CR>",
307 desc = "insert (colemak).",
308 },
309 ["quit"] = {
310 map = "q",
311 cmd = "<cmd>lua require('spectre').close()<CR>",
312 desc = "quit.",
313 },
314 },
315 },
316 },
317
318 -- [neotree]
319 -- https://github.com/nvim-neo-tree/neo-tree.nvim
320 {
321 "nvim-neo-tree/neo-tree.nvim",
322 dependencies = "MunifTanjim/nui.nvim",
323 cmd = "Neotree",
324 opts = function()
325 vim.g.neo_tree_remove_legacy_commands = true
326 local utils = require("base.utils")
327 local get_icon = utils.get_icon
328 return {
329 auto_clean_after_session_restore = true,
330 close_if_last_window = true,
331 buffers = {
332 show_unloaded = true
333 },
334 sources = { "filesystem", "buffers", "git_status" },
335 source_selector = {
336 winbar = true,
337 content_layout = "center",
338 sources = {
339 {
340 source = "filesystem",
341 display_name = get_icon("FolderClosed", 1, true) .. "File",
342 },
343 {
344 source = "buffers",
345 display_name = get_icon("DefaultFile", 1, true) .. "Bufs",
346 },
347 {
348 source = "git_status",
349 display_name = get_icon("Git", 1, true) .. "Git",
350 },
351 {
352 source = "diagnostics",
353 display_name = get_icon("Diagnostic", 1, true) .. "Diagnostic",
354 },
355 },
356 },
357 default_component_configs = {
358 indent = { padding = 0 },
359 icon = {
360 folder_closed = get_icon("FolderClosed"),
361 folder_open = get_icon("FolderOpen"),
362 folder_empty = get_icon("FolderEmpty"),
363 folder_empty_open = get_icon("FolderEmpty"),
364 default = get_icon "DefaultFile",
365 },
366 modified = { symbol = get_icon "FileModified" },
367 git_status = {
368 symbols = {
369 added = get_icon("GitAdd"),
370 deleted = get_icon("GitDelete"),
371 modified = get_icon("GitChange"),
372 renamed = get_icon("GitRenamed"),
373 untracked = get_icon("GitUntracked"),
374 ignored = get_icon("GitIgnored"),
375 unstaged = get_icon("GitUnstaged"),
376 staged = get_icon("GitStaged"),
377 conflict = get_icon("GitConflict"),
378 },
379 },
380 },
381 -- A command is a function that we can assign to a mapping (below)
382 commands = {
383 system_open = function(state)
384 require("base.utils").open_with_program(state.tree:get_node():get_id())
385 end,
386 parent_or_close = function(state)
387 local node = state.tree:get_node()
388 if
389 (node.type == "directory" or node:has_children())
390 and node:is_expanded()
391 then
392 state.commands.toggle_node(state)
393 else
394 require("neo-tree.ui.renderer").focus_node(
395 state,
396 node:get_parent_id()
397 )
398 end
399 end,
400 child_or_open = function(state)
401 local node = state.tree:get_node()
402 if node.type == "directory" or node:has_children() then
403 if not node:is_expanded() then -- if unexpanded, expand
404 state.commands.toggle_node(state)
405 else -- if expanded and has children, seleect the next child
406 require("neo-tree.ui.renderer").focus_node(
407 state,
408 node:get_child_ids()[1]
409 )
410 end
411 else -- if not a directory just open it
412 state.commands.open(state)
413 end
414 end,
415 copy_selector = function(state)
416 local node = state.tree:get_node()
417 local filepath = node:get_id()
418 local filename = node.name
419 local modify = vim.fn.fnamemodify
420
421 local results = {
422 e = { val = modify(filename, ":e"), msg = "Extension only" },
423 f = { val = filename, msg = "Filename" },
424 F = {
425 val = modify(filename, ":r"),
426 msg = "Filename w/o extension",
427 },
428 h = {
429 val = modify(filepath, ":~"),
430 msg = "Path relative to Home",
431 },
432 p = {
433 val = modify(filepath, ":."),
434 msg = "Path relative to CWD",
435 },
436 P = { val = filepath, msg = "Absolute path" },
437 }
438
439 local messages = {
440 { "\nChoose to copy to clipboard:\n", "Normal" },
441 }
442 for i, result in pairs(results) do
443 if result.val and result.val ~= "" then
444 vim.list_extend(messages, {
445 { ("%s."):format(i), "Identifier" },
446 { (" %s: "):format(result.msg) },
447 { result.val, "String" },
448 { "\n" },
449 })
450 end
451 end
452 vim.api.nvim_echo(messages, false, {})
453 local result = results[vim.fn.getcharstr()]
454 if result and result.val and result.val ~= "" then
455 vim.notify("Copied: " .. result.val)
456 vim.fn.setreg("+", result.val)
457 end
458 end,
459 find_in_dir = function(state)
460 local node = state.tree:get_node()
461 local path = node:get_id()
462 require("telescope.builtin").find_files {
463 cwd = node.type == "directory" and path
464 or vim.fn.fnamemodify(path, ":h"),
465 }
466 end,
467 },
468 window = {
469 width = 30,
470 mappings = {
471 ["<space>"] = false, -- disable space until we figure out which-key disabling
472 ["<S-CR>"] = "system_open",
473 ["[b"] = "prev_source",
474 ["]b"] = "next_source",
475 F = utils.is_available "telescope.nvim" and "find_in_dir" or nil,
476 O = "system_open",
477 Y = "copy_selector",
478 h = "parent_or_close",
479 l = "child_or_open",
480 },
481 },
482 filesystem = {
483 follow_current_file = {
484 enabled = true,
485 },
486 hijack_netrw_behavior = "open_current",
487 use_libuv_file_watcher = true,
488 },
489 event_handlers = {
490 {
491 event = "neo_tree_buffer_enter",
492 handler = function(_) vim.opt_local.signcolumn = "auto" end,
493 },
494 },
495 }
496 end,
497 },
498
499 -- code [folding mod] + [promise-asyn] dependency
500 -- https://github.com/kevinhwang91/nvim-ufo
501 -- https://github.com/kevinhwang91/promise-async
502 {
503 "kevinhwang91/nvim-ufo",
504 event = { "User BaseFile" },
505 dependencies = { "kevinhwang91/promise-async" },
506 opts = {
507 preview = {
508 mappings = {
509 scrollB = "<C-b>",
510 scrollF = "<C-f>",
511 scrollU = "<C-u>",
512 scrollD = "<C-d>",
513 },
514 },
515 provider_selector = function(_, filetype, buftype)
516 local function handleFallbackException(bufnr, err, providerName)
517 if type(err) == "string" and err:match "UfoFallbackException" then
518 return require("ufo").getFolds(bufnr, providerName)
519 else
520 return require("promise").reject(err)
521 end
522 end
523
524 -- only use indent until a file is opened
525 return (filetype == "" or buftype == "nofile") and "indent"
526 or function(bufnr)
527 return require("ufo")
528 .getFolds(bufnr, "lsp")
529 :catch(
530 function(err)
531 return handleFallbackException(bufnr, err, "treesitter")
532 end
533 )
534 :catch(
535 function(err)
536 return handleFallbackException(bufnr, err, "indent")
537 end
538 )
539 end
540 end,
541 },
542 },
543
544 -- nvim-neoclip [nvim clipboard]
545 -- https://github.com/AckslD/nvim-neoclip.lua
546 -- Read their docs to enable cross-session history.
547 {
548 "AckslD/nvim-neoclip.lua",
549 requires = 'nvim-telescope/telescope.nvim',
550 event = "User BaseFile",
551 opts = {}
552 },
553
554 -- zen-mode.nvim [distraction free mode]
555 -- https://github.com/folke/zen-mode.nvim
556 {
557 "folke/zen-mode.nvim",
558 cmd = "ZenMode",
559 },
560
561 -- suda.nvim [write as sudo]
562 -- https://github.com/lambdalisue/suda.vim
563 {
564 "lambdalisue/suda.vim",
565 cmd = { "SudaRead", "SudaWrite" },
566 },
567
568 -- vim-matchup [improved % motion]
569 -- https://github.com/andymass/vim-matchup
570 {
571 "andymass/vim-matchup",
572 event = "User BaseFile",
573 config = function()
574 vim.g.matchup_matchparen_deferred = 1 -- work async
575 vim.g.matchup_matchparen_offscreen = {} -- disable status bar icon
576 end,
577 },
578
579 -- hop.nvim [go to word visually]
580 -- https://github.com/smoka7/hop.nvim
581 {
582 "smoka7/hop.nvim",
583 cmd = { "HopWord" },
584 opts = { keys = "etovxqpdygfblzhckisuran" }
585 },
586
587 -- nvim-autopairs [auto close brackets]
588 -- https://github.com/windwp/nvim-autopairs
589 -- It's disabled by default, you can enable it with <space>ua
590 {
591 "windwp/nvim-autopairs",
592 event = "InsertEnter",
593 opts = {
594 check_ts = true,
595 ts_config = { java = false },
596 fast_wrap = {
597 map = "<M-e>",
598 chars = { "{", "[", "(", '"', "'" },
599 pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], "%s+", ""),
600 offset = 0,
601 end_key = "$",
602 keys = "qwertyuiopzxcvbnmasdfghjkl",
603 check_comma = true,
604 highlight = "PmenuSel",
605 highlight_grey = "LineNr",
606 },
607 },
608 config = function(_, opts)
609 local npairs = require("nvim-autopairs")
610 npairs.setup(opts)
611 if not vim.g.autopairs_enabled then npairs.disable() end
612
613 local is_cmp_loaded, cmp = pcall(require, "cmp")
614 if is_cmp_loaded then
615 cmp.event:on(
616 "confirm_done",
617 require("nvim-autopairs.completion.cmp").on_confirm_done {
618 tex = false }
619 )
620 end
621 end
622 },
623
624 -- lsp_signature.nvim [auto params help]
625 -- https://github.com/ray-x/lsp_signature.nvim
626 {
627 "ray-x/lsp_signature.nvim",
628 event = "User BaseFile",
629 opts = function()
630 -- Apply globals from 1-options.lua
631 local is_enabled = vim.g.lsp_signature_enabled
632 local round_borders = {}
633 if vim.g.lsp_round_borders_enabled then
634 round_borders = { border = 'rounded' }
635 end
636 return {
637 -- Window mode
638 floating_window = is_enabled, -- Display it as floating window.
639 hi_parameter = "IncSearch", -- Color to highlight floating window.
640 handler_opts = round_borders, -- Window style
641
642 -- Hint mode
643 hint_enable = false, -- Display it as hint.
644 hint_prefix = "👈 ",
645
646 -- Additionally, you can use <space>uH to toggle inlay hints.
647 toggle_key_flip_floatwin_setting = is_enabled
648 }
649 end,
650 config = function(_, opts) require('lsp_signature').setup(opts) end
651 },
652
653 -- nvim-lightbulb [lightbulb for code actions]
654 -- https://github.com/kosayoda/nvim-lightbulb
655 -- Show a lightbulb where a code action is available
656 {
657 'kosayoda/nvim-lightbulb',
658 enabled = vim.g.codeactions_enabled,
659 event = "User BaseFile",
660 opts = {
661 action_kinds = { -- show only for relevant code actions.
662 "quickfix",
663 },
664 ignore = {
665 ft = { "lua" }, -- ignore filetypes with bad code actions.
666 },
667 autocmd = {
668 enabled = true,
669 updatetime = 100,
670 },
671 sign = { enabled = false },
672 virtual_text = {
673 enabled = true,
674 text = "💡"
675 }
676 },
677 config = function(_, opts) require("nvim-lightbulb").setup(opts) end
678 },
679
680 -- distroupdate.nvim [distro update]
681 -- https://github.com/zeioth/distroupdate.nvim
682 {
683 "zeioth/distroupdate.nvim",
684 dependencies = { "nvim-lua/plenary.nvim" },
685 cmd = {
686 "DistroFreezePluginVersions",
687 "DistroReadChangelog",
688 "DistroReadVersion",
689 "DistroUpdate",
690 "DistroUpdateRevert"
691 },
692 opts = function()
693 local utils = require("base.utils")
694 local config_dir = utils.os_path(vim.fn.stdpath "config" .. "/lua/base/")
695 return {
696 channel = "stable", -- stable/nightly
697 hot_reload_files = {
698 config_dir .. "1-options.lua",
699 config_dir .. "4-mappings.lua"
700 },
701 hot_reload_callback = function()
702 vim.cmd(":silent! colorscheme " .. base.default_colorscheme) -- nvim colorscheme reload command
703 vim.cmd(":silent! doautocmd ColorScheme") -- heirline colorscheme reload event
704 end
705 }
706 end
707 },
708
709} -- end of return