# Text Editor

This line based text editor lives at text/ed.fs and allows the examination and
modification of text contents. The Grid-based [sys/grid] editor is text/ged.

The central structure of this unit is Edbuf (editor buffer) and its API is
described below. You will typically only want one copy of this structure and
this unit automatically instantiate one and binds it to the "edbuf" structbind.

This structure has all the methods you need to load, save and modify text
contents from within it. The buffer is an array [doc/mem/array] of Line
structures which themselves reference buffers in an Arena allocator
[doc/mem/arena]. Lines have a maximum length of ARENASZ bytes.

Interacting directly with the buffer structure gives you a maximum of
flexibility, but is a bit verbose. To make text operations more efficient,
shortcut words are provided. This collection of words is called the Convenience
Layer.

Because these shortcuts can clash with other words in the system, it is
recommended to load the text editor in its own context [doc/lib/context].

## Position

An important concept in the editor is the concept of "position", that is, a
number that uniquely identifies a line in the buffer as well as a character
within that line.

Position is represented as a single integer with the high 16 bits being the line
position (lpos) and the low 16 bits being the character position within the
line (cpos).

Line numbers and character numbers are 0-indexed.

## Clipboard

Ed seamlessly integrates with the clipboard [text/clip] by providing Edbuf's
":clipto" word to copy a range of the buffer to the clipboard.

## Line API

The Line structure describes a line in the Ed buffer. The line structure does
not include the LF character. This means that an empty line has a cnt of 0.

We maintain a separate character count and allocated size. It's possible,
through deletion, that these two numbers differ. When inserting or appending
contents to the line, if we have enough space, we don't needlessly reallocate in
the arena buffer.

Fields:

ptr     Pointer to the first character of the line
cnt     (16b) Number of characters in the line
allocsz (16b) Bytes allocated at ptr

Methods:

:range ( self -- a u )
  Return the range corresponding to this line

:init ( a u line -- )
  Initialize line fields to range "a u". "allocsz" is set to u.

:print ( self -- )
  Call "rtype" with the line's range.

:delchars ( idx n line -- )
  Delete "n" characters starting at index "idx".

:?realloc ( arena n line -- )
  Verify whether "n" characters could be added to the line. If not, reallocate
  a sufficiently big buffer in "arena" to accomodate those "n" new characters.

:append ( a u line -- )
  Assuming that there's enough space (aborts if not), append range "a u" at the
  end of the line.

:split ( idx line -- a u )
  Truncate line at "idx" and yield its right part as range "a u".

## Edbuf API

The Edbuf is mainly an Array of Line structures with various metadata attached
to them. The Line structures themselves don't hold actual contents, that content
is held in the Arena allocator that the Edbuf manage.

Among Edbuf's metadata, a very important one is the position, that is, where we
currently "are" in the buffer. That information is held by "pos". At all times,
that position remain "bounded" at the character level, that is, whenever we
select a new line, we ensure that the "cpos" part of the position is within
bounds of that line.

Edbuf extends the IO struct, allowing some pretty nice interactions with other
IO-aware words. How it works is rather simple: reading and writing happen at
current position which then advances the position by the IO operation size.

When writing, whenever a LF character is encountered, we insert a new line after
the selected one and move current position at the beginning of that empty line.

Therefore, "loading" a file contents to the buffer is a :write to it. Saving the
buffer somewhere else is a :read from it.

No special care is given to ASCII control characters except for LF. Try to avoid
them in your text files.

Fields:

buf       An arena buffer with the contents of lines.
lines     Array of Line structures.
pos       Cursor position

Words:

:new ( -- edbuf )
  Create a new Edbuf structure with its own "buf" arena.

:empty ( self -- )
  Empty the buffer, resetting positions and arena allocator.

:godown ( n self -- )
  Move position down by "n" lines.

:goup ( n self -- )
  Move position up by "n" lines.

:go ( n self -- )
  Move position to line number "n".

:goleft ( n self -- )
  Move position left by "n" characters, not crossing line boundaries.

:goright ( n self -- )
  Move position right by "n" characters, not crossing line boundaries.

:nextword ( self -- )
  If the cursor is on a whitespace, advance it to the next non-whitespace
  character on the same line, if any. If the cursor is on a non-whitespace,
  advance to the next whitespace, then to the next non-whitespace.

:nextws ( self -- )
  Same as :nextword, but switch "whitespace" and "non-whitespace".

:find ( str self -- )
  Find occurence of "str" in buffer, starting from current position. If found,
  position is moved to that occurence. If not, position stays unchanged.

:findnext ( self -- )
  Repeat the last :find.

:cprint ( self -- )
  Print current line with a second line under it containing a "^" character
  indicating the current "cpos".

:linecnt ( self -- cnt )
  Return total line count of the buffer.

:insert ( n self -- )
  Insert "n" empty lines at "lpos", displacing subsequent lines by "n".

:delchars ( n self -- )
  Delete "n" characters starting from current position to the right, not
  crossing line boundaries.

:replchar ( c self -- )
  Replace character at current position with "c".

:dellines ( n self -- )
  Delete "n" lines starting from "lpos".

:clipto ( pos self -- )
  Copy the contents between cursor and "pos", including newlines, to the
  clipboard.

:delto ( pos self -- )
  Delete contents between cursor and "pos", even across multiple lines. If the
  text to delete doesn't span whole lines, the "before" part of the first line
  is joined with the "after" part of the last line.

:appendline ( self -- )
  Append a new empty line after selection.

:insertline ( self -- )
  Insert a new empty line before selection.

## Convenience layer

The convenience layer is a collection of words with short names that makes
interacting with the API above easier. Its choice of names is broadly inspired
by UNIX's "vi" program.

The "input" words ("I", "o", "O") work in a peculiar way because they consume
the rest of the input line. For example, let's say that you type this:

    foo bar I Hello there

"foo", "bar" and "I" will be interpreted normally, but "Hello there" will be
copied without going through the interpreter. The content is then sent to the
buffer with :puts. The LF character is not sent (otherwise, every insert
operation would result in a new line being inserted).

"I" is used instead of "i" to avoid shadowing the iterator loop variable.

### API

Values:

print?   Boolean flags indicating whether all actions are followed by :cprint.
pagesz   Number of lines in a "page"

Words:

p ( -- )
  Print one "page".

s ( -- )
  Print "stats", that is, the current line with :cprint followed by another
  line with "current lineno" / "line count".

I ( "...\n" -- )
  Insert typed line before the current one and select it.

o ( "...\n" -- )
  Append a line after the current one with the typed contents.

O ( "...\n" -- )
  Insert a line before the current one with the typed contents.

f ( "...\n" -- )
  Find typed string in buffer.

n ( -- )
  Repeat the last find

dl ( n -- )
  Delete "n" lines starting from current pos.

dc ( n -- )
  Delete "n" characters starting from current pos.

g ( lineno -- )
  Select line number "lineno"

c- ( n -- )
  Go "n" characters to the left.

bol ( -- )
  Go to the beginning of the line.

c+ ( n -- )
  Go "n" characters to the right.

eol ( -- )
  Go to the end of the line.

l- ( n -- )
  Go "n" lines up.

l+ ( n -- )
  Go "n" lines down.

eof ( -- )
  Go to the last line of the buffer.

edload ( -- )
  Empty the buffer and load the entire contents of "file" into it.

edsave ( -- )
  Save contents of edbuf into "file", truncate and flush.

## See also

[doc/text/ged] [doc/sys/io] [doc/sys/file] [doc/mem/alloc] [doc/lib/context]
