luke.b//blog

Emulating Xmonad in JavaScript - Part 4

~ #nomad

A lot has changed since I last posted. Namely I have finally given this project a GitHub repo for anyone who wants to follow my progress or even give it a test drive.

The short version

Here’s a quick list of updates:

  • The project is no longer called “shmonad”, but rather nomad. This is for two reasons: anyone using this is almost certainly a development environment nomad and secondly, it’s an anagram of xmonad when you remove the “x”. See what I did there?
  • As I mentioned, nomad now has a GitHub page, although it is not officially released - use at your own risk.
  • Nomad now supports background and foreground colours!
  • The implementation for the sub terminal buffer and scrolling margins is now much more robust.

The longer version

This update has been made possible by my new favourite resource for implementing a terminal, [https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences](publicised by Microsoft). I have been studying this page in order to better my understanding of terminals but also to realise an improved underlying model of nomad.

The two biggest additions are support for formatting sequences and for scrolling margins.

Formatting

nomad now supports foreground and background colours. The biggest challenge here was understanding how to model ANSI terminal format state and how to apply it when text is inserted. Essentially the problem can be divided according to this diagram:

        // Diagrams to show the insertion of format and
        // it's effect on existing formats, f and the resulting
        // formats, r.
        //
        // format         |=========|
        // f0:              |==|
        // r0:            |=========|
        //
        // f1: |==============================|
        // r1: |==========|=========|=========|
        //
        // f2:                   |==============|
        // r2:            |=========|===========|
        //
        // f3: |==============|
        // r3: |==========|=========|

The above diagram shows how nomad splits the underlying format state when a new string of text is inserted. Each line contains a string of text equal in length to the width of the viewport. Each line has a sequence of format states which contain a start index, a length and a definition for how to colour the text it contains.

The method I chose to implement the function responsible for updating the format sequence of a line was TDD. The tests created here have become the first tests for nomad and I hope to add more to cover the vast majority of terminal features. More on this some other time! Suffice to say, a big refactor would really help to make components of nomad more testable.

Scrolling Margins

Some programs (like vim) require correct implementation of scroll margins within which lines of the buffer are shifted when inserting lines, deleting lines and of course scrolling up and down. Previously, nomad worked on the basis of a “scroll offset” - a single scalar value that indicated the viewport offset within the buffer. This is not generally how terminals represent state and was mostly my own estimation.

ANSI terminals in fact use a region of the viewport that represents which lines to scroll under certain conditions and which lines to leave unaffected. This is useful for title bars and footers in GUI-based programs.

As part of my debugging, I found it very useful to carefully look at the output sequences from vim. It allowed me to discern what vim was attempting to do during certain interactions and what nomad was doing wrong in response to that.

Ideally this could have been another opportunity for a TDD-based approach, but in this case nomad is not written in a way that easily allows for this. My current thinking is that each part of terminal state can be considered separate and therefore easily testable.

Fin

Please feel free to leave a comment and share your thoughts and if you’re keen for more posts like this, follow me! :)

See you next time folks, thanks for reading.