notable features in my neovim config

Posted on 00Z01

I have been using Neovim since my junior year of high school, and have gone through the phases of emulating an IDE with lazy.nvim, using dozens of plugins, to my current setup, where I rely on very few plugins and use a lot of features I built in myself.

Let us take a look at said features, the ones implemented directly in my config rather than through a plugin.

buffer tabline

This was inspired (and almost directly copied) from Adam Thiede’s blog, where he completely removed all of the neovim plugins he had been using.

-- nvim/lua/stuff.lua
function M.BufferTabline()
    vim.api.nvim_set_hl(0, "TablineBuffer", { link = "Normal" })
    local s = ''
    for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        if vim.api.nvim_buf_is_loaded(buf) and vim.fn.buflisted(buf) == 1 then
            local name = vim.fn.fnamemodify(vim.fn.bufname(buf), ":t")
            if buf == vim.api.nvim_get_current_buf() then
                s = s .. '%#TablineBuffer#[' .. name .. '] '
            else
                s = s .. '%#TablineBuffer# ' .. name .. '  '
            end
        end
    end
    return s
end
-- nvim/init.lua
o.showtabline = 2
o.tabline = [[%!v:lua.require('stuff').BufferTabline()]]

This function replaces the usual tabline with a buffer list of sorts.

It gets all buffers with nvim_list_bufs(), filters to only loaded and listed buffers, extracts each buffer’s filename (with no path), and adds each name to the tabline string. It then highlights the current buffer by wrapping it in [ ].

The two lines in init.lua set the tabline to use that function.

go to last location in buffer

This one is a small QoL feature that makes my neovim experience just a bit smoother.

-- nvim/init.lua
vim.api.nvim_create_autocmd("BufReadPost", {
    callback = function(args)
        local mark = vim.api.nvim_buf_get_mark(args.buf, '"')
        local line_count = vim.api.nvim_buf_line_count(args.buf)
        if mark[1] > 0 and mark[1] <= line_count then
            vim.api.nvim_win_set_cursor(0, mark)
            vim.schedule(function()
                vim.cmd("normal! zz")
            end)
        end
    end,
})

The autocommand triggers on BufReadPost, which happens when a file is opened.

It retrieves the last cursor position using vim.api.nvim_buf_get_mark(args.buf, '"'), which gets the saved mark for the buffer.

If the position is valid, the cursor is moved back to that location. Then, the screen is centered on the cursor using vim.cmd("normal! zz").

custom statusline

The default statusline had a bit too little information for me, and I did not want to use a plugin for that, so I took my favorite info sections from mini.statusline and built it myself.

function M.MyStatusLine()
    local mode_map = {
        n = 'Normal',
        i = 'Insert',
        R = 'Replace',
        v = 'Visual',
        V = 'V-Line',
        [''] = 'V-Block',
        c = 'Command',
        s = 'Select',
        S = 'S-Line',
        [''] = 'S-Block',
        t = 'Terminal',
    }
    local mode = vim.fn.mode()
    local mode_name = mode_map[mode] or mode:upper()

    local filepath = vim.fn.expand('%:~:.')
    if filepath == '' then filepath = '[No Name]' end

    local ft = vim.bo.filetype
    if ft == '' then ft = 'none' end

    local enc = vim.bo.fenc ~= '' and vim.bo.fenc or vim.o.enc
    local fmt = vim.bo.fileformat == 'dos' and 'CRLF' or 'LF'

    local function size()
        local b = vim.fn.getfsize(vim.fn.expand('%:p'))
        if b <= 0 then return '0B' end
        local s = { 'B', 'K', 'M', 'G' }
        local i = 1
        while b >= 1024 and i < 4 do
            b = b / 1024; i = i + 1
        end
        return ('%.1f%s'):format(b, s[i])
    end

    local l, c, tot = vim.fn.line('.'), vim.fn.virtcol('.'), vim.fn.line('$')
    local pct       = tot > 0 and math.floor(l * 100 / tot) or 0

    local left      = (' %s | %s '):format(mode_name, filepath)
    local right     = table.concat({ ft, enc .. '[' .. fmt .. ']', size(), pct .. '%% ' .. tot, l .. ':' .. c }, ' │ ')

    return left .. '%=' .. right .. ' '
end
--nvim/init.lua
o.statusline = [[%!v:lua.require('stuff').MyStatusLine()]]

My statusline shows the current mode, file path, filetype, encoding, file size, and cursor position with progress percentage. The left side displays the mode and file name, while the right side is right-aligned using %= and contains technical details and position info.

yank highlight

I have seen many a config with this feature. It makes it slightly easier to know exactly what I yanked (yes, I sometimes mess up my vim motions ._.).

-- nvim/init.lua
vim.api.nvim_create_autocmd("TextYankPost", {
    group = vim.api.nvim_create_augroup("HighlightYank", {}),
    pattern = "*",
    callback = function()
        vim.highlight.on_yank({ higroup = "IncSearch", timeout = 50 })
    end,
})

This autocommand runs after text is yanked (TextYankPost). It briefly highlights the yanked text using the IncSearch highlight group, giving visual feedback that the yank succeeded. The highlight lasts for 50 milliseconds before disappearing.

markdown2pdf

I tend to use Markdown a lot (what you are reading right now was written in markdown!), and I wanted an easy way to convert the current markdown file into a pdf, using pandoc.

--nvim/stuff.lua
function M.markdown2pdf(input)
    if not input or input == "" then
        print("Usage: markdown2pdf(<file.md>)")
        return
    end

    local f = io.open(input, "r")
    if not f then
        print("File not found: " .. input)
        return
    end
    f:close()

    local filename = input:match("([^/]+)%.md$")
    if not filename then
        print("Invalid input file (must end in .md)")
        return
    end

    local home = os.getenv("HOME") or "~"
    local output = home .. "/Downloads/" .. filename .. ".pdf"

    local cmd = string.format('pandoc "%s" -o "%s" -V geometry:margin=1in', input, output)
    local ok = os.execute(cmd)

    if ok == 0 then
        print("PDF saved to " .. output)
    else
        print("Conversion failed")
    end
end
--nvim/after/ftplugin/markdown.lua
local stuff = require('stuff')
vim.keymap.set('n', '<leader>mp', function()
    local input = vim.api.nvim_buf_get_name(0)
    stuff.markdown2pdf(input)
end)

plugins

These are the only plugins I use:

final thoughts

These custom-built features and plugins (or lack thereof) make my neovim experience extremely satisfying. Everything is fast and built exactly how I want it. I am still experimenting, and who knows? I might go back to using more plugins, or I might cut them out entirely.

I hope you got some inspiration for your config. Happy neovim hacking!

:wq