loading...
Cover image for I had nothing to do, so I built a text editor.

I had nothing to do, so I built a text editor.

faraazahmad profile image Syed Faraaz Ahmad ・4 min read

What do you do when you have nothing else left to do? I freak out.

So there I was, furiously looking for something to do. Maybe I could start a new open source project (because obviously I'm not going to complete my half finished ones), maybe learn a new programming language, maybe I could finally learn Svelte.

Needless to say, I had analysis paralysis.

But then it hit me, I was recently having problems sticking to a single text editor. I liked Sublime because of its speed but couldn't use it for much because of a lack of plugins support. I mostly use Visual Studio Code because its extensive plugins support but sometimes it can get really slow (thanks electron), its not that big a deal but if you encounter something slightly annoying way too many times, it will get on your nerves. I also tried Vim/Neovim and Emacs and my likeness for them (or lack thereof) is pretty much equal. So go away with your Vim vs Emacs arguments :)

So yeah, I started building my own, this is what it looks like right now.

Alt Text

I first thought of using Electron to build it, since it would be easy (yeah, right) building in technologies I already know. But wouldn't that be counter-productive in the end? The whole vscode community is doing the best job they possibly can and there's no way I can make a faster text editor in electron than they do.

So after a lot of irrelevant steps in my thinking process, I went with creating a text editor in the terminal with Rust (because I wanted to! and I liked it!). Now I'm not some genius who conceptualised all of this out of thin air, I followed
a tutorial by Philipp Flenker which is (thankfully) based on a tutorial I'd previously tried but didn't complete because it was in C.

Building a text editor for the terminal is super challenging as you have to build a lot of UI stuff from scratch and I will never take GUIs for granted ever again.

Never Again

How'd I do it?

To begin with, you need to clear the terminal window. You can do that you need to print the escape sequence \x1b[2J.

Escape sequences instruct the terminal to do various text formatting tasks, such as coloring text, moving the cursor around, and clearing parts of the screen.

Reading Key presses

In a terminal, all you have are key presses. You need to bind those key presses and their combinations to specific functions you want them to perform. Each key on your keyboard refers to a different byte. You can check them out by running a loop and printing all the input. In C, it is done like this:

#include<stdio.h>

int main() {
  char c;
  while (1) {
    scanf(&c);
    printf(c);
  }
  return 0;
}

When you press the arrow keys you can see how pressing letter keys and character keys produces different output bytes. From here on out, handling key presses is just matching the input against the bytes and performing the required actions.

I'm using a (great) package called termion that abstracts away these little details and makes my life easier. Here's some actual Rust code from the editor:

fn process_keypress(&mut self) -> Result<(), std::io::Error> {
        let pressed_key = Terminal::read_key()?;
        match pressed_key {
            Key::Ctrl('q') => self.quit(),
            },
            Key::Ctrl('f') => self.search(),
            Key::Ctrl('s') => self.save(),
            Key::Ctrl('h') => self.show_help(),
            Key::Char(c) => {
                // don't move cursor to the right if enter is pressed
                self.move_cursor(Key::Right);
                self.document.insert(&self.cursor_position, c);
            },
            Key::Delete => self.document.delete(&self.cursor_position),
            Key::Backspace => {
                // Backspace = going left and perform delete
                if self.cursor_position.x > 0 || self.cursor_position.y > 0 {
                    self.move_cursor(Key::Left);
                    self.document.delete(&self.cursor_position);
                }
            }
            Key::Up
            | Key::Down
            | Key::Left
            | Key::Right
            | Key::PageUp
            | Key::PageDown
            | Key::Home
            | Key::End => self.move_cursor(pressed_key),
            _ => (),
        }

        // ...

        Ok(())
    }

NOTE: Some code has been omitted to make it simple and readable for this post

You can see how the package provides abstractions for the Keys that eliminates my need to work with bytes.

Displaying a status bar

I think a text editor is not a serious text editor until it has a status bar. I kid of course, but it does provide a good user experience by displaying some important info while editing files. The message bar below it can show useful info like help messages and a text input while searching in file.

Roadmap

Although I've managed to build some basic functionality into the editor, there are a lot of features I'd like it to have:

  • Syntax highlighting
  • A gutter that displays line numbers
  • Multiple tabs support
  • Autocomplete
  • User specific preferences

Help me out

If this project interests you and you would like to help out or if you just want to take a look at the code, you can visit the Github repo here.

Why do I call it Loop? Well I've been watching Tales from The Loop and couldn't think of another name.

Posted on by:

faraazahmad profile

Syed Faraaz Ahmad

@faraazahmad

A passionate full-stack web developer.

Discussion

pic
Editor guide