# Text Editor

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

The central structure of this unit is called "edbuf" (editor buffer) and its API
is described below. There is initially one one of such buffers in memory, but
more can be added. There will typically be only one active edbuf at once and
the "edbuf" word points to it.

This structure has all the methods you need to load, save and modify text
contents from within it. The buffer is an array [mem/array] of "line"
structures which themselves reference buffers in an arena allocator
[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.

## 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.

## Multiple buffers

There can be more than one edbuf struct in memory at once. The "edbufs" variable
is a LL that holds all of the edbufs. You can add new ones, cycle through them,
all of that. See the API below.

## Position API

cpos ( pos -- cpos )
  Extract the character index from "pos". For example, if n=0, it means the
  cursor is at the beginning of the line.

lpos ( pos -- lpos )
  Extract the line number from "pos".

joinpos ( lpos cpos -- pos )
  Merge lpos and cpos in a single pos value.

## Line API

The "line" structure describes a line in the edbuf. 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

Words:

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

printline ( line -- )
  Call "rtype" with the line's range.

?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 manages.

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 stream struct [io/stream], allowing some pretty nice
interactions with other stream-aware words. How it works is rather simple:
reading and writing happen at current position which then advances the position
by the stream 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
filename  String buffer containing the path of the file associated to the buffer

Line management words:

line ( idx ed -- line )
  Yield the line struct at index "idx".

curline ( ed -- line )
  Yield line struct of current position.

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

empty? ( ed -- f )
  Yields whether "ed" is empty (contains a single empty line).

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

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

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

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

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

nextword ( ed -- )
  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 ( ed -- )
  Same as nextword, but switch "whitespace" and "non-whitespace".

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

edfindnext ( ed -- )
  Repeat the last edfind.

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

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

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

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

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

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

delto ( pos ed -- )
  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 ( ed -- )
  Append a new empty line after selection.

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

wordundercursor ( ed -- a u )
  Move pos to the first character of the word under the cursor, or to the
  beginning of the line, whichever comes first. Then, find the next whitespace
  that follow that new position and yield the range that represents the
  resulting word. Can yield a null range.

Buffer management words:

edbufs (variable)
  LL of edbufs, the LL structure being a 4b LL link followed by a 4b pointer to
  an edbuf struct. This LL is never empty.

curbuf (variable)
  Pointer to the LL representing the currently "active" struct.

edbuf ( -- ed )
  Yields the ed struct associated to "curbuf".

newedbuf ( -- ed )
  Create a new edbuf structure with its own "buf" arena.

addedbuf ( -- )
  Create a new edbuf and add it at the end of "edbufs", making the new edbuf
  the current one.

nextedbuf ( -- )
  Set "curbuf" to the next element of the LL. If none, cycle back to the
  beginning.

findedbuf ( strpath -- f )
  Search edbufs for one that is associated to "strpath". If found, that buffer
  becomes the current one and f=1. Otherwise, f=0.

findemptybuf ( -- f )
  Look for an empty buffer in "edbufs" and, if found, make it the "curbuf" and
  yield f=1. Otherwise, f=0.

?addedbuf ( -- )
  If an empty edbuf is available, make it active. Otherwise, add a new edbuf and
  make it active.

.edbuf ( ed -- )
  Print a short one-line description of the edbuf, that is, its associated
  filename, its line count, and a "*" if it's the currently active buffer.

.edbufs ( -- )
  Print descriptions of all edbufs in "edbufs".

## 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:

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.

sl ( -- )
  Split line at cursor, that is, append a line under the cursor, truncate the
  current line at the cursor and move the truncated contents to the newly added
  line.

jl ( -- )
  Join line at cursor with the line under it. That is, delete the line under
  the cursor and append the contents of the deleted line to the current line.

edload ( strpath -- )
  Empty edbuf, open "path", associate the buffer to it, then load the contents
  of the opened file into the buffer.

edload<< ( "..." -- )
  Shorcut for "word edload".

?edload ( strpath -- )
  Try "findedbuf" on "strpath". If it yields 0, make an empty edbuf active using
  "?addedbuf" and load "strpath" into it. In other words, this word always
  preserves existing buffers and never overwrite their contents.

?edload<< ( "..." -- )
  Shortcut for "word ?edload".

edsaveto ( file -- )
  Save the contents of edbuf to "file", truncating it if the new contents is
  smaller than the old one. flush the file, but don't close it.

edsave ( -- )
  Save contents of edbuf's associated filename, taking care of opening and then
  closing it. Errors out if there is no associated filename.