diff --git a/README.md b/README.md
index 8c5405c..152a3fd 100644
--- a/README.md
+++ b/README.md
@@ -1,77 +1,82 @@
# live-command.nvim
-
+
-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!
+> :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: see the effects of any command on your buffer in real-time. Preview macros, the `:norm` command, and more!

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*.
+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* they wish to preview.
-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.
+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+
-## :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 via 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,
}
```
+
-## :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.
+
+ vim-plug
-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).
+```vim
+Plug 'smjonas/live-command.nvim'
+```
+In your `init.lua`, call the setup function:
```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()
+```
+
+## :rocket: Getting started
+### Basic Usage
+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 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 = commands,
+ commands = {
+ Norm = { cmd = "norm" },
+ },
}
```
-\
-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:
+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 (affecting all custom commands), or per command.
+
+To change the default options globally, use the `defaults` table. The default settings are:
```lua
require("live-command").setup {
@@ -93,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.
---
@@ -101,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.
---
@@ -111,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/lua/live-command/cmd_executor.lua b/lua/live-command/cmd_executor.lua
new file mode 100644
index 0000000..53df784
--- /dev/null
+++ b/lua/live-command/cmd_executor.lua
@@ -0,0 +1,86 @@
+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(bufnr)
+ prev_lazyredraw = vim.o.lazyredraw
+ vim.o.lazyredraw = true
+ if refetch_lines then
+ 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(do_refetch_lines)
+ vim.o.lazyredraw = prev_lazyredraw
+ 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(bufnr)
+ local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") }
+ 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$")),
+ }
+ local new_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ return old_buf_lines, new_buf_lines, visible_line_range
+end
+
+---@param cmd string
+---@param opts livecmd.Config
+---@param bufnr number
+---@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, 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, 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
+
+return M
diff --git a/lua/live-command/config_validator.lua b/lua/live-command/config_validator.lua
new file mode 100644
index 0000000..3d70ef8
--- /dev/null
+++ b/lua/live-command/config_validator.lua
@@ -0,0 +1,56 @@
+local M = {}
+
+local user_command = require("live-command.user_command")
+
+---@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?
+---@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 {
+ command_name = { config.command_name, "string" },
+ enable_highlighting = { config.enable_highlighting, "boolean" },
+ inline_highlighting = { config.inline_highlighting, "boolean" },
+ 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/lua/live-command/differ.lua b/lua/live-command/differ.lua
new file mode 100644
index 0000000..64120d9
--- /dev/null
+++ b/lua/live-command/differ.lua
@@ -0,0 +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
new file mode 100644
index 0000000..3050d57
--- /dev/null
+++ b/lua/live-command/highlighter.lua
@@ -0,0 +1,154 @@
+local M = {}
+
+---@class livecmd.Highlight
+---@field line number
+---@field column number
+---@field length number
+---@field kind string
+
+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
+
+--- @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()
+ 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, new_lines
+ end
+
+ if line >= line_range[1] then
+ if hunk_kind == "deletion" and 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, new_lines
+ end
+
+ if line >= line_range[1] then
+ if inline_highlighting then
+ -- Get diff for each line in the hunk
+ 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 })
+ end
+ end
+ end
+ end
+ end
+ return highlights, new_lines
+end
+
+return M
diff --git a/lua/live-command/init.lua b/lua/live-command/init.lua
index f9f983d..b7068e7 100644
--- a/lua/live-command/init.lua
+++ b/lua/live-command/init.lua
@@ -1,6 +1,8 @@
local M = {}
-M.defaults = {
+---@type livecmd.Config
+M.default_config = {
+ command_name = "Preview",
enable_highlighting = true,
inline_highlighting = true,
hl_groups = {
@@ -8,245 +10,28 @@ M.defaults = {
deletion = "DiffDelete",
change = "DiffChange",
},
+ commands = {},
}
+local cmd_executor
local api = vim.api
----@type Logger
-local logger
+---@type livecmd.Config
+local merged_config
-local should_cache_lines = true
-local cached_lines
-local prev_lazyredraw
+---@type string[]?
+local received_lines
--- 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 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
- end
- 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
- 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$")),
- }
+---@type livecmd.Highlight[]?
+local received_highlights
- 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)
+---@param bufnr number
+---@param preview_ns number
+---@param highlights livecmd.Highlight[]
+---@param hl_groups table
+local apply_highlights = function(bufnr, preview_ns, highlights, hl_groups)
for _, hl in ipairs(highlights) do
- local hl_group = command.hl_groups[hl.kind]
+ local hl_group = hl_groups[hl.kind]
if hl_group ~= false then
api.nvim_buf_add_highlight(
bufnr,
@@ -258,112 +43,109 @@ local function command_preview(opts, preview_ns, preview_buf)
)
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)))
+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
end
-local function execute_command(command)
- logger.trace("Executing command: " .. command)
- vim.cmd(command)
- restore_buffer_state()
+local on_receive_buffer = function(bufnr, lines, highlights)
+ received_lines = lines
+ received_highlights = highlights
+ refresh_cmd_preview()
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,
- })
+---@param cmd string
+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
+ 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
-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)
+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
- 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
+M._test_mode = false
+
+---@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 M.preview_callback(cmd_to_preview, preview_ns, preview_buf)
+ 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
+---@class livecmd.CommandSpec
+---@field cmd string
+
+---@param cmd_name string
+---@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(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 = M.get_range_string(cmd) .. cmd_specs.cmd .. " " .. cmd.args
+ return M.preview_callback(cmd_to_preview, preview_ns, preview_buf)
+ end,
+ })
+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")
+---@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.
api.nvim_create_autocmd({ "CmdLineLeave" }, {
group = id,
-- Schedule wrap to run after a potential command execution
callback = vim.schedule_wrap(function()
- restore_buffer_state()
+ cmd_executor.teardown(true)
end),
})
end
----@param logger_ Logger
-M._set_logger = function(logger_)
- logger = logger_
+---@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 version of Neovim.",
+ vim.log.levels.ERROR
+ )
+ return
+ end
+ 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)
+ M.create_preview_command(merged_config.command_name)
+ create_previewable_commands(merged_config.commands)
+ create_autocmds()
end
-M.version = "1.2.1"
+M.version = "2.0.0"
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
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
diff --git a/migrate_to_v2.md b/migrate_to_v2.md
new file mode 100644
index 0000000..0374bae
--- /dev/null
+++ b/migrate_to_v2.md
@@ -0,0 +1,60 @@
+# Migration to v2.0
+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'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 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**:
+- 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 demonstrated 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 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. Alternatively, define a custom `:Reg` user command that bevaves like the old version:
+
+
+ View code
+
+```lua
+-- 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
+ 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
+})
+```
+
diff --git a/tests/e2e_spec.lua b/tests/e2e_spec.lua
new file mode 100644
index 0000000..9f2bd1b
--- /dev/null
+++ b/tests/e2e_spec.lua
@@ -0,0 +1,68 @@
+local live_command = require("live-command")
+local api = vim.api
+
+local bufnr
+
+local get_lines = function()
+ return api.nvim_buf_get_lines(bufnr, 0, -1, false)
+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()
+ 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()
+ 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()
+ 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()
+ live_command.create_previewable_command("G", { cmd = "g" })
+ vim.cmd("G/Second/d")
+ assert.are_same({ "First line" }, get_lines())
+ end)
+
+ it("#kek command spec in config", function()
+ vim.api.nvim_set_current_buf(bufnr)
+ 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()
+ it("norm command", function()
+ vim.cmd("Preview norm daw")
+ assert.are_same({ "line", "Second line" }, get_lines())
+ end)
+
+ it("norm command with count", function()
+ vim.cmd("Preview 2norm daw")
+ assert.are_same({ "First line", "line" }, get_lines())
+ end)
+
+ it("g command", function()
+ vim.cmd("Preview g/Second/d")
+ assert.are_same({ "First line" }, get_lines())
+ end)
+end)
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)