alefore / edge

Edge - a text editor
GNU General Public License v3.0
77 stars 2 forks source link

Edge - A text editor.

1. Introduction

Edge is a modal terminal-based text editor for GNU/Linux. This document describes the use of Edge.

A few characteristics of Edge:

1.1. Screenshots

The screenshot shows Edge (running under gnome-terminal) editing a C++ file (part of its own source code):

Edge Screenshot

There are multiple cursors in lines 2, 5, 7, 9, 19, 25, and 27 (among others), after a regular-expression search for the word buffer (which creates a cursor in each match). The cursor in line 19 is currently the only active cursor (so commands would currently not affect the remaining cursors).

The right-most column shows the scrollbar besides the tree (that corresponds to the syntax tree of the contents being edited).

2. Getting started

2.1. Running Edge

By default, Edge will create a buffer running a nested shell. If you run Edge on a file, it will open it and display it:

$ edge README.md

You can see the documentation for command line arguments if you run Edge with --help:

$ edge --help

Once in Edge, you can press ? to get help about your current buffer.

1.1. Installing

The following commands download, configure, and build Edge:

$ git clone https://github.com/alefore/edge.git
$ cd edge
$ ./autogen.sh && ./glog-0.4.0/autogen.sh && ./configure
$ make

You'll probably want to create a symlink to the rc directory in ~/.edge (or copy the contents). Among other things, Edge will read most of its initial key bindings from ~/.edge/hooks/start.cc so you won't get very far if that file is missing.

Edge depends on a few libraries and development programs. In Debian, you can install it with the following packages: libtool, make, g++ (version 14.0 or above), libncursesw6, libncurses-dev

2.1. Basic commands

Edge uses buffers to represent an open file or a process. A process may still be running and may have a full terminal/pts (e.g., a nested shell running within Edge).

In a buffer view, the following are some basic commands:

3. Navigating a buffer

3.1 Basic navigation

The cursor will start at the beginning of the file. You can use "h", "j", "k", "l" or the arrow keys to move it around.

The current structure alters the behavior of "h" and "l" thus:

3.2 Search for a regular expression

You can search for a string with "/": Edge will prompt you for a regular expression to search for and pressing Enter will take you to its first match after the current cursor position.

Search supports autocompletion, where hitting Tab will replace the current regular expression by the longest common prefix of its next 10 matches (configurable through variable xxx, see section 7). Press Tab once again to display a buffer with the list of matches.

To repeat a search, use the Search structure (section 6.1).

If the Word structure (section 6.1) is enabled, Edge will not prompt you for a regular expression: it will, instead, just search for the word under the cursor.

To search backwards, use the Reverse modifier (section 6.3).

3.3. Search for a character

You can search for a character with "f": this will take you to the next occurrence of the next character you type. This is roughly equivalent to typing "/", entering a character and pressing Enter.

To search backwards, use the Reverse modifier (section 6.3).

3.4. History of positions

Edge keeps a history of the positions in which you've been. You can go back to the previous position with "b".

The list of positions is kept in a buffer called "- positions".

This behavior is affected by variable push_positions_to_history (section 7.2.25).

TODO: Describe the effect of structures.

3.5. Goto beginning

Use "g" to go to the first character in the current line. Press "g" again to go to the last character in the current line.

If a repetition is active, the behavior changes to "go to the i-th character in the current line".

The current structure alters the behavior thus:

If the Reverse modifier is enabled, moves backwards from the end.

If the structure modifier is set to "FromEndToCurrentPosition", moves backwards from the end (unless the Reverse modifier is also enabled, in which case they cancel out).

(The current implementation is slightly different than this in how it handles repeated presses, but we'll likely change it to do this.)

3.6. Go to file

If the cursor is over a path that exists in the filesystem, pressing Enter creates a new buffer with the contents of that file (or, if a buffer for the file already exists, just takes you to it).

See section 5.5 for more commands to open a file.

3.7. Navigation mode

If you press "n", you activate a special navigation mode, which uses binary search to help you very quickly land in the character (in the current line) that you want.

This can be a very efficient way to reach a given position. For example, if you start at the beginning of a relatively long line with 100 characters, all positions are reachable with at most 8 keystrokes (including the initial "n").

In navigation mode, if you press "l" ("h") you'll move forwards (backwards) to the middle position between the current cursor position and the end (beginning) of the current line, discarding (for the purpose of subsequent navigation) all characters before (after) the current cursor position. Each subsequent press (of "l" or "h") moves the cursor in the corresponding direction.

The current structure alters the behaviors thus:

You exit navigation mode by pressing any character other than "l" or "h".

4. Editing a buffer

4.1. Inserting characters

To type some text into a buffer, use "i". This enters insert mode, which is indicated at the bottom of the screen by the word "type" (of "type (raw)" if the buffer is a command with a terminal). Press Esc to exit insert mode. Anything you type while in insert mode will be inserted into the buffer; for buffers with an associated terminal, the text will be written to the terminal.

If insert mode is activated with the Line structure, an empty line will be inserted above the current line and text will be inserted into it. The Reverse modifier will instead insert the line after the current line.

The insertion modifier (section 6.5) will specify if insert should preserve the contents that follow the cursor (default) or replace them as you type.

While inserting to a terminal, press ctrl+v to activate "literal" insertion: the next character will be inserted literally. This is mainly useful to insert an Esc code (instead of having Esc take you out of insert mode).

4.1.1. Autocomplete

If the dictionary variable is set to a non-empty string, pressing Tab will cause that string to be interpreted as the path to the dictionary file. The contents of the file will be loaded into a buffer and used to look for completions. Pressing Tab again will iterate through the entries in the dictionary following the first completion. See section 7.2.22 for more information on how to create a dictionary file.

4.2. Deleting contents

Press "d" to enter delete mode. The section to be deleted will be highlighted. Press "\n" to confirm and execute the deletion, or Esc to abort it and leave the buffer unmodified.

You can select modifiers in the delete mode (before pressing "\n"). For example:

4.2.1. Deleting words

When deleting words, the contents of variable word_characters section 7.2.xxx) are used to specify the list of characters that are considered part of a word. If the cursor position is in a non-word character, characters will be deleted until the beginning of the word, and then the "delete word" command will run).

4.3. Pasting characters

Press "p" to paste the last contents deleted by the delete command.

Whenever "d" deletes some contents, they go to a special buffer called "- paste buffer". You can modify the contents in this buffer directly to alter what "p" will paste.

The insertion modifier (section 6.5) will specify if insert should preserve the contents that follow the cursor (default) or replace them as you type.

This behavior is affected by variable delete_into_paste_buffer (section 7.2.26).

4.4. Undo & Redo

Edge maintains full undo history. Press "u" to undo the last edition. Navigation commands (that only alter the cursor position but not the contents of the buffer) are ignored: undo will revert the last transformation.

To "re do" activate the Reverse modifier (section 6.3).

4.5. Repeat the last command

You can repeat the last transformation at the current position by pressing ".".

You can group multiple commands to be repeated by pressing "sr" (short for Secondary > Record), then applying all the commands you want to group, and then pressing "sr" again. If you then press ".", you will repeat all the commands in the sequence.

4.6 Capitals swap

Press "~" to convert the current character from upper case to lower case (and viceversa) and advance by one position. Similar to deleting, you'll have to confirm the operation by pressing "\n".

5. Buffers

5.1. List of buffers

Press "al" ("Advanced > List") to open a buffer that contains the list of buffers. Each line in this buffer represents a buffer: an open file or the output of a subprocess (which may be running or may have already exited).

If you delete lines in this buffer (with "ed", see sections 6.1 and 4.2), this will close the associated buffers. If you have unsaved changes, you will lose them.

The list of buffers will be updated dynamically whenever you create new buffers or as they make progress (e.g. it will show the number of lines that have been read from a command that's currently running).

5.2. Reload a buffer

You can reload a buffer with "ar". If the buffer is a file, this discards any unsaved changes. If the buffer is a command (section 5.3), this will kill it with SIGTERM (if it's still running), wait for it to exit, and restart it.

If you're in the list of buffers (section 5.1), you can activate Line mode ("e", section 6.1) and press "ar" to reload the buffer that the cursor is currently on. During software development, this can provide a quick way to re-run commands like "make" during development and wait for them to terminate successfully (since the list of buffers will be updated dynamically as they make progress).

5.3. Run a command

You can run a command with "af" ("Advanced > Fork"). Edge will prompt you for a command to run and will create a buffer in which it will run this command. If a buffer for the command already existed, Edge will just reload the process (similar to "ar" as described in section 5.2).

If Line structure is active ("e", section 6.1), "af" will not prompt you for a command but will instead run the command given in the current line. For commands that you might expect to run in the future, we recommend creating a simple text file for your project in which you list them, so that you can quickly launch them with this mechanism.

By default, commands will be run without a controlling terminal. This can be toggled (which can be very useful e.g. for running a shell in a buffer) with the variable "pts" (section 7.2.1). For example, if you run "bash", you'll probably want to do "av" (set variable), then "pts" and Enter (to toggle the value of the boolean variable) and then press "ar" to restart the shell. Of course, this should be done automatically through the buffer-reload.cc script (which we'll start distributing soon).

5.4. Running many commands.

TODO: Describe aF

5.5. Open a file

5.5.1. Advanced > Open ("ao")

Use "ao" ("Advanced > Open") to open a file. Edge will prompt you for the filename. Tab will autocomplete (and pressing Tab again will show all the options). You can adjust the list of paths in which autocomplete will search for files by editing the special "- search paths" buffer (which should contain one path per line).

In practice, we tend to use "a." (section 5.6) much more frequently to open files. We also tend to navigate across files by moving the cursor over to a path and pressing Enter (section 3.6).

5.5.2. Path suffixes

Edge will look for an optional suffix to the path indicating the position in the file, which is mostly compatible with the output of GCC and other similar compilers. The following examples show the supported forms:

These suffixes can be given anywhere where Edge opens a file (e.g. they also work for files specified through the command line).

5.5.3. Anonymous buffers

If you open a buffer with an empty path (i.e. you press Return immediately after pressing "ao"), you'll create an anonymous buffer. Anonymous buffers can't be saved unless you give them a path (by assigning to the path variable, described in section 7.2.15).

5.6. Files view

If you press "a." ("Advanced > Current directory") in a buffer, a new buffer will be created (unless it already existed) displaying the contents of the directory containing the current buffer, one file (or directory) per line.

Like the list of buffers (section 5.1), this is a special buffer:

5.7. Save a file

Use "aw" ("Advanced > Write") in a buffer to save its contents.

Some buffers can't be saved and will display an error in the status.

5.8. Delete a buffer

Press "ad" ("Advanced > Delete") to delete (close) the current buffer.

A buffer is said to be dirty if any of these conditions holds:

If you attempt to close a dirty buffer:

See section 5.1 for another mechanism for closing buffers, by deleting lines from the list of buffers.

6. Structures & structure modifiers

Edge has a notion of "modifiers" that alter the behavior of commands. There are various kinds of modifiers, described in this section.

TODO: This section is somewhat obsolete.

6.1. Logical structures

The most important type of modifier is the logical "structure" that the next command will affect. To set the structure, press any of the following keys:

The default structure is the current character. The current structure will be shown at the bottom of the screen (unless the structure is set to the default value).

Once a command is executed that is affected by the structure modifier, it will set the structure back to the default value. However, redundantly setting the structure to the value it already has will activate "stickyness": in this case, the structure will not reset back to the default value.

Pressing Esc will reset the structure to the default.

Here are some examples:

6.2. Sub-structure modifiers ("[" and "]")

When the structure is set to anything other than character, "[" changes the semantics to mean "apply the command to the part of the structure from its beginning until the current cursor position". Conversely, "]" changes the meaning to "apply the command from the current cursor position until the end".

The current modifier will be shown at the bottom of the screen (next to the current structure modifier).

The specific behavior depends on the command. This is particularly useful with the delete command, where e.g. "]ed" could be used to delete to the beginning of the current line.

6.3. Reverse modifier ("r")

Pressing "r" will reverse the direction in which the command is applied. Most commands set the direction back to the default value (forwards), but if you press "rr", you'll activate sticky reverse mode. Press Esc to leave sticky mode.

6.4. Repetitions

You can type a number to set an amount of repetitions that affects the next command.

The specific behavior depends on the command. Most navigation and editing commands simply repeat the command the given number of times. Other commands may have their behavior altered in different ways.

6.5. Insertion Modifier ("R")

By default, commands that insert text will "push" existing contents after the current cursor position back. Press "R" to activate "replace" mode, which will instead overwrite previous contents of the buffer. After a command has been executed, the modifier will reset back to the default, but if you press "RR", you'll activate sticky replace mode. Press Esc to go back to the default behavior.

7. Variables

7.1. Setting variables

Type "av" to set the value of a given variable. You will be promoted for the variable you want to set. You can press Tab twice to see a list of all options that match the current input (which can be empty, to list all variables). Press Enter to edit the value of a variable.

If the variable is a boolean, its value will be toggled. Otherwise, you will be prompted for the new value for the variable.

For example, to adjust the line width of the current buffer, press "av", then type "line_width" (or a shorter prefix of that and use Tab to autocomplete), then Enter, then edit the current value, and then press Enter.

8. Cursors

One important feature of Edge is the support for multiple cursors. This allows the user to apply edit commands at various parts of a document simultaneously.

Most of the time only a single cursor, the "current cursor", is "active": commands will apply to that cursor only. However, after creating multiple cursors (section 8.1), the user can mark all cursors as active (section 8.3), in which case edit commands will apply at all cursor positions.

If the current buffer has more than one cursor (regardless of whether a single cursor is active or all are), Edge will display this in the status line (at the bottom), with a text such as "cursors:28".

For example, a user may "save the current position":

  1. Press "+" (section 8.1) to save the current position (i.e., create a new cursor at the current position).
  2. Scroll away to a different position in the document. The new cursor (created in step #1) remains in the old position.
  3. Perform some modifications at the new position.
  4. Press "-" (section 8.2) to erase the active cursor; the remaining cursor (from step #1) will now become active, bringing the user back to the position at which it was created.

As a more interesting example, a user may modify all occurrences of a word in a given region in a document:

  1. Scroll to the end of the region and press "+" (section 8.1) to create a new cursor.
  2. Scroll down to the beginning of the region. The region is now delimited by the only two cursors in the document.
  3. Press "c" to set the current logical structure (section 6.1) to the region delimited by the cursors.
  4. Press "/", enter a query string (regular expression) matching the word you want to replace and press Enter (section 3.2). This will remove all cursors in the current buffer and add one for every match for the regular expression inside the region.
  5. Press "_" (section 8.3) to make all cursors active.
  6. Enter some edit commands to delete the old word (e.g. "wd") and insert the new word (e.g. "i").
  7. Press "=" to remove all cursors but one.

8.1. Creating new cursors

This section describes the various ways a user can create new cursors.

The simplest way to create a cursor is by pressing "+": this creates a new cursor at the position of the current active cursor. The new cursor will become visible once the active cursor scrolls away from the current position.

If the logical structure (section 6.1) is Line and a cursor follows the current in the document, a new cursor will be created at the beginning of every line between the two cursors. This can be helpful to quickly indent (or otherwise modify) every single line in a given region. The Reverse modifier (section 6.3) causes the region affected to end (rather than start) at the current cursor; in other words, it creates new cursors before (rather than after) the current cursor.

Searching (with "/" as described in section 3.2) is another quick way to create a new cursor at every position in a document (or in a region) that matches a given regular expression.

8.2. Deleting cursors

Pressing "-" will delete the current cursor (and immediately jump to the next, or to the first cursor when deleting the last cursor in the buffer).

Pressing "=" will delete all cursors except for the current one. If you have created many cursors (for example, with a search query), this is a quicker way to discard them than through "-".

8.3. Toggling active cursors

Pressing "_" makes Edge toggle the value of boolean variable "multiple_cursors" (section 7.2.24), toggling between making commands apply to all cursors or only to the active one.

8.4. Push and Pop

Each buffer has a stack of sets of cursors. This can be a handy way to store a set of cursors, apply some transformation elsewhere, and restore the set.

Press "C+" to push: you'll copy the set of active cursors into a new entry in the stack.

Press "C-" to pop: you'll be replacing the set of active cursors with the last entry pushed into the stack, and removing that entry from the stack.

9. Extension language

9.1. Running commands

TODO: "ac"

9.2. Running scripts

You can press "c" to run a script. Edge will prompt you for the path to the script, starting at the path set in the variable editor_commands_path (section 7.2.18)

The contents at that path will be compiled and the resulting expression will be evaluated in the environment of the buffer.

9.3.1. bool Buffer::AddKeyboardTextTransformer(function<string(string)> transformer)

TODO: Move this somewhere else.

If you pass a function mapping a string to a string, it'll be called by Edge on every character that the user attempts to insert (by typing "i", section 4.1.). The transformer can return a different string, to override the string inserted. It could even alter the buffer (for example, erase the previous character, or adjust the current position).

If you call it multiple times, all transformers will be run in the order in which they've been added. Each transfomer will see the output of the previous ones.

10. Internals

                                ╭───────╮
                          ╭─────> Parse ├───────╮
╭──────────╮   ╭──────────┴─╮   │ Tree  │       │
│ Commands │   │ Background │   ╰───^───╯       │
│(bindings)<─╮ │   Thread   <──╮    │           │
╰───┬──────╯ │ ╰────────────╯  │    │           │
    │        ╰──────────────╮  │    │           │
    │  ┏━━━━━━━━━━━━━┓    ┏━┷━━┷━━━━┷━━┓    ╭───V─────────────────╮
    │  ┃ EditorState ┠────> OpenBuffer ┠────> MutableLineSequence │
    │  ┗━━━━┯━━━━━━━━┛    ┗━┯━━━━━━━┯━━┛    ╰───────┬─────────────╯
    │       │               │       │               │
    │       │     ╭─────────╯       │            ╭──V───╮   ╭───────────╮
    │   ╭───V─────V─╮   ╭───────────V──╮ ╭───<───┤ Line ├───> Modifiers │
    ╰───>Environment│   │ Process Info │ │       ╰──┬───╯   │ (colors…) │
        │  (cpp)    <─╮ │  (fd, pid…)  │ │          │       ╰───────────╯
        ╰─────┬─────╯ │ ╰──────────────╯ │   ╭──────V─────╮
              │       │                  │   │ LazyString │
        ╭─────V─────╮ ╰──<─────<─────<───╯   ╰───┬────────╯
        │ EdgeStruct│                            │
        │Instance<…>│                            ├╴> EditableString
        ╰───────────╯                            ├╴> StringAppend
                                                 ├╴> Substring
                                                 ╰╴> MoveableCharBuffer

TODO: SetStatus("foo"); TODO: Document the integration points (e.g. buffer-reload.cc). TODO: Useful examples