From d903ccfbb65ca5d8b47eb1e5f0576e0d6eaebff9 Mon Sep 17 00:00:00 2001 From: Yaro Date: Wed, 25 Dec 2024 11:29:17 +0000 Subject: [PATCH 1/4] fix: syntax highlighting for attached bufs --- lua/lua-console/utils.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/lua-console/utils.lua b/lua/lua-console/utils.lua index 099d992..2aa603b 100644 --- a/lua/lua-console/utils.lua +++ b/lua/lua-console/utils.lua @@ -476,6 +476,8 @@ local attach_toggle = function(buf) end mappings.set_evaluator_mappings(buf, toggle) + vim.api.nvim_set_option_value('syntax', 'on', { buf = buf }) + vim.notify( ('Evaluator %s for buffer [%s:%s]'):format(toggle and 'attached' or 'dettached', name, buf), vim.log.levels.INFO From fe60a916a5785f73efc03bf8f97db4dd095c8c5f Mon Sep 17 00:00:00 2001 From: Yaro Date: Fri, 24 Jan 2025 17:04:29 +0000 Subject: [PATCH 2/4] feat: improved output for multiline expressions/statements --- lua/lua-console/utils.lua | 23 ++++++++++++++++------- spec/unit/utils_spec.lua | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lua/lua-console/utils.lua b/lua/lua-console/utils.lua index 2aa603b..485fe82 100644 --- a/lua/lua-console/utils.lua +++ b/lua/lua-console/utils.lua @@ -5,6 +5,8 @@ local to_string = function(tbl, sep, trim) tbl = tbl or {} sep = sep or '\n' + if type(tbl) ~= 'table' then tbl = { tbl } end + local line = table.concat(tbl, sep) local patterns = { '\\r', '\\t', '\\n' } @@ -227,11 +229,11 @@ local function clean_stacktrace(error) return lines end -local function add_return(tbl) - if vim.fn.trim(tbl[#tbl]) == 'end' then return tbl end +local function add_return(tbl, lnum) + if to_string(tbl[lnum], '', true):match('^%s*end') then return tbl end local ret = vim.deepcopy(tbl) - ret[#ret] = 'return ' .. ret[#ret] + ret[lnum] = 'return ' .. ret[lnum] return ret end @@ -271,10 +273,17 @@ end function lua_evaluator(lines, ctx) vim.validate { lines = { lines, 'table' } } - local lines_with_return = add_return(lines) local env = ctx or get_ctx() + env._reset_last_assignment() + + local lines_with_return_first_line = add_return(lines, 1) + local lines_with_return_last_line = add_return(lines, #lines) - if not select(2, load(to_string(lines_with_return), '', 't', env)) then lines = lines_with_return end + if not select(2, load(to_string(lines_with_return_first_line), '', 't', env)) then + lines = lines_with_return_first_line + elseif not select(2, load(to_string(lines_with_return_last_line), '', 't', env)) then + lines = lines_with_return_last_line + end local code, error = load(to_string(lines), 'Lua console: ', 't', env) if error then return to_table(error) end @@ -289,9 +298,9 @@ function lua_evaluator(lines, ctx) table.remove(result, 1) if #result > 0 then - pretty_print(unpack(result)) + print_to_buffer(unpack(result)) else - pretty_print(nil) + print_to_buffer(nil) end else vim.list_extend(print_buffer, clean_stacktrace(err)) diff --git a/spec/unit/utils_spec.lua b/spec/unit/utils_spec.lua index 5b7b579..8a66d60 100644 --- a/spec/unit/utils_spec.lua +++ b/spec/unit/utils_spec.lua @@ -403,6 +403,44 @@ describe('lua-console.utils', function() assert.has_string(result, expected) end) + it('multiline expressions', function() + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + h.send_keys('V2j') + + content = h.to_table([[ + vim.iter({ 1, 2, 3, 4, 5 }):map(function(e) + return e + end):totable() + ]]) + + h.set_buffer(buf, content) + utils.eval_code_in_buffer() + + expected = config.buffer.result_prefix .. '{ 1, 2, 3, 4, 5 }' + + result = h.get_buffer(buf) + assert.has_string(result, expected) + end) + + it('multiline statements', function() + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + h.send_keys('V2j') + + content = h.to_table([[ + ret = vim.iter({ 1, 2, 3, 4, 5 }):map(function(e) + return e + end):totable() + ]]) + + h.set_buffer(buf, content) + utils.eval_code_in_buffer() + + expected = config.buffer.result_prefix .. '{ 1, 2, 3, 4, 5 }' + + result = h.get_virtual_text(buf, 0, 0) + assert.has_string(result, expected) + end) + it('shows nil as virtual text', function() vim.api.nvim_win_set_cursor(win, { 1, 0 }) h.send_keys('V2j') From 022d0b836c706c416d4cdb2e9d397c1fd921d0b6 Mon Sep 17 00:00:00 2001 From: Yaro Date: Fri, 24 Jan 2025 17:05:38 +0000 Subject: [PATCH 3/4] feat: improved virtual text for assignments --- lua/lua-console/utils.lua | 102 +++++++++++++++++++++++++------------- spec/unit/utils_spec.lua | 20 ++++++++ 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/lua/lua-console/utils.lua b/lua/lua-console/utils.lua index 485fe82..97f730d 100644 --- a/lua/lua-console/utils.lua +++ b/lua/lua-console/utils.lua @@ -125,9 +125,9 @@ local get_path_lnum = function(path) return path, lnum end ----Determines if there is an assigment on the line and returns its value ----@param line string[] -local get_assignment = function(line) +----Determines if there is an assigment on the line and returns its value +----@param line string[] +local get_line_assignment = function(line) if not line or #line == 0 then return end local lhs = line[1]:match('^(.-)%s*=') @@ -140,39 +140,29 @@ local get_assignment = function(line) end end -local print_buffer = {} - ----@param buf number ----@param lines string[] Text to append to current buffer after current selection -local append_current_buffer = function(buf, lines) - if not lines or #lines == 0 then return end - +local get_last_assignment = function() + local ctx = get_ctx() local lnum = vim.fn.line('.') - local prefix = config.buffer.result_prefix - local empty_results = { 'nil', '', '""', "''" } - - local virtual_text - local line = lines[#lines] - if vim.tbl_contains(empty_results, line) then - table.remove(lines) - virtual_text = line - end + local last_var = ctx._last_assignment + local last_val = ctx[ctx._last_assignment] - local assignment_value = get_assignment(vim.fn.getbufline(buf, lnum, lnum)) - if assignment_value ~= nil then virtual_text = assignment_value end + if not last_var then return end - if virtual_text then show_virtual_text(buf, 3, prefix .. virtual_text, lnum - 1, 'eol', 'Comment') end + local line + local offset = 0 - if #lines == 0 then return end + for i = lnum - 1, 0, -1 do + line = vim.api.nvim_buf_get_lines(0, i, i + 1, false)[1] - lines[1] = prefix .. lines[1] - table.insert(lines, 1, '') -- insert an empty line + if line:match('^%s*' .. last_var .. '%s*=') then break end + offset = offset + 1 + end - vim.api.nvim_buf_set_lines(buf, lnum, lnum, false, lines) + return last_var, last_val, offset end ----Pretty prints objects and appends to print_buffer +---Pretty prints objects ---@param ... any[] ---@return string[] local pretty_print = function(...) @@ -189,10 +179,47 @@ local pretty_print = function(...) result = result .. var_no .. vim.inspect(o) end - result = to_table(result) - vim.list_extend(print_buffer, result) + return to_table(result) +end + +local print_buffer = {} + +local print_to_buffer = function(...) + local ret = pretty_print(...) + vim.list_extend(print_buffer, ret) +end + +---@param buf number +---@param lines string[] Text to append to current buffer after current selection +local append_current_buffer = function(buf, lines) + if not lines or #lines == 0 then return end + + local lnum = vim.fn.line('.') + local prefix = config.buffer.result_prefix + local empty_results = { 'nil', '', '""', "''" } + + local virtual_text + local line = lines[#lines] + + local last_assigned_var, last_assigned_val, last_assignment_offset = get_last_assignment() + if last_assigned_var then + virtual_text = to_string(pretty_print(last_assigned_val), '', true) + show_virtual_text(buf, 3, prefix .. virtual_text, lnum - last_assignment_offset - 1, 'eol', 'Comment') + end + + if vim.tbl_contains(empty_results, line) then + table.remove(lines) + + virtual_text = get_line_assignment(vim.fn.getbufline(buf, lnum, lnum)) or line + show_virtual_text(buf, 4, prefix .. virtual_text, lnum - 1, 'eol', 'Comment') + end + + if #lines == 0 then return end - -- return result + lines[1] = prefix .. lines[1] + table.insert(lines, 1, '') -- insert an empty line + + vim.api.nvim_buf_set_lines(buf, lnum, lnum, false, lines) end local function remove_empty_lines(tbl) @@ -247,19 +274,26 @@ function get_ctx(buf) local ctx = lc.ctx[buf] if config.buffer.preserve_context and ctx then return ctx end - local env, mt = {}, {} + local env, mt, values = {}, {}, {} mt = { - print = pretty_print, + print = print_to_buffer, _ctx = function() - return vim.tbl_extend('force', {}, env) + return vim.deepcopy(values) end, _ctx_clear = function() lc.ctx[buf] = nil end, __index = function(_, key) - return mt[key] and mt[key] or _G[key] + return mt[key] and mt[key] or values[key] or _G[key] + end, + __newindex = function(_, k, v) + values[k] = v + mt._last_assignment = k end, + _reset_last_assignment = function() + mt._last_assignment = nil + end } lc.ctx[buf] = env diff --git a/spec/unit/utils_spec.lua b/spec/unit/utils_spec.lua index 8a66d60..6c12464 100644 --- a/spec/unit/utils_spec.lua +++ b/spec/unit/utils_spec.lua @@ -479,6 +479,26 @@ describe('lua-console.utils', function() assert.has_string(result, expected) end) + it('shows value of the last assignment on the corresponging line as virtual text', function() + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + h.send_keys('V4j') + + content = h.to_table([[ + a = 5 + for i = 1, 5 do + i = i + 5 + end + vim.bo.filetype = tostring(a + 5) .. 'test' + ]]) + h.set_buffer(buf, content) + utils.eval_code_in_buffer() + + expected = config.buffer.result_prefix .. '5' + + result = h.get_virtual_text(buf, 0, 0) + assert.has_string(result, expected) + end) + it('gives syntax error message', function() vim.api.nvim_win_set_cursor(win, { 1, 0 }) From 8d1ab9e3d4bc2f4419795cd05f8488f3271f10f1 Mon Sep 17 00:00:00 2001 From: Yaro Date: Fri, 24 Jan 2025 17:06:33 +0000 Subject: [PATCH 4/4] refact: minor refactor + updated docs --- README.md | 27 +++++++++++++++++++++------ lua/lazy.lua | 2 +- lua/lua-console.lua | 10 +++++----- lua/lua-console/mappings.lua | 2 +- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 151928a..d0d9636 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,21 @@ After installing Neovim, it took me some time to configure it, learn its setting I got fed up of constantly hitting `:`, typing `lua= command`, then typing `:messages` to see the output, only to find out that a made a typo or a syntax error and retyping the whole thing again, copying the paths from error stacktraces and so on. I needed something better, so there it is. +
+ ## ✨ Features - Evaluate single line expressions and statements, visually selected lines of code or the whole buffer -- Pretty print Lua objects, including function details and their source paths -- Show normal and error output in the console/buffer, including output of `print()`, errors and stacktraces. +- Pretty print Lua objects, including results of assignments, function details and their source paths +- Show normal and error output inline in the console/buffer, including output of `print()`, errors and stacktraces. - Syntax highlighting and autocompletion -- Load Neovim’s messages into console for inspection and copy/paste +- Load Neovim’s messages and output of ex commands into console for inspection and copy/paste - Open links from stacktraces and function sources - Save / Load / Autosave console session - Use as a scratch pad for code gists - Attach code evaluators to any buffer +
## 📦 Installation @@ -34,12 +37,18 @@ With [lazy.nvim](https://github.com/folke/lazy.nvim): ```lua return { "yarospace/lua-console.nvim", - lazy = true, keys = "`", opts = {}, + lazy = true, + keys = {'`', '`'}, + opts = {}, } ``` -otherwise, install with your favorite package manager and add -`require('lua-console').setup { custom_options }` somewhere in your config. +otherwise, install with your favorite package manager and add somewhere in your config: + +```lua +require('lua-console').setup { your_custom_options } +``` +
## ⚙️ Configuration @@ -87,6 +96,7 @@ opts = { +
## 🚀 Basic usage (with default mappings) @@ -110,6 +120,7 @@ opts = { saved whenever it is toggled or closed. - You can resize the console with `` and ``. +
## 📓 Notes on globals, locals and preserving execution context @@ -129,6 +140,7 @@ There are two functions available within the console: - `_ctx()` - will print the contents of the context - `_ctx_clear()` - clears the context +
## ⭐ Extra features @@ -257,6 +269,8 @@ There are two functions available within the console: ]] ``` +
+ ## Alternatives and comparison There are a number of alternatives available, notably: @@ -268,5 +282,6 @@ There are a number of alternatives available, notably: Initially, when starting with Lua and Neovim, I tried all the REPLs/code runners I could find. However, I was not satisfied with all of them in one way or another. Lua-console is an attempt to combine the best features of all of them, like REPL / scratch pad / code runner / debug console, while leaving the UX and config simple. +
## 🔥 All feedback and feature requests are very welcome! Enjoy! diff --git a/lua/lazy.lua b/lua/lazy.lua index 0e59a16..58c8884 100644 --- a/lua/lazy.lua +++ b/lua/lazy.lua @@ -1,6 +1,6 @@ return { 'yarospace/lua-console.nvim', lazy = true, - keys = '`', + keys = {'`', '`'}, opts = {}, } diff --git a/lua/lua-console.lua b/lua/lua-console.lua index 2d4c177..55dab37 100644 --- a/lua/lua-console.lua +++ b/lua/lua-console.lua @@ -1,4 +1,7 @@ -local config, mappings, utils, injections +local config = require('lua-console.config') +local mappings = require('lua-console.mappings') +local utils = require('lua-console.utils') +local injections = require('lua-console.injections') local get_or_create_buffer = function() --- @type number @@ -77,10 +80,7 @@ end local setup = function(opts) _G.Lua_console = { buf = false, win = false, height = 0, ctx = {} } - config = require('lua-console.config').setup(opts) - mappings = require('lua-console.mappings') - utils = require('lua-console.utils') - injections = require('lua-console.injections') + config.setup(opts) mappings.set_global_mappings() mappings.set_console_commands() diff --git a/lua/lua-console/mappings.lua b/lua/lua-console/mappings.lua index 3f04cf5..cfc948e 100644 --- a/lua/lua-console/mappings.lua +++ b/lua/lua-console/mappings.lua @@ -1,6 +1,5 @@ local M = {} -local console = require('lua-console') local config = require('lua-console.config') local utils = require('lua-console.utils') @@ -13,6 +12,7 @@ local function set_map(buf, map, opts, mode) end M.set_global_mappings = function() + local console = require('lua-console') local m = config.mappings set_map(nil, m.toggle, {