Writing a Code Editor with Layers (Part 2)

Davin Hills
4 min readMay 31, 2020

In Part 1 we covered some of the project goals as well as the basic interface for text storage. Let’s take a side road and talk a little about undo.

Undoing

Undo is another feature that is easy, hard. If we keep each version of the document as changes occur and then replace the document with the previous version when we undo, simple. Of course, if we have a 20Mb file that we keep a copy of each time a character is typed is going to be a bit of a resource problem. OK, let’s step back and look at the problem without thinking about specific implementations. What do we need to know to undo an edit? We need to know what was changed and what its state was before the edit. So what are the types of edits we would need to keep track of? Since we are viewing storage as a grouping of lines it makes sense to think of undoing as a line action. This leaves us with three action types.

  • Add a blank line
  • Remove a line
  • Change a line

Well, that does seem a bit simpler. Add and remove a line is fairly easy. Just track which line was added or removed and in the case of remove, we will need to save the text on the line we removed. Actually, let’s make remove two actions, a change line TEXT => BLANK, and a remove line action.

This leaves us with a line change. We will do two things to try and make the change line efficient. First, we can try and group actions like typing, we don’t really need an undo action for every single character pressed. We’ll use switching layers (mode) to trigger a group of edits as one undo. Second, we’ll use a diff algorithm to store the changes made.

Text Objects

As teased in part 1 let’s look at text objects. One of the secret sauces for fast text manipulation. We want the ability to define groups of text with contextual meaning and then use them for navigation and editing. The most basic editor has the concept of a character. You can delete them, move to the next or previous. Now we want the same ability with groups of text. Let’s start with a word in a line.

This is a test of the emergency broadcast system.

In our imaginary editor, our cursor is at the start of the above line. We press the right arrow key and we move from T to the h. What if, instead we want to move by word so pressing the arrow to move to is then to a then to test. As humans how do we identify a “word”? It is a group of letters surrounded by spaces, right? Since it’s a programming editor the rules get a little broader because function names can often have numbers and underscore in them. Regular expressions seem like the right tool for the job.

{
“name”: “word”,
“start”: “(\\b[a-zA-Z0–9_]+|^$|[^\\s\\t])”,
“simple”: true,
}

Simple denotes that it is definable with a single expression.

Oh, the power now at our fingertips! With a word we now know it’s beginning and end. We now have the ability to jump to the start of the next word, the end of the next word or if we add a count we can move to the beginning of the 5 words from our current position or delete the next three words. We are defiantly cooking with gas.

Here a few others. Think about how you would define them.

  • Block <> {} ()
  • Start or end of the document
  • Beginning or end of line
  • A “Double quoted-string” ‘Single quoted’ `Ticked quoted`

Cursor

We’re going to need a cursor both as a visual cue but also a reference for our actions. We can’t very well delete a line if we don’t know what line we are on. An obvious question might be why isn’t this part of the text storage? The first reason is that if we make the new implementation of text storage we don’t want to have to make a new cursor implementation but far more important is we may have the same text store open in different “windows” with different cursor positions or if we are pairing with another programmer they will want their own cursor or even multiple cursor support.

It’s so tempting to couple the text object with our cursor but we want to keep all these components as uncoupled as we can. We may also want to do visual selections so we should have a way of starting and stopping a selection. Let’s take a look at what a cursor interface might look like.

That is the end of Part 2. We have actually accomplished a lot.

  • Text storage with writers and readers
  • An under and redoer
  • Text objects for finding and editing groups of text
  • A Cursor for designating a position

Next time we’ll tackle syntax highlighting…

Cheers

Part 3 Syntax Detection

Part 4 Layers

--

--