Compare commits

...

10 commits

Author SHA1 Message Date
Igor Támara
b01dfdb992 Add more internal links and precisions on command arguments 2025-10-31 21:41:48 +01:00
Igor Támara
0494574c36 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.
2025-10-31 21:41:48 +01:00
Igor Támara
e837a66b97 Links between developer docs 2025-10-31 21:41:48 +01:00
Igor Támara
5eedc17542 Add explanation of exchanging data 2025-10-31 21:41:48 +01:00
Igor Támara
34ec8be355 Added more documentation to palette 2025-10-31 21:41:48 +01:00
Igor Támara
d14b44ed1c Added more documentation to editor 2025-10-31 21:41:48 +01:00
Igor Támara
a7bcd6dd9e Add some more information to palettes 2025-10-31 21:41:48 +01:00
Igor Támara
ca131d2135 Add initial docs for command, editor, keybind, palette 2025-10-31 21:41:48 +01:00
Igor Támara
1964cb7fd6 Add footer for edition 2025-10-31 21:41:48 +01:00
Igor Támara
c87d310f7a docs: Starting point for minimode and helix mode 2025-10-31 21:41:48 +01:00
11 changed files with 1023 additions and 45 deletions

View file

@ -45,7 +45,7 @@ Some [custom frontmatter](https://zine-ssg.io/docs/supermd/#frontmatter) fields
Directs to contribute editing this documentation. Used by `layout/tutorial.shtml`.
```ziggy
.githubedit** = "/docs/testing.md",
.githubedit** = "/docs/testing.smd",
```
### codepath (Optional)

View file

@ -44,27 +44,32 @@ offers services around the set of projects.
[]($section.id("commands"))
## Editor commands and modes
When a buffer is active, it has an `Editor` attached to it; an editor
might have associated tree-sitter support, 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 higher level, the selections,
cursors, cursor selections `CurSel` and the `View`. The commands are
used by `Modes` with `Keybindings`. The main mode is Flow and the
keybindings can be used to map to a mode built up entirely on solely
calling already created commands. An example of a mode created by
command composition is `Emacs` mode, for instance, it's possible to
create a nano mode with just keybindings. In the other hand, `Vim` and
`Helix` modes have particular definitions for commands that interact
with the buffers, being modal editors.
When a buffer is active, it has an Editor
attached to it; an editor might have associated tree-sitter support,
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
higher level, the selections, cursors, cursor selections `CurSel` and
the `View`. [Commands](/docs/architecture/command) are used by `Modes`
with [Keybindings](/docs/architecture/keybind). The main mode is Flow
and the keybindings can be used to map to a mode built up entirely on
solely calling already created commands. An example of a mode
created by command composition is `Emacs` mode, for instance, it's
possible to create a nano mode with just keybindings. In the other hand,
`Vim` and [Helix](/docs/mode/helix) modes have particular definitions
for commands that interact with the buffers, being modal editors.
[]($section.id("tui"))
## Text user interface
`Tui` governs it all offering support for `Palettes` that are known
in other environments as pickers, as well as offering information
through a set of `_views` (i.e. `logview`, `inputview`,
`inspector_view`) and `status` (i.e. `tabs`, `clock`, `branch`,
`linenum`).
`Tui` governs it all offering support for
[palettes](/docs/architecture/palette) that are known in other
environments as pickers, as well as offering information through a
set of `_views` (i.e. `logview`, `inputview`, `inspector_view`) and
`status` (i.e. `tabs`, `clock`, `branch`, `linenum`), in the statusbar
[minimodes](/docs/architecture/minimode) will be present too, those
that receive more keypresses to offer additional functionality, such
as finding in files, finding in the current buffer, open files
and replacing a character.
[]($section.id("oses"))
## Operating systems and UI
@ -77,16 +82,16 @@ and Android via Termux, while in Windows there is an special GUI.
## Communication between components
[Thespian](https://github.com/neurocyte/thespian) is in charge of
processes synchronization and allows sending messages between
different flow components, for example, when a widget needs
updating information from changing states of internal data and
when components or external processes take time and need to return
an answer, all this without blocking the user interface. Tree-sitter
queries to highlight the current file of a particular
language and LSPs usually take time by the nature of operations they
perform, integration with git and running a `shell` command via a
`task` all are coordinated thanks to the infrastructure that
Thespian provides.
processes synchronization and allows sending
[messages between different flow components](/docs/architecture/inner_data_exchange),
for example, when a widget needs updating information from changing
states of internal data and when components or external processes take
time and need to return an answer, all this without blocking the user
interface. Tree-sitter queries to highlight the current file of a
particular language and LSPs usually take time by the nature of
operations they perform, integration with git and running a `shell`
command via a `task` all are coordinated thanks to the infrastructure
that Thespian provides.
[]($section.id("languages"))
## Programming languages support
@ -111,7 +116,7 @@ feedback via `logview`. To view logs use `f11` to toggle the
previous messages, or alternatively, open flow with the option
`--show-logs`.
To log something import
To log something, first import
```zig
const log = @import("log");
@ -135,13 +140,17 @@ logger.print("{} unsaved buffer(s) remaining", .{remaining});
### View key presses
There are situations when you press some keys without the expected
behavior happening, to review if flow is getting the keys, or your desktop
environment or something else are capturing them, you will want to
invoke flow with the option `--show-input`.
behavior happening, to review if flow is getting the keys, the
[keybindings are associated](/docs/architecture/keybind), and are
executing the [desired command](/docs/architecture/command), or maybe
your desktop environment or something else is capturing them, you will
want to invoke flow with the option `--show-input`.
[]($section.id("next"))
## Next steps
* [Configure some keybinds](/docs/architecture/keybind)
* [Guidelines for contributions](/docs/contributing)
* [Create your own commands](/docs/architecture/command)
* [Take a peek on testing](/docs/testing)
* [Back to docs](/docs)

View file

@ -0,0 +1,176 @@
---
.title = "Commands",
.date = @date("2025-10-15T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "/docs/architecture/command.smd",
.codepath = "src/tui/editor.zig",
},
---
Commands are actions triggered to operate on buffers primarily. They are
present in `editor`, `tui`, `mode` and `minimodes`, it's possible to
find commands in other places, which will become evident when the need
arises.
[]($section.id('notes'))
## Previous notes
Note: Flow is programmed with [zig](https://ziglang.org/), if you are
familiar with C, C++, Rust, there are differences and reasonings that
might find useful when [learning Zig](https://ziglang.org/learn/). If
you are coming from higher level programming languages such as Python,
Ruby, C#, Java, Golang, Typescript it will be an opportunity to learn
about trades of managing memory and fast responses and some lower level
concepts present in Zig. If you are brand new to programming, some
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
[ziglings](https://ziglings.org/) to practice, as you learn the
language.
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
developing flow itself.
[]($section.id('creating'))
## Understanding and creating commands
A command is a function with a type like
```zig
pub fn copy(self: *Self, _: Context) Result
```
and a `Meta` definition with the same name and suffix `_meta`.
```zig
pub const copy_meta: Meta = .{ .description = "Copy selection to clipboard" };
```
`copy` command is defined in `editor.zig`, which copies the current
selections into the pimp internal clipboard. Commands are available to
all the modes if defined as `pub`.
`meta` holds the description appearing in the command palette and
optionally has arguments, the most common, an integer, that usually
constitutes a repetition parameter, targeting vim, emacs and helix
modes. As you dig in, there might be particularities on the parameters
accepted for a given command.
[]($section.id('calling'))
## Invoking another command
Commands can be bound to mnemonics in modes by convention. For example,
in Vim Mode `vim.zig`, `q` corresponds to (quit), the most famous one.
```zig
pub fn q(_: *void, _: Ctx) Result {
try command.cmd("quit", .{});
}
pub const q_meta: Meta = .{ .description = "q (quit)" };
```
Looking more closely, the first parameter in this case is of `*void`
type, given that this command is defined in `vim.zig` which is calling
the `quit` command defined in `editor.zig`. `cmd` takes care of routing
and finding the command wherever it is defined.
[]($section.id('tldr'))
## Chaining commands
Chaining commands is also common, and, by the way, swift. This is a
sample of applying first `save_file` command and then, the command
`quit`.
```zig
pub fn wq(_: *void, _: Ctx) Result {
try cmd("save_file", command.fmt(.{ "then", .{ "quit", .{} } }));
}
pub const wq_meta: Meta = .{ .description = "wq (write file and quit)" };
```
`cmd` is in charge of finding a command given its name, and parameters
sent to commands vary for each command.
Sometimes [keybinding](/docs/architecture/keybind) is enough to
accomplish a compound of already present commands.
[]($section.id('deepen'))
## Code organization
Is common to define private functions in a given module that are
invoked from commands, as usual, functions are meant to be reused and
help organize code.
For example, in hx mode `helix.zig` the `select_to_char_left_helix`
command uses the functions `helix_with_selections_const_arg` which
iterates over all cursels and applies the
`select_cursel_to_char_left_helix` function.
```zig
pub fn select_to_char_left_helix(_: *void, ctx: Ctx) Result {
try helix_with_selections_const_arg(ctx, &select_cursel_to_char_left_helix);
}
```
[]($section.id('command_arguments'))
### Sending parameters to commands
`goto_line` (in the case of vim and helix mode, you first type the
number and then the action, `gg`) is a command that exemplifies
receiving an integer parameter. As stated in its meta:
```zig
pub const goto_line_meta: Meta = .{ .arguments = &.{.integer} };
```
and to actually receiving the integer parameter, `goto_line` will
extract from the context like this:
```zig
pub fn goto_line(self: *Self, ctx: Context) Result {
var line: usize = 0;
if (!try ctx.args.match(.{tp.extract(&line)}))
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 when
packing parameters in the context. To pack the `command.fmt` we will
encode it like this, when invoking `goto_line`.
```zig
var the_line: usize = 43;
try command.cmd("goto_line", command.fmt(.{the_line - 1}));
```
Or calling the command directly, if we have a reference to the object
that holds the command.
```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, packing all of them in Command.Context.
A deeper explanation of the rules about parameter passing is exposed in
[inner data exchange](/docs/architecture/inner_data_exchange), given
that parameters can be sent not only to commands, but other broather
use cases.
[]($section.id('next'))
## Next steps
* [minimode](/docs/architecture/minimode) shows argument passing to
commands in reaction to keypresses.
* [Palettes](/docs/architecture/palette) invoke commands and pass
parameters to them.
* [Add tests](/docs/testing) to harden your code
* [Back to architecture](/docs/architecture)

View file

@ -0,0 +1,159 @@
---
.title = "Editor",
.date = @date("2025-10-19T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = true,
.custom = {
.githubedit = "docs/architecture/editor.smd",
.codepath ="src/tui/editor.zig",
},
---
The `editor` coordinates visualization and modification of
buffer contents, multiple cursors, selections and marks.
To get the most of this section, it's recommended to have
read the [architecture briefing](/docs/architecture), about
[commands](/docs/architecture/command) and
[keybinds](/docs/architecture/keybind).
[]($section.id("concepts"))
## Some concepts
The `primary Cursor` is presented always in the `Editor`,
signaling the part of the `Buffer` that can be modified and
manipulated as you see it. It scrolls on the current visible
portion of the buffer.
Other cursors can be in the `View` or in regions outside the
current view, depending on the size of both the buffer and
the editor in your device.
A `Selection` has two cursors that are not visible, they mark
the begin and the end of the selection, and CurSels are actually
what allow to have the concept of a cursor with a selection. A
`Cursel` is composed by a cursor and optionally a Selection.
Most of editors operations act on the set of CurSels and the
Primary Cursor is a particular case of the general case. And
as a note, the Primary Cursor is in fact a CurSel.
To complete the editor scenario, `Marks` have the potential
to become selections; the marks become evident to the eye
when in search mode, they are seen as the primary cursor
is positioned over an occurrence with a different color
according to the theme.
The Editor will be acting on Buffer.Root which is the root of
the tree representing the document that is being edited. The API
of the Buffer.Root is stable and offers the necessary to insert,
delete and move along the buffer, knowing if the end or the
beginning of the document have been reached when interacting
with a Cursor.
Cursors, Selections and Cursels don't know about the buffer, and
they need to receive a reference to them to have positions and
also sometimes receive metrics from the editor that help determine
if a cursor is about to exit boundaries of the buffer and have
everything "in place".
[]($section.id("commands"))
## Editor Commands
We mentioned earlier that most of the operations work on all
the cursors and selections, moreover, there are various
commands acting over the set of cursors, selections, cursels
or marks. Given said this, we will be using functions as
parameters in most of the situations. Functional programming
languages are popular in these scenarios, to name a prominent
one, Emacs and emacs lisp.
If the buffer is not to be modified, we will be using the
method `buf_root` to get the root of the buffer to find and
position the cursors. In the other hand, we will use
`buf_for_update()` when the buffer is to be modified.
The benefit of sending functions as parameters is that code
is reused and the codebase can grow without much sacrifice
when adding new functionalities, because one would be
thinking only in the current cursor and if required, the
operation will be applied to all the cursors, the same applies
to selections, cursels and marks.
[]($section.id("moving"))
## Moving
For example, to move the cursors a page up, we can look at
the command `move_page_up`, which uses the method
`with_cursors_and_view_const` sending the function
`move_cursor_page_up`.
Looking inside `with_cursors_and_view_const`, it iterates
over all the cursors and invokes `with_cursor_and_view_const`,
sending a cursor as a parameter, that function, will invoke
`move_cursor_page_up` whose commitment is to use the method
`move_page_up` from the cursor, sending the view and the
metrics of the editor.
The family of `move` functions is big, look for the methods
whose name has the word move in them. with the command palette
is possible to get a glimpse of them.
[]($section.id("selecting"))
## Selections
There are naming conventions for the functions that help
understanding what each one is doing, there has been taken
great deal of care to maintain consistency that needs to be
followed to make it easier to continue extending on
functionalities.
Look at the following set of functions, all of them act on
cursors, cursels and selections, in singular and plural, and
some of them receive arguments, the repeat functions act
many times over the set of the group that they are intended
to do so. The parameters and following the calls from one
to another will let you navigate and get the hang of their
usage.
```zig
fn with_cursor_const
fn with_cursel_const
fn with_cursels_const
fn with_selection_const
fn with_cursor_const_arg
fn with_cursors_const_arg
fn with_cursors_const_once
fn with_selection_const_arg
fn with_cursors_const_repeat
fn with_selections_const_arg
fn with_cursor_and_view_const
fn with_selections_const_once
fn with_cursors_and_view_const
fn with_selections_const_repeat
fn with_selection_and_view_const
fn with_selections_and_view_const
```
[]($section.id("modifying"))
## Modifying the buffer
The `select` family of functions is bigger than the set of `move`
functions, in contrast, the `cut`, `delete`, `insert`, `paste`
looks smaller, and this is accomplished making composition of
functions. Usually when modifying something, first there is
a process to locate the cursor, cursel o selection in the proper
place and then applying the modification.
[Discord](https://discord.com/invite/4wvteUPphx) and
[Github issues](https://github.com/neurocyte/flow/issues) are the
main channels to do so.
[]($section.id("next"))
## Next steps
* [minimodes](/docs/architecture/minimode) invoke commands defined in the editor
* [palettes](/docs/architecture/palette) are open by commands and when selected an item, a command
is invoked.
* Plenty of [commands](/docs/architecture/command) are defined in the editor.

View file

@ -0,0 +1,120 @@
---
.title = "Command arguments and message passing",
.date = @date("2025-10-27T00:00:00"),
.author = "CJ van den Berg",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "docs/architecture/inner_data_exchange.smd",
.codepath ="src/tui/editor.zig",
},
---
Flow uses actor model to offer an snappy experience when working with
projects that have tens of thousands of source files, also features
async communication with the threads that are working in independent
tasks supporting git interface, lsp and tree-sitter integration,
apart from the directory introspection to make available all the
files of the project, all of them expected s from an IDE. The
command arguments travel to the target command and are en/decoded
powered by [cbor](https://github.com/neurocyte/cbor), the same as the
parameters that are sent from one thread to another. The process
management is provided by
[thespian](https://github.com/neurocyte/thespian).
This chapter describes the mechanisms that flow has to pass arguments
between components.
[]($section.id('libraries'))
## Library usage
* The Thespian library sends and receives `thespian.message` values,
which are strongly typed, but schema free CBOR encoded structures.
It supports spawning, linking, killing, etc., of lightweight processes
(aka the "Actor Model" with "Green Threads") and provides async file
and network IO and child process management.
* The CBOR library encodes decodes CBOR structured data to/from Zig
variables
* Encoding happens via the `cbor.write*` functions. These are wrapped
by `command.fmt` and `thespian.message.fmt` which provide fast
allocation free encoding to a thread local buffer. Note that the CBOR
data encoded via the `*.fmt` functions will only survive until another
message is encoded and must be copied somewhere for more permanent
storage, or possibly sent somewhere via thespian.
* Decoding happens via the `cbor.match`, `cbor.extract` and
`cbor.extractAlloc` group of functions. `cbor.extract` functions do
not allocate and cannot be used to extract some types that require allocation.
`cbor.extractAlloc` functions _do_ allocate and can extract arrays and
structures that require allocation. Both `cbor.extract` and
`cbor.extractAlloc` produce strings that **reference** the original CBOR
data buffer. `thespian.message.match` and `thespian.extract` functions
are fairly simple wrappers.
The most basic example on deserialization of an integer value is shown
in [commands](/docs/architecture/command#command_arguments).
Cbor features en/decoding arrays, json and compounds of basic types and
the only requirement is to decode in the same order as encoding the
data, more samples on using cbor can be seen in
[cbor tests](https://github.com/neurocyte/cbor/blob/master/test/tests.zig).
For example, when interacting with the clipboard, the messages sent are
multiple chunks of information,
[]($section.id('scoping'))
## Buffer scoping
CBOR structures are mostly stored in a way that avoids allocation
entirely. This is really fast, but requires that you always know where
the CBOR data you are working with is stored.
* 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 method call
* `thespian.message.fmt` encoded messages are stored in the thread local
`thespian.message_buffer` and remain in scope only until the next
`thespian.message.fmt` call on the same thread
* `thespian.exit_message` encoded message are stored in the thread local
`thespian.error_message_buffer` and remain in scope only until the next
`thespian.exit_message` call on the same thread
* `command.fmt` encoded messages are stored in the thread local
`command.context_buffer` and remain in scope only until the next
`command.fmt` call on the same thread
All of this implies several things worth keeping in mind:
* `thespian.pid.send` will encode it's parameters to
`thespian.message_buffer` and then send them to the destination actor's
receive buffer. This will invalidate the contents of
`thespian.message_buffer` and destroy any message previously encoded
with `thespian.message.fmt` (on the same thread).
* Calling `command.fmt` inside a command that uses
`command.Context.args` will possibly invalidate the command's own
arguments. I say _possibly_ because the `ctx.arg` may come from
somewhere else entirely, like the actor's receive buffer if the command
was called remotely, or some other explicitly allocated buffer.
* Use `*.fmtbuf` to encode to different buffer if there may be scoping
issues. You can allocate and scope this buffer any way you want.
* Calling `thespian.exit_message` while propagating an error up the
stack that was previously created with `thespian.exit_message` will
overwrite the original error
* Don't ever try to free a CBOR buffer unless you know exactly where it
came from.
* Strings extracted from CBOR buffers are **references** into the
original CBOR data and will be invalidated implicitly when the CBOR
buffer they came from is invalidated/overwritten.
[]($section.id('next'))
## Next steps
* [Commands](/docs/architecture/command)
* [Minimodes](/docs/architecture/minimode)
* [Architecture](/docs/architecture)
[]($section.id('more'))
## More information
* [Deepwiki on cbor](https://deepwiki.com/neurocyte/cbor)
* [Samples of cbor usage](https://github.com/neurocyte/cbor/blob/master/test/tests.zig)
* [Deepwiki on thespian](https://deepwiki.com/neurocyte/thespian)

View file

@ -0,0 +1,179 @@
---
.title = "Keybinding",
.date = @date("2025-10-19T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "/docs/architecture/keybind.smd",
.codepath ="src/keybind/builtin/",
},
---
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
own keybinds to suit your use cases and make everything easier, faster
and more fluid when in flow.
First make sure you are aware of the
[existence of ctrl+f2 palette](/docs#key_controls) which
exposes a list of commands available for you to use, and when you select
a command, it's pasted in your current cursor position.
Using the command palette `Ctrl+Shift+p` and typing **Edit key
bindings**, takes you to a json file to extend flow, configuring
keybindings to suit your needs. According to the mode you are in, your
personal mode's file configuration will be opened. Explore the
the file to discover how commands are bound to some combos, key presses
and the different [imodes](#hierarchy) present.
Command palette can also be reached left clicking on the current
mode in the status bar.
[]($section.id('tldr'))
## ;TLDR;
Once you open the corresponding json file, locate inside the
[imode](#hierarchy)(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 will be invoked, the array accepts strings
like in
```js
["ctrl+alt+shift+p", "open_command_palette"]
```
To avoid screwing up the combinations, and putting flow in an unusable
state derived from a wrong mapping of key combinations, read on.
[]($section.id('defaults'))
## Resetting keys to factory defaults
User configured keybindings are stored in Flow's configuration directory
under `keys/mode.json` where mode can be `flow`, `emacs`, `vim`, `helix`
or customized ones. Removing the keys directory or the particular mode
file can take you out from a broken state.
[]($section.id('modes'))
## Keybinds for each mode
Keybinds are edited per mode, and other modes inherit what is defined
in your `flow.json` keybindings. Each mode override keybindings of its
parent mode. For example, if you are in **emacs** mode you will be
redirected to `emacs.json` and it will override the keybindings from
flow, and the default ones defined for emacs mode.
[introducing keybindings](/devlog/2024#2024-12-05T20:55:00) showcases
how to get to edit keybindings.
[]($section.id('hierarchy'))
## Keybindings hierarchy
Some terminology
* **Mode**: Stored in a json file, like flow mode declared in
`flow.json`.
* **Imode**: under the json file.
* **Major Imode**: `project` or descendant from `project`.
* **Minimodes**: To be used momentarily and do not inherit from
`project`.
In general a keybinding json shows this hierarchy:
```
Mode > Imode > press > Key and commands
map > map > array > array(array(string,numbers),strings,numbers)
```
`Mode` is the json file that holds a map, where each entry has a map
called `press` that is an array of arrays.
`project` is the main imode in `flow.json` and it can be noticed that
`normal` imode `inherits` from `project`, some modes have `release`,
usually one will be using only `press` inside `normal` imode or the
specific mode if inside `vim`, `helix` or `emacs` modes.
Looking further, it can be seen that
[minimodes](/docs/architecture/minimode) have their own keybinding
mappings defined in a particular imode.
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
hierarchy and it's common for major imodes to be descendants from
`project`.
[]($section.id('adding'))
## Adding a Keybinding
The most basic case to map a keybind to a command was covered in
[TLDR](#tldr) which used the combination of three keys pressed
simultaneously `ctrl`, `shift` and `p`, all of them where combined with
`+` to execute the command `open_command_palette`.
A common use case is to add a keybinding to invoke an already existing
command and chain it to another, making Flow more suited to your own
needs.
[]($section.id('shell'))
## Running shell commands
For example, `f5` by default is used to run `zig build test` outputting
its results to a *scratch buffer* called `test`.
The original definition is:
```js
["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]],
```
Note that:
The keybind is `f5`, which maps to the keycode generated by pressing
the `f5` key.
`create_scratchbuffer` is invoked receiving the parameter `*test*`
which results in creating a scratch buffer if didn't exist. And then
executing the command `shell_execute_insert` that receives the
paramaters `zig`, `build`, `test`. This latter command is executing
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.
```
[
"f5",
[
"create_scratch_buffer",
"*test*"
],
[
"shell_execute_insert",
"zig",
"build",
"test"
]
]
```
Observe [tasks running](/devlog/2025#2025-01-26T22:11:00) and maybe
consider using more keybindings or running tasks for your projects.
[]($section.id('next'))
## Next steps
If you realized that there is a handy combination that others can
benefit from or that a mode lacks the combination and it might be
included in flow, look at the
[contribution guidelines](/docs/contributing) to submit your findings
and solutions.
Probably binding commands is good, but maybe there is a feature in other
text editors that you miss and would love to have at your fingertips.
Then it's Zig time: [Adding commands](/docs/architecture/command) to
flow.
* Making flow even better with [tests](/docs/testing)
* [How to contribute](/docs/contributing)
* [Get in touch](https://discord.com/invite/4wvteUPphx) to share your
combos

View file

@ -0,0 +1,116 @@
---
.title = "Minimodes",
.date = @date("2025-10-15T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "/docs/architecture/minimode.smd",
.codepath ="src/tui/mode/mini/",
},
---
Minimodes commitment is to add functionality to the editor, are opened
for short periods of time and have their own set of keybindings to
execute an specific action, i.e. find something in the current buffer
or in project files, open/save a file, and, in modal modes(like vim
and helix), as receiving a number as a prefix to repeat an action many
times.
[]($section.id("anatomy"))
## Anatomy of minimodes
To create a minimode it's needed:
* A Keybinding
* An Action mapping
* A Minimode definition
[]($section.id("keybind"))
### Keybinding
When a key or a keystroke(set of keys) are pressed, the associated
minimode gets activated and will start to capture the key/strokes
until a special keybinding makes it exit, or an specific action exits
the minimode. Head to `src/keybind/builtin/flow.json`(flow keybinds)
and look for `mini_find`, where you will know which specific actions
are triggered by the keybindings of the `find` minimode.
[]($section.id("mapping"))
### Action mapping
Actions executed by each minimode are stored one per file under
`src/tui/mode/mini/`. The command that opens the door to the minimode
is linked from `src/tui/tui.zig` which calls the minimodes dynamically
when needed.
Look for `mini` inside `tui.zig` to find out which minimodes are present
and where to look, to learn how each minimode does its own task.
[]($section.id("definition"))
### Minimode definition
Possibly the simplest minimode that does not require defining a
particular widget is the `replace` minimode, used in
[helix](/docs/mode/helix) and vim mode. To enter the minimode in
Helix while in `NOR` or `INS` use the keybind **r**; it consumes
another key and replaces the current character under the main cursor
with the immediately pressed key after **r**. If there are multiple
selections, all the characters are replaced by the one typed after
**r**.
- The minimode needs to expose a `create` function with type
```zig
pub fn create(Allocator,command.Context) !struct { tui.Mode, tui.MiniMode }
```
Which is in charge of registering the minimode to be able to receive
events and will offer the minimode name, the one that appears in the
lower status bar while it is active, to let it be known that the
minimode is active. This is where all the instatiations are made. Which
leads to
- The `deinit` function whose type is
```zig
pub fn deinit(*Self)
```
- A `receive` function that will route events received casting the
type:
```zig
pub fn receive(*Self, tp.pid_ref, tp.message) error{Exit}!bool
```
- A `commands` field that will expose the minimode `Collection` of
`Commands`.
- An special command `mini_mode_insert_code_point` as an element of the
commands collection with type:
```zig
pub fn mini_mode_insert_code_point(*Self, Ctx) Result
```
acting as the default handler of the key presses that the minimode will
receive when there is no other keybind defined for the minimode.
All the keys were handled and managed by the default "invisible" widget
that processes the keys for the minimode. And there is room for custom
widgets.
[]($section.id("custom_widgets"))
## A custom widget
When there is a need for an specialized widget, it's possible to
define one, for example, the `file_browser` is used to load and
save files, and `numeric_input` is used to set the tab width for
example(look for it in the command palette `:`).
[]($section.id("next"))
## Next steps
* Head to [architecture](/docs/architecture)
* Review [commands](/docs/architecture/command)
* Review [keybindings](/docs/architecture/keybind)

View file

@ -0,0 +1,121 @@
---
.title = "Palettes",
.date = @date("2025-10-20T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "docs/architecture/palette.smd",
.codepath ="src/tui/mode/overlay/clipboard_palette.zig",
},
---
Palettes are overlay menus with auto complete that allow to select an
item from the presented list, applying a command with the selected
element, optionally deleting the selected item; it's possible to
close the palette without selecting anything(a.k.a. cancel), filter
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 are based on `src/tui/mode/overlay/palette.zig that does all the
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
receive inputs from the keyboard and execute the beforehand mentioned
actions in response.
To get the most of this section, it's recommended to have read about
[commands](/docs/architecture/command), and optionally,
[minimodes](/docs/architecture/minimode).
[]($section.id("anatomy"))
## Defining the palette
Palettes are under `tui/overlay` and use the facilities offered by
`palette.zig` to perform all the operations.
1. Defining the list of elements
2. Filtering the elements
3. Perform an action with the selected element
Note: Palettes are meant to show options and allowing to select the
options to execute an action on the given selection. To maintain as
readable as possible the code and thus extensible, the data to be used
should be prepared previously.
[]($section.id("basic"))
### Fields
Basic fields that give hints to the user and need to be set are:
```zig
pub const label = "Clipboard history";
pub const name = " clipboard";
pub const description = "clipboard";
pub const icon = " ";
```
[]($section.id("entries"))
### Defining the list of elements in the palette
`load_entries` is in charge of populating the visible entries, each one
with an index.
```zig
pub fn load_entries(palette: *Type) !usize
```
The index will identify the action to be taken.
When populating with each entry, there must be a relation that links the
option chosen with the required action, and this happens in
`add_menu_entry`.
```zig
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
```
The common line that will be used when registering the event to a
selected item is
```zig
try palette.menu.add_item_with_handler(value, select);
```
Which will apply the `select` function when the value is selected.
[]($section.id("select"))
### Acting on selection
When the selection happens, it is time to invoke the command with the
selection making sure to close the palette. Those actions will be
handled inside `select`, whose signature is:
```zig
fn select(menu: **Type.MenuType, button: *Type.ButtonType, pos: Type.Pos) void {
```
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
palette is ready, it's time to make the palette available as a command.
[]($section.id("register"))
## Registering the palette
Commands that open the palette are defined in `tui.zig` in a similar way
it is done with [minimodes](/docs/architecture/minimode). We have got
you covered if in doubt about
[how to create a command](/docs/architecture/command).
To view a complete implementation of a palette, take a look at
[clipboard history palette commit](https://github.com/neurocyte/flow/commit/634a18cb5685a3c3fcfc08301306e628d33c3256)
[]($section.id("next"))
## Next steps
* [Minimodes](/docs/architecture/minimode)
* [On commands](/docs/architecture/command)
* [Architecture](/docs/architecture)

View file

@ -15,7 +15,7 @@ command. The manual is included here for convenience.
* [Flow Control online help](/docs/help)
[]($section.id("basic_usage"))
## Basic Usage
```bash
flow file.zig:42 # Open at line 42
@ -24,7 +24,7 @@ flow --list-languages # Show all supported languages
flow --help # List of command line options
```
[]($section.id("key_controls"))
## Key Controls
| Command | Action |
|---------------------------|----------------------|

View file

@ -0,0 +1,76 @@
---
.title = "Helix Mode",
.date = @date("2025-10-10T00:00:00"),
.author = "Igor Támara",
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "/docs/mode/helix.smd",
.codepath = "src/tui/mode/helix.zig",
},
---
This document describes implementation of Helix Mode.
[]($section.id('what'))
## What and what not
The first and biggest difference is that Flow has a mode that emulates
Helix, or at least has equivalent of the worthiest actions that can be
done with Helix. The conversely is not true.
`:` opens up Flow's rich command palette that might have
functionalities Helix users are used to have, if you find something
missing, it's possible to
[open a Feature Request](https://github.com/neurocyte/flow/issues),
make sure to review
[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
[Discord](https://discord.gg/kzJC9fA7) to ask if or there is a
workaround, remember that it's possible to bounce back to Flow mode
if needed.
[]($section.id('enhancing'))
## Enhancing hx mode
This is a programmer's editor, you are more than welcome to enhance to
suit your needs that maybe coincide with others.
Please take a look at [architecture](/docs/architecture) and
[contributing](/docs/contributing) for an overview and the mechanics
of getting your changes into Flow.
hx mode is modal kind, the same as vim mode, and the file that has the
particular work to make it real is in `src/tui/mode/helix.zig`, adding
a `command` and the corresponding `meta` is what is required.
[More on commands](/docs/architecture/command).
[]($section.id('pickers'))
### Pickers
Flow hx mode offers most of the picker types equivalents with `panels`
and [palettes](/docs/architecture/palette). Example of panels are
the `g` `r` (go to reference from lsp) and `space` `/` (a.k.a find in
files). Examples of `palettes` are `space` `b` to pick a buffer or
`space` `f` to open a file in your project. Panels open below the
editor while palettes open overlapping the working area.
One medium sized project is to create a widget that has one input
widget with two panels, on the left, the list of options and, on the
right, the preview of the selected option and offer various keybindings
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
requesting [keybinds](/docs/architecture/keybind),
[commands](/docs/architecture/command),
[palettes](/docs/architecture/palette), or the special widget
mentioned previously.
More about the [architecture](/docs/architecture) or jump to
[contribution guidelines](/docs/contributing).
Join the [#helix-mode channel](https://discord.gg/sxdejrAA) and get in
touch with other hx users.

View file

@ -5,12 +5,13 @@
.layout = "tutorial.shtml",
.draft = false,
.custom = {
.githubedit = "/docs/testing.md",
.githubedit = "/docs/testing.smd",
.codepath ="test",
},
---
Currently flow tests are aimed to work as unit tests, it always is a good
idea to review the
Currently flow tests are aimed to work as unit tests.
If new to zig, it always is a good idea to review the
[zig tests documentation](https://ziglang.org/documentation/master/#Zig-Test)
and also an
[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"))
## 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:
```
@ -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
current behavior
* A bug is fixed
* A defined behavior could be thought different, for example when in a mode,
it was defined that something might diverge from other programs.
* A defined behavior could be thought different, for example when in a
mode, it was defined that something might diverge from other programs.
Tests are placed under `test` directory. Add your test in the file that
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
be tested.
For example in `src/tui/mode/helix.zig`, `test_internal` exposes the private
function
For example in `src/tui/mode/helix.zig`, `test_internal` exposes the
private function
```zig
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
[]($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:
@ -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
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"))
### Use additional tools to test a running flow session
@ -187,5 +208,6 @@ If in doubt about how to do something,
## Next steps
* [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)