11local async = require (' guard._async' )
22local util = require (' guard.util' )
33local filetype = require (' guard.filetype' )
4- local api = vim .api
5-
6- local M = {}
4+ local api , iter , filter = vim .api , vim .iter , vim .tbl_filter
75
86local function save_views (bufnr )
97 local views = {}
@@ -21,107 +19,42 @@ local function restore_views(views)
2119 end
2220end
2321
24- local function update_buffer (bufnr , prev_lines , new_lines , srow , erow , old_indent )
25- if not new_lines or # new_lines == 0 then
22+ local function update_buffer (bufnr , prev_content , new_content , srow , erow , old_indent )
23+ if not new_content or # new_content == 0 then
2624 return
2725 end
2826
29- local views = save_views (bufnr )
30- new_lines = vim .split (new_lines , ' \r ?\n ' )
31- if new_lines [# new_lines ] == ' ' then
32- new_lines [# new_lines ] = nil
33- end
27+ -- Always update if content changed (compare strings directly)
28+ if prev_content ~= new_content then
29+ local views = save_views (bufnr )
30+
31+ local new_lines = vim .split (new_content , ' \r ?\n ' )
32+ if new_lines [# new_lines ] == ' ' then
33+ new_lines [# new_lines ] = nil
34+ end
3435
35- if not vim .deep_equal (new_lines , prev_lines ) then
3636 api .nvim_buf_set_lines (bufnr , srow , erow , false , new_lines )
37+
3738 if util .getopt (' save_on_fmt' ) then
3839 api .nvim_command (' silent! noautocmd write!' )
3940 end
41+
4042 if old_indent then
4143 vim .cmd ((' silent %d,%dleft' ):format (srow + 1 , erow ))
4244 end
45+
4346 restore_views (views )
4447 end
4548end
4649
47- local function emit_event (status , data )
48- util .doau (' GuardFmt' , vim .tbl_extend (' force' , { status = status }, data or {}))
49- end
50-
5150local function fail (msg )
52- emit_event (' failed' , { msg = msg })
51+ util .doau (' GuardFmt' , {
52+ status = ' failed' ,
53+ msg = msg ,
54+ })
5355 vim .notify (' [Guard]: ' .. msg , vim .log .levels .WARN )
5456end
5557
56- --- Apply a single pure formatter
57- --- @async
58- --- @param buf number
59- --- @param range table ?
60- --- @param config table
61- --- @param fname string
62- --- @param cwd string
63- --- @param input string
64- --- @return string ? output
65- --- @return string ? error_msg
66- local function apply_pure_formatter (buf , range , config , fname , cwd , input )
67- -- Eval dynamic args
68- local cfg = vim .tbl_extend (' force' , {}, config )
69- if type (cfg .args ) == ' function' then
70- cfg .args = cfg .args (buf )
71- end
72-
73- if cfg .fn then
74- return cfg .fn (buf , range , input ), nil
75- end
76-
77- local result = async .await (1 , function (callback )
78- local handle = vim .system (util .get_cmd (cfg , fname , buf ), {
79- stdin = true ,
80- cwd = cwd ,
81- env = cfg .env ,
82- timeout = cfg .timeout ,
83- }, callback )
84- handle :write (input )
85- handle :write (nil )
86- end )
87-
88- if result .code ~= 0 and # result .stderr > 0 then
89- return nil , (' %s exited with code %d\n %s' ):format (cfg .cmd , result .code , result .stderr )
90- end
91-
92- return result .stdout , nil
93- end
94-
95- --- Apply a single impure formatter
96- --- @async
97- --- @param buf number
98- --- @param config table
99- --- @param fname string
100- --- @param cwd string
101- --- @return string ? error_msg
102- local function apply_impure_formatter (buf , config , fname , cwd )
103- -- Eval dynamic args
104- local cfg = vim .tbl_extend (' force' , {}, config )
105- if type (cfg .args ) == ' function' then
106- cfg .args = cfg .args (buf )
107- end
108-
109- local result = async .await (1 , function (callback )
110- vim .system (util .get_cmd (cfg , fname , buf ), {
111- text = true ,
112- cwd = cwd ,
113- env = cfg .env or {},
114- timeout = cfg .timeout ,
115- }, callback )
116- end )
117-
118- if result .code ~= 0 and # result .stderr > 0 then
119- return (' %s exited with code %d\n %s' ):format (cfg .cmd , result .code , result .stderr )
120- end
121-
122- return nil
123- end
124-
12558local function do_fmt (buf )
12659 buf = buf or api .nvim_get_current_buf ()
12760 local ft_conf = filetype [vim .bo [buf ].filetype ]
@@ -131,7 +64,6 @@ local function do_fmt(buf)
13164 return
13265 end
13366
134- -- Get format range
13567 local srow , erow = 0 , - 1
13668 local range = nil
13769 local mode = api .nvim_get_mode ().mode
@@ -141,70 +73,116 @@ local function do_fmt(buf)
14173 erow = range [' end' ][1 ]
14274 end
14375
144- local old_indent = (mode == ' V' ) and vim .fn .indent (srow + 1 ) or nil
76+ local old_indent
77+ if mode == ' V' then
78+ old_indent = vim .fn .indent (srow + 1 )
79+ end
14580
146- -- Get and filter configs
14781 local fmt_configs = util .eval (ft_conf .formatter )
14882 local fname , cwd = util .buf_get_info (buf )
14983
150- fmt_configs = vim . tbl_filter (function (config )
84+ fmt_configs = filter (function (config )
15185 return util .should_run (config , buf )
15286 end , fmt_configs )
15387
154- -- Check executability
155- for _ , config in ipairs (fmt_configs ) do
88+ local all_executable = not iter (fmt_configs ):any (function (config )
15689 if config .cmd and vim .fn .executable (config .cmd ) ~= 1 then
15790 util .report_error (config .cmd .. ' not executable' )
158- return
91+ return true
15992 end
93+ return false
94+ end )
95+
96+ if not all_executable then
97+ return
16098 end
16199
162- -- Classify formatters
163- local pure = vim .tbl_filter (function (config )
100+ local pure = filter (function (config )
164101 return config .fn or (config .cmd and config .stdin )
165102 end , fmt_configs )
166103
167- local impure = vim . tbl_filter (function (config )
104+ local impure = filter (function (config )
168105 return config .cmd and not config .stdin
169106 end , fmt_configs )
170107
171- -- Check range formatting compatibility
172108 if range and # impure > 0 then
173- local impure_cmds = vim .tbl_map (function (c )
174- return c .cmd
175- end , impure )
176109 util .report_error (' Cannot apply range formatting for filetype ' .. vim .bo [buf ].filetype )
110+ local impure_cmds = {}
111+ for _ , config in ipairs (impure ) do
112+ table.insert (impure_cmds , config .cmd )
113+ end
177114 util .report_error (table.concat (impure_cmds , ' , ' ) .. ' does not support reading from stdin' )
178115 return
179116 end
180117
181- emit_event (' pending' , { using = fmt_configs })
118+ util .doau (' GuardFmt' , {
119+ status = ' pending' ,
120+ using = fmt_configs ,
121+ })
122+
123+ local prev_lines = api .nvim_buf_get_lines (buf , srow , erow , false )
124+ local prev_content = table.concat (prev_lines , ' \n ' )
125+ local new_lines = prev_content
126+ local errno = nil
182127
183128 async .run (function ()
184- -- Initialize changedtick BEFORE any formatting (explicitly wait)
129+ -- Explicitly wait for changedtick initialization
185130 local changedtick = async .await (1 , function (callback )
186131 vim .schedule (function ()
187132 callback (api .nvim_buf_get_changedtick (buf ))
188133 end )
189134 end )
190135
191- local prev_lines = api .nvim_buf_get_lines (buf , srow , erow , false )
192- local new_lines = table.concat (prev_lines , ' \n ' )
193-
194136 -- Apply pure formatters sequentially
195137 for _ , config in ipairs (pure ) do
196- local output , err = apply_pure_formatter (buf , range , config , fname , cwd , new_lines )
197- if err then
198- fail (err )
199- return
138+ if errno then
139+ break
140+ end
141+
142+ -- Eval dynamic args
143+ local cfg = vim .tbl_extend (' force' , {}, config )
144+ if type (cfg .args ) == ' function' then
145+ cfg .args = cfg .args (buf )
146+ end
147+
148+ if cfg .fn then
149+ new_lines = cfg .fn (buf , range , new_lines )
150+ else
151+ local result = async .await (1 , function (callback )
152+ local handle = vim .system (util .get_cmd (cfg , fname , buf ), {
153+ stdin = true ,
154+ cwd = cwd ,
155+ env = cfg .env ,
156+ timeout = cfg .timeout ,
157+ }, callback )
158+ handle :write (new_lines )
159+ handle :write (nil )
160+ end )
161+
162+ if result .code ~= 0 and # result .stderr > 0 then
163+ errno = {
164+ cmd = cfg .cmd ,
165+ code = result .code ,
166+ stderr = result .stderr ,
167+ reason = cfg .cmd .. ' exited with errors' ,
168+ }
169+ else
170+ new_lines = result .stdout
171+ end
200172 end
201- new_lines = output
202173 end
203174
175+ -- Wait for schedule and update buffer
204176 async .await (1 , function (callback )
205177 vim .schedule (function ()
206- if not api .nvim_buf_is_valid (buf ) then
207- fail (' buffer no longer valid' )
178+ if errno then
179+ if errno .reason :match (' exited with errors$' ) then
180+ fail ((' %s exited with code %d\n %s' ):format (errno .cmd , errno .code , errno .stderr ))
181+ elseif errno .reason == ' buffer changed' then
182+ fail (' buffer changed during formatting' )
183+ else
184+ fail (errno .reason )
185+ end
208186 callback ()
209187 return
210188 end
@@ -215,42 +193,80 @@ local function do_fmt(buf)
215193 return
216194 end
217195
218- update_buffer (buf , prev_lines , new_lines , srow , erow , old_indent )
196+ if not api .nvim_buf_is_valid (buf ) then
197+ fail (' buffer no longer valid' )
198+ callback ()
199+ return
200+ end
201+
202+ update_buffer (buf , prev_content , new_lines , srow , erow , old_indent )
219203 callback ()
220204 end )
221205 end )
222206
207+ -- Stop if there was an error
208+ if errno then
209+ return
210+ end
211+
223212 -- Apply impure formatters sequentially
213+ local impure_error = nil
224214 for _ , config in ipairs (impure ) do
225- local err = apply_impure_formatter (buf , config , fname , cwd )
226- if err then
227- fail (err )
228- return
215+ -- Eval dynamic args
216+ local cfg = vim .tbl_extend (' force' , {}, config )
217+ if type (cfg .args ) == ' function' then
218+ cfg .args = cfg .args (buf )
219+ end
220+
221+ local result = async .await (1 , function (callback )
222+ vim .system (util .get_cmd (cfg , fname , buf ), {
223+ text = true ,
224+ cwd = cwd ,
225+ env = cfg .env or {},
226+ }, callback )
227+ end )
228+
229+ if result .code ~= 0 and # result .stderr > 0 then
230+ impure_error = {
231+ cmd = cfg .cmd ,
232+ code = result .code ,
233+ stderr = result .stderr ,
234+ }
235+ break
229236 end
230237 end
231238
232- -- Refresh buffer if impure formatters were used
239+ if impure_error then
240+ fail (
241+ (' %s exited with code %d\n %s' ):format (
242+ impure_error .cmd ,
243+ impure_error .code ,
244+ impure_error .stderr
245+ )
246+ )
247+ return
248+ end
249+
233250 if # impure > 0 then
234- async .await (1 , function (callback )
235- vim .schedule (function ()
236- api .nvim_buf_call (buf , function ()
237- local views = save_views (buf )
238- api .nvim_command (' silent! edit!' )
239- restore_views (views )
240- end )
241- callback ()
251+ vim .schedule (function ()
252+ api .nvim_buf_call (buf , function ()
253+ local views = save_views (buf )
254+ api .nvim_command (' silent! edit!' )
255+ restore_views (views )
242256 end )
243257 end )
244258 end
245259
246- emit_event (' done' )
260+ util .doau (' GuardFmt' , {
261+ status = ' done' ,
262+ })
247263
248264 if util .getopt (' refresh_diagnostic' ) then
249265 vim .diagnostic .show ()
250266 end
251267 end )
252268end
253269
254- M . do_fmt = do_fmt
255-
256- return M
270+ return {
271+ do_fmt = do_fmt ,
272+ }
0 commit comments