Skip to content

Commit 94cf1d5

Browse files
Grolafstevearc
authored andcommitted
feat: add undojoin as a format option (nvim-lua#488)
* feat: add new format option: undojoin This option allow user to automatically perform undojoin command before formatting. This is useful if the user uses an autosave plugin + format on save, because in case of undo, it will undo the last change AND the formatting. Without this option, it will only undo the formatting. * fix: passed linting * fix: apply undojoin for LSP formatting * doc: fix type annotations for apply_format * doc: regenerate documentation --------- Co-authored-by: Steven Arcangeli <[email protected]>
1 parent ad2aff9 commit 94cf1d5

File tree

6 files changed

+50
-13
lines changed

6 files changed

+50
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ Format a buffer
593593
| | bufnr | `nil\|integer` | Format this buffer (default 0) |
594594
| | async | `nil\|boolean` | If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded. |
595595
| | dry_run | `nil\|boolean` | If true don't apply formatting changes to the buffer |
596+
| | undojoin | `nil\|boolean` | Use undojoin to merge formatting changes with previous edit (default false) |
596597
| | formatters | `nil\|string[]` | List of formatters to run. Defaults to all formatters for the buffer filetype. |
597598
| | lsp_format | `nil\|conform.LspFormatOpts` | Configure if and when LSP should be used for formatting. Defaults to "never". |
598599
| | | > `"never"` | never use the LSP for formatting (default) |

doc/conform.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ format({opts}, {callback}): boolean *conform.forma
150150
completes, the formatting will be discarded.
151151
{dry_run} `nil|boolean` If true don't apply formatting changes to
152152
the buffer
153+
{undojoin} `nil|boolean` Use undojoin to merge formatting changes
154+
with previous edit (default false)
153155
{formatters} `nil|string[]` List of formatters to run. Defaults to all
154156
formatters for the buffer filetype.
155157
{lsp_format} `nil|conform.LspFormatOpts` Configure if and when LSP

lua/conform/init.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ end
340340
---@field bufnr nil|integer Format this buffer (default 0)
341341
---@field async nil|boolean If true the method won't block. Defaults to false. If the buffer is modified before the formatter completes, the formatting will be discarded.
342342
---@field dry_run nil|boolean If true don't apply formatting changes to the buffer
343+
---@field undojoin nil|boolean Use undojoin to merge formatting changes with previous edit (default false)
343344
---@field formatters nil|string[] List of formatters to run. Defaults to all formatters for the buffer filetype.
344345
---@field lsp_format? conform.LspFormatOpts Configure if and when LSP should be used for formatting. Defaults to "never".
345346
---@field quiet nil|boolean Don't show any notifications for warnings or failures. Defaults to false.
@@ -353,14 +354,15 @@ end
353354
---@param callback? fun(err: nil|string, did_edit: nil|boolean) Called once formatting has completed
354355
---@return boolean True if any formatters were attempted
355356
M.format = function(opts, callback)
356-
---@type {timeout_ms: integer, bufnr: integer, async: boolean, dry_run: boolean, lsp_format: "never"|"first"|"last"|"prefer"|"fallback", quiet: boolean, formatters?: string[], range?: conform.Range}
357+
---@type {timeout_ms: integer, bufnr: integer, async: boolean, dry_run: boolean, lsp_format: "never"|"first"|"last"|"prefer"|"fallback", quiet: boolean, formatters?: string[], range?: conform.Range, undojoin: boolean}
357358
opts = vim.tbl_extend("keep", opts or {}, {
358359
timeout_ms = 1000,
359360
bufnr = 0,
360361
async = false,
361362
dry_run = false,
362363
lsp_format = "never",
363364
quiet = false,
365+
undojoin = false,
364366
})
365367

366368
-- For backwards compatibility
@@ -430,7 +432,8 @@ M.format = function(opts, callback)
430432
return f.name
431433
end, formatters)
432434
log.debug("Running formatters on %s: %s", vim.api.nvim_buf_get_name(opts.bufnr), resolved_names)
433-
local run_opts = { exclusive = true, dry_run = opts.dry_run }
435+
---@type conform.RunOpts
436+
local run_opts = { exclusive = true, dry_run = opts.dry_run, undojoin = opts.undojoin }
434437
if opts.async then
435438
runner.format_async(opts.bufnr, formatters, opts.range, run_opts, cb)
436439
else
@@ -517,7 +520,8 @@ M.format_lines = function(formatter_names, lines, opts, callback)
517520
callback(err, new_lines)
518521
end
519522

520-
local run_opts = { exclusive = false, dry_run = false }
523+
---@type conform.RunOpts
524+
local run_opts = { exclusive = false, dry_run = false, undojoin = false }
521525
if opts.async then
522526
runner.format_lines_async(opts.bufnr, formatters, nil, lines, run_opts, handle_err)
523527
else

lua/conform/lsp_format.lua

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local util = require("vim.lsp.util")
44

55
local M = {}
66

7-
local function apply_text_edits(text_edits, bufnr, offset_encoding, dry_run)
7+
local function apply_text_edits(text_edits, bufnr, offset_encoding, dry_run, undojoin)
88
if
99
#text_edits == 1
1010
and text_edits[1].range.start.line == 0
@@ -25,11 +25,15 @@ local function apply_text_edits(text_edits, bufnr, offset_encoding, dry_run)
2525
new_lines,
2626
nil,
2727
false,
28-
dry_run
28+
dry_run,
29+
undojoin
2930
)
3031
elseif dry_run then
3132
return #text_edits > 0
3233
else
34+
if undojoin then
35+
vim.cmd.undojoin()
36+
end
3337
vim.lsp.util.apply_text_edits(text_edits, bufnr, offset_encoding)
3438
return #text_edits > 0
3539
end
@@ -124,8 +128,13 @@ function M.format(options, callback)
124128
)
125129
)
126130
else
127-
local this_did_edit =
128-
apply_text_edits(result, ctx.bufnr, client.offset_encoding, options.dry_run)
131+
local this_did_edit = apply_text_edits(
132+
result,
133+
ctx.bufnr,
134+
client.offset_encoding,
135+
options.dry_run,
136+
options.undojoin
137+
)
129138
changedtick = vim.b[bufnr].changedtick
130139

131140
if options.dry_run and this_did_edit then
@@ -145,8 +154,13 @@ function M.format(options, callback)
145154
local params = set_range(client, util.make_formatting_params(options.formatting_options))
146155
local result, err = client.request_sync(method, params, timeout_ms, bufnr)
147156
if result and result.result then
148-
local this_did_edit =
149-
apply_text_edits(result.result, bufnr, client.offset_encoding, options.dry_run)
157+
local this_did_edit = apply_text_edits(
158+
result.result,
159+
bufnr,
160+
client.offset_encoding,
161+
options.dry_run,
162+
options.undojoin
163+
)
150164
did_edit = did_edit or this_did_edit
151165

152166
if options.dry_run and did_edit then

lua/conform/runner.lua

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local M = {}
99
---@class (exact) conform.RunOpts
1010
---@field exclusive boolean If true, ensure only a single formatter is running per buffer
1111
---@field dry_run boolean If true, do not apply changes and stop after the first formatter attempts to do so
12+
---@field undojoin boolean Use undojoin to merge formatting changes with previous edit
1213

1314
---@param formatter_name string
1415
---@param ctx conform.Context
@@ -168,8 +169,18 @@ end
168169
---@param new_lines string[]
169170
---@param range? conform.Range
170171
---@param only_apply_range boolean
172+
---@param dry_run boolean
173+
---@param undojoin boolean
171174
---@return boolean any_changes
172-
M.apply_format = function(bufnr, original_lines, new_lines, range, only_apply_range, dry_run)
175+
M.apply_format = function(
176+
bufnr,
177+
original_lines,
178+
new_lines,
179+
range,
180+
only_apply_range,
181+
dry_run,
182+
undojoin
183+
)
173184
if bufnr == 0 then
174185
bufnr = vim.api.nvim_get_current_buf()
175186
end
@@ -251,6 +262,9 @@ M.apply_format = function(bufnr, original_lines, new_lines, range, only_apply_ra
251262

252263
if not dry_run then
253264
log.trace("Applying text edits: %s", text_edits)
265+
if undojoin then
266+
vim.cmd.undojoin()
267+
end
254268
vim.lsp.util.apply_text_edits(text_edits, bufnr, "utf-8")
255269
log.trace("Done formatting %s", bufname)
256270
end
@@ -535,7 +549,8 @@ M.format_async = function(bufnr, formatters, range, opts, callback)
535549
output_lines,
536550
range,
537551
not all_support_range_formatting,
538-
opts.dry_run
552+
opts.dry_run,
553+
opts.undojoin
539554
)
540555
end
541556
callback(err, did_edit)
@@ -609,7 +624,8 @@ M.format_sync = function(bufnr, formatters, timeout_ms, range, opts)
609624
final_result,
610625
range,
611626
not all_support_range_formatting,
612-
opts.dry_run
627+
opts.dry_run,
628+
opts.undojoin
613629
)
614630
return err, did_edit
615631
end

tests/fuzzer_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe("fuzzer", function()
2525
vim.api.nvim_set_current_buf(bufnr)
2626
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, buf_content)
2727
vim.bo[bufnr].modified = false
28-
runner.apply_format(0, buf_content, expected, nil, false)
28+
runner.apply_format(0, buf_content, expected, nil, false, false, false)
2929
assert.are.same(expected, vim.api.nvim_buf_get_lines(0, 0, -1, false))
3030
end
3131

0 commit comments

Comments
 (0)