This plugin is currently in the testing phase. Breaking changes might occur
Jump through :terminal
's errors (or any other buffer).
bufix.nvim
is a navigation plugin similar to the quickfix list, but works on arbitrary buffers like :terminal
buffers.
Some differences compared to the quickfix list
- bufix buffers work on "live" buffers like terminals. This makes them useful when running servers or logging commands like
tail -f
. - It parses using
:h vim.lpeg
. This means that you do not have to mess with:h errorformat
. - The cursor gets highlighted when jumping through errors (configurable).
Limitations:
- Currently, multi-line errors are not supported
This plugin is inspired by emacs' M-x compile.
cooler.webm
This plugin requires nvim >= 0.10
With lazy
{
"msaher/bufix.nvim"
-- calling setup is optional :)
}
Run :Bufix register
to register the current buffer as a bufix buffer (try doing it on a terminal buffer that has some output like :term grep -rn <string>
). You'll be able to jump through them using :Bufix next
and :Bufix prev
. You can also press <CR>
on a path to
jump directly to it
Handy keymaps:
vim.keymap.set("n", "]e", "<cmd>Bufix next<CR>", { desc = "Go to next error"})
vim.keymap.set("n", "[e", "<cmd>Bufix prev<CR>", { desc = "Go to prev error"})
--- or if you prefer to use the API
vim.keymap.set("n", "]e", function() require("bufix.api").goto_next() end , { desc = "Go to next error"}))
vim.keymap.set("n", "[e", function() require("bufix.api").goto_prev() end , { desc = "Go to prev error"}))
To always mark :terminal
as bufix buffers
local group = vim.api.nvim_create_augroup("BufixTerm", { clear = true })
vim.api.nvim_create_autocmd("TermOpen", {
group = group,
callback = function(opts)
require("bufix.api").register_buf(opts.buf) -- make it work with goto_next() and friends
end,
})
:Bufix
: Prompts for a sub command
:Bufix next
: Go to the next error in the nav buffer
:Bufix prev
: Go to the previous error in the nav buffer
:Bufix next-file
: Go to the next file in the nav buffer
:Bufix prev-file
: Go to the previous file in the nav buffer
Configuration is done through calling require("bufix").setup()
. It justs sets
cfg
to the require("bufix").config
table. The default configuration is:
-- DONT COPY PASTE THIS ITS JUST THE DEFAULT CONFIG
-- Read the next section to learn more about the options
require("bufix").setup({
--- Buffer local keymaps in bufix buffers. Such as <CR> to jump or <C-q> to
--- send to qflist
---@type boolean
want_buffer_keymaps = true,
--- How long to highlight the cursor after jumping. Set it to 0 to disable
--- it.
---@type number in milliseconds
locus_highlight_duration = 500,
---Add any aditional parsing rules
---See `:h bufix-rules` to learn more
---@type table
rules = {}
})
If want_buffer_keymaps
is true, then these buffer local mappings are set for
nav buffers (like task buffers):
vim.keymap.set("n", "<CR>" , require("bufix.api").goto_error_under_cursor, { buffer = buf, desc = "Go to error under cursor"})
vim.keymap.set("n", "g<CR>", require("bufix.api").display_error_under_cursor, { buffer = buf, desc = "Dispaly error under cursor"})
vim.keymap.set("n", "gj" , require("bufix.api").move_to_next, { buffer = buf, desc = "Move cursor to next error"})
vim.keymap.set("n", "gk" , require("bufix.api").move_to_prev, { buffer = buf, desc = "Move cursor to prev error"})
vim.keymap.set("n", "]]" , require("bufix.api").move_to_next_file, { buffer = buf, desc = "Move cursor to next file"})
vim.keymap.set("n", "[[" , require("bufix.api").move_to_prev_file, { buffer = buf, desc = "Move cursor to prev file"})
vim.keymap.set("n", "<C-q>", function()
require("bufix.api").send_to_qflist()
vim.cmd.copen()
end,
{ buffer = buf, desc = "Send errors to qflist"})
Where buf
is the bufix buffer.
api.goto_prev()
: Go to the previous error
api.goto_next_file()
: Go to the next error
api.goto_prev_file()
: Go to the prevous error
api.move_to_next()
: Move cursor to next error line
api.move_to_prev()
: Move cursor to previous error line
api.goto_error_under_cursor()
: Go to the error under the cursor
api.display_error_under_cursor()
: Visit file containing the error,
but do focus on its window
api.set_buf({buf})
: Makes {buf} the current nav buffer.
functions that operate on the current bufix buffer:
api.goto_error_under_cursor()
api.display_error_under_cursor()
api.move_to_next()
api.move_to_prev()
api.move_to_next_file()
api.move_to_prev_file()
api.register_buf({buf})
: Register {buf}
as a bufix buffer
If there's no bufix buffer, then sets {buf}
as the nav buffer. Otherwise, it
only adds error highlighting and sets b:bufix_buf = true
. When the current bufix
buffer gets deleted, {buf}
becomes a potential candidate to be the next bufix
buffer.
For example, if there are two bufix buffers A
and B
, and A
is the current
one. Once buffer A
gets deleted, buffer B
becomes the current bufix buffer.
api.send_to_qflist({buf})
: Parse {buf},
and send it the quickfix list
as per the error rules. {buf}
doesn't have
to be a bufix buffer. If want to use the
built-in :h :errorformat
, then use
:h cbuffer
instead.
api.match({line})
: Parses {line}
and returns a Capture
if successful.
The following highlights are provided:
BufixFilename
: highlight group for filename
BufixLine
: highlight group for line number
BufixLineEnd
: highlight group for ending line number
BufixCol
: highlight group for column number
BufixColEnd
: highlight group for ending column number<
874C
/p>
BufixType
: highlight group for error type
BufixCurrent
: highlight group for the sign column
arrow pointing at current error
Errors are parsed according to "rules". There are two types of rules:
:h vim.lpeg
grammars (advanced). Powerful and expressive, but might be overkill for simple error messages:h errorformat
. Uses vim's built-in error formats used in:h quickfix
.
To add new rules or overwrite existing ones configure the rules
in .setup()
.
require("bufix").setup({
rules = {
my_rule = vim.lpeg(<your_rule>)
another_rule = "<or_your_errorformat>"
}
})
Example using :h errorformat
:
require("bufix").setup({
rules = {
love = [[Error: %*[^\ ] error: %f:%l: %m]]
}
})
Each lpeg
rule returns a single table (we call it the Capture
) with entries
in the following form:
---@class Capture
---@field filename Span // only required part
---@field line? Span
---@field line_end? Span
---@field col? Span
---@field col_end? Span
---@field type? Span
(type
can either be "E" | "W" | "I"
for errors, warning, and information.
If unset, "E"
is assumed. Currently types are unused).
Where Span
is
---@class Span
---@field start number
---@field finish number
---@field value string | number
(only filename
and type
are strings. The rest are numbers)
To be more concrete consider this example:
local lpeg = vim.lpeg
local P, R, S, = lpeg.P, lpeg.R, lpeg.S,
local Cg, Ct, Cc, Cp = lpeg.Cg, lpeg.Ct, lpeg.Cc, lpeg.Cp
-- helper
local function Cg_span(patt, name)
return Cg(
Ct(
Cp() * -- start position
Cg(patt, "value") *
Cp() -- end
) / function(t)
return { start = t[1], value = t.value, finish = t[2] }
end,
name
)
end
require("bufix").setup {
rules = {
-- disable the gnu rule for whatever reason
gnu = lpeg(false),
-- define a new rule for the iar compiler
-- It has errors of the form:
-- "foo.c",3 Error[32]: Error message
-- "foo.c",3 Warning[32]: Error message
iar = Ct(
'"' * Cg_span((1 - "")^1, "filename") * '"' * -- "foo.c"
"," * Cg_span(R("09")^1 / tonumber, "line") * S(" \t")^0 * -- ,3
(P("Warning") * Cg_span(Cc"W", "type"))^-1 -- Warning (optional)
)
}
}
Quick explanation:
Ct()
is to make the pattern return a table (instead of multiple values)*
is for concatenating patternsCg_span
is a helper function that- Calls
Cp()
to capture the starting and ending positions - Calls
Cg()
to "tag" a pattern with a name (such as "value") - Passes the pattern to a function to turn into a table with keys
start
,value
andfinish
.
- Calls
R("09")^1
captures one or more digitsS(" \t")^0
captures optional white space.
To learn more see :h vim.lpeg
and :h vim.re
.
This plugin wouldn't have been possible without inspiration from: