@@ -60,8 +60,31 @@ local function apply_indent(lines, indentation)
60
60
end
61
61
end
62
62
63
+ --- @class LangRange
64
+ --- @field [ 1] string language
65
+ --- @field [ 2] integer start lnum
66
+ --- @field [ 3] integer start col
67
+ --- @field [ 4] integer end lnum
68
+ --- @field [ 5] integer end col
69
+
70
+ --- @param ranges LangRange[]
71
+ --- @param range LangRange
72
+ local function accum_range (ranges , range )
73
+ local last_range = ranges [# ranges ]
74
+ if last_range then
75
+ if last_range [1 ] == range [1 ] and last_range [4 ] == range [2 ] and last_range [5 ] == range [3 ] then
76
+ last_range [4 ] = range [4 ]
77
+ last_range [5 ] = range [5 ]
78
+ return
79
+ end
80
+ end
81
+ table.insert (ranges , range )
82
+ end
83
+
63
84
--- @class (exact ) conform.InjectedFormatterOptions
64
85
--- @field ignore_errors boolean
86
+ --- @field lang_to_ext table<string , string>
87
+ --- @field lang_to_formatters table<string , conform.FiletypeFormatter>
65
88
66
89
--- @type conform.FileLuaFormatterConfig
67
90
return {
@@ -72,6 +95,26 @@ return {
72
95
options = {
73
96
-- Set to true to ignore errors
74
97
ignore_errors = false ,
98
+ -- Map of treesitter language to file extension
99
+ -- A temporary file name with this extension will be generated during formatting
100
+ -- because some formatters care about the filename.
101
+ lang_to_ext = {
102
+ bash = " sh" ,
103
+ c_sharp = " cs" ,
104
+ elixir = " exs" ,
105
+ javascript = " js" ,
106
+ julia = " jl" ,
107
+ latex = " tex" ,
108
+ markdown = " md" ,
109
+ python = " py" ,
110
+ ruby = " rb" ,
111
+ rust = " rs" ,
112
+ teal = " tl" ,
113
+ typescript = " ts" ,
114
+ },
115
+ -- Map of treesitter language to formatters to use
116
+ -- (defaults to the value from formatters_by_ft)
117
+ lang_to_formatters = {},
75
118
},
76
119
condition = function (self , ctx )
77
120
local ok , parser = pcall (vim .treesitter .get_parser , ctx .buf )
@@ -93,12 +136,20 @@ return {
93
136
end
94
137
--- @type conform.InjectedFormatterOptions
95
138
local options = self .options
139
+
140
+ --- @param lang string
141
+ --- @return nil | conform.FiletypeFormatter
142
+ local function get_formatters (lang )
143
+ return options .lang_to_formatters [lang ] or conform .formatters_by_ft [lang ]
144
+ end
145
+
96
146
--- Disable diagnostic to pass the typecheck github action
97
147
--- This is available on nightly, but not on stable
98
148
--- Stable doesn't have any parameters, so it's safe to always pass `false`
99
149
--- @diagnostic disable-next-line : redundant-parameter
100
150
parser :parse (false )
101
151
local root_lang = parser :lang ()
152
+ --- @type LangRange[]
102
153
local regions = {}
103
154
104
155
for _ , tree in pairs (parser :trees ()) do
@@ -124,26 +175,26 @@ return {
124
175
do
125
176
--- @diagnostic disable-next-line : invisible
126
177
local lang , combined , ranges = parser :_get_injection (match , metadata )
127
- local has_formatters = conform .formatters_by_ft [lang ] ~= nil
128
- if lang and has_formatters and not combined and # ranges > 0 and lang ~= root_lang then
129
- local start_lnum
130
- local end_lnum
131
- -- Merge all of the ranges into a single range
178
+ if
179
+ lang
180
+ and get_formatters (lang ) ~= nil
181
+ and not combined
182
+ and # ranges > 0
183
+ and lang ~= root_lang
184
+ then
132
185
for _ , range in ipairs (ranges ) do
133
- if not start_lnum or start_lnum > range [1 ] + 1 then
134
- start_lnum = range [1 ] + 1
135
- end
136
- if not end_lnum or end_lnum < range [4 ] then
137
- end_lnum = range [4 ]
138
- end
139
- end
140
- if in_range (ctx .range , start_lnum , end_lnum ) then
141
- table.insert (regions , { lang , start_lnum , end_lnum })
186
+ accum_range (regions , { lang , range [1 ] + 1 , range [2 ], range [4 ] + 1 , range [5 ] })
142
187
end
143
188
end
144
189
end
145
190
end
146
191
192
+ if ctx .range then
193
+ regions = vim .tbl_filter (function (region )
194
+ return in_range (ctx .range , region [2 ], region [4 ])
195
+ end , regions )
196
+ end
197
+
147
198
-- Sort from largest start_lnum to smallest
148
199
table.sort (regions , function (a , b )
149
200
return a [2 ] > b [2 ]
@@ -171,7 +222,11 @@ return {
171
222
172
223
local formatted_lines = vim .deepcopy (lines )
173
224
for _ , replacement in ipairs (replacements ) do
174
- local start_lnum , end_lnum , new_lines = unpack (replacement )
225
+ local start_lnum , start_col , end_lnum , end_col , new_lines = unpack (replacement )
226
+ local prefix = formatted_lines [start_lnum ]:sub (1 , start_col )
227
+ local suffix = formatted_lines [end_lnum ]:sub (end_col + 1 )
228
+ new_lines [1 ] = prefix .. new_lines [1 ]
229
+ new_lines [# new_lines ] = new_lines [# new_lines ] .. suffix
175
230
for _ = start_lnum , end_lnum do
176
231
table.remove (formatted_lines , start_lnum )
177
232
end
@@ -184,12 +239,20 @@ return {
184
239
185
240
local num_format = 0
186
241
local tmp_bufs = {}
187
- local formatter_cb = function (err , idx , start_lnum , end_lnum , new_lines )
242
+ local formatter_cb = function (err , idx , region , input_lines , new_lines )
188
243
if err then
189
244
format_error = errors .coalesce (format_error , err )
190
245
replacements [idx ] = err
191
246
else
192
- replacements [idx ] = { start_lnum , end_lnum , new_lines }
247
+ -- If the original lines started/ended with a newline, preserve that newline.
248
+ -- Many formatters will trim them, but they're important for the document structure.
249
+ if input_lines [1 ] == " " and new_lines [1 ] ~= " " then
250
+ table.insert (new_lines , 1 , " " )
251
+ end
252
+ if input_lines [# input_lines ] == " " and new_lines [# new_lines ] ~= " " then
253
+ table.insert (new_lines , " " )
254
+ end
255
+ replacements [idx ] = { region [2 ], region [3 ], region [4 ], region [5 ], new_lines }
193
256
end
194
257
num_format = num_format - 1
195
258
if num_format == 0 then
@@ -200,14 +263,22 @@ return {
200
263
end
201
264
end
202
265
local last_start_lnum = # lines + 1
203
- for _ , region in ipairs (regions ) do
204
- local lang , start_lnum , end_lnum = unpack (region )
266
+ for i , region in ipairs (regions ) do
267
+ local lang = region [1 ]
268
+ local start_lnum = region [2 ]
269
+ local start_col = region [3 ]
270
+ local end_lnum = region [4 ]
271
+ local end_col = region [5 ]
205
272
-- Ignore regions that overlap (contain) other regions
206
273
if end_lnum < last_start_lnum then
207
274
num_format = num_format + 1
208
275
last_start_lnum = start_lnum
209
276
local input_lines = util .tbl_slice (lines , start_lnum , end_lnum )
210
- local ft_formatters = conform .formatters_by_ft [lang ]
277
+ input_lines [# input_lines ] = input_lines [# input_lines ]:sub (1 , end_col )
278
+ if start_col > 0 then
279
+ input_lines [1 ] = input_lines [1 ]:sub (start_col + 1 )
280
+ end
281
+ local ft_formatters = assert (get_formatters (lang ))
211
282
--- @type string[]
212
283
local formatter_names
213
284
if type (ft_formatters ) == " function" then
@@ -226,15 +297,18 @@ return {
226
297
-- extension to determine a run mode (see https://github.com/stevearc/conform.nvim/issues/194)
227
298
-- This is using the language name as the file extension, but that is a reasonable
228
299
-- approximation for now. We can add special cases as the need arises.
229
- local buf = vim .fn .bufadd (string.format (" %s.%s" , vim .api .nvim_buf_get_name (ctx .buf ), lang ))
300
+ local extension = options .lang_to_ext [lang ] or lang
301
+ local buf =
302
+ vim .fn .bufadd (string.format (" %s.%d.%s" , vim .api .nvim_buf_get_name (ctx .buf ), i , extension ))
230
303
-- Actually load the buffer to set the buffer context which is required by some formatters such as `filetype`
231
304
vim .fn .bufload (buf )
232
305
tmp_bufs [buf ] = true
233
306
local format_opts = { async = true , bufnr = buf , quiet = true }
234
307
conform .format_lines (formatter_names , input_lines , format_opts , function (err , new_lines )
308
+ log .trace (" Injected %s:%d:%d formatted lines %s" , lang , start_lnum , end_lnum , new_lines )
235
309
-- Preserve indentation in case the code block is indented
236
310
apply_indent (new_lines , indent )
237
- formatter_cb (err , idx , start_lnum , end_lnum , new_lines )
311
+ vim . schedule_wrap ( formatter_cb ) (err , idx , region , input_lines , new_lines )
238
312
end )
239
313
end
240
314
end
0 commit comments