editor section switched from draft to visible
This commit is contained in:
parent
9dd5c83138
commit
3417f6a97c
1 changed files with 217 additions and 52 deletions
|
|
@ -3,7 +3,7 @@
|
|||
.date = @date("2025-10-19T00:00:00"),
|
||||
.author = "Igor Támara",
|
||||
.layout = "tutorial.shtml",
|
||||
.draft = true,
|
||||
.draft = false,
|
||||
.custom = {
|
||||
.githubedit = "docs/architecture/editor.smd",
|
||||
.codepath = "src/tui/editor.zig",
|
||||
|
|
@ -48,7 +48,7 @@ makes the link between both of them, signaling the part of the `Buffer`
|
|||
that can be modified and manipulated as you see it. It scrolls on the
|
||||
current visible portion [view](#view_concept) of the buffer, when
|
||||
manipulated with the keyboard, the mouse can change the move the view
|
||||
while the primary mouse is offscreen; when keystrokes arrive to the
|
||||
while the primary mouse is off-screen; when keystrokes arrive to the
|
||||
editor, the view focuses to the primary cursor to make it visible and
|
||||
available to be used.
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ Cursor is a [CurSel](#cursel_concept).
|
|||
### Selection
|
||||
|
||||
A selection is represented by begin and end [cursors](#cursor_concept)
|
||||
and offers basic functions that will consitute the changes needed with
|
||||
and offers basic functions that will constitute the changes needed with
|
||||
deletions, insert replacements handled by the[editor](#tui_editor)
|
||||
services and [commands](/docs/architecture/command).
|
||||
|
||||
|
|
@ -104,10 +104,10 @@ 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".
|
||||
need to receive a reference to it in order to be aware of the
|
||||
restrictions and usually receive `metrics` from the editor that help
|
||||
determine if a cursor is about to exit the boundaries of the buffer
|
||||
and be inside bounds.
|
||||
|
||||
[]($section.id("commands"))
|
||||
## Editor Commands
|
||||
|
|
@ -117,7 +117,8 @@ 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.
|
||||
scenarios, to name a prominent one, Emacs and emacs lisp. Flow is
|
||||
inspired on it and others.
|
||||
|
||||
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
|
||||
|
|
@ -134,34 +135,130 @@ cursors, the same applies to selections, cursels and marks.
|
|||
## 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`.
|
||||
`move_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.
|
||||
>[]($block.attrs('line-numbers-js'))
|
||||
>```zig
|
||||
>pub fn move_page_up(self: *Self, _: Context) Result {
|
||||
> try self.send_editor_jump_source();
|
||||
> const root = try self.buf_root();
|
||||
> try self.with_cursors_and_view_const(root, move_cursor_page_up, &self.view);
|
||||
> self.clamp();
|
||||
>}
|
||||
>pub const move_page_up_meta: Meta = .{ .description = "Move cursor page up" };
|
||||
>```
|
||||
|
||||
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.
|
||||
which uses the method `with_cursors_and_view_const` sending the function
|
||||
`move_cursor_page_up`.
|
||||
|
||||
Looking inside `with_cursors_and_view_const`
|
||||
|
||||
>[]($block.attrs('line-numbers-js'))
|
||||
>```zig
|
||||
>fn with_cursors_and_view_const(self: *Self, root: Buffer.Root, move: cursor_view_operator_const, view: *const View) error{Stop}!void {
|
||||
> var someone_stopped = false;
|
||||
> for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel|
|
||||
> with_cursor_and_view_const(root, move, cursel, view, self.metrics) catch {
|
||||
> someone_stopped = true;
|
||||
> };
|
||||
> self.collapse_cursors();
|
||||
> return if (someone_stopped) error.Stop else {};
|
||||
>}
|
||||
>```
|
||||
|
||||
It iterates over all the cursors and invokes
|
||||
`with_cursor_and_view_const`, sending the `move` function, a cursor,
|
||||
remember that the function passed was `move_cursor_page_up` as seen
|
||||
previously.
|
||||
|
||||
The commitment of `move_cursor_page_up` is to use the method
|
||||
`move_page_up` from Cursor, sending editor `view` and `metrics`.
|
||||
|
||||
```zig
|
||||
fn move_cursor_page_up(root: Buffer.Root, cursor: *Cursor, view: *const View, metrics: Buffer.Metrics) !void {
|
||||
cursor.move_page_up(root, view, metrics);
|
||||
}
|
||||
```
|
||||
|
||||
The `move` functions set is numerous, look for the methods whose name
|
||||
have the word `move` in them. with the command palette is possible to get
|
||||
a glimpse of the available functions present(ctrl+f2).
|
||||
|
||||
[]($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.
|
||||
Function naming conventions help understand the purpose of each one,
|
||||
there has been taken great deal of care to maintain consistency, it's
|
||||
unlikely that there is need for new functions, in case of need, add
|
||||
it following what is already present.
|
||||
|
||||
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.
|
||||
Follows an example of a functionality that receives a repetition
|
||||
parameter, the `select_up` command, which given a number, takes all
|
||||
the current cursors and selections and select lines above them the
|
||||
number of times specified.
|
||||
|
||||
```zig
|
||||
pub fn select_up(self: *Self, ctx: Context) Result {
|
||||
const root = try self.buf_root();
|
||||
try self.with_selections_const_repeat(root, move_cursor_up, ctx);
|
||||
self.clamp();
|
||||
}
|
||||
pub const select_up_meta: Meta = .{ .description = "Select up", .arguments = &.{.integer} };
|
||||
```
|
||||
It's meta is guiding about the integer argument that the command can
|
||||
make use of. Invokes `with_selections_const_repeat` passing the
|
||||
function `move_cursor_up` as parameter and also the context, which
|
||||
actually holds the integer parameter holding the repetition information.
|
||||
|
||||
|
||||
>[]($block.attrs('line-numbers-js'))
|
||||
>```zig
|
||||
>pub fn with_selections_const_repeat(self: *Self, root: Buffer.Root, move: cursor_operator_const, ctx: Context) error{Stop}!void {
|
||||
> var someone_stopped = false;
|
||||
> var repeat: usize = 1;
|
||||
> _ = ctx.args.match(.{tp.extract(&repeat)}) catch false;
|
||||
> while (repeat > 0) : (repeat -= 1) {
|
||||
> for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel|
|
||||
> with_selection_const(root, move, cursel, self.metrics) catch {
|
||||
> someone_stopped = true;
|
||||
> };
|
||||
> self.collapse_cursors();
|
||||
> if (someone_stopped) break;
|
||||
> }
|
||||
> return if (someone_stopped) error.Stop else {};
|
||||
>}
|
||||
>```
|
||||
|
||||
Factor repetition is taken from the context in line XX, being used in
|
||||
the `while`, then for each repetition, the set of cursels is iterated,
|
||||
cursels hold cursors and selections, applying `with_selection_const`
|
||||
function, passing `root`(the buffer), `move_cursor_up` function, cursel
|
||||
and metrics as parameters. Note that after the function is applied to
|
||||
each cursel, self.collapse_cursors() is invoked, given that it's
|
||||
possible that some cursors in the operation have overlapped. With each
|
||||
cursel operation there is also a tracking of possible fails, continuing
|
||||
gracefully, one of such fails can be happening because come cursels
|
||||
might have been reaching the beginning of the buffer and there might be
|
||||
other cursels that have not yet reached the beginning of the file.
|
||||
|
||||
```zig
|
||||
pub fn with_selection_const(root: Buffer.Root, move: cursor_operator_const, cursel: *CurSel, metrics: Buffer.Metrics) error{Stop}!void {
|
||||
const sel = try cursel.enable_selection(root, metrics);
|
||||
try move(root, &sel.end, metrics);
|
||||
cursel.cursor = sel.end;
|
||||
cursel.check_selection(root, metrics);
|
||||
}
|
||||
```
|
||||
|
||||
If there are cursels that are not yet selections, their respective
|
||||
selections are activated with `cursel.enable_selection`, finally
|
||||
`move_cursor_up` (`move`s value is a pointer to the `move_cursor_up`)
|
||||
is applied, passing the end marker of the selection; with its new
|
||||
value, the cursor is positioned and finally a sanity check on cursel is
|
||||
applied with its method `check_selection`.
|
||||
|
||||
Flow allows navigating the code with `goto_definition` and `references`
|
||||
commands to deepen the understanding and following the different calls.
|
||||
|
||||
[]($section.id("modifying"))
|
||||
## Modifying the buffer
|
||||
|
|
@ -170,37 +267,105 @@ 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.
|
||||
the cursor, cursel or selection in the proper place and then applying
|
||||
the modification. There are cases when there is need to locate and
|
||||
act right away.
|
||||
|
||||
[Discord](https://discord.com/invite/4wvteUPphx) and
|
||||
[Github issues](https://github.com/neurocyte/flow/issues) are the main
|
||||
channels to do so.
|
||||
|
||||
## Helper function
|
||||
|
||||
When creating commands and services for the editor, sometimes is handy
|
||||
where are the cursels. It's possible to use `log_cursel`:
|
||||
When additions, changes and removal of buffer contents, editor offers
|
||||
the method `buf_for_update`; we will look first to the method
|
||||
`to_upper_cursel`, which by default in Flow, takes a CurSel, if it has
|
||||
no selection, the word that it is stepped on, if any, is selected and
|
||||
gets uppercased.
|
||||
|
||||
>[]($block.attrs('line-numbers-js'))
|
||||
>```zig
|
||||
>pub fn log_cursel(self: *Self, cursel: CurSel) void {
|
||||
> if (cursel.selection) |sel| {
|
||||
> self.logger.print("[{}:{}.{}] {}:{}.{} - {}:{}.{}", .{
|
||||
> cursel.cursor.row, cursel.cursor.col, cursel.cursor.target,
|
||||
> sel.begin.row, sel.begin.col, sel.begin.target,
|
||||
> sel.end.row, sel.end.col, sel.end.target,
|
||||
> });
|
||||
> } else {
|
||||
> self.logger.print("[{}:{}.{}] no selection", .{
|
||||
> cursel.cursor.row,
|
||||
> cursel.cursor.col,
|
||||
> cursel.cursor.target
|
||||
> });
|
||||
> }
|
||||
>fn to_upper_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||
> var root = root_;
|
||||
> const saved = cursel.*;
|
||||
> const sel = if (cursel.selection) |*sel| sel else ret: {
|
||||
> var sel = cursel.enable_selection(root, self.metrics) catch return error.Stop;
|
||||
> move_cursor_word_begin(root, &sel.begin, self.metrics) catch return error.Stop;
|
||||
> move_cursor_word_end(root, &sel.end, self.metrics) catch return error.Stop;
|
||||
> break :ret sel;
|
||||
> };
|
||||
> var sfa = std.heap.stackFallback(4096, self.allocator);
|
||||
> const sfa_allocator = sfa.get();
|
||||
> const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop;
|
||||
> defer sfa_allocator.free(cut_text);
|
||||
> const ucased = Buffer.unicode.get_letter_casing().toUpperStr(sfa_allocator, cut_text) catch return error.Stop;
|
||||
> defer sfa_allocator.free(ucased);
|
||||
> root = try self.delete_selection(root, cursel, allocator);
|
||||
> root = self.insert(root, cursel, ucased, allocator) catch return error.Stop;
|
||||
> cursel.* = saved;
|
||||
> return root;
|
||||
>}
|
||||
>```
|
||||
|
||||
Lines through 4 to 9 implement the selection description sketched
|
||||
previously, selecting the word if no selection was present to apply
|
||||
uppercasing.
|
||||
|
||||
And to reflect the changes in the buffer from lines 10 to 19:
|
||||
|
||||
1. copy contents,
|
||||
1. uppercase
|
||||
1. remove the selection,
|
||||
1. and add the uppercased in the position where the cut was made.
|
||||
making sure the cursel is positioned in the expected place.
|
||||
1. A `buffer`(`root`) is received, modified and finally returned.
|
||||
|
||||
As we saw, the method `to_upper_cursel` acts on a cursel, used by the
|
||||
`to_upper` command, which acts on all the present cursels, getting the
|
||||
buffer with the method `buf_for_update` to make modifications on it.
|
||||
|
||||
```zig
|
||||
pub fn to_upper(self: *Self, _: Context) Result {
|
||||
const b = try self.buf_for_update();
|
||||
const root = try self.with_cursels_mut_once(b.root, to_upper_cursel, b.allocator);
|
||||
try self.update_buf(root);
|
||||
self.clamp();
|
||||
}
|
||||
pub const to_upper_meta: Meta = .{ .description = "Convert selection or word to upper case" };
|
||||
```
|
||||
|
||||
via `with_cursels_mut_once` receives a buffer that will be modified
|
||||
and returns one with modifications, updating what is presented with
|
||||
the method `update_buf` and an allocator. The previously described
|
||||
`to_upper_cursel` is sent
|
||||
|
||||
>[]($block.attrs('line-numbers-js'))
|
||||
>```zig
|
||||
> fn with_cursels_mut_once(self: *Self, root_: Buffer.Root, move: cursel_operator_mut, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||
> var root = root_;
|
||||
> var someone_stopped = false;
|
||||
> for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
|
||||
> root = self.with_cursel_mut(root, move, cursel, allocator) catch ret: {
|
||||
> someone_stopped = true;
|
||||
> break :ret root;
|
||||
> };
|
||||
> };
|
||||
> self.collapse_cursors();
|
||||
> return if (someone_stopped) error.Stop else root;
|
||||
> }
|
||||
>```
|
||||
|
||||
Worth to note that the modified `root` is being passed to the function
|
||||
`with_cursel_mut` on each iteration over the cursels along with the
|
||||
allocator and as described previously in selection functions, tracking
|
||||
if there is some fail an error is popped up, whilst everything was
|
||||
successful, the modified `root` is returned. Now time to see the
|
||||
method `with_cursel_mut` that receives the pointer to `to_upper_cursel`
|
||||
along with root, cursel and allocator.
|
||||
|
||||
```zig
|
||||
fn with_cursel_mut(self: *Self, root: Buffer.Root, op: cursel_operator_mut, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||
return op(self, root, cursel, allocator);
|
||||
}
|
||||
```
|
||||
|
||||
As seen, the only task for `with_cursel_mut` is providing the required
|
||||
elements to let `to_upper_cursel` do what was described previously.
|
||||
|
||||
[]($section.id("next"))
|
||||
## Next steps
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue