Skip to content

Commit

Permalink
feat: file module
Browse files Browse the repository at this point in the history
  • Loading branch information
rcarriga committed Feb 19, 2024
1 parent c037b0a commit 5a3a06e
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 46 deletions.
62 changes: 56 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ both common asynchronous primitives and asynchronous APIs for Neovim's core.
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [`nio.control`](#niocontrol): Primitives for flow control in async functions
- [`nio.lsp`](#niolsp): A fully typed and documented async LSP client library, generated from the LSP specification.
- [`nio.file`](#niofile): Open and operate on files asynchronously
- [`nio.process`](#nioprocess): Run and control subprocesses asynchronously
- [`nio.uv`](#niouv): Async versions of `vim.loop` functions
- [`nio.ui`](#nioui): Async versions of vim.ui functions
- [`nio.tests`](#niotests): Async versions of plenary.nvim's test functions
- [Third Party Integration](#third-party-integration)

## Motivation

Work has been ongoing around async libraries in Neovim for years, with a lot of discussion around a [Neovim core
implementation](https://github.com/neovim/neovim/issues/19624). A lot of the motivation behind this library can be seen
implementation](https://github.com/neovim/neovim/issues/19624). Much of the motivation behind this library can be seen
in that discussion.

nvim-nio aims to provide a simple interface to Lua coroutines that doesn't feel like it gets in the way of your actual
Expand Down Expand Up @@ -82,7 +90,9 @@ For simple use cases tasks won't be too important but they support features such

nvim-nio comes with built-in modules to help with writing async code. See `:help nvim-nio` for extensive documentation.

`nio.control`: Primitives for flow control in async functions
### `nio.control`

Primitives for flow control in async functions

```lua
local event = nio.control.event()
Expand All @@ -104,7 +114,9 @@ local listeners = {
}
```

`nio.lsp`: A fully typed and documented async LSP client library, generated from the LSP specification.
### `nio.lsp`

A fully typed and documented async LSP client library, generated from the LSP specification.

```lua
local client = nio.lsp.get_clients({ name = "lua_ls" })[1]
Expand All @@ -120,7 +132,39 @@ for _, token in pairs(response.data) do
end
```

`nio.uv`: Async versions of `vim.loop` functions
### `nio.file`

Open and operate on files asynchronously

```lua
local file = nio.file.open("test.txt", "w+")

file.write("Hello, World!\n")

local content = file.read(nil, 0)
print(content)
```

### `nio.process`

Run and control subprocesses asynchronously

```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)
```

### `nio.uv`

Async versions of `vim.loop` functions

```lua
local file_path = "README.md"
Expand All @@ -140,14 +184,18 @@ assert(not close_err, close_err)
print(data)
```

`nio.ui`: Async versions of vim.ui functions
### `nio.ui`

Async versions of vim.ui functions

```lua
local value = nio.ui.input({ prompt = "Enter something: " })
print(("You entered: %s"):format(value))
```

`nio.tests`: Async versions of plenary.nvim's test functions
### `nio.tests`

Async versions of plenary.nvim's test functions

```lua
nio.tests.it("notifies listeners", function()
Expand All @@ -166,6 +214,8 @@ nio.tests.it("notifies listeners", function()
end)
```

### Third Party Integration

It is also easy to wrap callback style functions to make them asynchronous using `nio.wrap`, which allows easily
integrating third-party APIs with nvim-nio.

Expand Down
93 changes: 82 additions & 11 deletions doc/nio.txt
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,47 @@ 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*

Expand All @@ -374,6 +415,17 @@ stderr.
`run`({opts})

Run a process asynchronously.
>lua
local process = nio.process.run({
cmd = "printf", args = { "hello" }
})

local output = second.stdout.read()
print(output)
<

Processes can be chained together, passing output of one process as input to
another.
>lua
local first = nio.process.run({
cmd = "printf", args = { "hello" }
Expand All @@ -386,6 +438,23 @@ Run a process asynchronously.
local output = second.stdout.read()
print(output)
<

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)
<
Parameters~
{opts} `(nio.process.RunOpts)`
Return~
Expand All @@ -397,14 +466,12 @@ Return~
Fields~
{cmd} `(string)` Command to run
{args?} `(string[])` Arguments to pass to the command
{stdin?} `(integer|nio.streams.OSStreamReader|uv.uv_pipe_t|uv_pipe_t)` Stream,
pipe or file descriptor to use as stdin.
{stdout?} `(integer|nio.streams.OSStreamWriter|uv.uv_pipe_t|uv_pipe_t)`
Stream,
pipe or file descriptor to use as stdout.
{stderr?} `(integer|nio.streams.OSStreamWriter|uv.uv_pipe_t|uv_pipe_t)`
Stream,
pipe or file descriptor to use as stderr.
{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<string, string>)` Environment variables to pass to the
process
{cwd?} `(string)` Current working directory of the process
Expand All @@ -425,16 +492,16 @@ if an error occurred.
Inherits: `nio.streams.Stream`

Fields~
{read} `(async fun(n?: integer): string,string)` Read data from the stream,
{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|nil)` Write data to the stream.
Returns an error message if an error occurred.
{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`
Expand All @@ -456,6 +523,10 @@ Inherits: `nio.streams.StreamWriter, nio.streams.OSStream`
*nio.streams.OSStreamWriter*


*nio.streams.OSStreamReaderWriter*
Inherits: `nio.streams.OSStreamReader, nio.streams.OSStreamWriter`



==============================================================================
nio.uv *nio.uv*
Expand Down
59 changes: 59 additions & 0 deletions lua/nio/file.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
local uv = require("nio.uv")
local streams = require("nio.streams")

local nio = {}

---@class nio.file
nio.file = {}

---@class nio.file.File : nio.streams.OSStreamReaderWriter
---@field 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.

--- 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)
--- ```
---@param path string The path to the file
---@param flags uv.aliases.fs_access_flags|integer? The flags to open the file with, defaults to "r"
---@param 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
---
---@seealso |uv.fs_open|
function nio.file.open(path, flags, mode)
local err, fd = uv.fs_open(path, flags or "r", mode or 438)
if not fd then
return nil, err
end

local reader, reader_err = streams._file_reader(fd)
if not reader then
return nil, reader_err
end
local writer, writer_err = streams._writer(fd)
if not writer then
return nil, writer_err
end

local file = {
fd = fd,
close = function()
local close_err = uv.fs_close(fd)
reader.close()
writer.close()
return close_err
end,
write = writer.write,
read = reader.read,
}

return file
end

return nio.file
2 changes: 2 additions & 0 deletions lua/nio/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local control = require("nio.control")
local uv = require("nio.uv")
local tests = require("nio.tests")
local ui = require("nio.ui")
local file = require("nio.file")
local lsp = require("nio.lsp")
local process = require("nio.process")

Expand All @@ -28,6 +29,7 @@ nio.tests = tests
nio.tasks = tasks
nio.lsp = lsp
nio.process = process
nio.file = file

--- Run a function in an async context. This is the entrypoint to all async
--- functionality.
Expand Down
Loading

0 comments on commit 5a3a06e

Please sign in to comment.