1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
---This module replaces the default vim.lsp.buf.format() so that we can inject our own logic
local log = require("conform.log")
local util = require("vim.lsp.util")
local M = {}
local function apply_text_edits(text_edits, bufnr, offset_encoding, dry_run)
if
#text_edits == 1
and text_edits[1].range.start.line == 0
and text_edits[1].range.start.character == 0
and text_edits[1].range["end"].line >= vim.api.nvim_buf_line_count(bufnr)
and text_edits[1].range["end"].character == 0
then
local original_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
local new_lines = vim.split(text_edits[1].newText, "\r?\n", {})
-- If it had a trailing newline, remove it to make the lines match the expected vim format
if #new_lines > 1 and new_lines[#new_lines] == "" then
table.remove(new_lines)
end
log.debug("Converting full-file LSP format to piecewise format")
return require("conform.runner").apply_format(
bufnr,
original_lines,
new_lines,
nil,
false,
dry_run
)
elseif dry_run then
return #text_edits > 0
else
vim.lsp.util.apply_text_edits(text_edits, bufnr, offset_encoding)
return #text_edits > 0
end
end
---@param options table
---@return table[] clients
function M.get_format_clients(options)
local method = options.range and "textDocument/rangeFormatting" or "textDocument/formatting"
local clients
if vim.lsp.get_clients then
clients = vim.lsp.get_clients({
id = options.id,
bufnr = options.bufnr,
name = options.name,
method = method,
})
else
clients = vim.lsp.get_active_clients({
id = options.id,
bufnr = options.bufnr,
name = options.name,
})
clients = vim.tbl_filter(function(client)
return client.supports_method(method, { bufnr = options.bufnr })
end, clients)
end
if options.filter then
clients = vim.tbl_filter(options.filter, clients)
end
return clients
end
---@param options table
---@param callback fun(err?: string, did_edit?: boolean)
function M.format(options, callback)
options = options or {}
if not options.bufnr or options.bufnr == 0 then
options.bufnr = vim.api.nvim_get_current_buf()
end
local bufnr = options.bufnr
local range = options.range
local method = range and "textDocument/rangeFormatting" or "textDocument/formatting"
local clients = M.get_format_clients(options)
if #clients == 0 then
return callback("[LSP] Format request failed, no matching language servers.")
end
local function set_range(client, params)
if range then
local range_params =
util.make_given_range_params(range.start, range["end"], bufnr, client.offset_encoding)
params.range = range_params.range
end
return params
end
if options.async then
local changedtick = vim.b[bufnr].changedtick
local do_format
local did_edit = false
do_format = function(idx, client)
if not client then
return callback(nil, did_edit)
end
local params = set_range(client, util.make_formatting_params(options.formatting_options))
local auto_id = vim.api.nvim_create_autocmd("LspDetach", {
buffer = bufnr,
callback = function(args)
if args.data.client_id == client.id then
log.warn("LSP %s detached during format request", client.name)
callback("LSP detached")
end
end,
})
client.request(method, params, function(err, result, ctx, _)
vim.api.nvim_del_autocmd(auto_id)
if not result then
return callback(err or "No result returned from LSP formatter")
elseif not vim.api.nvim_buf_is_valid(bufnr) then
return callback("buffer was deleted")
elseif changedtick ~= require("conform.util").buf_get_changedtick(bufnr) then
return callback(
string.format(
"Async LSP formatter discarding changes for %s: concurrent modification",
vim.api.nvim_buf_get_name(bufnr)
)
)
else
local this_did_edit =
apply_text_edits(result, ctx.bufnr, client.offset_encoding, options.dry_run)
changedtick = vim.b[bufnr].changedtick
if options.dry_run and this_did_edit then
callback(nil, true)
else
did_edit = did_edit or this_did_edit
do_format(next(clients, idx))
end
end
end, bufnr)
end
do_format(next(clients))
else
local timeout_ms = options.timeout_ms or 1000
local did_edit = false
for _, client in pairs(clients) do
local params = set_range(client, util.make_formatting_params(options.formatting_options))
local result, err = client.request_sync(method, params, timeout_ms, bufnr)
if result and result.result then
local this_did_edit =
apply_text_edits(result.result, bufnr, client.offset_encoding, options.dry_run)
did_edit = did_edit or this_did_edit
if options.dry_run and did_edit then
callback(nil, true)
return true
end
elseif err then
if not options.quiet then
vim.notify(string.format("[LSP][%s] %s", client.name, err), vim.log.levels.WARN)
end
return callback(string.format("[LSP][%s] %s", client.name, err))
end
end
callback(nil, did_edit)
end
end
return M
|