3-autocmds.lua

  1-- General usage autocmds.
  2
  3-- DESCRIPTION:
  4-- All autocmds are defined here.
  5
  6--    Sections:
  7--       ## EXTRA LOGIC
  8--       -> 1. Events to load plugins faster.
  9--       -> 2. Save/restore window layout when possible.
 10--       -> 3. Launch alpha greeter on startup.
 11--       -> 4. Update neotree when closing the git client.
 12--       -> 5. Create parent directories when saving a file.
 13--
 14--       ## COOL HACKS
 15--       -> 6. Effect: URL underline.
 16--       -> 7. Customize right click contextual menu.
 17--       -> 8. Unlist quickfix buffers if the filetype changes.
 18--       -> 9. Close all notifications on BufWritePre.
 19--
 20--       ## COMMANDS
 21--       -> 10. Neotest commands.
 22--       ->     Extra commands.
 23
 24local autocmd = vim.api.nvim_create_autocmd
 25local cmd = vim.api.nvim_create_user_command
 26local utils = require("base.utils")
 27local is_available = utils.is_available
 28
 29-- ## EXTRA LOGIC -----------------------------------------------------------
 30-- 1. Events to load plugins faster → 'BaseFile'/'BaseGitFile'/'BaseDefered':
 31--    this is pretty much the same thing as the event 'BufEnter',
 32--    but without increasing the startup time displayed in the greeter.
 33autocmd({ "BufReadPost", "BufNewFile", "BufWritePost" }, {
 34  desc = "Nvim user events for file detection (BaseFile and BaseGitFile)",
 35  callback = function(args)
 36    local empty_buffer = vim.fn.resolve(vim.fn.expand "%") == ""
 37    local greeter = vim.api.nvim_get_option_value("filetype", { buf = args.buf }) == "alpha"
 38    local git_repo = utils.run_cmd(
 39    { "git", "-C", vim.fn.fnamemodify(vim.fn.resolve(vim.fn.expand "%"), ":p:h"), "rev-parse" }, false)
 40
 41    -- For any file exept empty buffer, or the greeter (alpha)
 42    if not (empty_buffer or greeter) then
 43      utils.trigger_event("User BaseFile")
 44
 45      -- Is the buffer part of a git repo?
 46      if git_repo then
 47        utils.trigger_event("User BaseGitFile")
 48      end
 49    end
 50  end,
 51})
 52autocmd({ "VimEnter" }, {
 53  desc = "Nvim user event that trigger a few ms after nvim starts",
 54  callback = function()
 55    -- If nvim is opened passing a filename, trigger the event inmediatelly.
 56    if #vim.fn.argv() >= 1 then
 57      -- In order to avoid visual glitches.
 58      utils.trigger_event("User BaseDefered", true)
 59      utils.trigger_event("BufEnter", true) -- also, initialize tabline_buffers.
 60    else                                    -- Wait some ms before triggering the event.
 61      vim.defer_fn(function()
 62        utils.trigger_event("User BaseDefered")
 63      end, 70)
 64    end
 65  end,
 66})
 67
 68-- 2. Save/restore window layout when possible.
 69autocmd({ "BufWinLeave", "BufWritePost", "WinLeave" }, {
 70  desc = "Save view with mkview for real files",
 71  callback = function(args)
 72    if vim.b[args.buf].view_activated then
 73      vim.cmd.mkview { mods = { emsg_silent = true } }
 74    end
 75  end,
 76})
 77autocmd("BufWinEnter", {
 78  desc = "Try to load file view if available and enable view saving for real files",
 79  callback = function(args)
 80    if not vim.b[args.buf].view_activated then
 81      local filetype =
 82          vim.api.nvim_get_option_value("filetype", { buf = args.buf })
 83      local buftype =
 84          vim.api.nvim_get_option_value("buftype", { buf = args.buf })
 85      local ignore_filetypes = { "gitcommit", "gitrebase", "svg", "hgcommit" }
 86      if
 87          buftype == ""
 88          and filetype
 89          and filetype ~= ""
 90          and not vim.tbl_contains(ignore_filetypes, filetype)
 91      then
 92        vim.b[args.buf].view_activated = true
 93        vim.cmd.loadview { mods = { emsg_silent = true } }
 94      end
 95    end
 96  end,
 97})
 98
 99-- 3. Launch alpha greeter on startup
100if is_available "alpha-nvim" then
101  autocmd({ "User", "BufEnter" }, {
102    desc = "Disable status and tablines for alpha",
103    callback = function(args)
104      local is_filetype_alpha = vim.api.nvim_get_option_value(
105        "filetype", { buf = 0 }) == "alpha"
106      local is_empty_file = vim.api.nvim_get_option_value(
107        "buftype", { buf = 0 }) == "nofile"
108      if ((args.event == "User" and args.file == "AlphaReady") or
109            (args.event == "BufEnter" and is_filetype_alpha)) and
110          not vim.g.before_alpha
111      then
112        vim.g.before_alpha = {
113          showtabline = vim.opt.showtabline:get(),
114          laststatus = vim.opt.laststatus:get()
115        }
116        vim.opt.showtabline, vim.opt.laststatus = 0, 0
117      elseif
118          vim.g.before_alpha
119          and args.event == "BufEnter"
120          and not is_empty_file
121      then
122        vim.opt.laststatus = vim.g.before_alpha.laststatus
123        vim.opt.showtabline = vim.g.before_alpha.showtabline
124        vim.g.before_alpha = nil
125      end
126    end,
127  })
128  autocmd("VimEnter", {
129    desc = "Start Alpha only when nvim is opened with no arguments",
130    callback = function()
131      -- Precalculate conditions.
132      local lines = vim.api.nvim_buf_get_lines(0, 0, 2, false)
133      local buf_not_empty = vim.fn.argc() > 0
134          or #lines > 1
135          or (#lines == 1 and lines[1]:len() > 0)
136      local buflist_not_empty = #vim.tbl_filter(
137        function(bufnr) return vim.bo[bufnr].buflisted end,
138        vim.api.nvim_list_bufs()
139      ) > 1
140      local buf_not_modifiable = not vim.o.modifiable
141
142      -- Return instead of opening alpha if any of these conditions occur.
143      if buf_not_modifiable or buf_not_empty or buflist_not_empty then
144        return
145      end
146      for _, arg in pairs(vim.v.argv) do
147        if arg == "-b"
148            or arg == "-c"
149            or vim.startswith(arg, "+")
150            or arg == "-S"
151        then
152          return
153        end
154      end
155
156      -- All good? Show alpha.
157      require("alpha").start(true, require("alpha").default_config)
158      vim.schedule(function() vim.cmd.doautocmd "FileType" end)
159    end,
160  })
161end
162
163-- 4. Update neotree when closin the git client.
164if is_available "neo-tree.nvim" then
165  autocmd("TermClose", {
166    pattern = { "*lazygit", "*gitui" },
167    desc = "Refresh Neo-Tree git when closing lazygit/gitui",
168    callback = function()
169      local manager_avail, manager = pcall(require, "neo-tree.sources.manager")
170      if manager_avail then
171        for _, source in ipairs {
172          "filesystem",
173          "git_status",
174          "document_symbols",
175        } do
176          local module = "neo-tree.sources." .. source
177          if package.loaded[module] then
178            manager.refresh(require(module).name)
179          end
180        end
181      end
182    end,
183  })
184end
185
186-- 5. Create parent directories when saving a file.
187autocmd("BufWritePre", {
188  desc = "Automatically create parent directories if they don't exist when saving a file",
189  callback = function(args)
190    local buf_is_valid_and_listed = vim.api.nvim_buf_is_valid(args.buf)
191        and vim.bo[args.buf].buflisted
192
193    if buf_is_valid_and_listed then
194      vim.fn.mkdir(vim.fn.fnamemodify(
195        vim.loop.fs_realpath(args.match) or args.match, ":p:h"), "p")
196    end
197  end,
198})
199
200-- ## COOL HACKS ------------------------------------------------------------
201-- 6. Effect: URL underline.
202vim.api.nvim_set_hl(0, 'HighlightURL', { underline = true })
203autocmd({ "VimEnter", "FileType", "BufEnter", "WinEnter" }, {
204  desc = "URL Highlighting",
205  callback = function() utils.set_url_effect() end,
206})
207
208-- 7. Customize right click contextual menu.
209autocmd("VimEnter", {
210  desc = "Disable right contextual menu warning message",
211  callback = function()
212    -- Disable right click message
213    vim.api.nvim_command [[aunmenu PopUp.How-to\ disable\ mouse]]
214    -- vim.api.nvim_command [[aunmenu PopUp.-1-]] -- You can remode a separator like this.
215    vim.api.nvim_command [[menu PopUp.Toggle\ \Breakpoint <cmd>:lua require('dap').toggle_breakpoint()<CR>]]
216    vim.api.nvim_command [[menu PopUp.-2- <Nop>]]
217    vim.api.nvim_command [[menu PopUp.Start\ \Compiler <cmd>:CompilerOpen<CR>]]
218    vim.api.nvim_command [[menu PopUp.Start\ \Debugger <cmd>:DapContinue<CR>]]
219    vim.api.nvim_command [[menu PopUp.Run\ \Test <cmd>:Neotest run<CR>]]
220  end,
221})
222
223-- 8. Unlist quickfix buffers if the filetype changes.
224autocmd("FileType", {
225  desc = "Unlist quickfist buffers",
226  pattern = "qf",
227  callback = function() vim.opt_local.buflisted = false end,
228})
229
230-- 9. Close all notifications on BufWritePre.
231autocmd("BufWritePre", {
232  desc = "Close all notifications on BufWritePre",
233  callback = function()
234    require("notify").dismiss({ pending = true, silent = true })
235  end,
236})
237
238-- ## COMMANDS --------------------------------------------------------------
239
240-- 10. Testing commands
241-- Aditional commands to the ones implemented in neotest.
242-------------------------------------------------------------------
243
244-- Customize this command to work as you like
245cmd("TestNodejs", function()
246  vim.cmd ":ProjectRoot"                 -- cd the project root (requires project.nvim)
247  vim.cmd ":TermExec cmd='npm run test'" -- convention to run tests on nodejs
248  -- You can generate code coverage by add this to your project's packages.json
249  -- "tests": "jest --coverage"
250end, { desc = "Run all unit tests for the current nodejs project" })
251
252-- Customize this command to work as you like
253cmd("TestNodejsE2e", function()
254  vim.cmd ":ProjectRoot"                -- cd the project root (requires project.nvim)
255  vim.cmd ":TermExec cmd='npm run e2e'" -- Conventional way to call e2e in nodejs (requires ToggleTerm)
256end, { desc = "Run e2e tests for the current nodejs project" })
257
258-- Extra commands
259----------------------------------------------
260
261-- Change working directory
262cmd("Cwd", function()
263  vim.cmd ":cd %:p:h"
264  vim.cmd ":pwd"
265end, { desc = "cd current file's directory" })
266
267-- Set working directory (alias)
268cmd("Swd", function()
269  vim.cmd ":cd %:p:h"
270  vim.cmd ":pwd"
271end, { desc = "cd current file's directory" })
272
273-- Write all buffers
274cmd("WriteAllBuffers", function()
275  vim.cmd "wa"
276end, { desc = "Write all changed buffers" })
277
278-- Close all notifications
279cmd("CloseNotifications", function()
280  require("notify").dismiss({ pending = true, silent = true })
281end, { desc = "Dismiss all notifications" })