@@ -57,7 +57,7 @@ M.spawn = function(opts, fn_transform, fn_done)
5757 local EOL = opts .EOL or " \n "
5858 local output_pipe = assert (uv .new_pipe (false ))
5959 local error_pipe = assert (uv .new_pipe (false ))
60- local write_cb_count , read_cb_count , on_exit_called = 0 , 0 , nil
60+ local write_cb_count , read_cb_count = 0 , 0
6161 local prev_line_content = nil
6262 local handle , pid
6363 local co = coroutine.running ()
@@ -71,6 +71,15 @@ M.spawn = function(opts, fn_transform, fn_done)
7171 -- cb_write_lines trumps cb_write
7272 if opts .cb_write_lines then opts .cb_write = opts .cb_write_lines end
7373
74+ local can_finish = function ()
75+ if not output_pipe :is_active () -- EOF signalled or process is aborting
76+ and read_cb_count == 0 -- no outstanding read_cb data processing
77+ and write_cb_count == 0 -- no outstanding write callbacks
78+ then
79+ return true
80+ end
81+ end
82+
7483 --- @diagnostic disable-next-line : redefined-local
7584 local finish = function (code , sig , from , pid )
7685 -- Uncomment to debug pipe closure timing issues (#1521)
@@ -130,19 +139,14 @@ M.spawn = function(opts, fn_transform, fn_done)
130139 end )(),
131140 verbatim = _is_win ,
132141 }, function (code , signal )
133- on_exit_called = true
134- if write_cb_count == 0
135- and read_cb_count == 0
136- and not output_pipe :is_active ()
137- -- on_exit is called before queue processing of the last line (#2205)
138- and (not opts .use_queue or queue :empty ())
139- then
142+ if can_finish () or code ~= 0 then
140143 -- Do not call `:read_stop` or `:close` here as we may have data
141144 -- reads outstanding on slower Windows machines (#1521), only call
142145 -- `finish` if all our `uv.write` calls are completed and the pipe
143146 -- is no longer active (i.e. no more read cb's expected)
144- finish (code , signal , 1 , pid )
147+ finish (code , signal , " [on_exit] " , pid )
145148 end
149+ handle :close ()
146150 end )
147151
148152 -- save current process pid
@@ -155,24 +159,20 @@ M.spawn = function(opts, fn_transform, fn_done)
155159 if err then
156160 -- can fail with premature process kill
157161 -- assert(not err)
158- finish (130 , 0 , 2 , pid )
159- elseif write_cb_count == 0
160- and read_cb_count == 0
161- and on_exit_called
162- and not output_pipe :is_active ()
163- then
164- -- spawn callback already called and did not close the pipe
165- -- due to write_cb_count>0, since this is the last call
166- -- we can close the fzf pipe
167- finish (0 , 0 , 3 , pid )
162+ finish (130 , 0 , " [write_cb: err]" , pid )
163+ elseif can_finish () then
164+ -- on_exit callback already called and did not close the
165+ -- pipe due to write_cb_count>0, since this is the last
166+ -- call we can close the fzf pipe
167+ finish (0 , 0 , " [write_cb: finish]" , pid )
168168 end
169169 end )
170170 end
171171
172172 --- @param data string data stream
173173 --- @param prev string ? rest of line from previous call
174174 --- @param trans function ? line transformation function
175- --- @return table , string line array , partial last line (no EOL )
175+ --- @return table , string ? line array , partial last line (no EOL )
176176 local function split_lines (data , prev , trans )
177177 local ret = {}
178178 local start_idx = 1
@@ -208,13 +208,11 @@ M.spawn = function(opts, fn_transform, fn_done)
208208 local process_data = function (data )
209209 data = data or prev_line_content and (prev_line_content .. EOL ) or nil
210210 if not data then
211- -- https://github.com/LazyVim/LazyVim/discussions/5264
212- -- The pipe can remain active *after* on_exit was called
213- if write_cb_count == 0
214- and read_cb_count == 0
215- and on_exit_called
216- then
217- finish (0 , 0 , 5 , pid )
211+ -- NOTE: this isn't called when prev_line_content is not nil but that's
212+ -- not a problem as the write_cb will call finish once the callback is done
213+ -- since the output_pipe is already in "closing" state
214+ if can_finish () then
215+ finish (0 , 0 , " [EOF]" , pid )
218216 end
219217 return
220218 end
@@ -267,33 +265,34 @@ M.spawn = function(opts, fn_transform, fn_done)
267265
268266 local read_cb = function (err , data )
269267 if err then
270- finish (130 , 0 , 4 , pid )
268+ finish (130 , 0 , " [read_cb: err] " , pid )
271269 return
272270 end
271+ if not data then
272+ -- EOF signalled, we can close the pipe
273+ output_pipe :close ()
274+ end
273275 if opts .use_queue then
274- if data then
275- queue :push (data )
276- elseif prev_line_content then
277- queue :push (prev_line_content .. EOL )
278- prev_line_content = nil
279- end
276+ if data then queue :push (data ) end
277+ -- Either we have outstanding data enqueued or the pipe is closing
278+ -- due to the above `output_pipe:close`, in both cases we need to
279+ -- resume the dequeue loop
280280 coroutine.resume (co )
281281 else
282- -- Schedule data processing, will call finish if data is nil
283- -- and no leftover data is present
284282 read_cb_count = read_cb_count + 1
285283 local process = function ()
286284 read_cb_count = read_cb_count - 1
287285 process_data (data )
288286 end
289- -- Avoid "attempt to yield across C-call boundary" by using vim.schedule
287+ -- Schedule data processing if we're in fast event
288+ -- avoids "attempt to yield across C-call boundary" by using vim.schedule
290289 if vim .in_fast_event () then vim .schedule (process ) else process () end
291290 end
292291 end
293292
294293 local err_cb = function (err , data )
295294 if err then
296- finish (130 , 0 , 9 , pid )
295+ finish (130 , 0 , " [err_cb] " , pid )
297296 end
298297 if not data then
299298 return
@@ -324,6 +323,10 @@ M.spawn = function(opts, fn_transform, fn_done)
324323 process_data (queue :pop ())
325324 end
326325 end
326+ -- process the leftover line from `processs_data`
327+ -- will call `finish` immediately if there's no last line
328+ -- otherwise, finish is called in the write callback
329+ process_data (nil )
327330 end
328331
329332 return handle , pid
0 commit comments