Millismos: Writing a simple forest-editor with brick.

Date 2019-08-28

With the new cursor as defined in the previous poss about forest cursors we can take the next step towards making a Purely Functional Semantic Forests Editor like Smos. In this post we will write a simple purely functional forest editor using brick, building on the previous simple tree editor: microsmos.

From a tree editor to a forest editor

The semantic difference between a tree and a forest is simple. A forest is just a list of trees.

That means that if we take the code from the microsmos blogpost, and change the cursor within the state to a forest cursor, we should be most of the way to a semantic forest editor.

data State = State
  { stateCursor :: ForestCursor TextCursor Text
  , stateMode :: Mode

data Mode
  = EditForest
  | EditText
  deriving (Show, Eq)

Constructive Compile Errors

To recap, the first type parameter to ForestCursor is the type of the selected node and Text is the type of the other nodes in the forest.

The compile errors that we'll need to fix mostly come from functions that work on a TreeCursor. In that case there are corresponding functions for ForestCursors.

  • treeCursorAppend ~> forestCursorAppend
  • treeCursorRemoveElem ~> forestCursorRemoveElem
  • treeCursorRemoveSubTree ~> forestCursorRemoveSubTree
  • ...

New rendering

Since the microsmos blogpost, the new cursor-brick library has come out. This has made a lot of the rendering code redundant.

Now we can just import Cursor.Brick and simply combine verticalForestCursorWidget and treeCursorWidget to easily build a forest cursor widget for our State.

verticalForestCursorWidget drawTextCTree (treeCursorWidget wrap cur) drawTextCTree (stateCursor s)

Here we can just re-use the drawTextCTree function from microsmos.

Extra features

To finish off the forest editor, we can add two more features: promoting and demoting nodes. Fortunately, there are functions for this in the cursor library that make this change really easy. This means that all we need to do is to add the following to the case expression for what to do with user input:

KChar 'h' -> mDo $ forestCursorPromoteElem toText toTextCursor
KChar 'H' -> mDo $ forestCursorPromoteSubTree toText toTextCursor
KChar 'l' -> mDo $ forestCursorDemoteElem toText toTextCursor
KChar 'L' -> mDo $ forestCursorDemoteSubTree toText toTextCursor


The full code can be found on github. Text cursors are available in the cursor package on Hackage. This post is part of an effort to encourage contributions to Smos. The simplest contribution could be to just try Smos out and provide feedback on the experience. Smos is a purely functional semantic editor of a subset of YAML that is intended to replace Emacs' Org-mode for Getting Things Done.

How to put your /nix directory on a separate device

Start your Haskell project from a template

Haskell templates
Announcing cursor-brick