notable features in my neovim config
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:
- live-preview.nvim: preview websites in the browser
- typst-preview.nvim: live rendering of typst files, also in the browser
- everforest-nvim: my preferred colorscheme
- mini.pick: fuzzy picker for finding files and live grep
- nvim-treesitter: better syntax highlighting
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
