I was looking into the possibility of fetching the output of stdout while the process is running, and I think I was able to come up with a relatively good solution.
I'd like to hear your opinion on this and possibly add this feature.
Proposal
It is as simple as changing the condition inside streams.lua as follows.
-- not read_err
-- -- n is specified, wait until buffer has n length but no need to wait for complete.set()
-- -- n is not specified, then wait until complete.set()
while not read_err and not complete.is_set() and (not n or #buffer < n) do
Example Usage
Here is an example code that uses this feature and as you can see in # outputs, the elapsed ms value when using stdout.read(100) changes incrementally opposed to when no n is provided stdout.read().
Please go to the bottom to see explanation of each function.
local nio = require("nio")
local function make_proc()
local proc = nio.process.run({
cmd = "ping",
args = { "google.com" },
})
assert(proc, "proc is nil")
return proc
end
local function test_no_read_n()
local proc = make_proc()
local start = vim.loop.hrtime()
nio.run(function()
nio.sleep(5 * 1000) -- wait 5 sec and kill ping
proc.signal(15)
end)
local out = proc.stdout.read()
local elapsed = (vim.loop.hrtime() - start) / 1000 / 1000
vim.print(string.format([[[%8.2f ms] '%s']], elapsed, out))
end
local function test_hundred_bytes()
local proc = make_proc()
local start = vim.loop.hrtime()
for i = 0, 9 do
local out = proc.stdout.read(100)
local elapsed = (vim.loop.hrtime() - start) / 1000 / 1000
vim.print(string.format([[[%8.2f ms, i: %s] '%s']], elapsed, i, out))
end
proc.signal(15)
end
local function test_print_each_newline()
local proc = make_proc()
local start = vim.loop.hrtime()
local buffer = ""
local line_count = 0
local iter_count = 0
while true do
local out = proc.stdout.read(100)
if not out then
break
end
iter_count = iter_count + 1
buffer = buffer .. out
while true do
local cr = string.find(buffer, "\n")
if not cr then
break
end
line_count = line_count + 1
local elapsed = (vim.loop.hrtime() - start) / 1000 / 1000
local msg = [[[%8.2f ms, line: %s, iter: %s] '%s']]
vim.print(string.format(msg, elapsed, line_count, iter_count, buffer:sub(1, cr - 1)))
buffer = buffer:sub(cr + 1)
end
if line_count >= 10 then
break
end
end
proc.signal(15)
end
nio.run(function()
vim.print("====== test_no_read_n ======")
-- `stdout.read()` (with no n) will wait until the process is finished.
-- So, we don't see any outputs until ping is done (5 sec).
test_no_read_n()
vim.print("====== test_hundred_bytes ======")
-- `stdout.read(100)` will fetch 100 bytes from the buffer.
-- This will output stdout **as soon as** there are more than 100 bytes from the last call.
test_hundred_bytes()
vim.print("====== test_print_each_newline ======")
-- Same as above but the function has mechanism to cut the buffer at "\n" using string.find.
-- This is meant to be implemented on the user side, but maybe it'd be nice if
-- nio could add this functionality as it is relatively simple. Perhaps...
-- `stdout.read(n: integer?, until: string?)` where
-- -- `stdout.read()`: wait and flush all
-- -- `stdout.read(100)`: flush 100 bytes at a time
-- -- `stdout.read(100, "\n")`: flush every 100 bytes but only return at every "\n"
-- -- `stdout.read(nil, "\n")`: ERROR, user must provide `n` as the best guess of the line length (which will vary between applications) for performance reasons.
test_print_each_newline()
end)
Thanks for the plugin as always @rcarriga !
I was looking into the possibility of fetching the output of stdout while the process is running, and I think I was able to come up with a relatively good solution.
I'd like to hear your opinion on this and possibly add this feature.
Proposal
It is as simple as changing the condition inside
streams.lua
as follows.Example Usage
Here is an example code that uses this feature and as you can see in
# outputs
, the elapsed ms value when usingstdout.read(100)
changes incrementally opposed to when non
is providedstdout.read()
.Please go to the bottom to see explanation of each function.
Output