# Dissecting line-edit feature

You can use line editing on gosh REPL if the terminal is capable.  The
feature is implemented entirely in Gauche itself, instead of relying on
external libraries such as libreadline.  This document describes the
internals of the line editing feature for those who want to fix bugs
or add new features.

## Modules involved in the line editing feature

There are number of modules involved in this feature.

- `gauche.interactive.editable-reader` : This module is autoloaded
   when `gosh` is invoked in the interactive mode.  First it tries to
   obtain a default console (`text.console`), and if it succeeds, create
   a line editor instance (`text.line-edit`) and returns several procedures
   to be called from `read-eval-print-loop`.  If it can't get a default
   console (e.g. the terminal is not capable, or the stdios are connected
   to non-terminal device), then it returns false values and
   `read-eval-print-loop` falls back to a basic I/O without editing
   capability.
   This module also calls load/save edit history API in the appropriate
   time.

- `text.console`: This module provides an abstract API to control consoles
   such as cursor placements.
   Currently it supports VT100-compatible console and Windows console.
   It used to be a standard to use termcap/terminfo to abstract the device
   difference, and curses for high-level console manipulations.  However,
   the world has moved on.  We no longer have wide variations of terminal
   controls, so that adding dependencies to those external libraries is
   overkill.  Besides, Windows console requires totally different controls
   than the traditional Unix consoles.  So we decided to roll our own.

- `text.line-edit`: This module effectively implements a text editor.
   It uses `text.gap-buffer` for the buffer structure and `data.ring-buffer`
   for the kill ring.  It uses `text.console` for drawing, so it is
   independent from the actual console device.  It also supports
   unlimited undoing, and save/load histories.

- `text.gap-buffer`: Implements a gap buffer, suitable for the back-end
   of text editor.

## Startup sequence - what happens before you get `gosh$` prompt

The main program of `gosh` calls `user#read-eval-print-loop` if no
script file is given.  If `-q` option is given to `gosh`, it is
`gauche#read-eval-print-loop` (defined in `libeval.scm`), which only
provides very basic REPL.  However, if `-q` option isn't given,
`gauche.interactive` is loaded first which shadows `read-eval-print-loop`,
so that `gauche.interactive#read-eval-print-loop` is invoked instead.

While loading `gauche.interactive`, `make-editable-reader` of
`gauche.inetractive.editable-reader` is invoked.  It tries to get
a default console by `text.console#make-default-console`.  If it can,
it returns four values - A procedure to read an S-expr, to read a line,
a thunk to skip trailing whitespaces, and `<line-edit-context>`.
The returned readers are line-edit enabled.

The first three procedures are passed to `gauche.interactive#make-repl-reader`,
which returns a reader that (1) recognizes top-level REPL commands,
and (2) consume trailing whitespaces after S-expression input.

If line editing is not enabled (by `-fno-line-edit` option, or from
an environment variable), or `make-editable-reader` can't obtain
a default console, a repl reader is created with
`(make-repl-reader read read-line consume-trailing-whitespaces)`.
Thus, it recognizes the top level REPL commands, but does not use
line editing.

The `gauche.interactive#read-eval-print-loop` needs a _reader_, an
_evaluator_, a _printer_, and a _prompter_.  _Reader_ and _prompter_
are constructed as follows.

. If line editing is enabled:
  - _Prompter_ actually does nothing, since the line editor needs to know
    the columns used by the prompt.
  - _Reader_ invokes `text.line-edit#read-line/edit`, which set the console
    to 'rare' mode, query the screen size and the current cursor location,
    show the prompt, and goes into editor mode.
. If line editing is disabled:
  - _Prompter_ displays the prompt.
  - _Reader_ reads from the current input port, and handles the top-level
    REPL command and trailing whitespaces if needed.

### The editor - `read-line/edit`

An instance of `<line-edit-context>` maintains the state of line editing.
It is coupled with an instance of `<console>`; while line editing is on,
all console I/O should be done through `<line-edit-context>`, or the
state it keeps and the actual content displayed on terminal become
out of sync.

Line editing is on when you call `read-line/edit` with an a line-edit
context.  It changes the console mode to monitor keystrokes, then
interacts with the user until they "commit" the input, then changes
the console mode back to the original mode and returns the text
the user committed.

The context has a table to map keystrokes to edit commands.  A new
context gets a copy of the default map.  You can modify the map of
a context without affecting other contexts.

### Defining edit commands

An edit command is a procedure that takes three arguments,
a context, a gap-buffer, and a keystroke object.  It may change
the content of the buffer, and must return one of the following
objects.  In general, edit commands shouldn't directly change the
state of the context.

- 'visible : the command changed something visible so we need to
       redisplay, but we don't need to save the change in
       the actual buffer. The selection is cleared.
- 'unchanged : no change visually and internally; we break
       undo sequence and reset last yank, but not clear
       selection.
       This occurs, for example, backward-char at the
       beginning of the input.
- 'nop : totally ignore the key input.
- 'redraw : we won't change the state of the editor, but
       we reobtain the cursor position before redrawing
       the buffer content.  This is necessary when the command
       wrote something on the screen and we don't want to clobber
       it.
- 'moved : the command only moved cursor pos.  requires redisplay,
        but we keep selection.
- #<eof> : end of input - either input port is closed, or
        the user typed EOT char when the buffer is empty.
- 'commit : record the current buffer to the history, and
        returns it.
- 'undone : the command undid the change.  we record the fact,
        for the consecutive undo behaves differently than
        other commands.
- <edit-directive> : the command changed the buffer contents,
        and the return value is an edit directive to undo the
        change.
- (yanked <edit-directive>) : this is only returned by yank and yank-pop
        command.

...(more to come)...
