# 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. ## 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: 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. insert ( n ed -- ) Insert "n" empty lines at "lpos", displacing subsequent lines by "n". 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: 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 ( 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. ## See also [text/ged] [io/stream] [fs/core] [mem/alloc]