Marked editor as draft
All the docs contained in this group of commits are marked as not draft, except for editor, which I will be working as I get more experience and get more precise on the various topics related to the editor and groups of functions.
This commit is contained in:
parent
e837a66b97
commit
0494574c36
9 changed files with 234 additions and 188 deletions
|
|
@ -44,7 +44,7 @@ offers services around the set of projects.
|
||||||
[]($section.id("commands"))
|
[]($section.id("commands"))
|
||||||
## Editor commands and modes
|
## Editor commands and modes
|
||||||
|
|
||||||
When a buffer is active, it has an [Editor](/docs/architecture/editor)
|
When a buffer is active, it has an Editor
|
||||||
attached to it; an editor might have associated tree-sitter support,
|
attached to it; an editor might have associated tree-sitter support,
|
||||||
given the file type detected, and offers common services that are aimed
|
given the file type detected, and offers common services that are aimed
|
||||||
to be used by `Commands` to manipulate the contents of a buffer at a
|
to be used by `Commands` to manipulate the contents of a buffer at a
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,26 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
Commands are actions triggered to operate on buffers primarily. They are
|
Commands are actions triggered to operate on buffers primarily. They are
|
||||||
present in `editor`, `tui`, `mode` and `minimodes`, it's possible to find
|
present in `editor`, `tui`, `mode` and `minimodes`, it's possible to
|
||||||
commands in other places, which will become evident when the need arises.
|
find commands in other places, which will become evident when the need
|
||||||
|
arises.
|
||||||
|
|
||||||
[]($section.id('notes'))
|
[]($section.id('notes'))
|
||||||
## Previous notes
|
## Previous notes
|
||||||
|
|
||||||
Note: Flow is programmed with [zig](https://ziglang.org/), if you are
|
Note: Flow is programmed with [zig](https://ziglang.org/), if you are
|
||||||
familiar with C, C++, Rust, there are differences and reasonings that might
|
familiar with C, C++, Rust, there are differences and reasonings that
|
||||||
find useful when [learning Zig](https://ziglang.org/learn/). If you are
|
might find useful when [learning Zig](https://ziglang.org/learn/). If
|
||||||
coming from higher level programming languages such as Python, Ruby, C#,
|
you are coming from higher level programming languages such as Python,
|
||||||
Java, Golang, Typescript it will be an opportunity to learn about trades of
|
Ruby, C#, Java, Golang, Typescript it will be an opportunity to learn
|
||||||
managing memory and fast responses and some lower level concepts present in
|
about trades of managing memory and fast responses and some lower level
|
||||||
Zig. If you are brand new to programming, some general concepts will be
|
concepts present in Zig. If you are brand new to programming, some
|
||||||
needed and practice in another language before getting into flow development.
|
general concepts will be needed and practice in another language before
|
||||||
|
getting into flow development.
|
||||||
|
|
||||||
If you are new to Zig, it's a good idea to take a look at
|
If you are new to Zig, it's a good idea to take a look at
|
||||||
[ziglings](https://ziglings.org/) to practice, as you learn the language.
|
[ziglings](https://ziglings.org/) to practice, as you learn the
|
||||||
|
language.
|
||||||
|
|
||||||
Maybe there is a [shell command invoked](/docs/architecture/keybind#shell)
|
Maybe there is a [shell command invoked](/docs/architecture/keybind#shell)
|
||||||
with a keybinding that can help in the task you are aiming at before
|
with a keybinding that can help in the task you are aiming at before
|
||||||
|
|
@ -49,20 +52,20 @@ pub const copy_meta: Meta = .{ .description = "Copy selection to clipboard" };
|
||||||
```
|
```
|
||||||
|
|
||||||
`copy` command is defined in `editor.zig`, which copies the current
|
`copy` command is defined in `editor.zig`, which copies the current
|
||||||
selections into the pimp internal clipboard. Commands are available to all
|
selections into the pimp internal clipboard. Commands are available to
|
||||||
the modes if defined as pub.
|
all the modes if defined as `pub`.
|
||||||
|
|
||||||
`meta` holds the description appearing in the command palette and optionally
|
`meta` holds the description appearing in the command palette and
|
||||||
has arguments, the most common, an integer, that usually constitutes a
|
optionally has arguments, the most common, an integer, that usually
|
||||||
repetition parameter, targeting vim, emacs and helix modes. As you dig in,
|
constitutes a repetition parameter, targeting vim, emacs and helix
|
||||||
there might be particularities on the parameters accepted for a given
|
modes. As you dig in, there might be particularities on the parameters
|
||||||
command.
|
accepted for a given command.
|
||||||
|
|
||||||
[]($section.id('calling'))
|
[]($section.id('calling'))
|
||||||
## Invoking another command
|
## Invoking another command
|
||||||
|
|
||||||
Commands can be bound to mnemonics in modes by convention. For example, in
|
Commands can be bound to mnemonics in modes by convention. For example,
|
||||||
Vim Mode `vim.zig`, `q` corresponds to (quit), the most famous one.
|
in Vim Mode `vim.zig`, `q` corresponds to (quit), the most famous one.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn q(_: *void, _: Ctx) Result {
|
pub fn q(_: *void, _: Ctx) Result {
|
||||||
|
|
@ -71,16 +74,17 @@ pub fn q(_: *void, _: Ctx) Result {
|
||||||
pub const q_meta: Meta = .{ .description = "q (quit)" };
|
pub const q_meta: Meta = .{ .description = "q (quit)" };
|
||||||
```
|
```
|
||||||
|
|
||||||
Looking more closely, the first parameter in this case is of `*void` type,
|
Looking more closely, the first parameter in this case is of `*void`
|
||||||
given that this command is defined in `vim.zig` which is calling the `quit`
|
type, given that this command is defined in `vim.zig` which is calling
|
||||||
command defined in `editor.zig`. `cmd` takes care of routing and finding
|
the `quit` command defined in `editor.zig`. `cmd` takes care of routing
|
||||||
the command wherever it is defined.
|
and finding the command wherever it is defined.
|
||||||
|
|
||||||
[]($section.id('tldr'))
|
[]($section.id('tldr'))
|
||||||
## Chaining commands
|
## Chaining commands
|
||||||
|
|
||||||
Chaining commands is also common, and, by the way, swift. This is a sample
|
Chaining commands is also common, and, by the way, swift. This is a
|
||||||
of applying first `save_file` command and then, the command `quit`.
|
sample of applying first `save_file` command and then, the command
|
||||||
|
`quit`.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn wq(_: *void, _: Ctx) Result {
|
pub fn wq(_: *void, _: Ctx) Result {
|
||||||
|
|
@ -89,20 +93,23 @@ pub fn wq(_: *void, _: Ctx) Result {
|
||||||
pub const wq_meta: Meta = .{ .description = "wq (write file and quit)" };
|
pub const wq_meta: Meta = .{ .description = "wq (write file and quit)" };
|
||||||
```
|
```
|
||||||
|
|
||||||
Sometimes [keybinding](/docs/architecture/keybind) is enough to accomplish
|
`cmd` is in charge of finding a command given its name, and parameters
|
||||||
a compound of already present commands, in others, zig programming is the
|
sent to commands vary for each command.
|
||||||
path to be taken.
|
|
||||||
|
Sometimes [keybinding](/docs/architecture/keybind) is enough to
|
||||||
|
accomplish a compound of already present commands.
|
||||||
|
|
||||||
[]($section.id('deepen'))
|
[]($section.id('deepen'))
|
||||||
## More in depth commands
|
## Code organization
|
||||||
|
|
||||||
Is common to define private functions in a given module that are invoked
|
Is common to define private functions in a given module that are
|
||||||
from commands, as usual, functions are meant to be reused and help organize
|
invoked from commands, as usual, functions are meant to be reused and
|
||||||
code.
|
help organize code.
|
||||||
|
|
||||||
For example, in hx mode `helix.zig` the `select_to_char_left_helix` command
|
For example, in hx mode `helix.zig` the `select_to_char_left_helix`
|
||||||
uses the functions `helix_with_selections_const_arg` which iterates over all
|
command uses the functions `helix_with_selections_const_arg` which
|
||||||
cursels and applies the `select_cursel_to_char_left_helix` function.
|
iterates over all cursels and applies the
|
||||||
|
`select_cursel_to_char_left_helix` function.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn select_to_char_left_helix(_: *void, ctx: Ctx) Result {
|
pub fn select_to_char_left_helix(_: *void, ctx: Ctx) Result {
|
||||||
|
|
@ -113,18 +120,16 @@ pub fn select_to_char_left_helix(_: *void, ctx: Ctx) Result {
|
||||||
[]($section.id('command_arguments'))
|
[]($section.id('command_arguments'))
|
||||||
### Sending parameters to commands
|
### Sending parameters to commands
|
||||||
|
|
||||||
`cmd` is in charge of finding a command given its name, and parameters sent
|
`goto_line` (in the case of vim and helix mode, you first type the
|
||||||
to commands vary for each command.
|
number and then the action, `gg`) is a command that exemplifies
|
||||||
|
receiving an integer parameter as stated in its meta:
|
||||||
The command `goto_line`, which receives as parameter an integer(in the case
|
|
||||||
of vim and helix mode, you first type the number and then the action, gg)
|
|
||||||
as stated in its meta:
|
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const goto_line_meta: Meta = .{ .arguments = &.{.integer} };
|
pub const goto_line_meta: Meta = .{ .arguments = &.{.integer} };
|
||||||
```
|
```
|
||||||
|
|
||||||
and to actually receiving the integer parameter:
|
and to actually receiving the integer parameter, `goto_line` will
|
||||||
|
extract it like this:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn goto_line(self: *Self, ctx: Context) Result {
|
pub fn goto_line(self: *Self, ctx: Context) Result {
|
||||||
|
|
@ -134,16 +139,28 @@ pub fn goto_line(self: *Self, ctx: Context) Result {
|
||||||
return error.InvalidGotoLineArgument;
|
return error.InvalidGotoLineArgument;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To send a parameter to a command, make sure that the type is exactly
|
||||||
|
the same when retrieving it. We will refer as encode and decode. Hence
|
||||||
|
for our terminology to send an integer parameter to a command, we
|
||||||
|
will encode it using `command.fmt` like in
|
||||||
|
|
||||||
|
```zig
|
||||||
|
var the_line: usize = 43;
|
||||||
|
try ed.goto_line(command.fmt(.{the_line - 1}));
|
||||||
|
```
|
||||||
|
|
||||||
|
It's possible to pass multiple parameters to commands, including arrays
|
||||||
|
and json, they all will be packed in Command.Context.
|
||||||
|
|
||||||
|
A deeper explanation of the rules about parameter passing is exposed in
|
||||||
|
[inner data exchange](/docs/architecture/inner_data_exchange).
|
||||||
|
|
||||||
[]($section.id('next'))
|
[]($section.id('next'))
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
There are more advanced topics related to commands that are covered in
|
* [minimode](/docs/architecture/minimode) shows argument passing to
|
||||||
the [editor](/docs/architecture/editor), that deeps in specific details
|
commands in reaction to keypresses.
|
||||||
of the editor and its interaction with cursors, selections and buffers
|
* [Palettes](/docs/architecture/palette) invoke commands and pass
|
||||||
modifications, among others.
|
parameters to them.
|
||||||
|
|
||||||
Learning about interactions with the buffer and editor is present in
|
|
||||||
[minimodes](/docs/architecture/minimode).
|
|
||||||
|
|
||||||
* [Add tests](/docs/testing) to harden your code
|
* [Add tests](/docs/testing) to harden your code
|
||||||
* [Back to architecture](/docs/architecture)
|
* [Back to architecture](/docs/architecture)
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
.date = @date("2025-10-19T00:00:00"),
|
.date = @date("2025-10-19T00:00:00"),
|
||||||
.author = "Igor Támara",
|
.author = "Igor Támara",
|
||||||
.layout = "tutorial.shtml",
|
.layout = "tutorial.shtml",
|
||||||
.draft = false,
|
.draft = true,
|
||||||
.custom = {
|
.custom = {
|
||||||
.githubedit = "docs/architecture/editor.smd",
|
.githubedit = "docs/architecture/editor.smd",
|
||||||
.codepath ="src/tui/editor.zig",
|
.codepath ="src/tui/editor.zig",
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,14 @@
|
||||||
Flow uses actor model to offer an snappy experience when working with
|
Flow uses actor model to offer an snappy experience when working with
|
||||||
projects that have tens of thousands of source files, also features
|
projects that have tens of thousands of source files, also features
|
||||||
async communication with the threads that are working in independent
|
async communication with the threads that are working in independent
|
||||||
tasks that contribute to have vcs, lsp and tree-sitter integration,
|
tasks supporting git interface, lsp and tree-sitter integration,
|
||||||
apart from the directory introspection to make available what is
|
apart from the directory introspection to make available all the
|
||||||
expected from an IDE. The command arguments travel to the target
|
files of the project, all of them expected s from an IDE. The
|
||||||
command and are en/decoded powered by
|
command arguments travel to the target command and are en/decoded
|
||||||
[cbor](https://github.com/neurocyte/cbor), the same as the parameters
|
powered by [cbor](https://github.com/neurocyte/cbor), the same as the
|
||||||
that are sent from one thread to another. The process management is
|
parameters that are sent from one thread to another. The process
|
||||||
provided by [thespian](https://github.com/neurocyte/thespian).
|
management is provided by
|
||||||
|
[thespian](https://github.com/neurocyte/thespian).
|
||||||
|
|
||||||
This chapter describes the mechanisms that flow has to pass arguments
|
This chapter describes the mechanisms that flow has to pass arguments
|
||||||
between components.
|
between components.
|
||||||
|
|
@ -41,20 +42,20 @@ variables
|
||||||
message is encoded and must be copied somewhere for more permanent
|
message is encoded and must be copied somewhere for more permanent
|
||||||
storage, or possibly sent somewhere via thespian.
|
storage, or possibly sent somewhere via thespian.
|
||||||
* Decoding happens via the `cbor.match`, `cbor.extract` and
|
* Decoding happens via the `cbor.match`, `cbor.extract` and
|
||||||
`cbor.extractAlloc` group of functions. `cbor.extract` functions do not
|
`cbor.extractAlloc` group of functions. `cbor.extract` functions do
|
||||||
allocate and cannot be used to extract some types that require allocation.
|
not allocate and cannot be used to extract some types that require allocation.
|
||||||
`cbor.extractAlloc` functions _do_ allocate and can extract arrays and
|
`cbor.extractAlloc` functions _do_ allocate and can extract arrays and
|
||||||
structures that require allocation. Both `cbor.extract` and
|
structures that require allocation. Both `cbor.extract` and
|
||||||
`cbor.extractAlloc` produce strings that **reference** the original CBOR
|
`cbor.extractAlloc` produce strings that **reference** the original CBOR
|
||||||
data buffer. `thespian.message.match` and `thespian.extract` functions
|
data buffer. `thespian.message.match` and `thespian.extract` functions
|
||||||
are fairly simple wrappers.
|
are fairly simple wrappers.
|
||||||
|
|
||||||
The most basic example on deserialization of an integer value can is shown
|
The most basic example on deserialization of an integer value is shown
|
||||||
in [commands](/docs/architecture/command#command_arguments).
|
in [commands](/docs/architecture/command#command_arguments).
|
||||||
|
|
||||||
Cbor features en/decoding arrays, json and compounds of basic types and the
|
Cbor features en/decoding arrays, json and compounds of basic types and
|
||||||
only requirement is to decode in the same order as encoding the data, more
|
the only requirement is to decode in the same order as encoding the
|
||||||
samples on using cbor can be seen in
|
data, more samples on using cbor can be seen in
|
||||||
[cbor tests](https://github.com/neurocyte/cbor/blob/master/test/tests.zig).
|
[cbor tests](https://github.com/neurocyte/cbor/blob/master/test/tests.zig).
|
||||||
|
|
||||||
For example, when interacting with the clipboard, the messages sent are
|
For example, when interacting with the clipboard, the messages sent are
|
||||||
|
|
@ -64,9 +65,9 @@ multiple chunks of information,
|
||||||
[]($section.id('scoping'))
|
[]($section.id('scoping'))
|
||||||
## Buffer scoping
|
## Buffer scoping
|
||||||
|
|
||||||
CBOR structures are mostly stored in a way that avoids allocation entirely.
|
CBOR structures are mostly stored in a way that avoids allocation
|
||||||
This is really fast, but requires that you always know where the CBOR data
|
entirely. This is really fast, but requires that you always know where
|
||||||
you are working with is stored.
|
the CBOR data you are working with is stored.
|
||||||
|
|
||||||
* Received messages are read directly from the thespian process (actor)
|
* Received messages are read directly from the thespian process (actor)
|
||||||
receive buffer and remain in scope only for the duration of an actor's
|
receive buffer and remain in scope only for the duration of an actor's
|
||||||
|
|
@ -86,30 +87,27 @@ All of this implies several things worth keeping in mind:
|
||||||
* `thespian.pid.send` will encode it's parameters to
|
* `thespian.pid.send` will encode it's parameters to
|
||||||
`thespian.message_buffer` and then send them to the destination actor's
|
`thespian.message_buffer` and then send them to the destination actor's
|
||||||
receive buffer. This will invalidate the contents of
|
receive buffer. This will invalidate the contents of
|
||||||
`thespian.message_buffer` and destroy any message previously encoded with
|
`thespian.message_buffer` and destroy any message previously encoded
|
||||||
`thespian.message.fmt` (on the same thread).
|
with `thespian.message.fmt` (on the same thread).
|
||||||
* Calling `command.fmt` inside a command that uses `command.Context.args`
|
* Calling `command.fmt` inside a command that uses
|
||||||
will possibly invalidate the command's own arguments. I say _possibly_
|
`command.Context.args` will possibly invalidate the command's own
|
||||||
because the `ctx.arg` may come from somewhere else entirely, like the
|
arguments. I say _possibly_ because the `ctx.arg` may come from
|
||||||
actor's receive buffer if the command was called remotely, or some other
|
somewhere else entirely, like the actor's receive buffer if the command
|
||||||
explicitly allocated buffer.
|
was called remotely, or some other explicitly allocated buffer.
|
||||||
* Use `*.fmtbuf` to encode to different buffer if there may be scoping
|
* Use `*.fmtbuf` to encode to different buffer if there may be scoping
|
||||||
issues. You can allocate and scope this buffer any way you want.
|
issues. You can allocate and scope this buffer any way you want.
|
||||||
* Calling `thespian.exit_message` while propagating an error up the stack
|
* Calling `thespian.exit_message` while propagating an error up the
|
||||||
that was previously created with `thespian.exit_message` will overwrite the
|
stack that was previously created with `thespian.exit_message` will
|
||||||
original error
|
overwrite the original error
|
||||||
* Don't ever try to free a CBOR buffer unless you know exactly where it came
|
* Don't ever try to free a CBOR buffer unless you know exactly where it
|
||||||
from.
|
came from.
|
||||||
* Strings extracted from CBOR buffers are **references** into the original
|
* Strings extracted from CBOR buffers are **references** into the
|
||||||
CBOR data and will be invalidated implicitly when the CBOR buffer they came
|
original CBOR data and will be invalidated implicitly when the CBOR
|
||||||
from is invalidated/overwritten.
|
buffer they came from is invalidated/overwritten.
|
||||||
|
|
||||||
[]($section.id('next'))
|
[]($section.id('next'))
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
Serialization and deserialization occurs almost everywhere
|
|
||||||
|
|
||||||
* [Editor](/docs/architecture/editor)
|
|
||||||
* [Commands](/docs/architecture/command)
|
* [Commands](/docs/architecture/command)
|
||||||
* [Minimodes](/docs/architecture/minimode)
|
* [Minimodes](/docs/architecture/minimode)
|
||||||
* [Architecture](/docs/architecture)
|
* [Architecture](/docs/architecture)
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@
|
||||||
If you are here, maybe is because you want to make flow behave according
|
If you are here, maybe is because you want to make flow behave according
|
||||||
to your key presses preferences or possibly you already have edited your
|
to your key presses preferences or possibly you already have edited your
|
||||||
own keybinds to suit your needs and are looking for some advanced
|
own keybinds to suit your needs and are looking for some advanced
|
||||||
topics to cope your use cases and make everything easier and
|
topics to cope your use cases and make everything easier, faster and
|
||||||
fluid when in Flow.
|
fluid when in Flow.
|
||||||
|
|
||||||
Using the command palette `Ctrl+Shift+p` and typing **Edit key
|
Using the command palette `Ctrl+Shift+p` and typing **Edit key
|
||||||
bindings**, takes you to the json file to extend Flow configuring
|
bindings**, takes you to a json file to extend Flow, configuring
|
||||||
keybindings to suit your needs. According to the mode you are in, the
|
keybindings to suit your needs. According to the mode you are in, the
|
||||||
corresponding file will be opened. The palette can also be reached left
|
corresponding file will be opened. The palette can also be reached left
|
||||||
clicking on the current mode in the status bar.
|
clicking on the current mode in the status bar.
|
||||||
|
|
@ -26,9 +26,9 @@ clicking on the current mode in the status bar.
|
||||||
## ;TLDR;
|
## ;TLDR;
|
||||||
|
|
||||||
Once you open the corresponding json file, locate inside the imode
|
Once you open the corresponding json file, locate inside the imode
|
||||||
(internal mode) that will accept the key or keys/combo and add an array,
|
(internal mode) that will accept the key or key/combos and add an array,
|
||||||
where the first element is the combination to map to the commands that
|
where the first element is the combination to map to the commands that
|
||||||
will happen, the array accepts strings like in
|
will be invoked, the array accepts strings like in
|
||||||
|
|
||||||
```js
|
```js
|
||||||
["ctrl+alt+shift+p", "open_command_palette"]
|
["ctrl+alt+shift+p", "open_command_palette"]
|
||||||
|
|
@ -52,7 +52,7 @@ Keybinds are edited per mode, and other modes inherit what is defined
|
||||||
in your `flow.json` keybindings. Each mode override keybindings of its
|
in your `flow.json` keybindings. Each mode override keybindings of its
|
||||||
parent mode. For example, if you are in **emacs** mode you will be
|
parent mode. For example, if you are in **emacs** mode you will be
|
||||||
redirected to `emacs.json` and it will override the keybindings from
|
redirected to `emacs.json` and it will override the keybindings from
|
||||||
flow.
|
flow, and the default ones defined for emacs mode.
|
||||||
|
|
||||||
[introducing keybindings](/devlog/2024#2024-12-05T20:55:00) showcases
|
[introducing keybindings](/devlog/2024#2024-12-05T20:55:00) showcases
|
||||||
how to get to edit keybindings.
|
how to get to edit keybindings.
|
||||||
|
|
@ -80,13 +80,13 @@ map > map > array > array(array(string,numbers),strings,numbers)
|
||||||
called `press` that is an array of arrays.
|
called `press` that is an array of arrays.
|
||||||
|
|
||||||
`project` is the main imode in `flow.json` and it can be noticed that
|
`project` is the main imode in `flow.json` and it can be noticed that
|
||||||
`normal` imode `inherit`s from `project`, some modes have `release`,
|
`normal` imode `inherits` from `project`, some modes have `release`,
|
||||||
usually one will be using only `press` inside `normal` imode or the
|
usually one will be using only `press` inside `normal` imode or the
|
||||||
specific mode if inside `vim`, `helix` or `emacs` modes.
|
specific mode if inside `vim`, `helix` or `emacs` modes.
|
||||||
|
|
||||||
Looking further, it can be seen that
|
Looking further, it can be seen that
|
||||||
[minimodes](/docs/architecture/minimode) have their keybinding mappings
|
[minimodes](/docs/architecture/minimode) have their own keybinding
|
||||||
defined in a particular imode.
|
mappings defined in a particular imode.
|
||||||
|
|
||||||
As stated previously, there is a mode hierarchy, the main mode is flow
|
As stated previously, there is a mode hierarchy, the main mode is flow
|
||||||
and other modes inherit from it. We remind that also imodes have a
|
and other modes inherit from it. We remind that also imodes have a
|
||||||
|
|
@ -111,20 +111,24 @@ needs.
|
||||||
For example, `f5` by default is used to run `zig build test` outputting
|
For example, `f5` by default is used to run `zig build test` outputting
|
||||||
its results to a *scratch buffer* called `test`.
|
its results to a *scratch buffer* called `test`.
|
||||||
|
|
||||||
|
The original definition is:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]],
|
["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]],
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that:
|
Note that:
|
||||||
|
|
||||||
The keybind is `f5`, which maps to the `f5` keycode. is invoking
|
The keybind is `f5`, which maps to the keycode generated by pressing
|
||||||
`create_scratchbuffer`, receiving the parameter `*test*` which results
|
the `f5` key.
|
||||||
in creating a scratch buffer if didn't exist. And then executing the
|
|
||||||
command `shell_execute_insert` that receives the paramaters `zig`,
|
`create_scratchbuffer` is invoked receiving the parameter `*test*`
|
||||||
`build`, `test`. This latter command is executing a shell command
|
which results in creating a scratch buffer if didn't exist. And then
|
||||||
called `zig` with the parameters `build` and `test`; if you don't have
|
executing the command `shell_execute_insert` that receives the
|
||||||
zig installed, it will not work, and you might want to remap `f5` to a
|
paramaters `zig`, `build`, `test`. This latter command is executing
|
||||||
different shell command.
|
a shell command called `zig` with the parameters `build` and `test`;
|
||||||
|
if you don't have zig installed, it will not work, and you might
|
||||||
|
want to remap `f5` to a different shell command.
|
||||||
|
|
||||||
```
|
```
|
||||||
[
|
[
|
||||||
|
|
@ -155,8 +159,8 @@ to submit your findings and solution.
|
||||||
|
|
||||||
Probably binding commands is good, but maybe there is a feature in other
|
Probably binding commands is good, but maybe there is a feature in other
|
||||||
text editors that you miss and would love to have it at your fingertips.
|
text editors that you miss and would love to have it at your fingertips.
|
||||||
Then it's Zig time:
|
Then it's Zig time: [Adding commands](/docs/architecture/command) to
|
||||||
[Adding commands](/docs/architecture/command) to flow.
|
flow.
|
||||||
|
|
||||||
|
|
||||||
* Making flow even better with [tests](/docs/testing)
|
* Making flow even better with [tests](/docs/testing)
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,12 @@
|
||||||
},
|
},
|
||||||
---
|
---
|
||||||
|
|
||||||
Minimodes can be used by other modes adding functionality
|
Minimodes commitment is to add functionality to the editor, are opened
|
||||||
to the editor, they have their own set of keybindings and
|
for short periods of time and have their own set of keybindings to
|
||||||
are used momentarily for an specific action, i.e. find
|
execute an specific action, i.e. find something in the current buffer
|
||||||
something in the current buffer or in project files,
|
or in project files, open/save a file, and, in modal modes(like vim
|
||||||
open/save a file, and, in modal modes(like vim and helix).
|
and helix), as receiving a number as a prefix to repeat an action many
|
||||||
An example of the latter is using numeric prefixes to
|
times.
|
||||||
repeat an action many times.
|
|
||||||
|
|
||||||
[]($section.id("anatomy"))
|
[]($section.id("anatomy"))
|
||||||
## Anatomy of minimodes
|
## Anatomy of minimodes
|
||||||
|
|
@ -30,51 +29,46 @@ To create a minimode it's needed:
|
||||||
[]($section.id("keybind"))
|
[]($section.id("keybind"))
|
||||||
### Keybinding
|
### Keybinding
|
||||||
|
|
||||||
When a key or a keystroke(set of keys) are pressed, the
|
When a key or a keystroke(set of keys) are pressed, the associated
|
||||||
associated minimode gets activated and will start to
|
minimode gets activated and will start to capture the key/strokes
|
||||||
capture the key/strokes until a special keybinding
|
until a special keybinding makes it exit, or an specific action exits
|
||||||
makes it exit, or an specific action exits the minimode.
|
the minimode. Head to `src/keybind/builtin/flow.json`(flow keybinds)
|
||||||
Head to `src/keybind/builtin/flow.json`(flow keybinds)
|
and look for `mini_find`, where you will know which specific actions
|
||||||
and look for `mini_find`, where you will know which
|
are triggered by the keybindings of the `find` minimode.
|
||||||
specific actions are triggered by the keybindings of the
|
|
||||||
minimode.
|
|
||||||
|
|
||||||
[]($section.id("mapping"))
|
[]($section.id("mapping"))
|
||||||
### Action mapping
|
### Action mapping
|
||||||
|
|
||||||
Actions executed by each minimode are stored one per
|
Actions executed by each minimode are stored one per file under
|
||||||
file under `src/tui/mode/mini/`. The command that
|
`src/tui/mode/mini/`. The command that opens the door to the minimode
|
||||||
opens opens the door to the minimode is linked from
|
is linked from `src/tui/tui.zig` which calls the minimodes dynamically
|
||||||
`src/tui/tui.zig` which calls the minimodes dynamically
|
|
||||||
when needed.
|
when needed.
|
||||||
|
|
||||||
Look for `mini` inside `tui.zig` to find out which minimodes
|
Look for `mini` inside `tui.zig` to find out which minimodes are present
|
||||||
are present and where to look to learn how each minimode
|
and where to look, to learn how each minimode does its own task.
|
||||||
does its own task.
|
|
||||||
|
|
||||||
[]($section.id("definition"))
|
[]($section.id("definition"))
|
||||||
### Minimode definition
|
### Minimode definition
|
||||||
|
|
||||||
Possibly the simplest minimode that does not require
|
Possibly the simplest minimode that does not require defining a
|
||||||
defining a particular widget is the `replace` minimode,
|
particular widget is the `replace` minimode, used in
|
||||||
used in [helix](/docs/mode/helix) and vim mode. To enter the
|
[helix](/docs/mode/helix) and vim mode. To enter the minimode in
|
||||||
minimode in Helix while in `NOR` or `INS` use the keybind
|
Helix while in `NOR` or `INS` use the keybind **r**; it consumes
|
||||||
**r**; it consumes another key and replaces the current
|
another key and replaces the current character under the main cursor
|
||||||
character under the main cursor with the immediately pressed
|
with the immediately pressed key after **r**. If there are multiple
|
||||||
key after **r**. If there are multiple selections, all the
|
selections, all the characters are replaced by the one typed after
|
||||||
characters are replaced by the one typed after **r**.
|
**r**.
|
||||||
|
|
||||||
- The minimode needs to expose a `create` function with
|
- The minimode needs to expose a `create` function with type
|
||||||
type
|
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn create(Allocator,command.Context) !struct { tui.Mode, tui.MiniMode }
|
pub fn create(Allocator,command.Context) !struct { tui.Mode, tui.MiniMode }
|
||||||
```
|
```
|
||||||
Which is in charge of registering the minimode to be able
|
Which is in charge of registering the minimode to be able to receive
|
||||||
to receive events and will offer the minimode name, the
|
events and will offer the minimode name, the one that appears in the
|
||||||
one that appears in the lower status bar while it is active,
|
lower status bar while it is active, to let it be known that the
|
||||||
to let it be known that the minimode is active. This is
|
minimode is active. This is where all the instatiations are made. Which
|
||||||
where all the instatiations are made. Which leads to
|
leads to
|
||||||
|
|
||||||
- The `deinit` function whose type is
|
- The `deinit` function whose type is
|
||||||
|
|
||||||
|
|
@ -82,29 +76,29 @@ where all the instatiations are made. Which leads to
|
||||||
pub fn deinit(*Self)
|
pub fn deinit(*Self)
|
||||||
```
|
```
|
||||||
|
|
||||||
- A `receive` function that will route events received
|
- A `receive` function that will route events received casting the
|
||||||
casting the type:
|
type:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn receive(*Self, tp.pid_ref, tp.message) error{Exit}!bool
|
pub fn receive(*Self, tp.pid_ref, tp.message) error{Exit}!bool
|
||||||
```
|
```
|
||||||
|
|
||||||
- A `commands` field that will expose the minimode `Collection`
|
- A `commands` field that will expose the minimode `Collection` of
|
||||||
of `Commands`.
|
`Commands`.
|
||||||
|
|
||||||
- An special command `mini_mode_insert_code_point` as an element
|
- An special command `mini_mode_insert_code_point` as an element of the
|
||||||
of the commands collection with type:
|
commands collection with type:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn mini_mode_insert_code_point(*Self, Ctx) Result
|
pub fn mini_mode_insert_code_point(*Self, Ctx) Result
|
||||||
```
|
```
|
||||||
|
|
||||||
acting as the default handler of the key presses that the minimode
|
acting as the default handler of the key presses that the minimode will
|
||||||
will receive when there is no other keybind defined for the minimode.
|
receive when there is no other keybind defined for the minimode.
|
||||||
|
|
||||||
All the keys were handled and managed by the default "invisible"
|
All the keys were handled and managed by the default "invisible" widget
|
||||||
widget that processes the keys for the minimode. And there is
|
that processes the keys for the minimode. And there is room for custom
|
||||||
room for custom widgets that are explained next.
|
widgets.
|
||||||
|
|
||||||
[]($section.id("custom_widgets"))
|
[]($section.id("custom_widgets"))
|
||||||
## A custom widget
|
## A custom widget
|
||||||
|
|
@ -119,5 +113,4 @@ example(look for it in the command palette `:`).
|
||||||
|
|
||||||
* Head to [architecture](/docs/architecture)
|
* Head to [architecture](/docs/architecture)
|
||||||
* Review [commands](/docs/architecture/command)
|
* Review [commands](/docs/architecture/command)
|
||||||
* Deep in the [editor](/docs/architecture/editor)
|
|
||||||
* Review [keybindings](/docs/architecture/keybind)
|
* Review [keybindings](/docs/architecture/keybind)
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,24 @@
|
||||||
},
|
},
|
||||||
---
|
---
|
||||||
|
|
||||||
Palettes are overlay menus that allow to select an item from the
|
Palettes are overlay menus with auto complete that allow to select an
|
||||||
presented list, applying a command with the selected element, optionally
|
item from the presented list, applying a command with the selected
|
||||||
deleting the selected item; it's possible to close the palette without
|
element, optionally deleting the selected item; it's possible to
|
||||||
selecting anything(a.k.a. cancel), filter the elements, and having
|
close the palette without selecting anything(a.k.a. cancel), filter
|
||||||
special elements that trigger different actions.
|
the elements, and having special elements that trigger different
|
||||||
|
actions, for example, the task palette.
|
||||||
|
|
||||||
Examples of palettes are command_palette, clipboard_palette, they all
|
Examples of palettes are `command_palette`, `clipboard_palette`, they
|
||||||
are based on palette.zig that does all the heavy lifting and sets the
|
all are based on `src/tui/mode/overlay/palette.zig that does all the
|
||||||
framework to create new palettes as simple as possible.
|
heavy lifting and sets the framework to create new palettes as simple
|
||||||
|
as possible.
|
||||||
|
|
||||||
Palettes are an special case of minimode and for instance a mode, they
|
Palettes are an special case of [minimode] and for instance a mode, they
|
||||||
receive inputs from keyboards and execute the beforehand mentioned
|
receive inputs from the keyboard and execute the beforehand mentioned
|
||||||
actions in response.
|
actions in response.
|
||||||
|
|
||||||
To get the most of this section, it's recommended to have read about
|
To get the most of this section, it's recommended to have read about
|
||||||
[commands](/docs/architecture/command), and optionally
|
[commands](/docs/architecture/command), and optionally,
|
||||||
[minimodes](/docs/architecture/minimode).
|
[minimodes](/docs/architecture/minimode).
|
||||||
|
|
||||||
[]($section.id("anatomy"))
|
[]($section.id("anatomy"))
|
||||||
|
|
@ -69,8 +71,7 @@ The index will identify the action to be taken.
|
||||||
|
|
||||||
When populating with each entry, there must be a relation that links the
|
When populating with each entry, there must be a relation that links the
|
||||||
option chosen with the required action, and this happens in
|
option chosen with the required action, and this happens in
|
||||||
`add_menu_entry` used when the user writes in the input to filter out
|
`add_menu_entry`.
|
||||||
options.
|
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
|
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
|
||||||
|
|
@ -80,14 +81,17 @@ The common line that will be used when registering the event to a
|
||||||
selected item is
|
selected item is
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
try palette.menu.add_item_with_handler(value.written(), select);
|
try palette.menu.add_item_with_handler(value, select);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Which will apply the `select` function when the value is selected.
|
||||||
|
|
||||||
|
|
||||||
[]($section.id("select"))
|
[]($section.id("select"))
|
||||||
### Acting on selection
|
### Acting on selection
|
||||||
|
|
||||||
When the selection happens, it is time to invoke the command with the
|
When the selection happens, it is time to invoke the command with the
|
||||||
selection and the palette needs to be closed. Those actions will be
|
selection making sure to close the palette. Those actions will be
|
||||||
handled inside `select`, whose signature is:
|
handled inside `select`, whose signature is:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
|
|
@ -96,7 +100,7 @@ fn select(menu: **Type.MenuType, button: *Type.ButtonType, pos: Type.Pos) void {
|
||||||
|
|
||||||
Other common operations in the palettes can be inspected looking at the
|
Other common operations in the palettes can be inspected looking at the
|
||||||
source code of the palettes, all of them import `palette.zig`. Once the
|
source code of the palettes, all of them import `palette.zig`. Once the
|
||||||
are ready, it's time to make the palette available as a command.
|
palette is ready, it's time to make the palette available as a command.
|
||||||
|
|
||||||
[]($section.id("register"))
|
[]($section.id("register"))
|
||||||
## Registering the palette
|
## Registering the palette
|
||||||
|
|
@ -113,6 +117,5 @@ To view a complete implementation of a palette, take a look at
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
* [Minimodes](/docs/architecture/minimode)
|
* [Minimodes](/docs/architecture/minimode)
|
||||||
* [Editor topics](/docs/architecture/editor)
|
|
||||||
* [On commands](/docs/architecture/command)
|
* [On commands](/docs/architecture/command)
|
||||||
* [Architecture](/docs/architecture)
|
* [Architecture](/docs/architecture)
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
This document describes implementation of Helix Mode.
|
This document describes implementation of Helix Mode.
|
||||||
|
|
||||||
|
[]($section.id('what'))
|
||||||
## What and what not
|
## What and what not
|
||||||
|
|
||||||
The first and biggest difference is that Flow has a mode that emulates
|
The first and biggest difference is that Flow has a mode that emulates
|
||||||
|
|
@ -26,12 +27,13 @@ make sure to review
|
||||||
[other issues](https://github.com/neurocyte/flow/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelix-mode)
|
[other issues](https://github.com/neurocyte/flow/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelix-mode)
|
||||||
to avoid repeating or see if there is anyone interested in porting on
|
to avoid repeating or see if there is anyone interested in porting on
|
||||||
[Discord](https://discord.gg/kzJC9fA7) to ask if or there is a
|
[Discord](https://discord.gg/kzJC9fA7) to ask if or there is a
|
||||||
workaoround, remember that it's possible to bounce back to Flow mode
|
workaround, remember that it's possible to bounce back to Flow mode
|
||||||
if needed.
|
if needed.
|
||||||
|
|
||||||
|
[]($section.id('enhancing'))
|
||||||
## Enhancing hx mode
|
## Enhancing hx mode
|
||||||
|
|
||||||
This is a programmer editor, you are more than welcome to enhance to
|
This is a programmer's editor, you are more than welcome to enhance to
|
||||||
suit your needs that maybe coincide with others.
|
suit your needs that maybe coincide with others.
|
||||||
|
|
||||||
Please take a look at [architecture](/docs/architecture) and
|
Please take a look at [architecture](/docs/architecture) and
|
||||||
|
|
@ -43,6 +45,7 @@ particular work to make it real is in `src/tui/mode/helix.zig`, adding
|
||||||
a `command` and the corresponding `meta` is what is required.
|
a `command` and the corresponding `meta` is what is required.
|
||||||
[More on commands](/docs/architecture/command).
|
[More on commands](/docs/architecture/command).
|
||||||
|
|
||||||
|
[]($section.id('pickers'))
|
||||||
### Pickers
|
### Pickers
|
||||||
|
|
||||||
Flow hx mode offers most of the picker types equivalents with `panels`
|
Flow hx mode offers most of the picker types equivalents with `panels`
|
||||||
|
|
@ -53,9 +56,12 @@ files). Examples of `palettes` are `space` `b` to pick a buffer or
|
||||||
editor while palettes open overlapping the working area.
|
editor while palettes open overlapping the working area.
|
||||||
|
|
||||||
One medium sized project is to create a widget that has one input
|
One medium sized project is to create a widget that has one input
|
||||||
widget, two panels, on the left, the list of options and, on the right,
|
widget with two panels, on the left, the list of options and, on the
|
||||||
the preview of the selected option and offer various keybindings to
|
right, the preview of the selected option and offer various keybindings
|
||||||
manipulate the objects inside both panels with filtering.
|
to manipulate the objects inside both panels with filtering.
|
||||||
|
|
||||||
|
[]($section.id('next'))
|
||||||
|
## Next steps
|
||||||
|
|
||||||
Said all of this, it's possible to start contributing via pull
|
Said all of this, it's possible to start contributing via pull
|
||||||
requesting [keybinds](/docs/architecture/keybind),
|
requesting [keybinds](/docs/architecture/keybind),
|
||||||
|
|
@ -65,3 +71,6 @@ mentioned previously.
|
||||||
|
|
||||||
More about the [architecture](/docs/architecture) or jump to
|
More about the [architecture](/docs/architecture) or jump to
|
||||||
[contribution guidelines](/docs/contributing).
|
[contribution guidelines](/docs/contributing).
|
||||||
|
|
||||||
|
Join the [#helix-mode channel](https://discord.gg/sxdejrAA) and get in
|
||||||
|
touch with other hx users.
|
||||||
|
|
@ -9,8 +9,9 @@
|
||||||
.codepath ="test",
|
.codepath ="test",
|
||||||
},
|
},
|
||||||
---
|
---
|
||||||
Currently flow tests are aimed to work as unit tests, it always is a good
|
Currently flow tests are aimed to work as unit tests.
|
||||||
idea to review the
|
|
||||||
|
If new to zig, it always is a good idea to review the
|
||||||
[zig tests documentation](https://ziglang.org/documentation/master/#Zig-Test)
|
[zig tests documentation](https://ziglang.org/documentation/master/#Zig-Test)
|
||||||
and also an
|
and also an
|
||||||
[introduction to testing](https://pedropark99.github.io/zig-book/Chapters/03-unittests.html).
|
[introduction to testing](https://pedropark99.github.io/zig-book/Chapters/03-unittests.html).
|
||||||
|
|
@ -26,7 +27,7 @@ Flow tests are placed in the directory `test`.
|
||||||
[]($section.id("running_tests"))
|
[]($section.id("running_tests"))
|
||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
To run the full set of tests, inside flow, use `F5`, which runs a task that
|
To run the full set of tests, inside flow, use `f5`, which runs a task that
|
||||||
invokes:
|
invokes:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -56,8 +57,8 @@ nature, for example, when there are a lot of branching
|
||||||
* You find something that could be changed in the future affecting the
|
* You find something that could be changed in the future affecting the
|
||||||
current behavior
|
current behavior
|
||||||
* A bug is fixed
|
* A bug is fixed
|
||||||
* A defined behavior could be thought different, for example when in a mode,
|
* A defined behavior could be thought different, for example when in a
|
||||||
it was defined that something might diverge from other programs.
|
mode, it was defined that something might diverge from other programs.
|
||||||
|
|
||||||
Tests are placed under `test` directory. Add your test in the file that
|
Tests are placed under `test` directory. Add your test in the file that
|
||||||
exercises the functionality and makes proof of it behaving as expected.
|
exercises the functionality and makes proof of it behaving as expected.
|
||||||
|
|
@ -77,8 +78,8 @@ In such cases the module that has the logic should provide a pub
|
||||||
`test_internal`, by convention, exposing the target functionalities to
|
`test_internal`, by convention, exposing the target functionalities to
|
||||||
be tested.
|
be tested.
|
||||||
|
|
||||||
For example in `src/tui/mode/helix.zig`, `test_internal` exposes the private
|
For example in `src/tui/mode/helix.zig`, `test_internal` exposes the
|
||||||
function
|
private function
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
fn move_cursor_long_word_right_end(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void
|
fn move_cursor_long_word_right_end(root: Buffer.Root, cursor: *Cursor, metrics: Buffer.Metrics) error{Stop}!void
|
||||||
|
|
@ -156,7 +157,8 @@ of adding a new test file for project manager.
|
||||||
## FAQ on tests
|
## FAQ on tests
|
||||||
|
|
||||||
[]($section.id("import_editor"))
|
[]($section.id("import_editor"))
|
||||||
### I need to test something that requires importing the editor ¿What do I do?
|
### I need to test something that requires importing the editor ¿What
|
||||||
|
do I do?
|
||||||
|
|
||||||
There are two paths from here:
|
There are two paths from here:
|
||||||
|
|
||||||
|
|
@ -171,7 +173,26 @@ Refactor the functions involved in the functionality to make them
|
||||||
not rely directly with editor and other higher level components, and
|
not rely directly with editor and other higher level components, and
|
||||||
test the lower level ones.
|
test the lower level ones.
|
||||||
|
|
||||||
An example of this can be seen in commands
|
For example, in `vim NORMAL` mode, the key `F` looks for a character to
|
||||||
|
the left in the same line, if the character is not found it goes to the
|
||||||
|
beginning of the line. In the case of `hx NOR` mode, the `F` key looks
|
||||||
|
for a character to the beginning of the file, if found, makes a
|
||||||
|
selection from the initial cursor position to the character found, if
|
||||||
|
not, no selection is made and the cursor is not moved at all.
|
||||||
|
|
||||||
|
|
||||||
|
Given that Helix has that movement and selection functionality, finding
|
||||||
|
the character was the first action and hence the search function is
|
||||||
|
the one tested in `test/tests_helix.zig`, given that positioning the
|
||||||
|
selection is rather simple compared to looking for the character. It
|
||||||
|
was decided to test the search functionality making it not depend
|
||||||
|
on editor, but only on the cursor, buffer, metrics and context, all
|
||||||
|
of them do not require graphic elements at all.
|
||||||
|
|
||||||
|
The group of functions `beyond_eol` can be seen in
|
||||||
|
[this commit](https://github.com/neurocyte/flow/pull/330/commits/baac14b3ae5243cef6461df42dae6fcf5ea15201)
|
||||||
|
and whose tests are
|
||||||
|
[here](https://github.com/neurocyte/flow/pull/330/commits/38a08aed49f4fbba18aab9ccbd3c8b9758414221).
|
||||||
|
|
||||||
[]($section.id("end_to_end"))
|
[]($section.id("end_to_end"))
|
||||||
### Use additional tools to test a running flow session
|
### Use additional tools to test a running flow session
|
||||||
|
|
@ -187,5 +208,6 @@ If in doubt about how to do something,
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
* [How to contribute](/docs/contributing)
|
* [How to contribute](/docs/contributing)
|
||||||
* [User Documentation](/docs)
|
* [Personalizing keybindings](/docs/architecture/keybind)
|
||||||
|
* [Enhance flow with commands](/docs/architecture/command)
|
||||||
* [Other Flow topics](/docs/architecture)
|
* [Other Flow topics](/docs/architecture)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue