Neovim 0.6.0 was recently released and has added a new feature called
virtual lines. This allows to attach a set of "out-of-buffer" lines to
some locations via extmark mechanism.
This patch changes how output is shown - instead of making a separate
split buffer it shows computed value as a set of virtual lines attached
to the "end" of the computed region.
---
This adds some comments describing why something being done.
lua/bqn.lua | 70 ++++++++++++++++++++++++-----------------------------
1 file changed, 32 insertions(+), 38 deletions(-)
diff --git a/lua/bqn.lua b/lua/bqn.lua
index de733b3..491477f 100644
--- a/lua/bqn.lua+++ b/lua/bqn.lua
@@ -1,36 +1,18 @@
-local buf = nil-local win = nil+local ns = vim.api.nvim_create_namespace('bqn-out')-local function check_buf()- if win == nil or not vim.api.nvim_win_is_valid(win) then- local prev = vim.api.nvim_get_current_win()- if buf == nil then- buf = vim.api.nvim_create_buf(false, false)- end- vim.api.nvim_buf_set_name(buf, "BQN")- vim.api.nvim_buf_set_option(buf, "buftype", "nofile")- vim.api.nvim_buf_set_option(buf, "swapfile", false)- vim.api.nvim_buf_set_option(buf, "modeline", false)- vim.cmd("below 3split")- vim.api.nvim_win_set_buf(vim.api.nvim_get_current_win(), buf)- win = vim.api.nvim_get_current_win()- vim.api.nvim_win_set_option(win, "wrap", false)- vim.api.nvim_set_current_win(prev)- elseif vim.api.nvim_win_is_valid(win) then- local winid = vim.api.nvim_eval("bufwinid(" .. buf .. ")")- if winid == -1 then- local prev = vim.api.nvim_get_current_win()- vim.cmd("below 3split")- vim.api.nvim_win_set_buf(vim.api.nvim_get_current_win(), buf)- win = vim.api.nvim_get_current_win()- vim.api.nvim_set_current_win(prev)- end+function evalBQN(from, to, pretty)+ -- Compute `to` position by looking back till we find first non-empty line.+ while to > 0 do+ local line = vim.api.nvim_buf_get_lines(0, to - 1, to, true)[1]+ if #line ~= 0 then break end+ to = to - 1 end
-end-function evalBQN(from, to, pretty)- local code = vim.api.nvim_buf_get_lines(0, from - 1, to, true)+ if from > to then+ from = to+ end+ local code = vim.api.nvim_buf_get_lines(0, 0, to, true) local program = ""
for k, v in ipairs(code) do
program = program .. v .. "\n"
@@ -49,23 +31,35 @@ function evalBQN(from, to, pretty)
if not found then
bqn = "BQN"
end
-- local executable = assert(io.popen(bqn .. " -" .. flag .. " \"" .. program .. "\""))+ local cmd = bqn .. " -" .. flag .. " \"" .. program .. "\""+ local executable = assert(io.popen(cmd)) local output = executable:read('*all')
- executable:close()-- check_buf() local lines = {}
local line_count = 0
for line in output:gmatch("[^\n]+") do
- table.insert(lines, line)+ table.insert(lines, {{' ' .. line, 'Comment'}}) line_count = line_count + 1
end
+ table.insert(lines, {{' ', 'Comment'}})++ -- Compute `cto` (clear to) position by looking forward from `to` till we+ -- find first non-empty line. We do this so we clear all "orphaned" virtual+ -- line blocks (which correspond to already deleted lines).+ local total_lines = vim.api.nvim_buf_line_count(0)+ local cto = to + 1+ while cto <= total_lines do+ local line = vim.api.nvim_buf_get_lines(0, cto-1, cto, true)[1]+ if #line ~= 0 then break end+ cto = cto + 1+ end- vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines)- vim.api.nvim_win_set_height(win, line_count)- vim.api.nvim_win_set_cursor(win, {vim.api.nvim_buf_line_count(buf), 0})+ vim.api.nvim_buf_clear_namespace(0, ns, from - 1, cto - 1)+ vim.api.nvim_buf_set_extmark(0, ns, to - 1, 0, {+ end_line = to - 1,+ end_col = 3,+ virt_lines=lines+ })end
return {
--
2.30.2
Ah, forgot about another thing — this patch changes how it determines
what to eval — it always sends code from the start of the buffer to
the current `to`. I actually think this is a good thing when you just
press Enter (you really want all previously defined bindings to be
available) but for visual mode — maybe it should send exactly what was
selected...
--
Andrey Popp / 8mayday@gmail.com
On Fri, Dec 03, 2021 at 03:06:24AM +0300, Andrey Popp wrote:
> Neovim 0.6.0 was recently released and has added a new feature called> virtual lines. This allows to attach a set of "out-of-buffer" lines to> some locations via extmark mechanism.> > This patch changes how output is shown - instead of making a separate> split buffer it shows computed value as a set of virtual lines attached> to the "end" of the computed region.
Wow, this looks awesome! I had no idea this feature was a thing. Thanks
for the contribution.
I did run into some minor issues: evaluating an empty buffer results in
an error: bqn.lua:57: Line number outside range
An error is thrown (bqn.lua:58: end_col value outside range) also when
evaluating a single variable:
a←3
a
Also, the :EvalBQNFile seems to always end up in an error:
bqn.lua:53: attempt to get length of local 'line' (a nil value)
The error messages look like these are probably quite easy to fix.
Also, I think it would be good to have a key binding that clears up the
virtual lines below executed line. This could be handy when executing
some intermediate result that you don't care about and would like to get
rid of the output.
The code looks good to me, I have no prior experience of Lua so you
probably know more than I do.
> ---> This adds some comments describing why something being done.> > lua/bqn.lua | 70 ++++++++++++++++++++++++-----------------------------> 1 file changed, 32 insertions(+), 38 deletions(-)> > diff --git a/lua/bqn.lua b/lua/bqn.lua> index de733b3..491477f 100644> --- a/lua/bqn.lua> +++ b/lua/bqn.lua> @@ -1,36 +1,18 @@> -local buf = nil> -local win = nil> +local ns = vim.api.nvim_create_namespace('bqn-out')> > -local function check_buf()> - if win == nil or not vim.api.nvim_win_is_valid(win) then> - local prev = vim.api.nvim_get_current_win()> - if buf == nil then> - buf = vim.api.nvim_create_buf(false, false)> - end> - vim.api.nvim_buf_set_name(buf, "BQN")> - vim.api.nvim_buf_set_option(buf, "buftype", "nofile")> - vim.api.nvim_buf_set_option(buf, "swapfile", false)> - vim.api.nvim_buf_set_option(buf, "modeline", false)> - vim.cmd("below 3split")> - vim.api.nvim_win_set_buf(vim.api.nvim_get_current_win(), buf)> - win = vim.api.nvim_get_current_win()> - vim.api.nvim_win_set_option(win, "wrap", false)> - vim.api.nvim_set_current_win(prev)> - elseif vim.api.nvim_win_is_valid(win) then> - local winid = vim.api.nvim_eval("bufwinid(" .. buf .. ")")> - if winid == -1 then> - local prev = vim.api.nvim_get_current_win()> - vim.cmd("below 3split")> - vim.api.nvim_win_set_buf(vim.api.nvim_get_current_win(), buf)> - win = vim.api.nvim_get_current_win()> - vim.api.nvim_set_current_win(prev)> - end> +function evalBQN(from, to, pretty)> + -- Compute `to` position by looking back till we find first non-empty line.> + while to > 0 do> + local line = vim.api.nvim_buf_get_lines(0, to - 1, to, true)[1]> + if #line ~= 0 then break end> + to = to - 1> end> -end> > -function evalBQN(from, to, pretty)> - local code = vim.api.nvim_buf_get_lines(0, from - 1, to, true)> + if from > to then> + from = to> + end> > + local code = vim.api.nvim_buf_get_lines(0, 0, to, true)> local program = ""> for k, v in ipairs(code) do> program = program .. v .. "\n"> @@ -49,23 +31,35 @@ function evalBQN(from, to, pretty)> if not found then> bqn = "BQN"> end> -> - local executable = assert(io.popen(bqn .. " -" .. flag .. " \"" .. program .. "\""))> + local cmd = bqn .. " -" .. flag .. " \"" .. program .. "\""> + local executable = assert(io.popen(cmd))> local output = executable:read('*all')> - executable:close()> -> - check_buf()> > local lines = {}> local line_count = 0> for line in output:gmatch("[^\n]+") do> - table.insert(lines, line)> + table.insert(lines, {{' ' .. line, 'Comment'}})> line_count = line_count + 1> end> + table.insert(lines, {{' ', 'Comment'}})> +> + -- Compute `cto` (clear to) position by looking forward from `to` till we> + -- find first non-empty line. We do this so we clear all "orphaned" virtual> + -- line blocks (which correspond to already deleted lines).> + local total_lines = vim.api.nvim_buf_line_count(0)> + local cto = to + 1> + while cto <= total_lines do> + local line = vim.api.nvim_buf_get_lines(0, cto-1, cto, true)[1]> + if #line ~= 0 then break end> + cto = cto + 1> + end> > - vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines)> - vim.api.nvim_win_set_height(win, line_count)> - vim.api.nvim_win_set_cursor(win, {vim.api.nvim_buf_line_count(buf), 0})> + vim.api.nvim_buf_clear_namespace(0, ns, from - 1, cto - 1)> + vim.api.nvim_buf_set_extmark(0, ns, to - 1, 0, {> + end_line = to - 1,> + end_col = 3,> + virt_lines=lines> + })> end> > return {> -- > 2.30.2>
--
Antti
On Fri, Dec 03, 2021 at 03:31:58AM +0300, Andrey Popp wrote:
> Ah, forgot about another thing — this patch changes how it determines> what to eval — it always sends code from the start of the buffer to> the current `to`. I actually think this is a good thing when you just> press Enter (you really want all previously defined bindings to be> available) but for visual mode — maybe it should send exactly what was> selected...
I think the enter behavior in this implementation makes sense. It
greatly reduces the amount of visual selections needed.
About the visual selection, I think your suggestion about sending
exactly the selection is the most reasonable thing to do. It could be
handy for example when there's an expensive computation somewhere in the
previous lines of the file and you would want to avoid evaluating that
every time.
--
Antti