Using the cursors as defined in the previous posts about list cursors and text cursors we can now take the first step toward writing a Purely Functional Semantic Editor. In this post we will write a purely functional text editor for a single line of text using
Picosmos: editing a line.
In the previous two posts: cursors: list and cursors: text we explored the cursors that we will need to be able to write an editor for a single line of text. Now it is time to put all the pices together, and write a simple editor for a single line of text. We will be using
brick to interface with the terminal interface, but you can use these concepts for a GUI as well, as long as you can somehow fit in the ELM architecture.
The central piece of the editor is a
brick App. It has three type variables:
s: The state of the application. In our case, that will be a text cursor.
e: The type for custom events. We will not use these, so we will leave it a paramter.
n: The type for names of pieces of the display. For this toy example, we will use
App will need a few things:
appDraw: A way to draw the state onto the screen. We do this by turning it into a
appChooseCursor: Not relevant for us now.
appHandleEvent: A way to change the state, given an event.
appStartEvent: Not relevant for us now.
appAttrMap: Not relevant for us now.
Drawing a text cursor
To implement the
draw :: TextCursor -> [Widget Text] function, we have to explain to
brick how to render a
TextCursor to the terminal screen. We do this by turning a
TextCursor into a
Widget Text. The reason why the result is a list, is so that you could draw multiple layers, but we will only use one layer.
Piece by piece, backward:
rebuildTextCursor :: TextCursor -> Textturns a
txtWrap :: Text -> Widget nturns a
Textinto a widget that displas the text, and wraps around if the display is too small.
showCursor "cursor" (Location (textCursorIndex tc, 0))adds the colored rectangle onto the display, where the user is looking within the text
padAll 1adds some nice padding
borderadds a nice border
centerLayercenters the result in the middle of the terminal screen.
Not so hard, right?
Dealing with user input
Whenever a user presses a button, we want to possibly change the current text cursor state. This is why we implement the
This code is very simple. It looks at the event that
brick received, and optionally changes the
KChar c -> mDo $ textCursorInsert c: Insert a character in front of the cursor if the user pressed a key with a letter
KLeft -> mDo textCursorSelectPrev: Go left in the text cursor if the user presses the left arrow key.
KRight -> mDo textCursorSelectNext: Go rigth in the text cursor if the user presses the left arrow key.
KHome -> continue $ textCursorSelectStart tc: Jump to the start if the user presses the home key.
KEnd -> continue $ textCursorSelectEnd tc: Jump to the end if the user presses the end key.
KBS -> mDo textCursorRemove: Remove a character if the user presses the backspace key.
KDel -> mDo textCursorDelete: Delete the next character if the user presses the delete key
KEsc -> halt tc: Stop the application if the user presses the escape key.
KEnter -> halt tc: Stop the application if the user presses the enter key.
_ -> continue tc: Do nothing if anything else happens, but do not stop the application.
That is it. Now we have a a functional line-of-text editor. 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 out smos 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.