Data types

Liquid is composed of different data types and functions that operates on those. The lowest level datatypes are immutable and the highest level, like the editor itself is a ref which is mutated by editor actions. Mutations are usually just applying a function to a lower level data type and replacing it in the editor.  

Slider

The heart of the editor is the slider. It is the datatype that stores the textual content of the editor. 

The slider is immutable, so every modification on the slider produces a new slider, which has the modification. In this way functions that operates on sliders can be pure and just take a slider and some parameters as input and have another slider as output. This makes testing and reasoning very easy and you do not have to extend or inherit anything to make your own operation on a slider. 

 

Empty slider: 

    {::before '()
     ::after '()
     ::point 0
     ::linenumber 1
     ::totallines 1
     ::marks {}}

 

The before list is list of string containing one character each. The elements represent the characters before the cursor in reverse order. So the word "Something" with the cursor right after "g", would have before looking like: 

 

    ("g" "n" "i" "h" "t" "e" "m" "o" "S")

 

The after list is also a list of string containing one character each. The elements represent the characters after the cursor, so if the word after the cursor is "Something" then the representation would look like: 

 

    ("S" "o" "m" "e" "t" "h" "i" "n" "g" )

 

This construction with the before in reverse and the after in normal order translates cursor movements into moving a character from the head of one list to the head of the other. That makes the time complexity of cursor movement O(1). 

Buffer

Besides some helper data the buffer could be considered a slider with a keymapping, syntax highlighting function and in some cases a relation to a file. Like the slider a buffer is immutable. Since every modification of a buffer produces a new one, it is very easy to get undo functionality. It is just a matter of popping an older version of the buffer. 

 

Empty buffer: 

    {::name ""
     ::slider (slider/create)
     ::slider-undo '()
     ::slider-stack '()
     ::filename nil
     ::dirty false
     ::mem-col 0
     ::highlighter nil
     ::keymap {}
     })

 

name A caption that can be used when navigating buffers. 

slider Contains the text inside the buffer. 

slider-undo A stack containing list of sliders to pop from when undoing. 

slider-stack Helper stack to keep track of all slider undos, to be able to undo undo. 

filename The filename to save to, if the buffer is related to a file. 

dirty Boolean to track if the buffer has been changed since last save. 

mem-col Helper variable to keep track of cursor column when naviating past empty lines or shorter lines. 

highlighter Function that defines how text should be highlighted. See below. 

keymap Mapping of keywords to functions. The keywords are associated to a keycombination. See below. 

Highlighter function

A highlighter function is a function that takes two inputs: a slider and a face. The face could be something like :string, :comment, :type1 or something similar. The face is assumed to be the face of the character before in the slider, while the one to be calculated is the face of the slider after. 

 

So if face is :string and the next character in the slider is not a quotation mark, then the function should probably return :string again. 

Keymap

A keymap is a map between a string representation, corresponding to key presses and functions, or other keymaps, so given a keymap like this: 

    {"C-s" #(promptapp/run editor/find-next '("SEARCH"))
     "v" editor/selection-toggle
     "g" {"g" editor/beginning-of-buffer
         "t" editor/top-align-page}}

Typing Ctrl+s will call the function #(promptapp/run editor/find-next '("SEARCH"))

Typing v will execute the function editor/selection-toggle

In the case with g a new keymap is returned and the editor will wait for the next input. So 

gg will result in editor/beginning-of-buffer and 

gt will result in editor/top-align-page and 

Editor

In contrast to the slider and the buffer the editor is sort of mutable. Inside it is immutable, but it is placed inside a ref, and the content of the ref is replaced when an action is performed on the editor. This also means that editor function do not take an editor as parameter, but mutates the same ref. So editor functions are not pure, which seems natural, since the editor is perceived as a state to the user, which can be change by actions. 

 

Most people who want to adapt the editor, but not change the core functionality will work on editor functions. Many custom actions will probably be composed by a series of editor actions. For example replacing a character with another can be composed of a deletion and an insertion and a movement, like this: 

 

    (defn replace-char
      "Replaces the char at current point
      with the given one."
      [s]
      (delete-char)
      (insert s)
      (backward-char))

 

The editor data structure looks like this: 

  {::buffers '()
   ::windows '()
   ::global-keymap {}
   ::file-eval {}
   ::settings {::searchpaths '()
               ::files '()
               ::snippets '()
               ::commands '()
               ::interactive '()}})

 

buffers A list of created buffers 

windows A list of windows. Each visible buffer is associated with a window with information about size and location on screen. 

global-keymap A map of keybindings which will be used if the current active buffer does not have a binding for a specific key. 

file-eval Used by the set-eval-function function to map file extensions to commands. So the extension "py" might be associated with a command to execute python files, like #(cshell/cmd "python" %)

settings A key value store, where values might be a list. searchpaths for example is used to store a list of path to search below when using typeahead to find files. Snippets are stored here and commands to be executed from typeahead as well.