1-base-behaviors.lua

  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