diff --git a/README.md b/README.md index 156377e..a0ab542 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/content/docs/architecture.smd b/content/docs/architecture.smd index 51a6924..5e8dbd4 100644 --- a/content/docs/architecture.smd +++ b/content/docs/architecture.smd @@ -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) \ No newline at end of file diff --git a/content/docs/architecture/command.smd b/content/docs/architecture/command.smd new file mode 100644 index 0000000..8d251cf --- /dev/null +++ b/content/docs/architecture/command.smd @@ -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) \ No newline at end of file diff --git a/content/docs/architecture/editor.smd b/content/docs/architecture/editor.smd new file mode 100644 index 0000000..2ec1887 --- /dev/null +++ b/content/docs/architecture/editor.smd @@ -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. diff --git a/content/docs/architecture/inner_data_exchange.smd b/content/docs/architecture/inner_data_exchange.smd new file mode 100644 index 0000000..45a94f8 --- /dev/null +++ b/content/docs/architecture/inner_data_exchange.smd @@ -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) \ No newline at end of file diff --git a/content/docs/architecture/keybind.smd b/content/docs/architecture/keybind.smd new file mode 100644 index 0000000..2ef1b31 --- /dev/null +++ b/content/docs/architecture/keybind.smd @@ -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 diff --git a/content/docs/architecture/minimode.smd b/content/docs/architecture/minimode.smd new file mode 100644 index 0000000..3a173e2 --- /dev/null +++ b/content/docs/architecture/minimode.smd @@ -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) diff --git a/content/docs/architecture/palette.smd b/content/docs/architecture/palette.smd new file mode 100644 index 0000000..41dfecf --- /dev/null +++ b/content/docs/architecture/palette.smd @@ -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) \ No newline at end of file diff --git a/content/docs/index.smd b/content/docs/index.smd index 5ed7ab1..641f3f7 100644 --- a/content/docs/index.smd +++ b/content/docs/index.smd @@ -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 | |---------------------------|----------------------| diff --git a/content/docs/mode/helix.smd b/content/docs/mode/helix.smd new file mode 100644 index 0000000..3b983f4 --- /dev/null +++ b/content/docs/mode/helix.smd @@ -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. \ No newline at end of file diff --git a/content/docs/testing.smd b/content/docs/testing.smd index 31c48d7..8684dd2 100644 --- a/content/docs/testing.smd +++ b/content/docs/testing.smd @@ -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) \ No newline at end of file