Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<br>

## ✨ 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

<br>

## 📦 Installation

Expand All @@ -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 = {'`', '<Leader>`'},
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 }
```

<br>

## ⚙️ Configuration

Expand Down Expand Up @@ -87,6 +96,7 @@ opts = {

</details>

<br>

## 🚀 Basic usage (with default mappings)

Expand All @@ -110,6 +120,7 @@ opts = {
saved whenever it is toggled or closed.
- You can resize the console with `<C-Up>` and `<C-Down>`.

<br>

## 📓 Notes on globals, locals and preserving execution context

Expand All @@ -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

<br>

## ⭐ Extra features

Expand Down Expand Up @@ -257,6 +269,8 @@ There are two functions available within the console:
]]
```

<br>

## Alternatives and comparison

There are a number of alternatives available, notably:
Expand All @@ -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.

<br>

## 🔥 All feedback and feature requests are very welcome! Enjoy!
2 changes: 1 addition & 1 deletion lua/lazy.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
return {
'yarospace/lua-console.nvim',
lazy = true,
keys = '`',
keys = {'`', '<Leader>`'},
opts = {},
}
10 changes: 5 additions & 5 deletions lua/lua-console.lua
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion lua/lua-console/mappings.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
local M = {}

local console = require('lua-console')
local config = require('lua-console.config')
local utils = require('lua-console.utils')

Expand All @@ -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, {
Expand Down
127 changes: 86 additions & 41 deletions lua/lua-console/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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' }

Expand Down Expand Up @@ -123,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*=')
Expand All @@ -138,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(...)
Expand All @@ -187,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

lines[1] = prefix .. lines[1]
table.insert(lines, 1, '') -- insert an empty line

-- return result
vim.api.nvim_buf_set_lines(buf, lnum, lnum, false, lines)
end

local function remove_empty_lines(tbl)
Expand Down Expand Up @@ -227,11 +256,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
Expand All @@ -245,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
Expand All @@ -271,10 +307,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()

if not select(2, load(to_string(lines_with_return), '', 't', env)) then lines = lines_with_return end
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_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
Expand All @@ -289,9 +332,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))
Expand Down Expand Up @@ -476,6 +519,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
Expand Down
Loading
Loading