DEV Community

Cover image for Writing useful lua functions to my neovim
Sérgio Araújo
Sérgio Araújo

Posted on • Edited on

5

Writing useful lua functions to my neovim

Intro

I used to have a very large vimrc but now, I decided to write my own init.lua, after writing almost everything essential I started to port my functions, things like the Preserve function, so I can delete trailing spaces without moving my cursor.

local M = {}

M.preserve = function(arguments)
    local arguments = string.format("keepjumps keeppatterns execute %q", arguments)
    -- local original_cursor = vim.fn.winsaveview()
    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
    vim.api.nvim_command(arguments)
    local lastline = vim.fn.line("$")
    -- vim.fn.winrestview(original_cursor)
    if line > lastline then
        line = lastline
    end
    vim.api.nvim_win_set_cursor({ 0 }, { line, col })
end

return M

Enter fullscreen mode Exit fullscreen mode

After writing the above function you can do:

vim.cmd([[cnoreab cls Preserve]])
vim.cmd([[command! Preserve lua preserve('%s/\\s\\+$//ge')]])
vim.cmd([[command! Reindent lua preserve("sil keepj normal! gg=G")]])
vim.cmd([[command! BufOnly lua preserve("silent! %bd|e#|bd#")]])
Enter fullscreen mode Exit fullscreen mode

Change file header helper

What about updating your file header every time you save, I mean, type:
:update<cr>?


--> :lua changeheader()
-- This function is called with the BufWritePre event (autocmd)
-- and when I want to save a file I use ":update" which
-- only writes a buffer if it was modified
M.changeheader = function()
    -- We only can run this function if the file is modifiable
    if not vim.api.nvim_buf_get_option(vim.api.nvim_get_current_buf(), "modifiable") then
        return
    end
    if vim.fn.line("$") >= 7 then
        os.setlocale("en_US.UTF-8") -- show Sun instead of dom (portuguese)
        time = os.date("%a, %d %b %Y %H:%M")
        M.preserve("sil! keepp keepj 1,7s/\\vlast (modified|change):\\zs.*/ " .. time .. "/ei")
    end
end
Enter fullscreen mode Exit fullscreen mode

Next section you can see how the function above is triggered/associated with the BufWritePre event, changing automatically your file header where you can read: "Last Change:".

For you autocommands you can do:

-- autocommands
--- This function is taken from https://github.com/norcalli/nvim_utils
function nvim_create_augroups(definitions)
    for group_name, definition in pairs(definitions) do
        api.nvim_command('augroup '..group_name)
        api.nvim_command('autocmd!')
        for _, def in ipairs(definition) do
            local command = table.concat(vim.tbl_flatten{'autocmd', def}, ' ')
            api.nvim_command(command)
        end
        api.nvim_command('augroup END')
    end
end

local autocmds = {
    reload_vimrc = {
        -- Reload vim config automatically
        {"BufWritePost",[[$VIM_PATH/{*.vim,*.yaml,vimrc} nested source $MYVIMRC | redraw]]};
    };
    change_header = {
        {"BufWritePre", "*", "lua changeheader()"}
    };
    packer = {
        { "BufWritePost", "plugins.lua", "PackerCompile" };
    };
    terminal_job = {
        { "TermOpen", "*", [[tnoremap <buffer> <Esc> <c-\><c-n>]] };
        { "TermOpen", "*", "startinsert" };
        { "TermOpen", "*", "setlocal listchars= nonumber norelativenumber" };
    };
    restore_cursor = {
        { 'BufRead', '*', [[call setpos(".", getpos("'\""))]] };
    };
    save_shada = {
        {"VimLeave", "*", "wshada!"};
    };
    resize_windows_proportionally = {
        { "VimResized", "*", ":wincmd =" };
    };
    toggle_search_highlighting = {
        { "InsertEnter", "*", "setlocal nohlsearch" };
    };
    lua_highlight = {
        { "TextYankPost", "*", [[silent! lua vim.highlight.on_yank() {higroup="IncSearch", timeout=400}]] };
    };
    ansi_esc_log = {
        { "BufEnter", "*.log", ":AnsiEsc" };
    };
}

nvim_create_augroups(autocmds)
-- autocommands END
Enter fullscreen mode Exit fullscreen mode

Squeeze blank lines

M.squeeze_blank_lines = function()
-- references: https://vi.stackexchange.com/posts/26304/revisions
    if vim.bo.binary == false and vim.opt.filetype:get() ~= "diff" then
        local old_query = vim.fn.getreg("/") -- save search register
        M.preserve("sil! 1,.s/^\\n\\{2,}/\\r/gn") -- set current search count number
        local result = vim.fn.searchcount({ maxcount = 1000, timeout = 500 }).current
        local line, col = unpack(vim.api.nvim_win_get_cursor(0))
        M.preserve("sil! keepp keepj %s/^\\n\\{2,}/\\r/ge")
        M.preserve("sil! keepp keepj %s/\\v($\\n\\s*)+%$/\\r/e")
        if result > 0 then
            vim.api.nvim_win_set_cursor({ 0 }, { (line - result), col })
        end
        vim.fn.setreg("/", old_query) -- restore search register
    end
end
Enter fullscreen mode Exit fullscreen mode

map helper

-- map helper
local function map(mode, lhs, rhs, opts)
    local options = {noremap = true}
    if opts then options = vim.tbl_extend('force', options, opts) end
    vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end

map('n', '<leader>d', ':lua delblanklines()<cr>')
Enter fullscreen mode Exit fullscreen mode

Delete buffer or quit

The code below is in my utils.lua which is a lua module, so it has on its first line

local M = {}
Enter fullscreen mode Exit fullscreen mode

and at the end

return M
Enter fullscreen mode Exit fullscreen mode

It is mapped in my keymaps.lua to <leader>bd mnemonic to buffer delete and it runs some tests to determine if the file is modified, if it has a name and so on, in practice if you are in the last buffer it will close the neovim instance, in case of unsaved files it will ask you.

-- Esta função detecta se o buffer foi modificado
-- se o buffer tem nome roda um update
-- se não tem nome pergunta se quer salvar (perguntando também o nome)
-- o processo de salvamento do arquivo pode ser cancelado com Ctrl-c
-- ao final ele vai fechar o neovim se for o último buffer
-- caso contrário ele apenas fecha o buffer corrente

-- deleta o buffer se for o último buffer sai do nvim
M.bd_or_quit = function()
  if M.buf_modified() then
    local bufname = vim.api.nvim_buf_get_name(0) -- Obter o nome do buffer atual

    if bufname ~= "" then -- Se o buffer tiver nome
      vim.cmd("update!") -- Salvar as alterações no arquivo existente
    else -- Se o buffer não tiver nome
      -- Perguntar se deseja salvar
      local ok, response = pcall(vim.fn.confirm, "Deseja salvar o buffer?", "&Sim\n&Não", 2)
      if not ok or response ~= 1 then
           vim.cmd(M.get_listed_bufs() == 1 'qa!' or 'bd!') -- Fechar o buffer sem salvar
        return -- Cancelar se o usuário não quiser salvar
      end

      -- Loop para obter o nome do arquivo
      local filename
      repeat
        local ok
        ok, filename = pcall(vim.fn.input, "Digite o nome do arquivo (ou Ctrl+C para cancelar): ")
        if not ok then
          return -- Cancelar se o usuário pressionar Ctrl+C
        end
      until filename ~= ""

      -- Verificar se o arquivo já existe e perguntar se deseja sobrescrever
      local file_exists = vim.fn.filereadable(filename)
      if file_exists == 1 then -- Se o arquivo já existe
        local ok, response = pcall(vim.fn.confirm, "O arquivo já existe. Deseja sobrescrever?", "&Sim\n&Não", 2)
        if not ok or response ~= 1 then
          return -- Cancelar se o usuário não confirmar a sobrescrita
        end
      end

      -- Salvar o arquivo com o nome fornecido
      vim.cmd("w " .. filename)
    end
  end

  -- Verificar se é o último buffer listado
  if M.get_listed_bufs() == 1 then
    vim.cmd("qa!") -- Sair sem salvar se for o último buffer
  else
    vim.cmd("bd") -- Deletar o buffer
    vim.cmd("bn") -- Ir para o próximo buffer
  end
end

-- verifica se o buffer foi modificado
M.buf_modified = function(buf)
  local buf = buf or vim.api.nvim_get_current_buf()
  return vim.api.nvim_buf_get_option(buf, 'modified')
end

-- retorna a quantidade de buffers listáveis
M.get_listed_bufs = function()
  local listed_buffers = vim.tbl_filter(function(bufnr)
    return vim.api.nvim_buf_get_option(bufnr, "buflisted")
  end, vim.api.nvim_list_bufs())
  return #listed_buffers
end
Enter fullscreen mode Exit fullscreen mode

This article can change/grow a little bit

References:

The main source I have used was this post on neovim.discourse.group

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay