*nvim-nio.txt* A library for asynchronous IO in Neovim ============================================================================== nvim-nio *nvim-nio* nio....................................................................|nio| nio.control....................................................|nio.control| nio.lsp............................................................|nio.lsp| nio.process....................................................|nio.process| nio.streams....................................................|nio.streams| nio.uv..............................................................|nio.uv| nio.ui..............................................................|nio.ui| nio.tests........................................................|nio.tests| A library for asynchronous IO in Neovim, inspired by the asyncio library in Python. The library focuses on providing both common asynchronous primitives and asynchronous APIs for Neovim's core. nio *nio* *nio.run()* `run`({func}, {cb}) Run a function in an async context. This is the entrypoint to all async functionality. >lua local nio = require("nio") nio.run(function() nio.sleep(10) print("Hello world") end) < Parameters~ {func} `(function)` {cb?} `(fun(success: boolean,...))` Callback to invoke when the task is complete. If success is false then the parameters will be an error message and a traceback of the error, otherwise it will be the result of the async function. Return~ `(nio.tasks.Task)` *nio.wrap()* `wrap`({func}, {argc}, {opts}) Creates an async function with a callback style function. >lua local nio = require("nio") local sleep = nio.wrap(function(ms, cb) vim.defer_fn(cb, ms) end, 2) nio.run(function() sleep(10) print("Slept for 10ms") end) < Parameters~ {func} `(function)` A callback style function to be converted. The last argument must be the callback. {argc} `(integer)` The number of arguments of func. Must be included. {opts?} `(nio.WrapOpts)` Additional options. Return~ `(function)` Returns an async function *nio.WrapOpts* Fields~ {strict?} `(boolean)` If true (default), an error will be thrown if the wrapped function is called from a non-async context. *nio.create()* `create`({func}, {argc}) Takes an async function and returns a function that can run in both async and non async contexts. When running in an async context, the function can return values, but when run in a non-async context, a Task object is returned and an extra callback argument can be supplied to receive the result, with the same signature as the callback for `nio.run`. This is useful for APIs where users don't want to create async contexts but which are still used in async contexts internally. Parameters~ {func} `(async)` function {argc?} `(integer)` The number of arguments of func. Must be included if there are arguments. *nio.gather()* `gather`({functions}) Run a collection of async functions concurrently and return when all have finished. If any of the functions fail, all pending tasks will be cancelled and the error will be re-raised Parameters~ {functions} `(function[])` Return~ `(any[])` Results of all functions *nio.current_task()* `current_task`() Get the current running task, if any. Return~ `(nio.tasks.Task|nil)` *nio.first()* `first`({functions}) Run a collection of async functions concurrently and return the result of the first to finish. Parameters~ {functions} `(function[])` Return~ `(any)` *nio.sleep()* `sleep`({ms}) Suspend the current task for given time. Parameters~ {ms} `(number)` Time in milliseconds *nio.scheduler()* `scheduler`() Yields to the Neovim scheduler to be able to call the API. nio.api *nio.api* Safely proxies calls to the vim.api module while in an async context. nio.fn *nio.fn* Safely proxies calls to the vim.fn module while in an async context. ============================================================================== nio.control *nio.control* Provides primitives for flow control in async functions *nio.control.event()* `event`() Create a new event An event can signal to multiple listeners to resume execution The event can be set from a non-async context. >lua local event = nio.control.event() local worker = nio.run(function() nio.sleep(1000) event.set() end) local listeners = { nio.run(function() event.wait() print("First listener notified") end), nio.run(function() event.wait() print("Second listener notified") end), } < Return~ `(nio.control.Event)` *nio.control.Event* Fields~ {set} `(fun(max_woken?: integer): nil)` Set the event and signal to all (or limited number of) listeners that the event has occurred. If max_woken is provided and there are more listeners then the event is cleared immediately {wait} `(async fun(): nil)` Wait for the event to occur, returning immediately if already set {clear} `(fun(): nil)` Clear the event {is_set} `(fun(): boolean)` Returns true if the event is set *nio.control.future()* `future`() Create a new future An future represents a value that will be available at some point and can be awaited upon. The future result can be set from a non-async context. >lua local future = nio.control.future() nio.run(function() nio.sleep(100 * math.random(1, 10)) if not future.is_set() then future.set("Success!") end end) nio.run(function() nio.sleep(100 * math.random(1, 10)) if not future.is_set() then future.set_error("Failed!") end end) local success, value = pcall(future.wait) print(("%s: %s"):format(success, value)) < Return~ `(nio.control.Future)` *nio.control.Future* Fields~ {set} `(fun(value): nil)` Set the future value and wake all waiters. {set_error} `(fun(message): nil)` Set the error for this future to raise to waiters {wait} `(async fun(): any)` Wait for the value to be set, returning immediately if already set {is_set} `(fun(): boolean)` Returns true if the future is set *nio.control.queue()* `queue`({max_size}) Create a new FIFO queue with async support. >lua local queue = nio.control.queue() local producer = nio.run(function() for i = 1, 10 do nio.sleep(100) queue.put(i) end queue.put(nil) end) while true do local value = queue.get() if value == nil then break end print(value) end print("Done") < Parameters~ {max_size?} `(integer)` The maximum number of items in the queue, defaults to no limit Return~ `(nio.control.Queue)` *nio.control.Queue* Fields~ {size} `(fun(): number)` Returns the number of items in the queue {max_size} `(fun(): number|nil)` Returns the maximum number of items in the queue {get} `(async fun(): any)` Get a value from the queue, blocking if the queue is empty {get_nowait} `(fun(): any)` Get a value from the queue, erroring if queue is empty. {put} `(async fun(value: any): nil)` Put a value into the queue {put_nowait} `(fun(value: any): nil)` Put a value into the queue, erroring if queue is full. *nio.control.semaphore()* `semaphore`({value}) Create an async semaphore that allows up to a given number of acquisitions. >lua local semaphore = nio.control.semaphore(2) local value = 0 for _ = 1, 10 do nio.run(function() semaphore.with(function() value = value + 1 nio.sleep(10) print(value) -- Never more than 2 value = value - 1 end) end) end < Parameters~ {value} `(integer)` The number of allowed concurrent acquisitions Return~ `(nio.control.Semaphore)` *nio.control.Semaphore* Fields~ {with} `(async fun(callback: fun(): nil): nil)` Run the callback with the semaphore acquired {acquire} `(async fun(): nil)` Acquire the semaphore {release} `(fun(): nil)` Release the semaphore ============================================================================== nio.lsp *nio.lsp* *nio.lsp.Client* Fields~ {request} `(nio.lsp.RequestClient)` Interface to all requests that can be sent by the client {notify} `(nio.lsp.NotifyClient)` Interface to all notifications that can be sent by the client {server_capabilities} `(nio.lsp.types.ServerCapabilities)` *nio.lsp.get_clients()* `get_clients`({filters}) Get active clients, optionally matching the given filters Equivalent to |vim.lsp.get_clients| Parameters~ {filters?} `(nio.lsp.GetClientsFilters)` Return~ `(nio.lsp.Client[])` *nio.lsp.GetClientsFilters* Fields~ {id?} `(integer)` {name?} `(string)` {bufnr?} `(integer)` {method?} `(string)` *nio.lsp.get_client_by_id()* `get_client_by_id`({id}) Gets a client by id, or nil if the id is invalid. The returned client may not yet be fully initialized. Equivalent to |vim.lsp.get_client_by_id| Parameters~ {id} `(integer)` Return~ `(nio.lsp.Client)` | nil *nio.lsp.convert_client()* `convert_client`({client}) Create an async client for the given client Parameters~ {client} `(table)` Neovim core LSP client object Return~ `(nio.lsp.Client)` ============================================================================== nio.file *nio.file* *nio.file.File* Inherits: `nio.streams.OSStreamReaderWriter` Fields~ {read} `(async fun(n: integer?, offset: integer?):string?,string?)` Read data from the stream, optionally up to n bytes otherwise until EOF is reached. Returns the data read or error message if an error occurred. If offset is provided, data will be read from that position in the file, otherwise the current position will be used. *nio.file.open()* `open`({path}, {flags}, {mode}) Open a file with the given flags and mode >lua local file = nio.file.open("test.txt", "w+") file.write("Hello, World!\n") local content = file.read(nil, 0) file.close() print(content) < Parameters~ {path} `(string)` The path to the file {flags} `(uv.aliases.fs_access_flags|integer?)` The flags to open the file with, defaults to "r" {mode} `(number?)` The mode to open the file with, defaults to 644 Return~ `(nio.file.File?)` File object Return~ `(string?)` Error message if an error occurred while opening See also ~ |uv.fs_open| ============================================================================== nio.process *nio.process* Wrapper for a running process, providing access to its stdio streams and methods to interact with it. *nio.process.Process* Fields~ {pid} `(integer)` ID of the invoked process {signal} `(fun(signal: integer|uv.aliases.signals))` Send a signal to the process {result} `(async fun(close: boolean): number,(string|nil)[])` Wait for the process to exit and return the exit code, optionally closing all streams. {stdin} `(nio.streams.OSStreamWriter)` Stream to write to the process stdin. {stdout} `(nio.streams.OSStreamReader)` Stream to read from the process stdout. {stderr} `(nio.streams.OSStreamReader)` Stream to read from the process stderr. {close} `(async fun():(string|nil)[])` Close all streams, returning any errors that occurred. *nio.process.run()* `run`({opts}) Run a process asynchronously. >lua local process = nio.process.run({ cmd = "printf", args = { "hello" } }) local output = second.stdout.read() print(output) process.close() < Processes can be chained together, passing output of one process as input to another. >lua local first = nio.process.run({ cmd = "printf", args = { "hello" } }) local second = nio.process.run({ cmd = "cat", stdin = first.stdout }) local output = second.stdout.read() print(output) first.close() second.close() < The stdio fields can also be file objects. >lua local path = nio.fn.tempname() local file = nio.file.open(path, "w+") local process = nio.process.run({ cmd = "printf", args = { "hello" }, stdout = file, }) process.result() local output = file.read(nil, 0) print(output) process.close() -- Closes the file < Parameters~ {opts} `(nio.process.RunOpts)` Return~ `(nio.process.Process?)` Process object for the running process Return~ `(string?)` Error message if an error occurred *nio.process.RunOpts* Fields~ {cmd} `(string)` Command to run {args?} `(string[])` Arguments to pass to the command {stdin?} `(integer|nio.streams.OSStream|uv_pipe_t)` Stream, pipe or file to use as stdin. {stdout?} `(integer|nio.streams.OSStream|uv_pipe_t)` Stream, pipe or file to use as stdout. {stderr?} `(integer|nio.streams.OSStream|uv_pipe_t)` Stream, pipe or file to use as stderr. {env?} `(table)` Environment variables to pass to the process {cwd?} `(string)` Current working directory of the process {uid?} `(integer)` User ID of the process {gid?} `(integer)` Group ID of the process ============================================================================== nio.streams *nio.streams* *nio.streams.Stream* Fields~ {close} `(async fun(): string|nil)` Close the stream. Returns an error message if an error occurred. *nio.streams.Reader* Inherits: `nio.streams.Stream` Fields~ {read} `(async fun(n?: integer): string?,string?)` Read data from the stream, optionally up to n bytes otherwise until EOF is reached. Returns the data read or error message if an error occurred. *nio.streams.Writer* Inherits: `nio.streams.Stream` Fields~ {write} `(async fun(data: string): string?)` Write data to the stream. Returns an error message if an error occurred. *nio.streams.OSStream* Inherits: `nio.streams.Stream` Fields~ {fd} `(integer)` The file descriptor of the stream *nio.streams.StreamReader* Inherits: `nio.streams.Reader, nio.streams.Stream` Inherits: `nio.streams.Writer, nio.streams.Stream` *nio.streams.StreamWriter* *nio.streams.OSStreamReader* Inherits: `nio.streams.StreamReader, nio.streams.OSStream` Inherits: `nio.streams.StreamWriter, nio.streams.OSStream` *nio.streams.OSStreamWriter* *nio.streams.OSStreamReaderWriter* Inherits: `nio.streams.OSStreamReader, nio.streams.OSStreamWriter` ============================================================================== nio.uv *nio.uv* Provides asynchronous versions of vim.loop functions. See corresponding function documentation for parameter and return information. >lua local file_path = "README.md" local open_err, file_fd = nio.uv.fs_open(file_path, "r", 438) assert(not open_err, open_err) local stat_err, stat = nio.uv.fs_fstat(file_fd) assert(not stat_err, stat_err) local read_err, data = nio.uv.fs_read(file_fd, stat.size, 0) assert(not read_err, read_err) local close_err = nio.uv.fs_close(file_fd) assert(not close_err, close_err) print(data) < Fields~ {close} `(async fun(handle: uv_handle_t))` {fs_open} `(async fun(path: any, flags: uv.aliases.fs_access_flags|integer, mode: any): (string|nil,integer|nil))` {fs_read} `(async fun(fd: integer, size: integer, offset?: integer): (string|nil,string|nil))` {fs_close} `(async fun(fd: integer): (string|nil,boolean|nil))` {fs_unlink} `(async fun(path: string): (string|nil,boolean|nil))` {fs_write} `(async fun(fd: any, data: any, offset?: any): (string|nil,integer|nil))` {fs_mkdir} `(async fun(path: string, mode: integer): (string|nil,boolean|nil))` {fs_mkdtemp} `(async fun(template: string): (string|nil,string|nil))` {fs_rmdir} `(async fun(path: string): (string|nil,boolean|nil))` {fs_stat} `(async fun(path: string): (string|nil,uv.aliases.fs_stat_table|nil))` {fs_fstat} `(async fun(fd: integer): (string|nil,uv.aliases.fs_stat_table|nil))` {fs_lstat} `(async fun(path: string): (string|nil,uv.aliases.fs_stat_table|nil))` {fs_statfs} `(async fun(path: string): (string|nil,uv.aliases.fs_statfs_stats|nil))` {fs_rename} `(async fun(old_path: string, new_path: string): (string|nil,boolean|nil))` {fs_fsync} `(async fun(fd: integer): (string|nil,boolean|nil))` {fs_fdatasync} `(async fun(fd: integer): (string|nil,boolean|nil))` {fs_ftruncate} `(async fun(fd: integer, offset: integer): (string|nil,boolean|nil))` {fs_sendfile} `(async fun(out_fd: integer, in_fd: integer, in_offset: integer, length: integer): (string|nil,integer|nil))` {fs_access} `(async fun(path: string, mode: integer): (string|nil,boolean|nil))` {fs_chmod} `(async fun(path: string, mode: integer): (string|nil,boolean|nil))` {fs_fchmod} `(async fun(fd: integer, mode: integer): (string|nil,boolean|nil))` {fs_utime} `(async fun(path: string, atime: number, mtime: number): (string|nil,boolean|nil))` {fs_futime} `(async fun(fd: integer, atime: number, mtime: number): (string|nil,boolean|nil))` {fs_link} `(async fun(path: string, new_path: string): (string|nil,boolean|nil))` {fs_symlink} `(async fun(path: string, new_path: string, flags?: integer): (string|nil,boolean|nil))` {fs_readlink} `(async fun(path: string): (string|nil,string|nil))` {fs_realpath} `(async fun(path: string): (string|nil,string|nil))` {fs_chown} `(async fun(path: string, uid: integer, gid: integer): (string|nil,boolean|nil))` {fs_fchown} `(async fun(fd: integer, uid: integer, gid: integer): (string|nil,boolean|nil))` {fs_lchown} `(async fun(path: string, uid: integer, gid: integer): (string|nil,boolean|nil))` {fs_copyfile} `(async fun(path: any, new_path: any, flags?: any): (string|nil,boolean|nil))` {fs_opendir} `(async fun(path: string, entries?: integer): (string|nil,luv_dir_t|nil))` {fs_readdir} `(async fun(dir: luv_dir_t): (string|nil,uv.aliases.fs_readdir_entries[]|nil))` {fs_closedir} `(async fun(dir: luv_dir_t): (string|nil,boolean|nil))` {fs_scandir} `(async fun(path: string): (string|nil,uv_fs_t|nil))` {shutdown} `(async fun(stream: uv_stream_t): string|nil)` {listen} `(async fun(stream: uv_stream_t): string|nil)` {write} `(async fun(stream: uv_stream_t, data: string|string[]): uv.uv_write_t|nil)` {write2} `(async fun(stream: uv_stream_t, data: string|string[], send_handle: uv_stream_t): string|nil)` ============================================================================== nio.ui *nio.ui* Async versions of vim.ui functions. *nio.ui.input()* `input`({args}) Prompt the user for input. See |vim.ui.input()| for details. >lua local value = nio.ui.input({ prompt = "Enter something: " }) print(("You entered: %s"):format(value)) < Parameters~ {args} `(nio.ui.InputArgs)` *nio.ui.InputArgs* Fields~ {prompt} `(string|nil)` Text of the prompt {default} `(string|nil)` Default reply to the input {completion} `(string|nil)` Specifies type of completion supported for input. Supported types are the same that can be supplied to a user-defined command using the "-complete=" argument. See |:command-completion| {highlight} `(function)` Function that will be used for highlighting user inputs. *nio.ui.select()* `select`({items}, {args}) Prompts the user to pick from a list of items See |vim.ui.select()| for details. < local value = nio.ui.select({ "foo", "bar", "baz" }, { prompt = "Select something: " }) print(("You entered: %s"):format(value)) < Parameters~ {items} `(any[])` {args} `(nio.ui.SelectArgs)` *nio.ui.SelectArgs* Fields~ {prompt} `(string|nil)` Text of the prompt. Defaults to `Select one of:` {format_item} `(function|nil)` Function to format an individual item from `items`. Defaults to `tostring`. {kind} `(string|nil)` Arbitrary hint string indicating the item shape. Plugins reimplementing `vim.ui.select` may wish to use this to infer the structure or semantics of `items`, or the context in which select() was called. ============================================================================== nio.tests *nio.tests* Wrappers around plenary.nvim's test functions for writing async tests >lua a.it("notifies listeners", function() local event = nio.control.event() local notified = 0 for _ = 1, 10 do nio.run(function() event.wait() notified = notified + 1 end) end event.set() nio.sleep(10) assert.equals(10, notified) end) Fields~ {it} `(fun(name: string, async_fun: fun()))` {before_each} `(fun(async_fun: fun()))` {after_each} `(fun(async_fun: fun()))` *nio.tests.with_async_context()* `with_async_context`({async_func}, {...}) the given function, applied to the remaining arguments, in an context. The return value (or values) is the return value of asynchronous function. Parameters~ {async_func} `(function)` Function to execute {...} `(any)` Arguments to `async_func` Return~ `(any)` ... Return values of `async_func` vim:tw=78:ts=8:noet:ft=help:norl: