eno writer

003 - what to work on / modal editing / moving by word

eno writer

Deciding what to work on

I have a clear vision of how I want eno to work. Charting a development path to realise that vision is an entirely different thing. Every morning the question is, "what should I work on today?"

There are some obvious answers to this question. I know that a word processor will need to have certain features. For instance, if you cannot type text into a document, it won't be very useful. There are also some clear sequences of dependencies. You cannot write the code to load a document before you write the code to save a document. Otherwise you wouldn't know what the loading code has to do.

We can imagine our nascent product as a dot on a map and all the features it needs to have are dots surrounding it . We need to connect roads from our starting dot to the next closest dots. Then we connect onward to further away dots. With this approach, the product development will look like an ever growing circle as we connect the next closest dot each time and slowly expand the ring of the product's features outward.

Unfortunately it's not always this simple. One hard learned lesson I learned in product development is to race toward areas of uncertainty. Areas of uncertainty are things that you are not sure will work or how they will work. Perhaps you don't know if something is technically possible, or perhaps you aren't sure if something will be as good as you imagine. You want to dig into these with urgency. If it turns out something is impossible it is much worse to learn that six months into development than three.

We can imagine this as a foggy area on the map far away from our starting location. We want to connect the most direct sequence of dots to get to that area so we can see what lies under the fog. We also might want to build the quickest, cheapest roads possible. Perhaps if the delete key works but not the backspace key that is fine if it helps us get to that uncertain area quicker.

On the flip side, there are extremely well defined problems that we can leave until the eleventh hour. I know eventually eno will need to export to PDF. I have absolutely zero doubt that this will be doable and there are countless resources available on how to do this. So, while this is an essential feature to ship eno, I will leave it until very late in development to implement. On the map, these are big dots far in the distance that we have visited many times before. We know I can connect roads out that way anytime we want.

This week I ended up hanging my hat on a third heuristic. I want to be able to use my own product! I started writing this post in eno and immediately encountered many rough edges. In the worst cases the program would just completely crash when I did innocuous things like move the cursor to the end of a line. It was also irritating in subtle ways. I had no way to select a range of text and delete it so I would just have to press backspace over and over. So I started to smooth over these rough edges. It forces me to be honest with myself about the product. In my head it's great, but when I actually use it, it's terrible. That's very motivating.

I envision this as going back to roads I already connected on the map and paving them over, adding street lights, medians, etc. Some of the changes are seemingly trivial but make a big difference. I found it very hard to read the text because it was all squished together so I added padding so there is space between paragraphs to visually separate them. Ultimately, I still broke down and wrote this post in Google Docs so I have more to do on this front next week.

One of the riskiest design decisions I have made for eno is that it will be a modal editor. This is a concept that was popularized by a code editor called vi that was written in 1976 and its successor vim (vi iMproved) written in 1991. I am fairly certain most people who are not programmers have never heard of these programs before. And that's exactly why this is such a risky product decision. Moreover, even among programmers, vim is notoriously difficult to use. The StackOveflow question "How do I exit Vim" is one of the all-time most upvoted questions.

With this background, I was very encouraged to have multiple commenters on LinkedIn over the past weeks ask if eno would have vim style keybindings. Especially because all of these commenters were not career programmers (though I think most of them had done programming in the past). It looked like a small glitter of hope that I could bring the power of modal editors to the business world.

So, what exactly is a modal editor? Microsoft Word has one mode. When you put your cursor somewhere in a document and start typing on the keyboard, the letters that you type get inserted into the document body. In a modal editor, this is called "insert mode" and it is one of three modes.

The default mode when you open vim is "normal mode". In "normal mode" when you type letters on the keyboard they do not insert letters into the document. Instead they do all kinds of other useful things. For instance, if you press w, your cursor moves one word forward. If you press dw you delete the word in front of your cursor. If you press f., your cursor advances to the next .. If you press / then you will start searching the document for whatever letters you type next. You can then press n to advance through the results and move through the document. If your cursor is over some text that is surrounded by brackets, you press vi( to select all the text within the brackets and then can press c to change it to some new text. If you press ctrl+d you move down half a page. If you press g100 you instantly move to line 100 in the document. If you press ctrl+w s then you split the window into two separate panes so you can look at two different parts of the document at once. You can do this multiple times and rearrange the panes on the screen to show whatever pieces of documents you want to see all at one time. The list goes on and on and you can actually add plugins to vim to make keys do anything you can imagine.

Modal editors are incredibly powerful because they allow you to navigate documents with extreme speed. You also never need to move your hands off the main part of your keyboard. It is my belief that people spend far more time reading and editing documents than writing them. So why should the keyboard be dedicated to writing when most of the time you want to be moving around the document or making small precise changes?

It's guaranteed that this will be a very polarizing choice, but I think the venn diagram of people who will try an experimental new word processor and people who are open to learning an entire new paradigm of document editing has significant overlap. I also know that modal editing is an essential part of the puzzle to making users of eno many times more productive than they currently are with Word. If eno is not able to give its users superpowers, it is unlikely to be successful.

Cursor movements

In the spirit of modal editors, for the code portion of the devlog this week I will share my code for moving the cursor one word forward. It is yet another example of how computers are often far more simplistic and boring than we expect.

    fn moveByWord(self: *Editor, forwards: bool) void {
        const c = self.cursor_ctx orelse return;
        const idx = c.text_idx orelse return;
        const max_idx: u16 = @as(u16, @intCast(c.block_text.len)) - 1;
        const target_text_idx = r: {
            // we must skip the space before the current word if we are going
            // backwards
            var num_spaces_to_find: u16 = if (forwards) 1 else 2;
            var num_found: u16 = 0;
            var i: u16 = @intCast(idx);
            while (true) {
                if (c.block_text[i] == ' ') {
                    // keep looking through any contiguous spaces before
                    // counting this as a word
                    if (forwards) {
                        while (i < max_idx and c.block_text[i + 1] == ' ') : (i += 1) {}
                    } else if (!forwards and i > 0) {
                        while (i > 0 and c.block_text[i - 1] == ' ') : (i -= 1) {}
                    }
                    num_found += 1;
                    if (num_found >= num_spaces_to_find) {
                        i += 1;
                        // do not try to navigate past the end of the text
                        if (forwards and i > max_idx) i = max_idx;
                        break :r i;
                    }
                }
                // we reached the start or end without finding a space
                if ((!forwards and i == 0) or (forwards and i == c.block_text.len - 1)) {
                    break :r i;
                }
                if (forwards) i += 1 else i -= 1;
            }
        };
        self.setNextCursorTargetToTextIndex(c.block, target_text_idx);
    }

Coming into this function, we look up a CursorContext struct on the Editor that tells us information about where the cursor is positioned, including the index in the string of text it is over. For example if we had the string "Quick brown fox" and our cursor was on the k it would be at index 4 (0 being the first index where the Q is).

To find boundaries of words we are really looking for spaces. So, we start a loop where each iteration we step one character forwards from the starting index and check to see if it is a space. We also support moving backwards, in which case we subtract from the starting index each time instead. Once we have found a space, we do another quick loop to see if there are any spaces immediately next to it so we can treat that altogether as one. Finally we pick the index immediately after the space which will be the first letter of the word we want to move to. We then tell the editor to move the cursor there at its next opportunity. You can see see how some minor modifications would allow this code to support commands like 3w which should move the cursor forward three words.

Powered by Buttondown.

We also have an RSS feed