Skip to content

Commit 558a0f1

Browse files
authored
Merge pull request #34 from smjonas/rewrite
Rewrite for better architecture, easier extensibility; add a general :Preview command
2 parents 79f89a2 + ee22428 commit 558a0f1

File tree

13 files changed

+795
-475
lines changed

13 files changed

+795
-475
lines changed

README.md

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,82 @@
11
# live-command.nvim
2-
![version](https://img.shields.io/badge/version-1.2.1-brightgreen)
2+
![version](https://img.shields.io/badge/version-2.0.0-brightgreen)
33

4-
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!
4+
> :exclamation: Version 2.0 has been released with breaking changes! Be sure to check out the [migration guide](./migrate_to_v2.md).
5+
6+
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!
57

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

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

15-
This plugin tries to change that: it provides a **simple API for creating previewable commands**
16-
in Neovim. Just specify the command you want to run and live-command will do all the
17-
work for you. This includes viewing **individual insertions, changes and deletions** as you
18-
type.
16+
This plugin addresses that limitation by offering a **simple API for creating previewable commands**
17+
in Neovim. Just specify the command you want to preview and live-command will handle the rest.
18+
This includes viewing **individual insertions, changes and deletions** as you type.
1919

2020
## Requirements
2121
Neovim 0.8+
2222

23-
## :rocket: Getting started
24-
Install using your favorite package manager and call the setup function with a table of
25-
commands to create. Here is an example that creates a previewable `:Norm` command:
23+
## :inbox_tray: Installation
24+
Install via your favorite package manager and call the `setup` function:
25+
26+
<details>
27+
<summary>lazy.nvim</summary>
28+
2629
```lua
2730
use {
2831
"smjonas/live-command.nvim",
29-
-- live-command supports semantic versioning via tags
30-
-- tag = "1.*",
32+
-- live-command supports semantic versioning via Git tags
33+
-- tag = "2.*",
3134
config = function()
32-
require("live-command").setup {
33-
commands = {
34-
Norm = { cmd = "norm" },
35-
},
36-
}
35+
require("live-command").setup()
3736
end,
3837
}
3938
```
39+
</details>
4040

41-
## :gear: Usage and Customization
42-
Each command you want to preview requires a name (must be upper-case) and the name of
43-
an existing command that is run on each keypress.
41+
<details>
42+
<summary>vim-plug</summary>
4443

45-
Here is a list of available settings:
46-
47-
| Key | Type | Description
48-
| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------
49-
| cmd | string | The name of an existing command to preview.
50-
| 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.
51-
| 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.
52-
53-
### Example
54-
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).
44+
```vim
45+
Plug 'smjonas/live-command.nvim'
46+
```
47+
In your `init.lua`, call the setup function:
5548
```lua
56-
local commands = {
57-
Reg = {
58-
cmd = "norm",
59-
-- This will transform ":5Reg a" into ":norm 5@a"
60-
args = function(opts)
61-
return (opts.count == -1 and "" or opts.count) .. "@" .. opts.args
62-
end,
63-
range = "",
64-
},
65-
}
49+
require("live-command").setup()
50+
```
51+
</details>
6652

53+
## :rocket: Getting started
54+
### Basic Usage
55+
The easiest way to use **live-command** is with the provided `:Preview` command.
56+
For example, `:Preview delete` will show you a preview of deleting the current line.
57+
You can also provide a count or a range to the command, such as `:'<,'>Preview norm A;`, which
58+
shows the effect of appending a semicolon to every visually selected line.
59+
60+
### Creating Previewable Commands
61+
For a more convenient experience, **live-command** allows you to define custom previewable commands.
62+
This can be done by passing a list of commands to the `setup` function.
63+
For instance, to define a custom `:Norm` command that can be previewed, use the following:
64+
```lua
6765
require("live-command").setup {
68-
commands = commands,
66+
commands = {
67+
Norm = { cmd = "norm" },
68+
},
6969
}
7070
```
71-
\
72-
All of the following options can be set globally (for all created commands), or per command.
7371

74-
To change the default options globally, use the `defaults` table. The defaults are:
72+
Each command you want to preview needs a name (which must be uppercase) and
73+
an existing command to run on each keypress, specified via the `cmd` field.
74+
75+
## :gear: Customization
76+
77+
All of the following options can be set globally (affecting all custom commands), or per command.
78+
79+
To change the default options globally, use the `defaults` table. The default settings are:
7580

7681
```lua
7782
require("live-command").setup {
@@ -93,28 +98,28 @@ require("live-command").setup {
9398

9499
Default: `true`
95100

96-
Whether highlights should be shown. If `false`, only text changes are shown.
101+
Determines whether highlights should be shown. If `false`, only text changes are shown, without any highlights.
97102

98103
---
99104

100105
`inline_highlighting: boolean`
101106

102107
Default: `true`
103108

104-
If `true`, differing lines will be compared in a second run of the diff algorithm. This
105-
can result in multiple highlights per line. Otherwise, the whole line will be highlighted as
106-
a single change highlight.
109+
If `true`, differing lines will be compared in a second run of the diff algorithm
110+
to identify smaller differences. This can result in multiple highlights per line.
111+
If set to `false`, the whole line will be highlighted as a single change.
107112

108113
---
109114

110115
`hl_groups: table<string, string|boolean>`
111116

112117
Default: `{ insertion = "DiffAdd", deletion = "DiffDelete", change = "DiffChange" }`
113118

114-
A list of highlight groups per edit type (insertion, deletion or change) used for highlighting buffer changes.
115-
The table will be merged with the defaults so you can omit any keys that are the same as the default.
116-
If a value is set to `false`, no highlights will be shown for that type. If `hl_groups.deletion` is `false`,
117-
deletion edits will not be undone which is otherwise done to make the text changes visible.
119+
A table mapping edit types (insertion, deletion or change) to highlight groups used for highlighting buffer changes.
120+
This table is merged with the defaults, allowing you to omit any keys that match the default.
121+
If a value is set to `false`, no highlights will be shown for that type.
122+
If `hl_groups.deletion` is `false`, deletion edits will not be undone, so deleted text won't be highlighted.
118123

119124
---
120125

lua/live-command/cmd_executor.lua

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
local M = {}
2+
3+
local differ = require("live-command.differ")
4+
local highlighter = require("live-command.highlighter")
5+
local logger = require("live-command.logger")
6+
7+
---@type string
8+
local latest_cmd
9+
10+
local running = false
11+
12+
local refetch_lines = true
13+
14+
---@tyle string[]
15+
local cached_lines
16+
17+
---@type boolean
18+
local prev_lazyredraw
19+
20+
local setup = function(bufnr)
21+
prev_lazyredraw = vim.o.lazyredraw
22+
vim.o.lazyredraw = true
23+
if refetch_lines then
24+
cached_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
25+
refetch_lines = false
26+
else
27+
logger.trace("did not refetch for cmd " .. latest_cmd)
28+
end
29+
return cached_lines
30+
end
31+
32+
M.teardown = function(do_refetch_lines)
33+
vim.o.lazyredraw = prev_lazyredraw
34+
refetch_lines = do_refetch_lines
35+
if vim.v.errmsg ~= "" then
36+
logger.error(("An error occurred in the preview function:\n%s"):format(vim.inspect(vim.v.errmsg)))
37+
end
38+
end
39+
40+
local execute_command = function(cmd, bufnr)
41+
local old_buf_lines = setup(bufnr)
42+
local visible_line_range = { vim.fn.line("w0"), vim.fn.line("w$") }
43+
vim.api.nvim_buf_call(bufnr, function()
44+
vim.cmd(cmd)
45+
end)
46+
-- M.teardown(false)
47+
visible_line_range = {
48+
math.max(visible_line_range[1], vim.fn.line("w0")),
49+
math.max(visible_line_range[2], vim.fn.line("w$")),
50+
}
51+
local new_buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
52+
return old_buf_lines, new_buf_lines, visible_line_range
53+
end
54+
55+
---@param cmd string
56+
---@param opts livecmd.Config
57+
---@param bufnr number
58+
---@param update_buffer_cb fun(bufnr:number,updated_buffer_lines:string[],highlights:livecmd.Highlight[]?)
59+
M.submit_command = function(cmd, opts, bufnr, update_buffer_cb)
60+
if cmd == latest_cmd then
61+
return
62+
end
63+
latest_cmd = cmd
64+
if not running then
65+
running = true
66+
local old_buf_lines, new_buf_lines, line_range = execute_command(cmd, bufnr)
67+
if not opts.enable_highlighting then
68+
update_buffer_cb(bufnr, new_buf_lines, nil)
69+
running = false
70+
return
71+
end
72+
local diff = differ.get_diff(old_buf_lines, new_buf_lines)
73+
local highlights, updated_buf_lines = highlighter.get_highlights(
74+
diff,
75+
old_buf_lines,
76+
new_buf_lines,
77+
line_range,
78+
opts.inline_highlighting,
79+
opts.hl_groups.deletion ~= false
80+
)
81+
update_buffer_cb(bufnr, updated_buf_lines, highlights)
82+
running = false
83+
end
84+
end
85+
86+
return M

lua/live-command/config_validator.lua

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
local M = {}
2+
3+
local user_command = require("live-command.user_command")
4+
5+
---@class livecmd.Config.HlGroups
6+
---@field insertion string|false
7+
---@field deletion string|false
8+
---@field change string|false
9+
10+
---@class livecmd.Config
11+
---@field command_name string?
12+
---@field enable_highlighting boolean?
13+
---@field inline_highlighting boolean?
14+
---@field hl_groups livecmd.Config.HlGroups?
15+
---@field commands table<string, livecmd.CommandSpec>
16+
17+
local show_diagnostics_message = function(config)
18+
local message = [[
19+
Version 2.0 of live-command.nvim has dropped support for the "args" and "range" keys in the command specification.
20+
The following commands in your configuration are affected: %s. Please remove or modify them.
21+
See the migration guide for more information: https://github.com/smjonas/live-command.nvim/blob/main/migrate_to_v2.md
22+
]]
23+
local affected_cmds = {}
24+
for cmd_name, cmd_spec in pairs(config.commands) do
25+
if cmd_spec.args ~= nil or cmd_spec.range ~= nil then
26+
table.insert(affected_cmds, '"' .. cmd_name .. '"')
27+
end
28+
end
29+
local cmd_names = table.concat(affected_cmds, ", ")
30+
local formatted_message = string.format(message, cmd_names)
31+
vim.notify(formatted_message, vim.log.levels.INFO)
32+
end
33+
34+
---@param config livecmd.Config
35+
M.validate_config = function(config)
36+
vim.validate {
37+
command_name = { config.command_name, "string" },
38+
enable_highlighting = { config.enable_highlighting, "boolean" },
39+
inline_highlighting = { config.inline_highlighting, "boolean" },
40+
hl_groups = { config.hl_groups, "table" },
41+
commands = { config.commands, "table" },
42+
}
43+
for cmd_name, cmd_spec in pairs(config.commands) do
44+
if cmd_spec.args ~= nil or cmd_spec.range ~= nil then
45+
vim.notify(
46+
'[live-command.nvim] Some unsupported features are used in your config. Please run ":LiveCommand diagnose" for details.',
47+
vim.log.levels.WARN
48+
)
49+
user_command.register_argument_handler("diagnose", function()
50+
show_diagnostics_message(config)
51+
end)
52+
end
53+
end
54+
end
55+
56+
return M

lua/live-command/differ.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
local M = {}
2+
3+
M.get_diff = function(old_lines, new_lines)
4+
return vim.diff(table.concat(old_lines, "\n"), table.concat(new_lines, "\n"), {
5+
result_type = "indices",
6+
})
7+
end
8+
9+
return M

0 commit comments

Comments
 (0)