Skip to content

Commit e34f8f5

Browse files
committed
fix: use remote server for executing commands
1 parent bc7e094 commit e34f8f5

File tree

5 files changed

+331
-78
lines changed

5 files changed

+331
-78
lines changed

README.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
# live-command.nvim
2-
![version](https://img.shields.io/badge/version-1.2.1-brightgreen)
2+
![version](https://img.shields.io/badge/version-1.3.0-brightgreen)
33

44
Text editing in Neovim with immediate visual feedback: view the effects of any command on your buffer contents live. Preview macros, the `:norm` command & more!
55

66
![live-command.nvim demo video](https://user-images.githubusercontent.com/40792180/235201812-adc95327-65cc-4ae4-8c2e-804853dd0c02.gif)
77
<p><sub>Theme: <a href="https://github.com/folke/tokyonight.nvim">tokyonight.nvim</a></sub></p>
88

99
## :sparkles: Motivation and Features
10-
In version 0.8, Neovim has introduced the `command-preview` feature.
11-
Contrary to what "command preview" suggests, previewing any given
12-
command does not work out of the box: you need to manually update the buffer text and set
13-
highlights *for every command*.
10+
In Neovim version 0.8, the `command-preview` feature has been introduced.
11+
Despite its name, it does not enable automatic previewing of any command.
12+
Instead, users must manually update the buffer text and set highlights *for each command*.
1413

15-
This plugin tries to change that: it provides a **simple API for creating previewable commands**
16-
in Neovim. Just specify the command you want to run and live-command will do all the
17-
work for you. This includes viewing **individual insertions, changes and deletions** as you
18-
type.
14+
This plugin aims to address this issue by offering a **simple API for creating previewable commands**
15+
in Neovim. Simply provide the command you want to preview and live-command will do all the
16+
work for you. This includes viewing **individual insertions, changes and deletions** as you type.
17+
18+
After the most recent update, live-command now spawns a separate Neovim instance to execute commands.
19+
This avoids many issues encountered when running the command directly in the current Neovim instance
20+
([#6](https://github.com/smjonas/live-command.nvim/issues/6), [#16](https://github.com/smjonas/live-command.nvim/issues/16), [#24](https://github.com/smjonas/live-command.nvim/issues/24), [#28](https://github.com/smjonas/live-command.nvim/issues/28)).
1921

2022
## Requirements
2123
Neovim 0.8+
2224

2325
## :rocket: Getting started
2426
Install using your favorite package manager and call the setup function with a table of
25-
commands to create. Here is an example that creates a previewable `:Norm` command:
27+
commands to create. Here is an example for `lazy.nvim` that creates a previewable `:Norm` command:
2628
```lua
27-
use {
29+
{
2830
"smjonas/live-command.nvim",
2931
-- live-command supports semantic versioning via tags
3032
-- tag = "1.*",
@@ -84,7 +86,6 @@ require("live-command").setup {
8486
change = "DiffChange",
8587
},
8688
},
87-
debug = false,
8889
}
8990
```
9091

@@ -119,15 +120,6 @@ deletion edits will not be undone which is otherwise done to make the text chang
119120

120121
---
121122

122-
`debug: boolean`
123-
124-
Default: `false`
125-
126-
If `true`, more stuff (not only errors) will be logged. After previewing a command,
127-
you can view the log by running `:LiveCommandLog`.
128-
129-
---
130-
131123
Like this project? Give it a :star: to show your support!
132124

133125
Also consider checking out my other plugin [inc-rename.nvim](https://github.com/smjonas/inc-rename.nvim),

lua/live-command/init.lua

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@ M.defaults = {
1010
},
1111
}
1212

13+
local api = vim.api
14+
15+
---@type Logger
16+
local logger
17+
18+
---@class Remote
19+
local remote
20+
21+
---@type number?
22+
local chan_id
23+
24+
local cursor_row, cursor_col
1325
local should_cache_lines = true
1426
local cached_lines
1527
local prev_lazyredraw
1628

17-
local logs = {}
18-
local function log(msg, level)
19-
level = level or "TRACE"
20-
if M.debug or level ~= "TRACE" then
21-
msg = type(msg) == "function" and msg() or msg
22-
logs[level] = logs[level] or {}
23-
for _, line in ipairs(vim.split(msg .. "\n", "\n")) do
24-
table.insert(logs[level], line)
25-
end
26-
end
27-
end
28-
2929
-- Inserts str_2 into str_1 at the given position.
3030
local function string_insert(str_1, str_2, pos)
3131
return str_1:sub(1, pos - 1) .. str_2 .. str_1:sub(pos)
@@ -47,7 +47,7 @@ local function add_inline_highlights(line, cached_lns, updated_lines, undo_delet
4747
local line_a = splice(cached_lns[line])
4848
local line_b = splice(updated_lines[line])
4949
local line_diff = vim.diff(line_a, line_b, { result_type = "indices" })
50-
log(function()
50+
logger.trace(function()
5151
return ("Changed lines (line %d):\nOriginal: '%s' (len=%d)\nUpdated: '%s' (len=%d)\n\nInline hunks: %s"):format(
5252
line,
5353
cached_lns[line],
@@ -85,6 +85,7 @@ local function add_inline_highlights(line, cached_lns, updated_lines, undo_delet
8585
-- Observation: when changing "line" to "tes", there should not be an offset (-2)
8686
-- after changing "lin" to "t" (because we are not modifying the line)
8787
highlight.column = highlight.column + col_offset
88+
highlight.hunk = nil
8889
table.insert(highlights, highlight)
8990

9091
if defer then
@@ -104,10 +105,10 @@ local function get_diff_highlights(cached_lns, updated_lines, line_range, opts)
104105
local hunks = vim.diff(table.concat(cached_lns, "\n"), table.concat(updated_lines, "\n"), {
105106
result_type = "indices",
106107
})
107-
log(("Visible line range: %d-%d"):format(line_range[1], line_range[2]))
108+
logger.trace(("Visible line range: %d-%d"):format(line_range[1], line_range[2]))
108109

109110
for i, hunk in ipairs(hunks) do
110-
log(function()
111+
logger.trace(function()
111112
return ("Hunk %d/%d: %s"):format(i, #hunks, vim.inspect(hunk))
112113
end)
113114

@@ -123,7 +124,7 @@ local function get_diff_highlights(cached_lns, updated_lines, line_range, opts)
123124
end_line = start_line + (count_a - count_b) - 1
124125
end
125126

126-
log(function()
127+
logger.trace(function()
127128
return ("Lines %d-%d:\nOriginal: %s\nUpdated: %s"):format(
128129
start_line,
129130
end_line,
@@ -176,29 +177,35 @@ end
176177
-- Expose functions to tests
177178
M._preview_across_lines = get_diff_highlights
178179

179-
local function run_buf_cmd(buf, cmd)
180-
vim.api.nvim_buf_call(buf, function()
181-
log(function()
182-
return ("Previewing command: %s (current line = %d)"):format(cmd, vim.api.nvim_win_get_cursor(0)[1])
183-
end)
184-
vim.cmd(cmd)
180+
---@param cmd string
181+
local function run_cmd(cmd)
182+
if not chan_id then
183+
logger.trace("run_cmd: skipped as chan_id is not set")
184+
return
185+
end
186+
187+
local cursor_pos = api.nvim_win_get_cursor(0)
188+
cursor_row, cursor_col = cursor_pos[1], cursor_pos[2]
189+
190+
logger.trace(function()
191+
return ("Previewing command: %s (l=%d,c=%d)"):format(cmd, cursor_row, cursor_col)
185192
end)
193+
return remote.run_cmd(chan_id, cmd, cursor_row, cursor_col)
186194
end
187195

188196
-- Called when the user is still typing the command or the command arguments
189197
local function command_preview(opts, preview_ns, preview_buf)
190198
-- Any errors that occur in the preview function are not directly shown to the user but stored in vim.v.errmsg.
191199
-- Related: https://github.com/neovim/neovim/issues/18910.
192200
vim.v.errmsg = ""
193-
logs = {}
194201
local args = opts.cmd_args
195202
local command = opts.command
196203

197-
local bufnr = vim.api.nvim_get_current_buf()
204+
local bufnr = api.nvim_get_current_buf()
198205
if should_cache_lines then
199206
prev_lazyredraw = vim.o.lazyredraw
200207
vim.o.lazyredraw = true
201-
cached_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
208+
cached_lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
202209
should_cache_lines = false
203210
end
204211

@@ -207,10 +214,11 @@ local function command_preview(opts, preview_ns, preview_buf)
207214
local prev_errmsg = vim.v.errmsg
208215
local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") }
209216

217+
local updated_lines
210218
if opts.line1 == opts.line2 then
211-
run_buf_cmd(bufnr, ("%s %s"):format(command.cmd, args))
219+
updated_lines = run_cmd(("%s %s"):format(command.cmd, args))
212220
else
213-
run_buf_cmd(bufnr, ("%d,%d%s %s"):format(opts.line1, opts.line2, command.cmd, args))
221+
updated_lines = run_cmd(("%d,%d%s %s"):format(opts.line1, opts.line2, command.cmd, args))
214222
end
215223

216224
vim.v.errmsg = prev_errmsg
@@ -220,20 +228,19 @@ local function command_preview(opts, preview_ns, preview_buf)
220228
math.max(visible_line_range[2], vim.fn.line("w$")),
221229
}
222230

223-
local updated_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
224231
local set_lines = function(lines)
225232
-- TODO: is this worth optimizing?
226-
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
233+
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
227234
if preview_buf then
228-
vim.api.nvim_buf_set_lines(preview_buf, 0, -1, false, lines)
235+
api.nvim_buf_set_lines(preview_buf, 0, -1, false, lines)
229236
end
230237
end
231238

232239
if not opts.line1 or not command.enable_highlighting then
233240
set_lines(updated_lines)
234241
-- This should not happen
235242
if not opts.line1 then
236-
log("No line1 range provided", "ERROR")
243+
logger.error("No line1 range provided")
237244
end
238245
return 2
239246
end
@@ -247,15 +254,15 @@ local function command_preview(opts, preview_ns, preview_buf)
247254
undo_deletions = command.hl_groups["deletion"] ~= false,
248255
inline_highlighting = command.inline_highlighting,
249256
})
250-
log(function()
257+
logger.trace(function()
251258
return "Highlights: " .. vim.inspect(highlights)
252259
end)
253260

254261
set_lines(updated_lines)
255262
for _, hl in ipairs(highlights) do
256263
local hl_group = command.hl_groups[hl.kind]
257264
if hl_group ~= false then
258-
vim.api.nvim_buf_add_highlight(
265+
api.nvim_buf_add_highlight(
259266
bufnr,
260267
preview_ns,
261268
hl_group,
@@ -272,20 +279,20 @@ local function restore_buffer_state()
272279
vim.o.lazyredraw = prev_lazyredraw
273280
should_cache_lines = true
274281
if vim.v.errmsg ~= "" then
275-
log(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg)), "ERROR")
282+
logger.error(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg)))
276283
end
277284
end
278285

279286
local function execute_command(command)
280-
log("Executing command: " .. command)
287+
logger.trace("Executing command: " .. command)
281288
vim.cmd(command)
282289
restore_buffer_state()
283290
end
284291

285292
local create_user_commands = function(commands)
286293
for name, command in pairs(commands) do
287294
local args, range
288-
vim.api.nvim_create_user_command(name, function(opts)
295+
api.nvim_create_user_command(name, function(opts)
289296
local range_string = range and range
290297
or (
291298
opts.range == 2 and ("%s,%s"):format(opts.line1, opts.line2)
@@ -341,7 +348,47 @@ local validate_config = function(config)
341348
end
342349
end
343350

351+
local create_autocmds = function()
352+
local id = api.nvim_create_augroup("command_preview.nvim", { clear = true })
353+
354+
api.nvim_create_autocmd("CmdlineEnter", {
355+
group = id,
356+
callback = function()
357+
remote.sync(chan_id)
358+
end,
359+
})
360+
361+
-- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time
362+
api.nvim_create_autocmd("CmdLineLeave", {
363+
group = id,
364+
-- Schedule wrap to run after a potential command execution
365+
callback = vim.schedule_wrap(function()
366+
restore_buffer_state()
367+
end),
368+
})
369+
370+
api.nvim_create_autocmd("VimLeavePre", {
371+
group = id,
372+
callback = function()
373+
if chan_id then
374+
vim.fn.chanclose(chan_id)
375+
end
376+
end,
377+
})
378+
379+
-- Setting dirty = true on FocusGained is important with multiple Nvim instances
380+
api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "BufEnter", "FocusGained" }, {
381+
group = id,
382+
callback = remote.on_buffer_updated,
383+
})
384+
end
385+
344386
M.setup = function(user_config)
387+
-- Avoid an infinite loop when invoked from a child process
388+
if vim.env.LIVECOMMAND_NVIM_SERVER == "1" then
389+
return
390+
end
391+
345392
if vim.fn.has("nvim-0.8.0") ~= 1 then
346393
vim.notify(
347394
"[live-command] This plugin requires at least Neovim 0.8. Please upgrade your Neovim version.",
@@ -353,28 +400,21 @@ M.setup = function(user_config)
353400
local config = vim.tbl_deep_extend("force", M.defaults, user_config or {})
354401
validate_config(config)
355402
create_user_commands(config.commands)
403+
logger = require("live-command.logger")
404+
remote = require("live-command.remote")
356405

357-
local id = vim.api.nvim_create_augroup("command_preview.nvim", { clear = true })
358-
-- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time.
359-
vim.api.nvim_create_autocmd({ "CmdLineLeave" }, {
360-
group = id,
361-
-- Schedule wrap to run after a potential command execution
362-
callback = vim.schedule_wrap(function()
363-
restore_buffer_state()
364-
end),
365-
})
366-
367-
M.debug = user_config.debug
406+
remote.init_rpc(logger, function(id)
407+
chan_id = id
408+
logger.set_chan_id(chan_id)
409+
end)
410+
create_autocmds()
411+
end
368412

369-
vim.api.nvim_create_user_command("LiveCommandLog", function()
370-
local msg = ("live-command log\n================\n\n%s%s"):format(
371-
logs.ERROR and "[ERROR]\n" .. table.concat(logs.ERROR, "\n") .. (logs.TRACE and "\n" or "") or "",
372-
logs.TRACE and "[TRACE]\n" .. table.concat(logs.TRACE, "\n") or ""
373-
)
374-
vim.notify(msg)
375-
end, { nargs = 0 })
413+
---@param logger_ Logger
414+
M._set_logger = function(logger_)
415+
logger = logger_
376416
end
377417

378-
M.version = "1.2.1"
418+
M.version = "1.3.0"
379419

380420
return M

0 commit comments

Comments
 (0)