From d460067d47948725a6f25b20f31ea8bbfdfe4622 Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 5 Jun 2023 14:17:16 +0200 Subject: [PATCH 01/24] docs: update readme --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8c5405c..b046169 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,13 @@ Text editing in Neovim with immediate visual feedback: view the effects of any c

Theme: tokyonight.nvim

## :sparkles: Motivation and Features -In version 0.8, Neovim has introduced the `command-preview` feature. -Contrary to what "command preview" suggests, previewing any given -command does not work out of the box: you need to manually update the buffer text and set -highlights *for every command*. - -This plugin tries to change that: it provides a **simple API for creating previewable commands** -in Neovim. Just specify the command you want to run and live-command will do all the -work for you. This includes viewing **individual insertions, changes and deletions** as you -type. +In Neovim version 0.8, the `command-preview` feature has been introduced. +Despite its name, it does not enable automatic previewing of any command. +Instead, users must manually update the buffer text and set highlights *for each command*. + +This plugin aims to address this issue by offering a **simple API for creating previewable commands** +in Neovim. Simply provide the command you want to preview and live-command will do all the +work for you. This includes viewing **individual insertions, changes and deletions** as you type. ## Requirements Neovim 0.8+ From 7f23cfd89f622ec48a08bffbd7be41ce5f5c09e3 Mon Sep 17 00:00:00 2001 From: smjonas Date: Tue, 3 Oct 2023 15:35:11 +0200 Subject: [PATCH 02/24] Start rewrite with better architecture --- lua/live-command/cmd_executor.lua | 38 +++ lua/live-command/differ.lua | 6 + lua/live-command/highlighter.lua | 6 + lua/live-command/init.lua | 373 ++---------------------------- 4 files changed, 73 insertions(+), 350 deletions(-) create mode 100644 lua/live-command/cmd_executor.lua create mode 100644 lua/live-command/differ.lua create mode 100644 lua/live-command/highlighter.lua diff --git a/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua new file mode 100644 index 0000000..301e083 --- /dev/null +++ b/lua/live-command/cmd_executor.lua @@ -0,0 +1,38 @@ +local M = {} + +local differ = require("live-command.differ") +local highlighter = require("live-command.highlighter") + +local latest_cmd +local running = false + +local execute_command = function(cmd, bufnr) + local old_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + vim.cmd(cmd) + -- Emulate slow-running command + for i = 1, 1000000000 do + old_buf_lines = old_buf_lines + end + local new_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + return old_buf_lines, new_buf_lines +end + +---@param cmd string +---@param bufnr number +M.submit_command = function(cmd, bufnr, on_receive_highlights) + if cmd == latest_cmd then + return + end + latest_cmd = cmd + if not running then + running = true + local old_buf_lines, new_buf_lines = execute_command(cmd, bufnr) + local diff = differ.get_diff(old_buf_lines, new_buf_lines) + local highlights = highlighter.get_highlights(diff) + highlights = cmd + on_receive_highlights(highlights, bufnr) + running = false + end +end + +return M diff --git a/lua/live-command/differ.lua b/lua/live-command/differ.lua new file mode 100644 index 0000000..c568013 --- /dev/null +++ b/lua/live-command/differ.lua @@ -0,0 +1,6 @@ +local M = {} + +M.get_diff = function(old_lines, new_lines) +end + +return M diff --git a/lua/live-command/highlighter.lua b/lua/live-command/highlighter.lua new file mode 100644 index 0000000..8641bdc --- /dev/null +++ b/lua/live-command/highlighter.lua @@ -0,0 +1,6 @@ +local M = {} + +M.get_highlights = function(diff) +end + +return M diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index f9f983d..652a0b1 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -1,369 +1,42 @@ local M = {} -M.defaults = { - enable_highlighting = true, - inline_highlighting = true, - hl_groups = { - insertion = "DiffAdd", - deletion = "DiffDelete", - change = "DiffChange", - }, -} - +local cmd_executor = require("live-command.cmd_executor") local api = vim.api ----@type Logger -local logger - -local should_cache_lines = true -local cached_lines -local prev_lazyredraw - --- Inserts str_2 into str_1 at the given position. -local function string_insert(str_1, str_2, pos) - return str_1:sub(1, pos - 1) .. str_2 .. str_1:sub(pos) -end - --- Inserts a newline character after each character of s and returns the table of characters. -local function splice(s) - local chars = {} - for i = 1, #s do - chars[2 * i - 1] = s:sub(i, i) - chars[2 * i] = "\n" - end - return table.concat(chars) -end - -local unpack = table.unpack or unpack - -local function add_inline_highlights(line, cached_lns, updated_lines, undo_deletions, highlights) - local line_a = splice(cached_lns[line]) - local line_b = splice(updated_lines[line]) - local line_diff = vim.diff(line_a, line_b, { result_type = "indices" }) - logger.trace(function() - return ("Changed lines (line %d):\nOriginal: '%s' (len=%d)\nUpdated: '%s' (len=%d)\n\nInline hunks: %s"):format( - line, - cached_lns[line], - #cached_lns[line], - updated_lines[line], - #updated_lines[line], - vim.inspect(line_diff) - ) - end) +local received_highlights - local defer - local col_offset = 0 - for _, line_hunk in ipairs(line_diff) do - local start_a, count_a, start_b, count_b = unpack(line_hunk) - local hunk_kind = (count_a == 0 and "insertion") or (count_b == 0 and "deletion") or "change" - - if hunk_kind ~= "deletion" or undo_deletions then - local highlight = { - hunk = line_hunk, - kind = hunk_kind, - line = line, - -- Add 1 because when count is zero, start_b / start_b is the position before the deletion - column = (hunk_kind == "deletion") and start_b + 1 or start_b, - length = (hunk_kind == "deletion") and count_a or count_b, - } - - if highlight.kind == "deletion" and undo_deletions then - local deleted_part = cached_lns[line]:sub(start_a, start_a + count_a - 1) - -- Restore deleted characters - updated_lines[line] = string_insert(updated_lines[line], deleted_part, col_offset + start_b + 1) - defer = function() - col_offset = col_offset + #deleted_part - end - end - -- Observation: when changing "line" to "tes", there should not be an offset (-2) - -- after changing "lin" to "t" (because we are not modifying the line) - highlight.column = highlight.column + col_offset - highlight.hunk = nil - table.insert(highlights, highlight) - - if defer then - defer() - defer = nil +local create_preview_command = function() + api.nvim_create_user_command("Preview", function() end, { + nargs = "*", + preview = function(opts, preview_ns, preview_buf) + local cmd = opts.args + vim.v.errmsg = cmd + if received_highlights then + api.nvim_set_current_line(received_highlights) end + cmd_executor.submit_command(cmd, preview_buf or 0, M.receive_highlights) + return 2 end - end -end - --- Expose function to tests -M._add_inline_highlights = add_inline_highlights - -local function get_diff_highlights(cached_lns, updated_lines, line_range, opts) - local highlights = {} - -- Using the on_hunk callback and returning -1 to cancel causes an error so don't use that - local hunks = vim.diff(table.concat(cached_lns, "\n"), table.concat(updated_lines, "\n"), { - result_type = "indices", }) - logger.trace(("Visible line range: %d-%d"):format(line_range[1], line_range[2])) - - for i, hunk in ipairs(hunks) do - logger.trace(function() - return ("Hunk %d/%d: %s"):format(i, #hunks, vim.inspect(hunk)) - end) - - local start_a, count_a, start_b, count_b = hunk[1], hunk[2], hunk[3], hunk[4] - local hunk_kind = (count_a < count_b and "insertion") or (count_a > count_b and "deletion") - if hunk_kind then - local start_line, end_line - if hunk_kind == "insertion" then - start_line = start_b + count_a - end_line = start_a + (count_b - count_a) - else - start_line = start_a + count_b - end_line = start_line + (count_a - count_b) - 1 - end - - logger.trace(function() - return ("Lines %d-%d:\nOriginal: %s\nUpdated: %s"):format( - start_line, - end_line, - vim.inspect(vim.list_slice(cached_lns, start_line, end_line)), - vim.inspect(vim.list_slice(updated_lines, start_line, end_line)) - ) - end) - - for line = start_line, end_line do - -- Outside of visible area, skip current or all hunks - if line > line_range[2] then - return highlights - end - - if line >= line_range[1] then - if hunk_kind == "deletion" and opts.undo_deletions then - -- Hunk was deleted: reinsert lines - table.insert(updated_lines, line, cached_lns[line]) - end - if updated_lines[line] == "" then - -- Make empty lines visible - updated_lines[line] = " " - end - table.insert(highlights, { kind = hunk_kind, line = line, column = 1, length = -1 }) - end - end - else - -- Change edit - for line = start_b, start_b + count_b - 1 do - -- Outside of visible area, skip current or all hunks - if line > line_range[2] then - return highlights - end - - if line >= line_range[1] then - if opts.inline_highlighting then - -- Get diff for each line in the hunk - add_inline_highlights(line, cached_lns, updated_lines, opts.undo_deletions, highlights) - else - -- Use a single highlight for the whole line - table.insert(highlights, { kind = "change", line = line, column = 1, length = -1 }) - end - end - end - end - end - return highlights -end - --- Expose functions to tests -M._preview_across_lines = get_diff_highlights - -local function run_buf_cmd(buf, cmd) - api.nvim_buf_call(buf, function() - logger.trace(function() - return ("Previewing command: %s (current line = %d)"):format(cmd, api.nvim_win_get_cursor(0)[1]) - end) - vim.cmd(cmd) - end) end --- Called when the user is still typing the command or the command arguments -local function command_preview(opts, preview_ns, preview_buf) - -- Any errors that occur in the preview function are not directly shown to the user but stored in vim.v.errmsg. - -- Related: https://github.com/neovim/neovim/issues/18910. - vim.v.errmsg = "" - local args = opts.cmd_args - local command = opts.command - - local bufnr = api.nvim_get_current_buf() - if should_cache_lines then - prev_lazyredraw = vim.o.lazyredraw - vim.o.lazyredraw = true - cached_lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - should_cache_lines = false +local refresh_cmd_preview = function() + local backspace = api.nvim_replace_termcodes("", true, false, true) + -- Hack to trigger command preview again after new buffer contents have been computed + if api.nvim_get_mode().mode == "c" then + api.nvim_feedkeys("a" .. backspace, "n", false) end - - -- Ignore any errors that occur while running the command. - -- This reduces noise when a plugin modifies vim.v.errmsg (whether accidentally or not). - local prev_errmsg = vim.v.errmsg - local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") } - - if opts.line1 == opts.line2 then - run_buf_cmd(bufnr, ("%s %s"):format(command.cmd, args)) - else - run_buf_cmd(bufnr, ("%d,%d%s %s"):format(opts.line1, opts.line2, command.cmd, args)) - end - - vim.v.errmsg = prev_errmsg - -- Adjust range to account for potentially inserted lines / scroll - visible_line_range = { - math.max(visible_line_range[1], vim.fn.line("w0")), - math.max(visible_line_range[2], vim.fn.line("w$")), - } - - local updated_lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - local set_lines = function(lines) - -- TODO: is this worth optimizing? - api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - if preview_buf then - api.nvim_buf_set_lines(preview_buf, 0, -1, false, lines) - end - end - - if not opts.line1 or not command.enable_highlighting then - set_lines(updated_lines) - -- This should not happen - if not opts.line1 then - logger.error("No line1 range provided") - end - return 2 - end - - -- An empty buffer is represented as { "" }, change it to {} - if not updated_lines[2] and updated_lines[1] == "" then - updated_lines = {} - end - - local highlights = get_diff_highlights(cached_lines, updated_lines, visible_line_range, { - undo_deletions = command.hl_groups["deletion"] ~= false, - inline_highlighting = command.inline_highlighting, - }) - logger.trace(function() - return "Highlights: " .. vim.inspect(highlights) - end) - - set_lines(updated_lines) - for _, hl in ipairs(highlights) do - local hl_group = command.hl_groups[hl.kind] - if hl_group ~= false then - api.nvim_buf_add_highlight( - bufnr, - preview_ns, - hl_group, - hl.line - 1, - hl.column - 1, - hl.length == -1 and -1 or hl.column + hl.length - 1 - ) - end - end - return 2 end -local function restore_buffer_state() - vim.o.lazyredraw = prev_lazyredraw - should_cache_lines = true - if vim.v.errmsg ~= "" then - logger.error(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg))) - end -end - -local function execute_command(command) - logger.trace("Executing command: " .. command) - vim.cmd(command) - restore_buffer_state() -end - -local create_user_commands = function(commands) - for name, command in pairs(commands) do - local args, range - api.nvim_create_user_command(name, function(opts) - local range_string = range and range - or ( - opts.range == 2 and ("%s,%s"):format(opts.line1, opts.line2) - or opts.range == 1 and tostring(opts.line1) - or "" - ) - execute_command(("%s%s %s"):format(range_string, command.cmd, args)) - end, { - nargs = "*", - range = true, - preview = function(opts, preview_ns, preview_buf) - opts.command = command - args = command.args - if args then - -- Update command args if provided - args = type(args) == "function" and args(opts) or args - else - args = opts.args - end - opts.cmd_args = args - range = command.range - return command_preview(opts, preview_ns, preview_buf) - end, - }) - end -end - -local validate_config = function(config) - local defaults = config.defaults - vim.validate { - defaults = { defaults, "table", true }, - commands = { config.commands, "table" }, - } - local possible_opts = { "enable_highlighting", "inline_highlighting", "hl_groups" } - for _, command in pairs(config.commands) do - for _, opt in ipairs(possible_opts) do - if command[opt] == nil and defaults and defaults[opt] ~= nil then - command[opt] = defaults[opt] - else - command[opt] = command[opt] or M.defaults[opt] - end - end - command.hl_groups = vim.tbl_deep_extend("force", {}, M.defaults.hl_groups, command.hl_groups) - - vim.validate { - cmd = { command.cmd, "string" }, - args = { command.args, { "string", "function" }, true }, - range = { command.range, { "string" }, true }, - ["command.enable_highlighting"] = { command.enable_highlighting, "boolean", true }, - ["command.inline_highlighting"] = { command.inline_highlighting, "boolean", true }, - ["command.hl_groups"] = { command.hl_groups, "table", true }, - } - end -end - -M.setup = function(user_config) - if vim.fn.has("nvim-0.8.0") ~= 1 then - vim.notify( - "[live-command] This plugin requires at least Neovim 0.8. Please upgrade your Neovim version.", - vim.log.levels.ERROR - ) - return - end - - local config = vim.tbl_deep_extend("force", M.defaults, user_config or {}) - validate_config(config) - create_user_commands(config.commands) - logger = require("live-command.logger") - - local id = api.nvim_create_augroup("command_preview.nvim", { clear = true }) - -- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time. - api.nvim_create_autocmd({ "CmdLineLeave" }, { - group = id, - -- Schedule wrap to run after a potential command execution - callback = vim.schedule_wrap(function() - restore_buffer_state() - end), - }) +M.receive_highlights = function(highlights, bufnr) + received_highlights = highlights + refresh_cmd_preview() end ----@param logger_ Logger -M._set_logger = function(logger_) - logger = logger_ +M.setup = function() + create_preview_command() end -M.version = "1.2.1" +M.version = "2.0.0" return M From 5f6a3e708295c6c3f82a0dae5f347274d0d50f0a Mon Sep 17 00:00:00 2001 From: smjonas Date: Sun, 8 Oct 2023 20:00:48 +0200 Subject: [PATCH 03/24] Add highlighter code --- lua/live-command/cmd_executor.lua | 17 ++-- lua/live-command/differ.lua | 3 + lua/live-command/highlighter.lua | 144 +++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 9 deletions(-) diff --git a/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua index 301e083..bf2b4ee 100644 --- a/lua/live-command/cmd_executor.lua +++ b/lua/live-command/cmd_executor.lua @@ -8,16 +8,17 @@ local running = false local execute_command = function(cmd, bufnr) local old_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") } vim.cmd(cmd) - -- Emulate slow-running command - for i = 1, 1000000000 do - old_buf_lines = old_buf_lines - end + visible_line_range = { + math.max(visible_line_range[1], vim.fn.line("w0")), + math.max(visible_line_range[2], vim.fn.line("w$")), + } local new_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) - return old_buf_lines, new_buf_lines + return old_buf_lines, new_buf_lines, visible_line_range end ----@param cmd string +---@param cmd LiveCommand ---@param bufnr number M.submit_command = function(cmd, bufnr, on_receive_highlights) if cmd == latest_cmd then @@ -26,9 +27,9 @@ M.submit_command = function(cmd, bufnr, on_receive_highlights) latest_cmd = cmd if not running then running = true - local old_buf_lines, new_buf_lines = execute_command(cmd, bufnr) + local old_buf_lines, new_buf_lines, visible_line_range = execute_command(cmd, bufnr) local diff = differ.get_diff(old_buf_lines, new_buf_lines) - local highlights = highlighter.get_highlights(diff) + local highlights = highlighter.get_highlights(diff, old_buf_lines, new_buf_lines, p) highlights = cmd on_receive_highlights(highlights, bufnr) running = false diff --git a/lua/live-command/differ.lua b/lua/live-command/differ.lua index c568013..64120d9 100644 --- a/lua/live-command/differ.lua +++ b/lua/live-command/differ.lua @@ -1,6 +1,9 @@ local M = {} M.get_diff = function(old_lines, new_lines) + return vim.diff(table.concat(old_lines, "\n"), table.concat(new_lines, "\n"), { + result_type = "indices", + }) end return M diff --git a/lua/live-command/highlighter.lua b/lua/live-command/highlighter.lua index 8641bdc..d42f459 100644 --- a/lua/live-command/highlighter.lua +++ b/lua/live-command/highlighter.lua @@ -1,6 +1,148 @@ local M = {} -M.get_highlights = function(diff) +-- @class livecmd.Highlight +-- @field line number +-- @field column number +-- @field length number +-- @field hlgroup number + +local logger = require("live-command.logger") + +-- Inserts str_2 into str_1 at the given position. +local function string_insert(str_1, str_2, pos) + return str_1:sub(1, pos - 1) .. str_2 .. str_1:sub(pos) +end + +-- Inserts a newline character after each character of s and returns the table of characters. +local function splice(s) + local chars = {} + for i = 1, #s do + chars[2 * i - 1] = s:sub(i, i) + chars[2 * i] = "\n" + end + return table.concat(chars) +end + +local function add_inline_highlights(line, old_lines, new_lines, undo_deletions, highlights) + local line_a = splice(old_lines[line]) + local line_b = splice(new_lines[line]) + local line_diff = vim.diff(line_a, line_b, { result_type = "indices" }) + logger.trace(function() + return ("Changed lines (line %d):\nOriginal: '%s' (len=%d)\nUpdated: '%s' (len=%d)\n\nInline hunks: %s"):format( + line, + old_lines[line], + #old_lines[line], + new_lines[line], + #new_lines[line], + vim.inspect(line_diff) + ) + end) + + local defer + local col_offset = 0 + for _, line_hunk in ipairs(line_diff) do + local start_a, count_a, start_b, count_b = unpack(line_hunk) + local hunk_kind = (count_a == 0 and "insertion") or (count_b == 0 and "deletion") or "change" + + if hunk_kind ~= "deletion" or undo_deletions then + local highlight = { + hunk = line_hunk, + kind = hunk_kind, + line = line, + -- Add 1 because when count is zero, start_b / start_b is the position before the deletion + column = (hunk_kind == "deletion") and start_b + 1 or start_b, + length = (hunk_kind == "deletion") and count_a or count_b, + } + + if highlight.kind == "deletion" and undo_deletions then + local deleted_part = old_lines[line]:sub(start_a, start_a + count_a - 1) + -- Restore deleted characters + new_lines[line] = string_insert(new_lines[line], deleted_part, col_offset + start_b + 1) + defer = function() + col_offset = col_offset + #deleted_part + end + end + -- Observation: when changing "line" to "tes", there should not be an offset (-2) + -- after changing "lin" to "t" (because we are not modifying the line) + highlight.column = highlight.column + col_offset + highlight.hunk = nil + table.insert(highlights, highlight) + + if defer then + defer() + defer = nil + end + end + end +end + +M.get_highlights = function(diff, old_lines, new_lines, line_range, opts) + local highlights = {} + for i, hunk in ipairs(diff) do + logger.trace(function() + return ("Hunk %d/%d: %s"):format(i, #diff, vim.inspect(hunk)) + end) + + local start_a, count_a, start_b, count_b = hunk[1], hunk[2], hunk[3], hunk[4] + local hunk_kind = (count_a < count_b and "insertion") or (count_a > count_b and "deletion") + if hunk_kind then + local start_line, end_line + if hunk_kind == "insertion" then + start_line = start_b + count_a + end_line = start_a + (count_b - count_a) + else + start_line = start_a + count_b + end_line = start_line + (count_a - count_b) - 1 + end + + logger.trace(function() + return ("Lines %d-%d:\nOriginal: %s\nUpdated: %s"):format( + start_line, + end_line, + vim.inspect(vim.list_slice(old_lines, start_line, end_line)), + vim.inspect(vim.list_slice(new_lines, start_line, end_line)) + ) + end) + + for line = start_line, end_line do + -- Outside of visible area, skip current or all hunks + if line > line_range[2] then + return highlights + end + + if line >= line_range[1] then + if hunk_kind == "deletion" and opts.undo_deletions then + -- Hunk was deleted: reinsert lines + table.insert(new_lines, line, old_lines[line]) + end + if new_lines[line] == "" then + -- Make empty lines visible + new_lines[line] = " " + end + table.insert(highlights, { kind = hunk_kind, line = line, column = 1, length = -1 }) + end + end + else + -- Change edit + for line = start_b, start_b + count_b - 1 do + -- Outside of visible area, skip current or all hunks + if line > line_range[2] then + return highlights + end + + if line >= line_range[1] then + if opts.inline_highlighting then + -- Get diff for each line in the hunk + add_inline_highlights(line, old_lines, new_lines, opts.undo_deletions, highlights) + else + -- Use a single highlight for the whole line + table.insert(highlights, { kind = "change", line = line, column = 1, length = -1 }) + end + end + end + end + end + return highlights end return M From cd20aae4d2660fa2600a03ac10b5544cda0bcc94 Mon Sep 17 00:00:00 2001 From: smjonas Date: Fri, 27 Oct 2023 15:28:01 +0200 Subject: [PATCH 04/24] Update highlighting and set lines --- lua/live-command/cmd_executor.lua | 29 ++++++++--- lua/live-command/highlighter.lua | 30 +++++++----- lua/live-command/init.lua | 80 +++++++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 23 deletions(-) diff --git a/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua index bf2b4ee..909e6f5 100644 --- a/lua/live-command/cmd_executor.lua +++ b/lua/live-command/cmd_executor.lua @@ -3,9 +3,13 @@ local M = {} local differ = require("live-command.differ") local highlighter = require("live-command.highlighter") +---@type string local latest_cmd + local running = false +local prev_lazyredraw + local execute_command = function(cmd, bufnr) local old_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") } @@ -18,20 +22,33 @@ local execute_command = function(cmd, bufnr) return old_buf_lines, new_buf_lines, visible_line_range end ----@param cmd LiveCommand +---@param cmd string +---@param opts livecmd.Config ---@param bufnr number -M.submit_command = function(cmd, bufnr, on_receive_highlights) +---@param update_buffer_cb fun(bufnr:number,updated_buffer_lines:string[],highlights:livecmd.Highlight[]?) +M.submit_command = function(cmd, opts, bufnr, update_buffer_cb) if cmd == latest_cmd then return end latest_cmd = cmd if not running then running = true - local old_buf_lines, new_buf_lines, visible_line_range = execute_command(cmd, bufnr) + local old_buf_lines, new_buf_lines, line_range = execute_command(cmd, bufnr) + if not opts.enable_highlighting then + update_buffer_cb(bufnr, new_buf_lines, nil) + running = false + return + end local diff = differ.get_diff(old_buf_lines, new_buf_lines) - local highlights = highlighter.get_highlights(diff, old_buf_lines, new_buf_lines, p) - highlights = cmd - on_receive_highlights(highlights, bufnr) + local highlights, updated_buf_lines = highlighter.get_highlights( + diff, + old_buf_lines, + new_buf_lines, + line_range, + opts.inline_highlighting, + opts.hl_groups.deletion ~= false + ) + update_buffer_cb(bufnr, updated_buf_lines, highlights) running = false end end diff --git a/lua/live-command/highlighter.lua b/lua/live-command/highlighter.lua index d42f459..be827f0 100644 --- a/lua/live-command/highlighter.lua +++ b/lua/live-command/highlighter.lua @@ -1,10 +1,10 @@ local M = {} --- @class livecmd.Highlight --- @field line number --- @field column number --- @field length number --- @field hlgroup number +---@class livecmd.Highlight +---@field line number +---@field column number +---@field length number +---@field hlgroup string|false local logger = require("live-command.logger") @@ -76,7 +76,13 @@ local function add_inline_highlights(line, old_lines, new_lines, undo_deletions, end end -M.get_highlights = function(diff, old_lines, new_lines, line_range, opts) +--- @param old_lines string[] +--- @param new_lines string[] +--- @param line_range {start:number, end:number} +--- @param inline_highlighting boolean +--- @param undo_deletions boolean +--- @return livecmd.Highlight[], string[] +M.get_highlights = function(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) local highlights = {} for i, hunk in ipairs(diff) do logger.trace(function() @@ -107,11 +113,11 @@ M.get_highlights = function(diff, old_lines, new_lines, line_range, opts) for line = start_line, end_line do -- Outside of visible area, skip current or all hunks if line > line_range[2] then - return highlights + return highlights, new_lines end if line >= line_range[1] then - if hunk_kind == "deletion" and opts.undo_deletions then + if hunk_kind == "deletion" and undo_deletions then -- Hunk was deleted: reinsert lines table.insert(new_lines, line, old_lines[line]) end @@ -127,13 +133,13 @@ M.get_highlights = function(diff, old_lines, new_lines, line_range, opts) for line = start_b, start_b + count_b - 1 do -- Outside of visible area, skip current or all hunks if line > line_range[2] then - return highlights + return highlights, new_lines end if line >= line_range[1] then - if opts.inline_highlighting then + if inline_highlighting then -- Get diff for each line in the hunk - add_inline_highlights(line, old_lines, new_lines, opts.undo_deletions, highlights) + add_inline_highlights(line, old_lines, new_lines, undo_deletions, highlights) else -- Use a single highlight for the whole line table.insert(highlights, { kind = "change", line = line, column = 1, length = -1 }) @@ -142,7 +148,7 @@ M.get_highlights = function(diff, old_lines, new_lines, line_range, opts) end end end - return highlights + return highlights, new_lines end return M diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 652a0b1..529cb33 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -1,22 +1,84 @@ local M = {} +---@class livecmd.Config.HlGroups +---@field insertion string|false +---@field deletion string|false +---@field change string|false + +---@class livecmd.Config +---@field enable_highlighting boolean +---@field inline_highlighting boolean +---@field hl_groups livecmd.Config.HlGroups + +---@type livecmd.Config +M.defaults = { + enable_highlighting = true, + inline_highlighting = true, + hl_groups = { + insertion = "DiffAdd", + deletion = "DiffDelete", + change = "DiffChange", + }, +} + local cmd_executor = require("live-command.cmd_executor") local api = vim.api +local prev_lazyredraw + +---@type string[] +local received_lines + +---@type livecmd.Highlight[] local received_highlights +---@param bufnr number +---@param preview_ns number +---@param highlights livecmd.Highlight[] +local apply_highlights = function(bufnr, preview_ns, highlights) + for _, hl in ipairs(highlights) do + if hl.hlgroup ~= false then + api.nvim_buf_add_highlight( + bufnr, + preview_ns, + hl.hlgroup, + hl.line - 1, + hl.column - 1, + hl.length == -1 and -1 or hl.column + hl.length - 1 + ) + end + end +end + +local setup = function() + vim.v.errmsg = "" + prev_lazyredraw = vim.o.lazyredraw + vim.o.lazyredraw = true +end + +local teardown = function() + if vim.v.errmsg ~= "" then + -- l(vim.v.errmsg, vim.log.levels.ERROR) + end + vim.o.lazyredraw = prev_lazyredraw +end + local create_preview_command = function() api.nvim_create_user_command("Preview", function() end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) + setup() local cmd = opts.args - vim.v.errmsg = cmd + if received_lines then + api.nvim_buf_set_lines(0, 0, -1, false, received_lines) + end if received_highlights then - api.nvim_set_current_line(received_highlights) + apply_highlights(0, preview_ns, received_highlights) end - cmd_executor.submit_command(cmd, preview_buf or 0, M.receive_highlights) + cmd_executor.submit_command(cmd, M.defaults, 0, M.receive_buffer) + teardown() return 2 - end + end, }) end @@ -28,12 +90,20 @@ local refresh_cmd_preview = function() end end -M.receive_highlights = function(highlights, bufnr) +M.receive_buffer = function(bufnr, lines, highlights) + received_lines = lines received_highlights = highlights refresh_cmd_preview() end M.setup = function() + if vim.fn.has("nvim-0.8.0") ~= 1 then + vim.notify( + "[live-command] This plugin requires at least Neovim 0.8. Please upgrade to a more recent vers1ion of Neovim.", + vim.log.levels.ERROR + ) + return + end create_preview_command() end From a8cc6b6c68a51a01a04c4039bb71628ce4abe1c8 Mon Sep 17 00:00:00 2001 From: smjonas Date: Fri, 3 Nov 2023 23:15:40 +0100 Subject: [PATCH 05/24] Get initial highlights working (still buggy) --- lua/live-command/highlighter.lua | 2 +- lua/live-command/init.lua | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lua/live-command/highlighter.lua b/lua/live-command/highlighter.lua index be827f0..3050d57 100644 --- a/lua/live-command/highlighter.lua +++ b/lua/live-command/highlighter.lua @@ -4,7 +4,7 @@ local M = {} ---@field line number ---@field column number ---@field length number ----@field hlgroup string|false +---@field kind string local logger = require("live-command.logger") diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 529cb33..4c87ff3 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -35,13 +35,15 @@ local received_highlights ---@param bufnr number ---@param preview_ns number ---@param highlights livecmd.Highlight[] -local apply_highlights = function(bufnr, preview_ns, highlights) +---@param hl_groups table +local apply_highlights = function(bufnr, preview_ns, highlights, hl_groups) for _, hl in ipairs(highlights) do - if hl.hlgroup ~= false then + local hl_group = hl_groups[hl.kind] + if hl_group ~= false then api.nvim_buf_add_highlight( bufnr, preview_ns, - hl.hlgroup, + hl_group, hl.line - 1, hl.column - 1, hl.length == -1 and -1 or hl.column + hl.length - 1 @@ -51,15 +53,11 @@ local apply_highlights = function(bufnr, preview_ns, highlights) end local setup = function() - vim.v.errmsg = "" prev_lazyredraw = vim.o.lazyredraw vim.o.lazyredraw = true end local teardown = function() - if vim.v.errmsg ~= "" then - -- l(vim.v.errmsg, vim.log.levels.ERROR) - end vim.o.lazyredraw = prev_lazyredraw end @@ -71,12 +69,15 @@ local create_preview_command = function() local cmd = opts.args if received_lines then api.nvim_buf_set_lines(0, 0, -1, false, received_lines) + -- vim.g.kek = "received:" .. received_lines[1] .. ", new:" .. vim.g.lel + -- require("live-command.logger").trace(function() + -- return "rcv hls" .. vim.inspect(received_highlights) .. (vim.v.errmsg or "") + -- end) end if received_highlights then - apply_highlights(0, preview_ns, received_highlights) + apply_highlights(0, preview_ns, received_highlights, M.defaults.hl_groups) end cmd_executor.submit_command(cmd, M.defaults, 0, M.receive_buffer) - teardown() return 2 end, }) @@ -93,7 +94,7 @@ end M.receive_buffer = function(bufnr, lines, highlights) received_lines = lines received_highlights = highlights - refresh_cmd_preview() + -- refresh_cmd_preview() end M.setup = function() From e3ace8761c35c2e1fd48a05b324f51be502f416e Mon Sep 17 00:00:00 2001 From: smjonas Date: Fri, 3 Nov 2023 23:47:32 +0100 Subject: [PATCH 06/24] Add setup and teardown --- lua/live-command/cmd_executor.lua | 28 +++++++++++++++++++++++++++- lua/live-command/init.lua | 13 +++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua index 909e6f5..ff2f5f2 100644 --- a/lua/live-command/cmd_executor.lua +++ b/lua/live-command/cmd_executor.lua @@ -2,18 +2,44 @@ local M = {} local differ = require("live-command.differ") local highlighter = require("live-command.highlighter") +local logger = require("live-command.logger") ---@type string local latest_cmd local running = false +local refetch_lines = true + +---@tyle string[] +local cached_lines + +---@type boolean local prev_lazyredraw +local setup = function() + prev_lazyredraw = vim.o.lazyredraw + vim.o.lazyredraw = true + if refetch_lines then + cached_lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + refetch_lines = false + end + return cached_lines +end + +M.teardown = function() + vim.o.lazyredraw = prev_lazyredraw + refetch_lines = true + if vim.v.errmsg ~= "" then + logger.error(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg))) + end +end + local execute_command = function(cmd, bufnr) - local old_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local old_buf_lines = setup() local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") } vim.cmd(cmd) + M.teardown() visible_line_range = { math.max(visible_line_range[1], vim.fn.line("w0")), math.max(visible_line_range[2], vim.fn.line("w$")), diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 4c87ff3..edce128 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -97,6 +97,18 @@ M.receive_buffer = function(bufnr, lines, highlights) -- refresh_cmd_preview() end +local create_autocmds = function() + local id = api.nvim_create_augroup("command_preview.nvim", { clear = true }) + -- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time. + api.nvim_create_autocmd({ "CmdLineLeave" }, { + group = id, + -- Schedule wrap to run after a potential command execution + callback = vim.schedule_wrap(function() + cmd_executor.teardown() + end), + }) +end + M.setup = function() if vim.fn.has("nvim-0.8.0") ~= 1 then vim.notify( @@ -106,6 +118,7 @@ M.setup = function() return end create_preview_command() + create_autocmds() end M.version = "2.0.0" From 20a8a8fefaec8b9db6339f95f38336c215b6cfb5 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sat, 18 Nov 2023 11:25:32 +0100 Subject: [PATCH 07/24] Initial working version --- lua/live-command/cmd_executor.lua | 18 +++++++++++------- lua/live-command/init.lua | 20 ++++---------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua index ff2f5f2..53df784 100644 --- a/lua/live-command/cmd_executor.lua +++ b/lua/live-command/cmd_executor.lua @@ -17,29 +17,33 @@ local cached_lines ---@type boolean local prev_lazyredraw -local setup = function() +local setup = function(bufnr) prev_lazyredraw = vim.o.lazyredraw vim.o.lazyredraw = true if refetch_lines then - cached_lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + cached_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) refetch_lines = false + else + logger.trace("did not refetch for cmd " .. latest_cmd) end return cached_lines end -M.teardown = function() +M.teardown = function(do_refetch_lines) vim.o.lazyredraw = prev_lazyredraw - refetch_lines = true + refetch_lines = do_refetch_lines if vim.v.errmsg ~= "" then logger.error(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg))) end end local execute_command = function(cmd, bufnr) - local old_buf_lines = setup() + local old_buf_lines = setup(bufnr) local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") } - vim.cmd(cmd) - M.teardown() + vim.api.nvim_buf_call(bufnr, function() + vim.cmd(cmd) + end) + -- M.teardown(false) visible_line_range = { math.max(visible_line_range[1], vim.fn.line("w0")), math.max(visible_line_range[2], vim.fn.line("w$")), diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index edce128..1d427e2 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -52,30 +52,18 @@ local apply_highlights = function(bufnr, preview_ns, highlights, hl_groups) end end -local setup = function() - prev_lazyredraw = vim.o.lazyredraw - vim.o.lazyredraw = true -end - -local teardown = function() - vim.o.lazyredraw = prev_lazyredraw -end - local create_preview_command = function() api.nvim_create_user_command("Preview", function() end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) - setup() local cmd = opts.args if received_lines then api.nvim_buf_set_lines(0, 0, -1, false, received_lines) - -- vim.g.kek = "received:" .. received_lines[1] .. ", new:" .. vim.g.lel - -- require("live-command.logger").trace(function() - -- return "rcv hls" .. vim.inspect(received_highlights) .. (vim.v.errmsg or "") - -- end) + received_lines = nil end if received_highlights then apply_highlights(0, preview_ns, received_highlights, M.defaults.hl_groups) + received_highlights = nil end cmd_executor.submit_command(cmd, M.defaults, 0, M.receive_buffer) return 2 @@ -94,7 +82,7 @@ end M.receive_buffer = function(bufnr, lines, highlights) received_lines = lines received_highlights = highlights - -- refresh_cmd_preview() + refresh_cmd_preview() end local create_autocmds = function() @@ -104,7 +92,7 @@ local create_autocmds = function() group = id, -- Schedule wrap to run after a potential command execution callback = vim.schedule_wrap(function() - cmd_executor.teardown() + cmd_executor.teardown(true) end), }) end From 9e304df953dba646064bfee52dca6642512c5587 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sat, 18 Nov 2023 13:53:39 +0100 Subject: [PATCH 08/24] Add config validator, start create_preview_command function --- lua/live-command/config_validator.lua | 24 ++++++++ lua/live-command/init.lua | 86 ++++++++++++++++----------- 2 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 lua/live-command/config_validator.lua diff --git a/lua/live-command/config_validator.lua b/lua/live-command/config_validator.lua new file mode 100644 index 0000000..36f68c8 --- /dev/null +++ b/lua/live-command/config_validator.lua @@ -0,0 +1,24 @@ +local M = {} + +---@class livecmd.Config.HlGroups +---@field insertion string|false +---@field deletion string|false +---@field change string|false + +---@class livecmd.Config +---@field command_name string? +---@field enable_highlighting boolean? +---@field inline_highlighting boolean? +---@field hl_groups livecmd.Config.HlGroups? + +---@param config livecmd.Config +M.validate_config = function(config) + vim.validate { + command_name = { config.command_name, "string" }, + enable_highlighting = { config.enable_highlighting, "boolean" }, + inline_highlighting = { config.inline_highlighting, "boolean" }, + hl_groups = { config.hl_groups, "table" }, + } +end + +return M diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 1d427e2..8d556e6 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -1,17 +1,8 @@ local M = {} ----@class livecmd.Config.HlGroups ----@field insertion string|false ----@field deletion string|false ----@field change string|false - ----@class livecmd.Config ----@field enable_highlighting boolean ----@field inline_highlighting boolean ----@field hl_groups livecmd.Config.HlGroups - ---@type livecmd.Config -M.defaults = { +M.default_config = { + command_name = "Preview", enable_highlighting = true, inline_highlighting = true, hl_groups = { @@ -21,10 +12,11 @@ M.defaults = { }, } -local cmd_executor = require("live-command.cmd_executor") +local cmd_executor local api = vim.api -local prev_lazyredraw +---@type livecmd.Config +local merged_config ---@type string[] local received_lines @@ -52,25 +44,6 @@ local apply_highlights = function(bufnr, preview_ns, highlights, hl_groups) end end -local create_preview_command = function() - api.nvim_create_user_command("Preview", function() end, { - nargs = "*", - preview = function(opts, preview_ns, preview_buf) - local cmd = opts.args - if received_lines then - api.nvim_buf_set_lines(0, 0, -1, false, received_lines) - received_lines = nil - end - if received_highlights then - apply_highlights(0, preview_ns, received_highlights, M.defaults.hl_groups) - received_highlights = nil - end - cmd_executor.submit_command(cmd, M.defaults, 0, M.receive_buffer) - return 2 - end, - }) -end - local refresh_cmd_preview = function() local backspace = api.nvim_replace_termcodes("", true, false, true) -- Hack to trigger command preview again after new buffer contents have been computed @@ -79,12 +52,48 @@ local refresh_cmd_preview = function() end end -M.receive_buffer = function(bufnr, lines, highlights) +local on_receive_buffer = function(bufnr, lines, highlights) received_lines = lines received_highlights = highlights refresh_cmd_preview() end +local preview_callback = function(opts, preview_ns, preview_buf) + local cmd = opts.args + if received_lines then + api.nvim_buf_set_lines(0, 0, -1, false, received_lines) + received_lines = nil + end + if received_highlights then + apply_highlights(0, preview_ns, received_highlights, merged_config.hl_groups) + received_highlights = nil + end + cmd_executor.submit_command(cmd, merged_config, 0, on_receive_buffer) + return 2 +end + +M.create_preview_command = function(cmd_name, cmd_to_run) + ---@type string + local cmd_string + vim.validate { + cmd_name = { cmd_name, "string" }, + cmd_to_run = { cmd_to_run, { "string", "function" } }, + } + cmd_to_run = type(cmd_to_run) == "string" and function() + return cmd_to_run + end or cmd_to_run + + api.nvim_create_user_command(cmd_name, function(cmd) + vim.cmd(cmd_string) + end, { + nargs = "*", + preview = function(opts, preview_ns, preview_buf) + cmd_string = cmd_to_run(opts) + return preview_callback(opts, preview_ns, preview_buf) + end, + }) +end + local create_autocmds = function() local id = api.nvim_create_augroup("command_preview.nvim", { clear = true }) -- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time. @@ -97,7 +106,8 @@ local create_autocmds = function() }) end -M.setup = function() +---@param user_config livecmd.Config? +M.setup = function(user_config) if vim.fn.has("nvim-0.8.0") ~= 1 then vim.notify( "[live-command] This plugin requires at least Neovim 0.8. Please upgrade to a more recent vers1ion of Neovim.", @@ -105,7 +115,13 @@ M.setup = function() ) return end - create_preview_command() + cmd_executor = require("live-command.cmd_executor") + merged_config = vim.tbl_deep_extend("force", M.default_config, user_config or {}) + require("live-command.config_validator").validate_config(merged_config) + -- Creates a :Preview command that simply executes its arguments as a command + M.create_preview_command(merged_config.command_name, function(opts) + return opts.args + end) create_autocmds() end From 2a0c71b4602ae41d41fc2f0b7869c018b33a7f91 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sat, 18 Nov 2023 14:18:20 +0100 Subject: [PATCH 09/24] Continue with create_preview_command --- lua/live-command/init.lua | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 8d556e6..dd7c468 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -73,22 +73,29 @@ local preview_callback = function(opts, preview_ns, preview_buf) end M.create_preview_command = function(cmd_name, cmd_to_run) - ---@type string - local cmd_string + ---@type string|table + local cmd vim.validate { cmd_name = { cmd_name, "string" }, cmd_to_run = { cmd_to_run, { "string", "function" } }, } - cmd_to_run = type(cmd_to_run) == "string" and function() - return cmd_to_run - end or cmd_to_run + if type(cmd_to_run) == "string" then + local original_cmd_name = cmd_to_run + cmd_to_run = function(opts) + -- Inherit all arguments except the cmd to execute from the user command + opts.cmd = original_cmd_name + return opts + end + end api.nvim_create_user_command(cmd_name, function(cmd) - vim.cmd(cmd_string) + vim.print(cmd) + vim.g.kk = cmd + vim.cmd(cmd) end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) - cmd_string = cmd_to_run(opts) + cmd = cmd_to_run(opts) return preview_callback(opts, preview_ns, preview_buf) end, }) From 01e4072343d19adb25d5770c2a7907264b40e1ed Mon Sep 17 00:00:00 2001 From: smjonas Date: Wed, 13 Dec 2023 20:25:22 +0100 Subject: [PATCH 10/24] Add tests --- lua/live-command/init.lua | 39 +++++++++++++++++++++------------------ tests/e2e_spec.lua | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 tests/e2e_spec.lua diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index dd7c468..01960ef 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -18,10 +18,10 @@ local api = vim.api ---@type livecmd.Config local merged_config ----@type string[] +---@type string[]? local received_lines ----@type livecmd.Highlight[] +---@type livecmd.Highlight[]? local received_highlights ---@param bufnr number @@ -72,31 +72,32 @@ local preview_callback = function(opts, preview_ns, preview_buf) return 2 end +M._test_mode = false + +---@param cmd_name string +---@param cmd_to_run string|fun(table):string M.create_preview_command = function(cmd_name, cmd_to_run) - ---@type string|table - local cmd vim.validate { cmd_name = { cmd_name, "string" }, cmd_to_run = { cmd_to_run, { "string", "function" } }, } + ---@type table + local cmd if type(cmd_to_run) == "string" then - local original_cmd_name = cmd_to_run - cmd_to_run = function(opts) - -- Inherit all arguments except the cmd to execute from the user command - opts.cmd = original_cmd_name - return opts - end + cmd = api.nvim_parse_cmd(cmd_to_run, {}) end - api.nvim_create_user_command(cmd_name, function(cmd) - vim.print(cmd) - vim.g.kk = cmd - vim.cmd(cmd) + api.nvim_create_user_command(cmd_name, function() + -- vim.g.kekw = 2 + -- assert(cmd_value) + vim.cmd("norm daw") end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) - cmd = cmd_to_run(opts) - return preview_callback(opts, preview_ns, preview_buf) + if type(cmd_to_run) == "function" then + cmd = api.nvim_parse_cmd(cmd_to_run(opts), {}) + end + return preview_callback(cmd, preview_ns, preview_buf) end, }) end @@ -126,9 +127,11 @@ M.setup = function(user_config) merged_config = vim.tbl_deep_extend("force", M.default_config, user_config or {}) require("live-command.config_validator").validate_config(merged_config) -- Creates a :Preview command that simply executes its arguments as a command - M.create_preview_command(merged_config.command_name, function(opts) - return opts.args + M.create_preview_command(merged_config.command_name, function(cmd_opts) + -- return cmd_opts.args + return "norm daw" end) + create_autocmds() end diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua new file mode 100644 index 0000000..6aa8cac --- /dev/null +++ b/tests/e2e_spec.lua @@ -0,0 +1,38 @@ +local live_command = require("live-command") +local api = vim.api + +local bufnr +local preview_cmd = function(cmd) + api.nvim_set_current_buf(bufnr) + live_command.create_preview_command("Preview", cmd) + vim.cmd("Preview " .. cmd) +end + +local get_lines = function() + return api.nvim_buf_get_lines(bufnr, 0, -1, false) +end + +describe("Preview", function() + setup(function() + live_command.setup() + end) + + before_each(function() + bufnr = api.nvim_create_buf(false, true) + api.nvim_buf_set_lines(bufnr, 0, -1, false, { "First line", "Second line" }) + end) + + -- it("g command", function() + -- local cmd = "g/Second/d" + -- run_cmd(cmd) + -- print("rastrst") + -- print(vim.g.kekw) + -- -- assert.are_same({ "First line" }, get_lines()) + -- end) + + it("norm command", function() + local cmd = "norm daw" + preview_cmd(cmd) + assert.are_same({ "line", "Second line" }, get_lines()) + end) +end) From f141f43aecb857b34c5a0a272986bfed22f675b3 Mon Sep 17 00:00:00 2001 From: smjonas Date: Wed, 20 Dec 2023 22:51:59 +0100 Subject: [PATCH 11/24] Make :Preview and create_command_preview tests pass --- lua/live-command/init.lua | 35 ++++++++++------------ tests/e2e_spec.lua | 61 +++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 01960ef..3e6d953 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -74,30 +74,29 @@ end M._test_mode = false ----@param cmd_name string ----@param cmd_to_run string|fun(table):string -M.create_preview_command = function(cmd_name, cmd_to_run) +local get_command_string = function(cmd_to_run, cmd_dict) vim.validate { - cmd_name = { cmd_name, "string" }, cmd_to_run = { cmd_to_run, { "string", "function" } }, } - ---@type table - local cmd if type(cmd_to_run) == "string" then - cmd = api.nvim_parse_cmd(cmd_to_run, {}) + return cmd_to_run end + return cmd_to_run(cmd_dict) +end - api.nvim_create_user_command(cmd_name, function() - -- vim.g.kekw = 2 - -- assert(cmd_value) - vim.cmd("norm daw") +---@param cmd_name string +---@param cmd_to_run string|fun():string +M.create_preview_command = function(cmd_name, cmd_to_run) + vim.validate { + cmd_name = { cmd_name, "string" }, + } + api.nvim_create_user_command(cmd_name, function(cmd_dict) + vim.cmd(get_command_string(cmd_to_run, cmd_dict)) end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) - if type(cmd_to_run) == "function" then - cmd = api.nvim_parse_cmd(cmd_to_run(opts), {}) - end - return preview_callback(cmd, preview_ns, preview_buf) + local cmd_string = get_command_string(cmd_to_run, opts) + return preview_callback(cmd_string, preview_ns, preview_buf) end, }) end @@ -127,11 +126,9 @@ M.setup = function(user_config) merged_config = vim.tbl_deep_extend("force", M.default_config, user_config or {}) require("live-command.config_validator").validate_config(merged_config) -- Creates a :Preview command that simply executes its arguments as a command - M.create_preview_command(merged_config.command_name, function(cmd_opts) - -- return cmd_opts.args - return "norm daw" + M.create_preview_command(merged_config.command_name, function(cmd_dict) + return cmd_dict.args end) - create_autocmds() end diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua index 6aa8cac..9e4a756 100644 --- a/tests/e2e_spec.lua +++ b/tests/e2e_spec.lua @@ -2,9 +2,14 @@ local live_command = require("live-command") local api = vim.api local bufnr -local preview_cmd = function(cmd) +local create_and_run_preview_cmd = function(cmd) + api.nvim_set_current_buf(bufnr) + live_command.create_preview_command("CustomPreviewCommand", cmd) + vim.cmd("CustomPreviewCommand " .. cmd) +end + +local preview = function(cmd) api.nvim_set_current_buf(bufnr) - live_command.create_preview_command("Preview", cmd) vim.cmd("Preview " .. cmd) end @@ -12,27 +17,51 @@ local get_lines = function() return api.nvim_buf_get_lines(bufnr, 0, -1, false) end -describe("Preview", function() - setup(function() - live_command.setup() +setup(function() + live_command.setup() +end) + +before_each(function() + bufnr = api.nvim_create_buf(false, true) + api.nvim_buf_set_lines(bufnr, 0, -1, false, { "First line", "Second line" }) +end) + +describe("create_preview_command works for", function() + it("norm command", function() + local cmd = "norm daw" + create_and_run_preview_cmd(cmd) + assert.are_same({ "line", "Second line" }, get_lines()) end) - before_each(function() - bufnr = api.nvim_create_buf(false, true) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { "First line", "Second line" }) + it("norm command with count", function() + local cmd = "2norm daw" + create_and_run_preview_cmd(cmd) + assert.are_same({ "First line", "line" }, get_lines()) end) - -- it("g command", function() - -- local cmd = "g/Second/d" - -- run_cmd(cmd) - -- print("rastrst") - -- print(vim.g.kekw) - -- -- assert.are_same({ "First line" }, get_lines()) - -- end) + it("g command", function() + local cmd = "g/Second/d" + create_and_run_preview_cmd(cmd) + assert.are_same({ "First line" }, get_lines()) + end) +end) +describe(":Preview works for", function() it("norm command", function() local cmd = "norm daw" - preview_cmd(cmd) + preview(cmd) assert.are_same({ "line", "Second line" }, get_lines()) end) + + it("norm command with count", function() + local cmd = "2norm daw" + preview(cmd) + assert.are_same({ "First line", "line" }, get_lines()) + end) + + it("g command", function() + local cmd = "g/Second/d" + preview(cmd) + assert.are_same({ "First line" }, get_lines()) + end) end) From ef8cf2542bae5091421a20cba8dafa71c0650324 Mon Sep 17 00:00:00 2001 From: smjonas Date: Wed, 27 Dec 2023 17:21:40 +0100 Subject: [PATCH 12/24] refactor: add LiveCommand command with possibility to add handlers --- lua/live-command/config_validator.lua | 5 ++++ lua/live-command/logger.lua | 9 ++++--- lua/live-command/user_command.lua | 34 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 lua/live-command/user_command.lua diff --git a/lua/live-command/config_validator.lua b/lua/live-command/config_validator.lua index 36f68c8..8ed3ec0 100644 --- a/lua/live-command/config_validator.lua +++ b/lua/live-command/config_validator.lua @@ -1,5 +1,7 @@ local M = {} +local user_command = require("live-command.user_command") + ---@class livecmd.Config.HlGroups ---@field insertion string|false ---@field deletion string|false @@ -19,6 +21,9 @@ M.validate_config = function(config) inline_highlighting = { config.inline_highlighting, "boolean" }, hl_groups = { config.hl_groups, "table" }, } + user_command.register_argument_handler("help", function() + print("Help") + end) end return M diff --git a/lua/live-command/logger.lua b/lua/live-command/logger.lua index 448a2c7..dc972cd 100644 --- a/lua/live-command/logger.lua +++ b/lua/live-command/logger.lua @@ -1,6 +1,8 @@ ---@class Logger local M = {} +local user_command = require("live-command.user_command") + ---@type Log[] local logs = {} @@ -20,7 +22,7 @@ M.error = function(msg) table.insert(logs, { msg = msg, level = vim.log.levels.ERROR }) end -vim.api.nvim_create_user_command("LiveCommandLog", function() +local show_log = function() local msgs = {} for i, log in ipairs(logs) do local level = "" @@ -31,8 +33,9 @@ vim.api.nvim_create_user_command("LiveCommandLog", function() end msgs[i] = level .. (type(log.msg) == "function" and log.msg() or log.msg) end - vim.notify(table.concat(msgs, "\n")) -end, { nargs = 0 }) +end + +user_command.register_argument_handler("log", show_log) return M diff --git a/lua/live-command/user_command.lua b/lua/live-command/user_command.lua new file mode 100644 index 0000000..c0d9482 --- /dev/null +++ b/lua/live-command/user_command.lua @@ -0,0 +1,34 @@ +local M = {} + +local handlers = {} + +---@param arg string +---@param handler fun(string) +M.register_argument_handler = function(arg, handler) + handlers[arg] = handler +end + +local create_user_command = function() + vim.api.nvim_create_user_command("LiveCommand", function(selected) + local arg = selected.fargs[1] + local handler = handlers[arg] + if handler then + handler(arg) + else + vim.notify("[live-command] Unknown argument " .. arg) + end + end, { + nargs = 1, + complete = function(arglead, _, _) + local args = vim.tbl_keys(handlers) + -- Only complete arguments that start with arglead + return vim.tbl_filter(function(arg) + return arg:match("^" .. arglead) + end, args) + end, + }) +end + +create_user_command() + +return M From 20c8220bb95dae1c8cedcf6fcfdabdbd07e1b472 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sat, 6 Jan 2024 00:23:34 +0100 Subject: [PATCH 13/24] docs: update readme --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b046169..68b0d20 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # live-command.nvim -![version](https://img.shields.io/badge/version-1.2.1-brightgreen) +![version](https://img.shields.io/badge/version-2.0.0-brightgreen) 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! @@ -18,25 +18,55 @@ work for you. This includes viewing **individual insertions, changes and deletio ## Requirements Neovim 0.8+ -## :rocket: Getting started -Install using your favorite package manager and call the setup function with a table of -commands to create. Here is an example that creates a previewable `:Norm` command: +## :inbox_tray: Installation +Install using your favorite package manager and call the `setup` function: + +
+ lazy.nvim + ```lua use { "smjonas/live-command.nvim", - -- live-command supports semantic versioning via tags - -- tag = "1.*", + -- live-command supports semantic versioning via Git tags + -- tag = "2.*", config = function() - require("live-command").setup { - commands = { - Norm = { cmd = "norm" }, - }, - } + require("live-command").setup() end, } ``` +
+ +
+ vim-plug + +```vim +Plug 'smjonas/live-command.nvim' +``` +Somewhere in your init.lua, you will need to call the setup function: +```lua +require("live-command").setup() +``` +
+ +## :rocket: Getting started +### Basic Usage +The simplest way to use **live-command** is with the provided `:Preview` command. +For instance, `:Preview delete` will show you a preview of deleting the current line. +You can also pass a count or a range to the command, e.g. `:'<,'>Preview norm A;` will +show the effect of appending a semicolon to every line selected in visual mode. + +### Creating Previewable Commands +For a more convenient experience, **live-command** allows you to create custom previewable commands. +This is done by passing a list of commands to the setup function. +For example, you can define a custom `:Norm` command that can be previewed as follows: +```lua +require("live-command").setup { + commands = { + Norm = { cmd = "norm" }, + }, +} +``` -## :gear: Usage and Customization Each command you want to preview requires a name (must be upper-case) and the name of an existing command that is run on each keypress. @@ -66,7 +96,9 @@ require("live-command").setup { commands = commands, } ``` -\ + +## :gear: Customization + All of the following options can be set globally (for all created commands), or per command. To change the default options globally, use the `defaults` table. The defaults are: From 032f1b68b9e17eeee629b506c0784b0335b4e53f Mon Sep 17 00:00:00 2001 From: smjonas Date: Sun, 8 Sep 2024 21:14:57 +0200 Subject: [PATCH 14/24] Preview command working again --- lua/live-command/init.lua | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 3e6d953..e1a9cc1 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -74,29 +74,17 @@ end M._test_mode = false -local get_command_string = function(cmd_to_run, cmd_dict) - vim.validate { - cmd_to_run = { cmd_to_run, { "string", "function" } }, - } - if type(cmd_to_run) == "string" then - return cmd_to_run - end - return cmd_to_run(cmd_dict) -end +---@class livecmd.Command +---@field cmd string ---@param cmd_name string ----@param cmd_to_run string|fun():string -M.create_preview_command = function(cmd_name, cmd_to_run) - vim.validate { - cmd_name = { cmd_name, "string" }, - } - api.nvim_create_user_command(cmd_name, function(cmd_dict) - vim.cmd(get_command_string(cmd_to_run, cmd_dict)) +M.create_preview_command = function(cmd_name) + api.nvim_create_user_command(cmd_name, function(cmd) + vim.cmd(cmd.args) end, { nargs = "*", preview = function(opts, preview_ns, preview_buf) - local cmd_string = get_command_string(cmd_to_run, opts) - return preview_callback(cmd_string, preview_ns, preview_buf) + return preview_callback(opts, preview_ns, preview_buf) end, }) end @@ -125,10 +113,7 @@ M.setup = function(user_config) cmd_executor = require("live-command.cmd_executor") merged_config = vim.tbl_deep_extend("force", M.default_config, user_config or {}) require("live-command.config_validator").validate_config(merged_config) - -- Creates a :Preview command that simply executes its arguments as a command - M.create_preview_command(merged_config.command_name, function(cmd_dict) - return cmd_dict.args - end) + M.create_preview_command(merged_config.command_name) create_autocmds() end From 3423950a02f1d8586a81d10cba7c4f0a221ebac3 Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 10:52:07 +0200 Subject: [PATCH 15/24] Fix create_previewable_command (works with range) --- lua/live-command/init.lua | 35 ++++++++++++++++++++++++++++------- tests/e2e_spec.lua | 17 ++++++++--------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index e1a9cc1..6477338 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -58,8 +58,8 @@ local on_receive_buffer = function(bufnr, lines, highlights) refresh_cmd_preview() end -local preview_callback = function(opts, preview_ns, preview_buf) - local cmd = opts.args +---@param cmd string +local preview_callback = function(cmd, preview_ns, preview_buf) if received_lines then api.nvim_buf_set_lines(0, 0, -1, false, received_lines) received_lines = nil @@ -74,17 +74,38 @@ end M._test_mode = false ----@class livecmd.Command +---@param preview_cmd_name string +M.create_preview_command = function(preview_cmd_name) + api.nvim_create_user_command(preview_cmd_name, function(cmd) + vim.cmd(cmd.args) + end, { + nargs = "*", + preview = function(opts, preview_ns, preview_buf) + local cmd_to_preview = opts.args + return preview_callback(cmd_to_preview, preview_ns, preview_buf) + end, + }) +end + +---@class livecmd.CommandOpts ---@field cmd string ---@param cmd_name string -M.create_preview_command = function(cmd_name) +---@param cmd_opts livecmd.CommandOpts +M.create_previewable_command = function(cmd_name, cmd_opts) api.nvim_create_user_command(cmd_name, function(cmd) - vim.cmd(cmd.args) + local range_string = ( + cmd.range == 2 and ("%s,%s"):format(cmd.line1, cmd.line2) + or cmd.range == 1 and tostring(cmd.line1) + or "" + ) + vim.cmd(range_string .. cmd_opts.cmd .. " " .. cmd.args) end, { nargs = "*", - preview = function(opts, preview_ns, preview_buf) - return preview_callback(opts, preview_ns, preview_buf) + range = true, + preview = function(cmd, preview_ns, preview_buf) + cmd.name = cmd_opts.cmd + return preview_callback(cmd, preview_ns, preview_buf) end, }) end diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua index 9e4a756..0246b31 100644 --- a/tests/e2e_spec.lua +++ b/tests/e2e_spec.lua @@ -2,10 +2,9 @@ local live_command = require("live-command") local api = vim.api local bufnr -local create_and_run_preview_cmd = function(cmd) +local create_preview_cmd = function(cmd_name, cmd_opts) api.nvim_set_current_buf(bufnr) - live_command.create_preview_command("CustomPreviewCommand", cmd) - vim.cmd("CustomPreviewCommand " .. cmd) + live_command.create_previewable_command(cmd_name, cmd_opts) end local preview = function(cmd) @@ -28,20 +27,20 @@ end) describe("create_preview_command works for", function() it("norm command", function() - local cmd = "norm daw" - create_and_run_preview_cmd(cmd) + create_preview_cmd("Norm", { cmd = "norm" }) + vim.cmd("Norm daw") assert.are_same({ "line", "Second line" }, get_lines()) end) it("norm command with count", function() - local cmd = "2norm daw" - create_and_run_preview_cmd(cmd) + create_preview_cmd("Norm", { cmd = "norm" }) + vim.cmd("2Norm daw") assert.are_same({ "First line", "line" }, get_lines()) end) it("g command", function() - local cmd = "g/Second/d" - create_and_run_preview_cmd(cmd) + create_preview_cmd("G", { cmd = "g" }) + vim.cmd("G/Second/d") assert.are_same({ "First line" }, get_lines()) end) end) From 51e2f0a6495e91854212cb1c27550be4623e76f5 Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 11:41:48 +0200 Subject: [PATCH 16/24] Update warning message for incorrect module name --- lua/live_command/init.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lua/live_command/init.lua b/lua/live_command/init.lua index e3708aa..caa5b4b 100644 --- a/lua/live_command/init.lua +++ b/lua/live_command/init.lua @@ -1,11 +1,7 @@ local M = {} M.setup = function(user_config) - vim.notify( - "[live-command]: The Lua module live_command has been renamed to live-command to match the repo name.\n" - .. 'Please change require("live_command") to require("live-command").', - vim.log.levels.WARN - ) + vim.notify('[live-command]: Please change require("live_command") to require("live-command").', vim.log.levels.WARN) require("live-command").setup(user_config) end From 224e7c910ad41bb667a9fdc5eee1e6685551afaa Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 12:19:01 +0200 Subject: [PATCH 17/24] Fix preview in create_previewable_command --- lua/live-command/config_validator.lua | 7 ++---- lua/live-command/init.lua | 32 +++++++++++++++++---------- tests/e2e_spec.lua | 10 +++++++++ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lua/live-command/config_validator.lua b/lua/live-command/config_validator.lua index 8ed3ec0..bcfe81d 100644 --- a/lua/live-command/config_validator.lua +++ b/lua/live-command/config_validator.lua @@ -1,7 +1,5 @@ local M = {} -local user_command = require("live-command.user_command") - ---@class livecmd.Config.HlGroups ---@field insertion string|false ---@field deletion string|false @@ -12,6 +10,7 @@ local user_command = require("live-command.user_command") ---@field enable_highlighting boolean? ---@field inline_highlighting boolean? ---@field hl_groups livecmd.Config.HlGroups? +---@field commands table ---@param config livecmd.Config M.validate_config = function(config) @@ -20,10 +19,8 @@ M.validate_config = function(config) enable_highlighting = { config.enable_highlighting, "boolean" }, inline_highlighting = { config.inline_highlighting, "boolean" }, hl_groups = { config.hl_groups, "table" }, + commands = { config.commands, "table" }, } - user_command.register_argument_handler("help", function() - print("Help") - end) end return M diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 6477338..44f07d7 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -10,6 +10,7 @@ M.default_config = { deletion = "DiffDelete", change = "DiffChange", }, + commands = {}, } local cmd_executor @@ -72,6 +73,10 @@ local preview_callback = function(cmd, preview_ns, preview_buf) return 2 end +local get_range_string = function(cmd) + return (cmd.range == 2 and ("%s,%s"):format(cmd.line1, cmd.line2) or cmd.range == 1 and tostring(cmd.line1) or "") +end + M._test_mode = false ---@param preview_cmd_name string @@ -87,29 +92,31 @@ M.create_preview_command = function(preview_cmd_name) }) end ----@class livecmd.CommandOpts +---@class livecmd.CommandSpec ---@field cmd string ---@param cmd_name string ----@param cmd_opts livecmd.CommandOpts -M.create_previewable_command = function(cmd_name, cmd_opts) +---@param cmd_specs livecmd.CommandSpec +M.create_previewable_command = function(cmd_name, cmd_specs) api.nvim_create_user_command(cmd_name, function(cmd) - local range_string = ( - cmd.range == 2 and ("%s,%s"):format(cmd.line1, cmd.line2) - or cmd.range == 1 and tostring(cmd.line1) - or "" - ) - vim.cmd(range_string .. cmd_opts.cmd .. " " .. cmd.args) + vim.cmd(get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args) end, { nargs = "*", range = true, preview = function(cmd, preview_ns, preview_buf) - cmd.name = cmd_opts.cmd - return preview_callback(cmd, preview_ns, preview_buf) + local cmd_to_preview = get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args + return preview_callback(cmd_to_preview, preview_ns, preview_buf) end, }) end +---@param cmd_specs table +local create_previewable_commands = function(cmd_specs) + for cmd_name, cmd_spec in pairs(cmd_specs) do + M.create_previewable_command(cmd_name, cmd_spec) + end +end + local create_autocmds = function() local id = api.nvim_create_augroup("command_preview.nvim", { clear = true }) -- We need to be able to tell when the command was cancelled so the buffer lines are refetched next time. @@ -126,7 +133,7 @@ end M.setup = function(user_config) if vim.fn.has("nvim-0.8.0") ~= 1 then vim.notify( - "[live-command] This plugin requires at least Neovim 0.8. Please upgrade to a more recent vers1ion of Neovim.", + "[live-command] This plugin requires at least Neovim 0.8. Please upgrade to a more recent version of Neovim.", vim.log.levels.ERROR ) return @@ -135,6 +142,7 @@ M.setup = function(user_config) merged_config = vim.tbl_deep_extend("force", M.default_config, user_config or {}) require("live-command.config_validator").validate_config(merged_config) M.create_preview_command(merged_config.command_name) + create_previewable_commands(merged_config.commands) create_autocmds() end diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua index 0246b31..59062b5 100644 --- a/tests/e2e_spec.lua +++ b/tests/e2e_spec.lua @@ -43,6 +43,16 @@ describe("create_preview_command works for", function() vim.cmd("G/Second/d") assert.are_same({ "First line" }, get_lines()) end) + + it("command spec in config", function() + live_command.setup { + commands = { + ABC = { cmd = "norm" }, + }, + } + vim.cmd("ABC daw") + assert.are_same({ "line", "Second line" }, get_lines()) + end) end) describe(":Preview works for", function() From f1779026eb098536540587f97c6e1742f004971b Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 21:01:15 +0200 Subject: [PATCH 18/24] docs: update migration guide --- README.md | 2 ++ lua/live-command/init.lua | 10 +++---- migrate_to_v2.md | 60 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 migrate_to_v2.md diff --git a/README.md b/README.md index 68b0d20..1dcc9de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # live-command.nvim ![version](https://img.shields.io/badge/version-2.0.0-brightgreen) +> :exclamation: Version 2.0 has been released with breaking changes! Please check out the [migration guide](./migrate_to_v2.md). + 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! ![live-command.nvim demo video](https://user-images.githubusercontent.com/40792180/235201812-adc95327-65cc-4ae4-8c2e-804853dd0c02.gif) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 44f07d7..613cd82 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -60,7 +60,7 @@ local on_receive_buffer = function(bufnr, lines, highlights) end ---@param cmd string -local preview_callback = function(cmd, preview_ns, preview_buf) +M.preview_callback = function(cmd, preview_ns, preview_buf) if received_lines then api.nvim_buf_set_lines(0, 0, -1, false, received_lines) received_lines = nil @@ -73,7 +73,7 @@ local preview_callback = function(cmd, preview_ns, preview_buf) return 2 end -local get_range_string = function(cmd) +M.get_range_string = function(cmd) return (cmd.range == 2 and ("%s,%s"):format(cmd.line1, cmd.line2) or cmd.range == 1 and tostring(cmd.line1) or "") end @@ -99,13 +99,13 @@ end ---@param cmd_specs livecmd.CommandSpec M.create_previewable_command = function(cmd_name, cmd_specs) api.nvim_create_user_command(cmd_name, function(cmd) - vim.cmd(get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args) + vim.cmd(M.get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args) end, { nargs = "*", range = true, preview = function(cmd, preview_ns, preview_buf) - local cmd_to_preview = get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args - return preview_callback(cmd_to_preview, preview_ns, preview_buf) + local cmd_to_preview = M.get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args + return M.preview_callback(cmd_to_preview, preview_ns, preview_buf) end, }) end diff --git a/migrate_to_v2.md b/migrate_to_v2.md new file mode 100644 index 0000000..6e4443c --- /dev/null +++ b/migrate_to_v2.md @@ -0,0 +1,60 @@ +# Migration to v2.0 +This is a guide for users that want to migrate to version `2.0` of `live-command`. +If you want to stay on version `1.0`, you can also pin the plugin to the tag `v1.0`. + +## What has changed in version 2.0? +Version 2.0 is a rewrite of the plugin for better maintainability and future extensibility. +It simplifies the user-facing API while improving the architecture of the plugin and adding a new `:Preview` command. + +**Breaking change**: +- Custom command specifications now only consist of a `cmd` value (a string); `args` + and `range` have been removed. See + +**New feature**: +- New generic `:Preview` command that allows to preview any command without having to + define it in the configuration. This is useful to test out the capabilities of + `live-command` or if you don't use a command as often to warrant a separate user command. + The command itself does not take a range or count. Example: `:Preview '<,'>norm daw` + previews deletion of the first word of the selected lines. + +## How can I migrate from older versions? +In versions `1.x`, the following example was provided showing how to preview the results of a macro: +```lua +local commands = { + Reg = { + cmd = "norm", + -- This will transform ":5Reg a" into ":norm 5@a" + args = function(opts) + return (opts.count == -1 and "" or opts.count) .. "@" .. opts.args + end, + range = "", + }, +} +``` +In `v2.0`, you have two options: +1. Define a command `Norm = { cmd = "norm" }` and use it as `:Norm @` (e.g., `:Norm 5@a` to apply macro stored in register `a` five times) +2. Define a custom `:Reg` user command like this that works just like the old version: + +
+ View code + +```lua +-- Turns ":5Reg a" into ":norm 5@a" +local function get_command_string(cmd) + local get_range_string = require("live-command").get_range_string + local args = (cmd.count == -1 and "" or cmd.count) .. "@" .. cmd.args + return get_range_string(cmd) .. "norm " .. args +end + +vim.api.nvim_create_user_command("Reg", function(cmd) + vim.cmd(get_command_string(cmd)) +end, { + nargs = "?", + range = true, + preview = function(cmd, preview_ns, preview_buf) + local cmd_to_preview = get_command_string(cmd) + return require("live-command").preview_callback(cmd_to_preview, preview_ns, preview_buf) + end +}) +``` +
From dc2850b34a54fdfdd6c6bf24a0b2dff7df27a365 Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 21:29:09 +0200 Subject: [PATCH 19/24] docs: update migration guide --- migrate_to_v2.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/migrate_to_v2.md b/migrate_to_v2.md index 6e4443c..14a295f 100644 --- a/migrate_to_v2.md +++ b/migrate_to_v2.md @@ -1,6 +1,6 @@ # Migration to v2.0 This is a guide for users that want to migrate to version `2.0` of `live-command`. -If you want to stay on version `1.0`, you can also pin the plugin to the tag `v1.0`. +If you want to stay on the previous major version, you can pin the plugin to the tag [`1.x`](https://github.com/smjonas/live-command.nvim/releases/tag/1.x). ## What has changed in version 2.0? Version 2.0 is a rewrite of the plugin for better maintainability and future extensibility. @@ -8,7 +8,7 @@ It simplifies the user-facing API while improving the architecture of the plugin **Breaking change**: - Custom command specifications now only consist of a `cmd` value (a string); `args` - and `range` have been removed. See + and `range` have been removed. See [next section](#how-can-i-migrate-from-older-versions). **New feature**: - New generic `:Preview` command that allows to preview any command without having to @@ -31,8 +31,8 @@ local commands = { }, } ``` -In `v2.0`, you have two options: -1. Define a command `Norm = { cmd = "norm" }` and use it as `:Norm @` (e.g., `:Norm 5@a` to apply macro stored in register `a` five times) +In version `2.0`, you have two options: +1. Define a command `Norm = { cmd = "norm" }` and use it as `:Norm @` (e.g., `:Norm 5@a` to apply macro stored in register `a` five times). 2. Define a custom `:Reg` user command like this that works just like the old version:
From 8cd4f0e6cf03a047d51280db624a54b56d7a8090 Mon Sep 17 00:00:00 2001 From: smjonas Date: Mon, 9 Sep 2024 22:49:29 +0200 Subject: [PATCH 20/24] feat: show warning if unsupported features are detected --- README.md | 29 +------------------------- lua/live-command/config_validator.lua | 30 +++++++++++++++++++++++++++ migrate_to_v2.md | 2 +- tests/e2e_spec.lua | 6 ++++++ 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1dcc9de..efdc238 100644 --- a/README.md +++ b/README.md @@ -70,34 +70,7 @@ require("live-command").setup { ``` Each command you want to preview requires a name (must be upper-case) and the name of -an existing command that is run on each keypress. - -Here is a list of available settings: - -| Key | Type | Description -| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ -| cmd | string | The name of an existing command to preview. -| args | string? \| function(arg: string?, opts: table) -> string | Arguments passed to the command. If a function, takes in the options passed to the command and must return the transformed argument(s) `cmd` will be called with. `opts` has the same structure as the `opts` table passed to the `nvim_create_user_command` callback function. If `nil`, the arguments are supplied from the command-line while the user is typing the command. -| range | string? | The range to prepend to the command. Set this to `""` if you don't want the new command to receive a count, e.g. when turning `:9Reg a` into `:norm 9@a`. If `nil`, the range will be supplied from the command entered. - -### Example -The following example creates a `:Reg` command which allows you to preview the effects of macros (e.g. `:5Reg a` to run macro `a` five times). -```lua -local commands = { - Reg = { - cmd = "norm", - -- This will transform ":5Reg a" into ":norm 5@a" - args = function(opts) - return (opts.count == -1 and "" or opts.count) .. "@" .. opts.args - end, - range = "", - }, -} - -require("live-command").setup { - commands = commands, -} -``` +an existing command that is run on each keypress via the `cmd` field. ## :gear: Customization diff --git a/lua/live-command/config_validator.lua b/lua/live-command/config_validator.lua index bcfe81d..3d70ef8 100644 --- a/lua/live-command/config_validator.lua +++ b/lua/live-command/config_validator.lua @@ -1,5 +1,7 @@ local M = {} +local user_command = require("live-command.user_command") + ---@class livecmd.Config.HlGroups ---@field insertion string|false ---@field deletion string|false @@ -12,6 +14,23 @@ local M = {} ---@field hl_groups livecmd.Config.HlGroups? ---@field commands table +local show_diagnostics_message = function(config) + local message = [[ +Version 2.0 of live-command.nvim has dropped support for the "args" and "range" keys in the command specification. +The following commands in your configuration are affected: %s. Please remove or modify them. +See the migration guide for more information: https://github.com/smjonas/live-command.nvim/blob/main/migrate_to_v2.md + ]] + local affected_cmds = {} + for cmd_name, cmd_spec in pairs(config.commands) do + if cmd_spec.args ~= nil or cmd_spec.range ~= nil then + table.insert(affected_cmds, '"' .. cmd_name .. '"') + end + end + local cmd_names = table.concat(affected_cmds, ", ") + local formatted_message = string.format(message, cmd_names) + vim.notify(formatted_message, vim.log.levels.INFO) +end + ---@param config livecmd.Config M.validate_config = function(config) vim.validate { @@ -21,6 +40,17 @@ M.validate_config = function(config) hl_groups = { config.hl_groups, "table" }, commands = { config.commands, "table" }, } + for cmd_name, cmd_spec in pairs(config.commands) do + if cmd_spec.args ~= nil or cmd_spec.range ~= nil then + vim.notify( + '[live-command.nvim] Some unsupported features are used in your config. Please run ":LiveCommand diagnose" for details.', + vim.log.levels.WARN + ) + user_command.register_argument_handler("diagnose", function() + show_diagnostics_message(config) + end) + end + end end return M diff --git a/migrate_to_v2.md b/migrate_to_v2.md index 14a295f..6d1d6f8 100644 --- a/migrate_to_v2.md +++ b/migrate_to_v2.md @@ -1,6 +1,6 @@ # Migration to v2.0 This is a guide for users that want to migrate to version `2.0` of `live-command`. -If you want to stay on the previous major version, you can pin the plugin to the tag [`1.x`](https://github.com/smjonas/live-command.nvim/releases/tag/1.x). +If you want to avoid any breaking changes, you can pin the plugin to the tag [`1.x`](https://github.com/smjonas/live-command.nvim/releases/tag/1.x). ## What has changed in version 2.0? Version 2.0 is a rewrite of the plugin for better maintainability and future extensibility. diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua index 59062b5..0c98d44 100644 --- a/tests/e2e_spec.lua +++ b/tests/e2e_spec.lua @@ -38,6 +38,12 @@ describe("create_preview_command works for", function() assert.are_same({ "First line", "line" }, get_lines()) end) + it("norm command with range", function() + create_preview_cmd("Norm", { cmd = "norm" }) + vim.cmd("1,2Norm daw") + assert.are_same({ "line", "line" }, get_lines()) + end) + it("g command", function() create_preview_cmd("G", { cmd = "g" }) vim.cmd("G/Second/d") From 2565a47d8f4aa51651712f4b11bd423aebe2a9a7 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sat, 14 Sep 2024 21:33:04 +0200 Subject: [PATCH 21/24] docs: update wording --- README.md | 56 ++++++++++++++++++++++++------------------------ migrate_to_v2.md | 32 +++++++++++++-------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index efdc238..152a3fd 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,27 @@ # live-command.nvim ![version](https://img.shields.io/badge/version-2.0.0-brightgreen) -> :exclamation: Version 2.0 has been released with breaking changes! Please check out the [migration guide](./migrate_to_v2.md). +> :exclamation: Version 2.0 has been released with breaking changes! Be sure to check out the [migration guide](./migrate_to_v2.md). -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! +Text editing in Neovim with immediate visual feedback: see the effects of any command on your buffer in real-time. Preview macros, the `:norm` command, and more! ![live-command.nvim demo video](https://user-images.githubusercontent.com/40792180/235201812-adc95327-65cc-4ae4-8c2e-804853dd0c02.gif)

Theme: tokyonight.nvim

## :sparkles: Motivation and Features -In Neovim version 0.8, the `command-preview` feature has been introduced. +In Neovim version 0.8, the `command-preview` feature was introduced. Despite its name, it does not enable automatic previewing of any command. -Instead, users must manually update the buffer text and set highlights *for each command*. +Instead, users must manually update the buffer text and set highlights *for each command* they wish to preview. -This plugin aims to address this issue by offering a **simple API for creating previewable commands** -in Neovim. Simply provide the command you want to preview and live-command will do all the -work for you. This includes viewing **individual insertions, changes and deletions** as you type. +This plugin addresses that limitation by offering a **simple API for creating previewable commands** +in Neovim. Just specify the command you want to preview and live-command will handle the rest. +This includes viewing **individual insertions, changes and deletions** as you type. ## Requirements Neovim 0.8+ ## :inbox_tray: Installation -Install using your favorite package manager and call the `setup` function: +Install via your favorite package manager and call the `setup` function:
lazy.nvim @@ -44,7 +44,7 @@ use { ```vim Plug 'smjonas/live-command.nvim' ``` -Somewhere in your init.lua, you will need to call the setup function: +In your `init.lua`, call the setup function: ```lua require("live-command").setup() ``` @@ -52,15 +52,15 @@ require("live-command").setup() ## :rocket: Getting started ### Basic Usage -The simplest way to use **live-command** is with the provided `:Preview` command. -For instance, `:Preview delete` will show you a preview of deleting the current line. -You can also pass a count or a range to the command, e.g. `:'<,'>Preview norm A;` will -show the effect of appending a semicolon to every line selected in visual mode. +The easiest way to use **live-command** is with the provided `:Preview` command. +For example, `:Preview delete` will show you a preview of deleting the current line. +You can also provide a count or a range to the command, such as `:'<,'>Preview norm A;`, which +shows the effect of appending a semicolon to every visually selected line. ### Creating Previewable Commands -For a more convenient experience, **live-command** allows you to create custom previewable commands. -This is done by passing a list of commands to the setup function. -For example, you can define a custom `:Norm` command that can be previewed as follows: +For a more convenient experience, **live-command** allows you to define custom previewable commands. +This can be done by passing a list of commands to the `setup` function. +For instance, to define a custom `:Norm` command that can be previewed, use the following: ```lua require("live-command").setup { commands = { @@ -69,14 +69,14 @@ require("live-command").setup { } ``` -Each command you want to preview requires a name (must be upper-case) and the name of -an existing command that is run on each keypress via the `cmd` field. +Each command you want to preview needs a name (which must be uppercase) and +an existing command to run on each keypress, specified via the `cmd` field. ## :gear: Customization -All of the following options can be set globally (for all created commands), or per command. +All of the following options can be set globally (affecting all custom commands), or per command. -To change the default options globally, use the `defaults` table. The defaults are: +To change the default options globally, use the `defaults` table. The default settings are: ```lua require("live-command").setup { @@ -98,7 +98,7 @@ require("live-command").setup { Default: `true` -Whether highlights should be shown. If `false`, only text changes are shown. +Determines whether highlights should be shown. If `false`, only text changes are shown, without any highlights. --- @@ -106,9 +106,9 @@ Whether highlights should be shown. If `false`, only text changes are shown. Default: `true` -If `true`, differing lines will be compared in a second run of the diff algorithm. This -can result in multiple highlights per line. Otherwise, the whole line will be highlighted as -a single change highlight. +If `true`, differing lines will be compared in a second run of the diff algorithm +to identify smaller differences. This can result in multiple highlights per line. +If set to `false`, the whole line will be highlighted as a single change. --- @@ -116,10 +116,10 @@ a single change highlight. Default: `{ insertion = "DiffAdd", deletion = "DiffDelete", change = "DiffChange" }` -A list of highlight groups per edit type (insertion, deletion or change) used for highlighting buffer changes. -The table will be merged with the defaults so you can omit any keys that are the same as the default. -If a value is set to `false`, no highlights will be shown for that type. If `hl_groups.deletion` is `false`, -deletion edits will not be undone which is otherwise done to make the text changes visible. +A table mapping edit types (insertion, deletion or change) to highlight groups used for highlighting buffer changes. +This table is merged with the defaults, allowing you to omit any keys that match the default. +If a value is set to `false`, no highlights will be shown for that type. +If `hl_groups.deletion` is `false`, deletion edits will not be undone, so deleted text won't be highlighted. --- diff --git a/migrate_to_v2.md b/migrate_to_v2.md index 6d1d6f8..0374bae 100644 --- a/migrate_to_v2.md +++ b/migrate_to_v2.md @@ -1,24 +1,24 @@ # Migration to v2.0 -This is a guide for users that want to migrate to version `2.0` of `live-command`. -If you want to avoid any breaking changes, you can pin the plugin to the tag [`1.x`](https://github.com/smjonas/live-command.nvim/releases/tag/1.x). +This is a guide for users looking to migrate to version `2.0` of `live-command`. +If you'd prefer to avoid breaking changes, you can pin the plugin to tag [`1.x`](https://github.com/smjonas/live-command.nvim/releases/tag/1.x) tag. -## What has changed in version 2.0? -Version 2.0 is a rewrite of the plugin for better maintainability and future extensibility. -It simplifies the user-facing API while improving the architecture of the plugin and adding a new `:Preview` command. +## What's new in version 2.0? +Version 2.0 is a complete rewrite, aimed at improving maintainability and future extensibility. +It introduces a simplified user-facing API, alongside improvements to the architecture of the plugin and the addition of a new `:Preview` command. -**Breaking change**: -- Custom command specifications now only consist of a `cmd` value (a string); `args` - and `range` have been removed. See [next section](#how-can-i-migrate-from-older-versions). +**Breaking changes**: +- Custom command specifications now only consist of a `cmd` value (a string). The `args` + and `range` fields have been removed. See [next section](#how-can-i-migrate-from-older-versions) for details. **New feature**: -- New generic `:Preview` command that allows to preview any command without having to - define it in the configuration. This is useful to test out the capabilities of - `live-command` or if you don't use a command as often to warrant a separate user command. - The command itself does not take a range or count. Example: `:Preview '<,'>norm daw` - previews deletion of the first word of the selected lines. +- A new generic `:Preview` command allows you to preview any command without needing to + define it in your configuration. This is useful for testing `live-command`'s capabilities + or for previewing commands you use infrequently, where creating a separate user command doesn't + seem necessary. The command does not accept a range or count. Example: `:Preview '<,'>norm daw` + previews the deletion of the first word in the selected lines. ## How can I migrate from older versions? -In versions `1.x`, the following example was provided showing how to preview the results of a macro: +In versions `1.x`, the following example demonstrated how to preview the results of a macro: ```lua local commands = { Reg = { @@ -33,13 +33,13 @@ local commands = { ``` In version `2.0`, you have two options: 1. Define a command `Norm = { cmd = "norm" }` and use it as `:Norm @` (e.g., `:Norm 5@a` to apply macro stored in register `a` five times). -2. Define a custom `:Reg` user command like this that works just like the old version: +2. Alternatively, define a custom `:Reg` user command that bevaves like the old version:
View code ```lua --- Turns ":5Reg a" into ":norm 5@a" +-- Transforms ":5Reg a" into ":norm 5@a" local function get_command_string(cmd) local get_range_string = require("live-command").get_range_string local args = (cmd.count == -1 and "" or cmd.count) .. "@" .. cmd.args From 3a33d21330d11293af92d240dbfb48bb311af566 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sun, 15 Sep 2024 13:26:55 +0200 Subject: [PATCH 22/24] test: fix highlighter tests --- tests/highlighter_spec.lua | 167 +++++++++++++++++++++++++++++++++++++ tests/init_spec.lua | 100 ---------------------- 2 files changed, 167 insertions(+), 100 deletions(-) create mode 100644 tests/highlighter_spec.lua delete mode 100644 tests/init_spec.lua diff --git a/tests/highlighter_spec.lua b/tests/highlighter_spec.lua new file mode 100644 index 0000000..e6211f7 --- /dev/null +++ b/tests/highlighter_spec.lua @@ -0,0 +1,167 @@ +local highlighter = require("live-command.highlighter") + +describe("inline_highlights", function() + local function compute_diff(old_lines, new_lines) + return vim.diff(old_lines[1], new_lines[1], { result_type = "indices" }) + end + + -- Checks for the case when the end of the line was unchanged + it("single insertion", function() + local old_lines = { "word" } + local new_lines = { "new word" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "insertion", line = 1, column = 1, length = 4 }, + }, highlights) + assert.are_same({ "new word" }, updated_lines) + end) + + it("insertions", function() + local old_lines = { "word" } + local new_lines = { "new word new" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "insertion", line = 1, column = 1, length = 4 }, + { kind = "insertion", line = 1, column = 9, length = 4 }, + }, highlights) + assert.are_same({ "new word new" }, updated_lines) + end) + + it("insertions + deletion", function() + local old_lines = { "a test" } + local new_lines = { "test1" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "deletion", line = 1, column = 1, length = 2 }, + { kind = "insertion", line = 1, column = 7, length = 1 }, + }, highlights) + assert.are_same({ "a test1" }, updated_lines) + end) + + it("change 1", function() + local old_lines = { " table.insert" } + local new_lines = { " x.insert" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "change", line = 1, column = 7, length = 1 }, + }, highlights) + assert.are_same({ " x.insert" }, updated_lines) + end) + + it("change 2", function() + local old_lines = { "config = function()" } + local new_lines = { "test = Function()" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "change", line = 1, column = 1, length = 4 }, + { kind = "change", line = 1, column = 8, length = 1 }, + }, highlights) + assert.are_same({ "test = Function()" }, updated_lines) + end) + + it("change should not use negative column values", function() + local old_lines = { "line" } + local new_lines = { "tes" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "change", line = 1, column = 1, length = 1 }, + { kind = "insertion", line = 1, column = 3, length = 1 }, + }, highlights) + assert.are_same({ "tes" }, updated_lines) + end) + + -- TODO: create the same test but when undo_deletions = false + it("change + deletion", function() + local old_lines = { "-- require plugins.nvim-surround" } + local new_lines = { "lel" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "change", line = 1, column = 1, length = 1 }, + { kind = "deletion", line = 1, column = 3, length = 2 }, + { kind = "deletion", line = 1, column = 6, length = 19 }, + }, highlights) + assert.are_same({ "le plugins.nvim-surround" }, updated_lines) + end) + + it("deletion", function() + local old_lines = { "local tests" } + local new_lines = { "local s" } + + local diff = compute_diff(old_lines, new_lines) + local line_range = { 1, #new_lines } + + local inline_highlighting = true + local undo_deletions = true + + local highlights, updated_lines = + highlighter.get_highlights(diff, old_lines, new_lines, line_range, inline_highlighting, undo_deletions) + + assert.are_same({ + { kind = "deletion", line = 1, column = 7, length = 4 }, + }, highlights) + assert.are_same({ "local tests" }, updated_lines) + end) +end) diff --git a/tests/init_spec.lua b/tests/init_spec.lua deleted file mode 100644 index 123f2a4..0000000 --- a/tests/init_spec.lua +++ /dev/null @@ -1,100 +0,0 @@ -local live_command = require("live-command") - -describe("inline_highlights", function() - setup(function() - live_command._set_logger(require("live-command.logger")) - end) - - -- Checks for the case when the end of the line was unchanged - it("single insertion", function() - local highlights = {} - local updated_lines = { "new word" } - live_command._add_inline_highlights(1, { "word" }, updated_lines, true, highlights) - - assert.are_same({ - { kind = "insertion", line = 1, column = 1, length = 4 }, - }, highlights) - assert.are_same({ "new word" }, updated_lines) - end) - - it("insertions", function() - local highlights = {} - local updated_lines = { "new word new" } - live_command._add_inline_highlights(1, { "word" }, updated_lines, true, highlights) - - assert.are_same({ - { kind = "insertion", line = 1, column = 1, length = 4 }, - { kind = "insertion", line = 1, column = 9, length = 4 }, - }, highlights) - assert.are_same({ "new word new" }, updated_lines) - end) - - it("insertions + deletion", function() - local highlights = {} - local updated_lines = { "test1" } - live_command._add_inline_highlights(1, { "a test" }, updated_lines, true, highlights) - - assert.are_same({ - { kind = "deletion", line = 1, column = 1, length = 2 }, - { kind = "insertion", line = 1, column = 7, length = 1 }, - }, highlights) - assert.are_same({ "a test1" }, updated_lines) - end) - - it("change 1", function() - local highlights = {} - local updated_lines = { " x.insert" } - live_command._add_inline_highlights(1, { " table.insert" }, updated_lines, true, highlights) - - assert.are_same({ - { kind = "change", line = 1, column = 7, length = 1 }, - }, highlights) - assert.are_same({ " x.insert" }, updated_lines) - end) - - it("change 2", function() - local highlights = {} - local updated_lines = { "test = Function()" } - live_command._add_inline_highlights(1, { "config = function()" }, updated_lines, true, highlights) - assert.are_same({ - { kind = "change", line = 1, column = 1, length = 4 }, - { kind = "change", line = 1, column = 8, length = 1 }, - }, highlights) - assert.are_same({ "test = Function()" }, updated_lines) - end) - - it("change should not use negative column values", function() - local highlights = {} - local updated_lines = { "tes" } - live_command._add_inline_highlights(1, { "line" }, updated_lines, true, highlights) - - assert.are_same({ - { kind = "change", line = 1, column = 1, length = 1 }, - { kind = "insertion", line = 1, column = 3, length = 1 }, - }, highlights) - assert.are_same({ "tes" }, updated_lines) - end) - - -- TODO: create the same test but when undo_deletions = false - it("change + deletion", function() - local highlights = {} - local updated_lines = { "lel" } - live_command._add_inline_highlights(1, { "-- require plugins.nvim-surround" }, updated_lines, true, highlights) - assert.are_same({ - { kind = "change", line = 1, column = 1, length = 1 }, - { kind = "deletion", line = 1, column = 3, length = 2 }, - { kind = "deletion", line = 1, column = 6, length = 19 }, - }, highlights) - assert.are_same({ "le plugins.nvim-surround" }, updated_lines) - end) - - it("deletion", function() - local highlights = {} - local updated_lines = { "local s" } - live_command._add_inline_highlights(1, { "local tests" }, updated_lines, true, highlights) - assert.are_same({ - { kind = "deletion", line = 1, column = 7, length = 4 }, - }, highlights) - assert.are_same({ "local tests" }, updated_lines) - end) -end) From 2ed5c5058160410d15d32ec1fa7d364d294dfab5 Mon Sep 17 00:00:00 2001 From: smjonas Date: Sun, 15 Sep 2024 13:33:32 +0200 Subject: [PATCH 23/24] test: fix failing e2e tests --- tests/e2e_spec.lua | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua index 0c98d44..9f2bd1b 100644 --- a/tests/e2e_spec.lua +++ b/tests/e2e_spec.lua @@ -2,55 +2,44 @@ local live_command = require("live-command") local api = vim.api local bufnr -local create_preview_cmd = function(cmd_name, cmd_opts) - api.nvim_set_current_buf(bufnr) - live_command.create_previewable_command(cmd_name, cmd_opts) -end - -local preview = function(cmd) - api.nvim_set_current_buf(bufnr) - vim.cmd("Preview " .. cmd) -end local get_lines = function() return api.nvim_buf_get_lines(bufnr, 0, -1, false) end -setup(function() - live_command.setup() -end) - before_each(function() bufnr = api.nvim_create_buf(false, true) api.nvim_buf_set_lines(bufnr, 0, -1, false, { "First line", "Second line" }) + api.nvim_set_current_buf(bufnr) end) describe("create_preview_command works for", function() it("norm command", function() - create_preview_cmd("Norm", { cmd = "norm" }) + live_command.create_previewable_command("Norm", { cmd = "norm" }) vim.cmd("Norm daw") assert.are_same({ "line", "Second line" }, get_lines()) end) it("norm command with count", function() - create_preview_cmd("Norm", { cmd = "norm" }) + live_command.create_previewable_command("Norm", { cmd = "norm" }) vim.cmd("2Norm daw") assert.are_same({ "First line", "line" }, get_lines()) end) it("norm command with range", function() - create_preview_cmd("Norm", { cmd = "norm" }) + live_command.create_previewable_command("Norm", { cmd = "norm" }) vim.cmd("1,2Norm daw") assert.are_same({ "line", "line" }, get_lines()) end) it("g command", function() - create_preview_cmd("G", { cmd = "g" }) + live_command.create_previewable_command("G", { cmd = "g" }) vim.cmd("G/Second/d") assert.are_same({ "First line" }, get_lines()) end) - it("command spec in config", function() + it("#kek command spec in config", function() + vim.api.nvim_set_current_buf(bufnr) live_command.setup { commands = { ABC = { cmd = "norm" }, @@ -63,20 +52,17 @@ end) describe(":Preview works for", function() it("norm command", function() - local cmd = "norm daw" - preview(cmd) + vim.cmd("Preview norm daw") assert.are_same({ "line", "Second line" }, get_lines()) end) it("norm command with count", function() - local cmd = "2norm daw" - preview(cmd) + vim.cmd("Preview 2norm daw") assert.are_same({ "First line", "line" }, get_lines()) end) it("g command", function() - local cmd = "g/Second/d" - preview(cmd) + vim.cmd("Preview g/Second/d") assert.are_same({ "First line" }, get_lines()) end) end) From ee2242872bda27bc0831b5ef99e3ab51226d9b8d Mon Sep 17 00:00:00 2001 From: smjonas Date: Sun, 15 Sep 2024 13:56:48 +0200 Subject: [PATCH 24/24] Release version 2.0.0 --- lua/live-command/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua index 613cd82..b7068e7 100644 --- a/lua/live-command/init.lua +++ b/lua/live-command/init.lua @@ -87,7 +87,7 @@ M.create_preview_command = function(preview_cmd_name) nargs = "*", preview = function(opts, preview_ns, preview_buf) local cmd_to_preview = opts.args - return preview_callback(cmd_to_preview, preview_ns, preview_buf) + return M.preview_callback(cmd_to_preview, preview_ns, preview_buf) end, }) end