8484--- @field EOL_data ? string
8585--- @field process1 ? boolean
8686--- @field profiler ? boolean
87- --- @field use_queue ? boolean
8887
8988--- @param opts fzf-lua.SpawnOpts
9089--- @param fn_transform function ?
@@ -103,34 +102,31 @@ M.spawn = function(opts, fn_transform, fn_done)
103102 or opts .cmd :match (" %s%-%-null" )
104103 or opts .cmd :match (" %s%-Z" ))
105104 and " \0 " or " \n "
105+ local EOL_byte = EOL_data :byte ()
106106 local output_pipe = assert (uv .new_pipe (false ))
107107 local error_pipe = assert (uv .new_pipe (false ))
108- local write_cb_count , read_cb_count = 0 , 0
109- local prev_line_content --- @type string ?
108+ local write_cb_count = 0
110109 local handle , pid --- @type uv.uv_process_t , integer
111- local co = coroutine.running ()
112- local queue = require (" fzf-lua.lib.queue " ).new ()
110+ local strbuf = ( vim . F . nil_wrap ( require )( " vim._core.stringbuffer " ) or
111+ require (" fzf-lua.lib.stringbuffer " ) ).new ()
113112 local work_ctx
114113
115- -- Disable queue if running headless due to
116- -- "Attempt to yield across a C-call boundary"
117- opts .use_queue = not _G ._fzf_lua_is_headless and opts .use_queue
118-
119114 -- cb_write_lines trumps cb_write
120115 --- @diagnostic disable-next-line : assign-type-mismatch
121116 if opts .cb_write_lines then opts .cb_write = opts .cb_write_lines end
122117
123118 local can_finish = function ()
124119 if not output_pipe :is_active () -- EOF signalled or process is aborting
125- and read_cb_count == 0 -- no outstanding read_cb data processing
126120 and write_cb_count == 0 -- no outstanding write callbacks
121+ and # strbuf == 0
127122 then
128123 return true
129124 end
130125 end
131126
132127 --- @diagnostic disable-next-line : redefined-local
133128 local finish = function (code , sig , from , pid )
129+ os.execute (" notify-send " .. write_cb_count )
134130 -- Uncomment to debug pipe closure timing issues (#1521)
135131 -- output_pipe:close(function() print("closed o") end)
136132 -- error_pipe:close(function() print("closed e") end)
@@ -139,7 +135,7 @@ M.spawn = function(opts, fn_transform, fn_done)
139135 if opts .cb_finish then
140136 opts .cb_finish (code , sig , from , pid )
141137 end
142- queue : clear ()
138+ strbuf : reset ()
143139 if not handle :is_closing () then
144140 handle :kill (" sigterm" )
145141 vim .defer_fn (function ()
@@ -187,7 +183,7 @@ M.spawn = function(opts, fn_transform, fn_done)
187183 if opts .cb_pid then opts .cb_pid (pid ) end
188184
189185 local function write_cb (data )
190- write_cb_count = write_cb_count + 1
186+ -- write_cb_count = write_cb_count + 1
191187 opts .cb_write (data , function (err )
192188 write_cb_count = write_cb_count - 1
193189 if err then
@@ -204,126 +200,75 @@ M.spawn = function(opts, fn_transform, fn_done)
204200 end
205201
206202 --- @param data string data stream
207- --- @param prev string ? rest of line from previous call
208- --- @param trans function ? line transformation function
209- --- @return table , string ? line array , partial last line (no EOL )
210- local function split_lines (data , prev , trans )
203+ --- @return string , string ? line array , partial last line (no EOL )
204+ local function split_lines (data )
205+ -- io.stderr:write("[DEBUG] worker init")
206+ if not _G .fzf_lua_worker_init then
207+ _G .fzf_lua_worker_init = true
208+ -- local __FILE__ = assert(debug.getinfo(1, "S")).source:gsub("^@", "")
209+ -- package.path = ("%s/?.lua;"):format(vim.fs.dirname(vim.fs.dirname(__FILE__))) .. package.path
210+ -- require("fzf-lua")
211+ -- pcall(require, "fzf-lua.make_entry")
212+ end
211213 local ret = {}
212214 local start_idx = 1
213215 repeat
214- local nl_idx = data :find (EOL_data , start_idx , true )
216+ local nl_idx = data :find (EOL_data or " \n " , start_idx , true )
215217 if nl_idx then
216218 local cr = data :byte (nl_idx - 1 , nl_idx - 1 ) == 13 -- \r
217219 local line = data :sub (start_idx , nl_idx - (cr and 2 or 1 ))
218- if prev then
219- line = prev .. line
220- prev = nil
221- end
222- if trans then line = trans (line ) end
223- if line then table.insert (ret , line ) end
220+ -- if trans then line = trans(line) end
221+ if line then ret [# ret + 1 ] = line end
224222 start_idx = nl_idx + 1
225- else
226- -- assert(start_idx <= #data)
227- if prev and # prev > 4096 then
228- -- chunk size is 64K, limit previous line length to 4K
229- -- max line length is therefor 4K + 64K (leftover + full chunk)
230- -- without this we can memory fault on extremely long lines (#185)
231- -- or have UI freezes (#211)
232- prev = prev :sub (1 , 4096 )
233- end
234- prev = (prev or " " ) .. data :sub (start_idx )
235223 end
236224 until not nl_idx or start_idx > # data
237- return ret , prev
238- end
239-
240- --- Called with nil to process the leftover data
241- --- @param data string ?
242- local process_data = function (data )
243- if not data and prev_line_content then
244- data = prev_line_content .. EOL
245- prev_line_content = nil
246- end
247- if not data then
248- -- NOTE: this isn't called when prev_line_content is not nil but that's
249- -- not a problem as the write_cb will call finish once the callback is done
250- -- since the output_pipe is already in "closing" state
251- if can_finish () then
252- finish (0 , 0 , " [EOF]" , pid )
253- end
254- return
255- end
256- if not fn_transform then
257- write_cb (data )
258- else
259- -- NOTE: cannot use due to "yield across a C-call boundary"
260- -- if co and not work_ctx then
261- -- work_ctx = uv.new_work(split_lines, function(lines, prev)
262- -- coroutine.resume(co, lines, prev)
263- -- end)
264- -- end
265- local nlines , lines = 0 , nil
266- local t_st = opts .profiler and uv .hrtime ()
267- if t_st then write_cb (string.format (" [DEBUG] start: %.0f (ns)" .. EOL , t_st )) end
268- if work_ctx then
269- -- should never get here, work_ctx is never initialized
270- -- code remains as a solemn reminder to my efforts of making
271- -- multiprocess=false a lag free experience
272- if prev_line_content then uv .queue_work (work_ctx , data , prev_line_content ) end
273- lines , prev_line_content = coroutine.yield ()
274- else
275- lines , prev_line_content = split_lines (data , prev_line_content ,
276- -- NOTE `fn_transform=true` is used to force line split without transformation
277- type (fn_transform ) == " function" and fn_transform or nil )
225+ ret [# ret + 1 ] = " "
226+ return table.concat (ret , " \n " )
227+ end
228+
229+ work_ctx = uv .new_work (split_lines , write_cb )
230+
231+ local co = coroutine.create (function ()
232+ local stop = 0
233+ while true do
234+ local len = # strbuf
235+ local ref = strbuf :ref ()
236+ if output_pipe :is_closing () then
237+ if len == 0 then return end
238+ if ref [len - 1 ] ~= EOL_byte then strbuf :put (EOL_byte ) end -- make split_lines happy
239+ write_cb_count = write_cb_count + 1
240+ return work_ctx :queue (strbuf :get ())
278241 end
279- nlines = nlines + # lines
280- if # lines > 0 then
281- if opts .cb_write_lines then
282- write_cb (lines )
283- else
284- -- Testing shows better performance writing the entire table at once as opposed to
285- -- calling 'write_cb' for every line after 'fn_transform', we therefore only use
286- -- `process1` when using "mini.icons" as `vim.filetype.match` causes a signigicant
287- -- delay and having to wait for all lines to be processed has an apparent lag
288- if opts .process1 then
289- vim .tbl_map (function (l ) write_cb (l .. EOL ) end , lines )
290- else
291- write_cb (table.concat (lines , EOL ) .. EOL )
292- end
242+ local eol = len
243+ for i = len - 1 , stop , - 1 do
244+ if ref [i ] == EOL_byte then
245+ eol = i
246+ break
293247 end
294248 end
295- if t_st then
296- local t_e = vim .uv .hrtime ()
297- write_cb (string.format (" [DEBUG] finish:%.0f (ns) %d lines took %.0f (ms)" .. EOL ,
298- t_e , nlines , (t_e - t_st ) / 1e6 ))
249+ if eol == len then
250+ stop = len -- no EOL found, wait for more data
251+ coroutine.yield ()
252+ else
253+ local data = strbuf :get (eol + 1 )
254+ stop = # strbuf
255+ write_cb_count = write_cb_count + 1
256+ work_ctx :queue (data )
299257 end
300258 end
301- end
259+ if can_finish () then finish (0 , 0 , " [EOF]" , pid ) end
260+ end )
302261
303262 local read_cb = function (err , data )
304263 if err then
305- finish (130 , 0 , " [read_cb: err]" , pid )
306- return
307- end
308- if not data then
309- -- EOF signalled, we can close the pipe
264+ return finish (130 , 0 , " [read_cb: err]" , pid )
265+ elseif data then
266+ strbuf :put (data )
267+ else -- EOF signalled, we can close the pipe
310268 output_pipe :close ()
311269 end
312- if opts .use_queue then
313- if data then queue :push (data ) end
314- -- Either we have outstanding data enqueued or the pipe is closing
315- -- due to the above `output_pipe:close`, in both cases we need to
316- -- resume the dequeue loop
317- coroutine.resume (co )
318- else
319- read_cb_count = read_cb_count + 1
320- local process = function ()
321- read_cb_count = read_cb_count - 1
322- process_data (data )
323- end
324- -- Schedule data processing if we're in fast event
325- -- avoids "attempt to yield across C-call boundary" by using vim.schedule
326- if vim .in_fast_event () then vim .schedule (process ) else process () end
270+ if # strbuf > 0 or output_pipe :is_closing () then
271+ assert (coroutine.resume (co ))
327272 end
328273 end
329274
@@ -352,20 +297,6 @@ M.spawn = function(opts, fn_transform, fn_done)
352297 error_pipe :read_start (err_cb )
353298 end
354299
355- if opts .use_queue then
356- while not (output_pipe :is_closing () and queue :empty ()) do
357- if queue :empty () then
358- coroutine.yield ()
359- else
360- process_data (queue :pop ())
361- end
362- end
363- -- process the leftover line from `processs_data`
364- -- will call `finish` immediately if there's no last line
365- -- otherwise, finish is called in the write callback
366- process_data (nil )
367- end
368-
369300 return handle , pid
370301end
371302
@@ -628,6 +559,7 @@ M.spawn_stdio = function(opts)
628559 cmd = cmd ,
629560 cb_finish = on_finish ,
630561 cb_write = on_write ,
562+ -- cb_write_lines = on_write_lines,
631563 cb_err = on_err ,
632564 process1 = opts .process1 ,
633565 profiler = opts .profiler ,
0 commit comments