Skip to content

source.organizeImports code action sends edits even when none are necessary #2523

@nojnhuh

Description

@nojnhuh

Zig Version

0.15.2

ZLS Version

0.15.0

Client / Code Editor / Extensions

neovim v0.11.5 with built-in LSP, no plugins

Steps to Reproduce and Observed Behavior

Start neovim with the following init.lua:

vim.lsp.set_log_level(vim.lsp.log_levels.DEBUG)

vim.lsp.config("zls", {
	cmd = { "/home/jon/github/zls/zig-out/bin/zls" }, -- built with `zig build`
})
vim.lsp.enable("zls")

And the following test.zig file:

const a = @import("a");
nvim --clean -u init.lua test.zig

Then run the following command in neovim to invoke the code action:

:lua vim.lsp.buf.code_action({context = {only = {"source.organizeImports"}}, apply = true})

Neovim's LSP client logs show that edits are being sent even though they don't result in any changes:

[DEBUG][2025-11-29 15:24:48] ...m/lsp/client.lua:674	"LSP[zls]"	"client.request"	1	"textDocument/codeAction"	{ context = { diagnostics = {}, only = { "source.organizeImports" }, triggerKind = 1 }, range = { ["end"] = <1>{ character = 11, line = 2 }, start = <table 1> }, textDocument = { uri = "file:///tmp/test.zig" } }	<function 1>	1
[DEBUG][2025-11-29 15:24:48] .../vim/lsp/rpc.lua:277	"rpc.send"	{ id = 3, jsonrpc = "2.0", method = "textDocument/codeAction", params = { context = { diagnostics = {}, only = { "source.organizeImports" }, triggerKind = 1 }, range = { ["end"] = <1>{ character = 11, line = 2 }, start = <table 1> }, textDocument = { uri = "file:///tmp/test.zig" } } }
[DEBUG][2025-11-29 15:24:48] .../vim/lsp/rpc.lua:391	"rpc.receive"	{ id = 3, jsonrpc = "2.0", result = { { edit = { changes = { ["file:///tmp/test.zig"] = { { newText = 'const a = @import("a");\n\n', range = { ["end"] = { character = 0, line = 0 }, start = { character = 0, line = 0 } } }, { newText = "", range = { ["end"] = { character = 0, line = 2 }, start = { character = 0, line = 0 } } } } } }, isPreferred = true, kind = "source.organizeImports", title = "organize @import" } } }

At least for Neovim, those edits still dirty the buffer and add to the undo stack. When I make a change unrelated to imports, a "change, organizeImports, undo" sequence to return to the contents of the file before the change actually requires two undos (one for the no-op organizeImports edits and one for the change I wanted to undo). It also seems to cause diagnostics displayed with virtual text to flicker when the code action is run.

Expected Behavior

When a code action's edits result in no changes, those edits should not be sent to the client. I don't see where that assumption is explicitly stated in the spec, but servers are allowed to return null from code action requests.

For one point of reference, gopls does what I expect and sends no edits in that case:

[DEBUG][2025-11-29 15:21:55] ...m/lsp/client.lua:674	"LSP[gopls]"	"client.request"	1	"textDocument/codeAction"	{ context = { diagnostics = {}, only = { "source.organizeImports" }, triggerKind = 2 }, range = { ["end"] = <1>{ character = 0, line = 0 }, start = <table 1> }, textDocument = { uri = "file:///home/jon/github/capz/main.go" } }	<function 1>	1
[DEBUG][2025-11-29 15:21:55] .../vim/lsp/rpc.lua:277	"rpc.send"	{ id = 5, jsonrpc = "2.0", method = "textDocument/codeAction", params = { context = { diagnostics = {}, only = { "source.organizeImports" }, triggerKind = 2 }, range = { ["end"] = <1>{ character = 0, line = 0 }, start = <table 1> }, textDocument = { uri = "file:///home/jon/github/capz/main.go" } } }
[DEBUG][2025-11-29 15:21:55] .../vim/lsp/rpc.lua:391	"rpc.receive"	{ id = 5, jsonrpc = "2.0" }

Log Output

info  ( main ): Starting ZLS      0.15.0 @ '/home/jon/github/zls/zig-out/bin/zls'
info  ( main ): Log File:         /home/jon/.cache/zls/zls.log (debug)
info  ( main ): Loaded config:    /home/jon/.config/zls.json
info  (server): Client Info:      Neovim (0.11.5+v0.11.5)
debug (server): Offset Encoding:  'utf-8'
info  (server): Set config option 'enable_build_on_save' to true
info  (server): Set config option 'warn_style' to true
info  (server): Set config option 'builtin_path' to "/home/jon/.cache/zls/builtin.zig"
info  (server): Set config option 'zig_lib_path' to "/usr/lib/zig"
info  (server): Set config option 'zig_exe_path' to "/usr/bin/zig"
info  (server): Set config option 'build_runner_path' to "/home/jon/.cache/zls/build_runner/cf46548b062a7e79e448e80c05616097/build_runner.zig"
info  (server): Set config option 'global_cache_path' to "/home/jon/.cache/zls"

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions