Skip to content
heff

A terminal app

Chapter 4’s program moves the cursor by itself. In this chapter you move it, with the keys of a terminal. Write it in app/hello/main.hf, in place of chapter 4’s program:

module app.hello.main
import co
import std.io
import std.terminal as Terminal
import std.terminal.widget as Widget
import hello.cursor
import hello.todo
import hello.zipper

Keys

toggle : Todo -> Todo
toggle t = if pending t.status then finish t else { t | status = Open }
-- | what a key does at the cursor
act : {e : HFunctor} -> {{cur : cursor <: e}} -> Widget.Key -> Co e Unit
act ArrowDown = do
_ <- next
pure MkUnit
act ArrowUp = do
_ <- previous
pure MkUnit
act (Char ' ') = change toggle
act _ = pure MkUnit
step : Widget.Key -> Zipper Todo -> Widget.Step (Zipper Todo) (List Todo)
step (Char 'q') z = Done (toList z)
step k z = Continue (runCursor (act k) z)

act says what each key does with the cursor: the arrows move it, and space marks the todo under it done, or open again. A key is written by its name alone, ArrowDown or Char ' ', without Widget..

step runs a key’s action on the zipper by runCursor. q is Done: the app ends, with the list as it is then.

The screen

-- (a todo's row)
row : (String -> Widget.Span) -> Todo -> Widget.Row
row shown t = [shown (show t)]
view : Widget.Size -> Zipper Todo -> Widget.Frame
view size z =
{ rows =
Widget.fill
(size.rows - 1)
(List.map (row Widget.plain) (List.reverse z.before)
++ (row Widget.inverse z.focus :: List.map (row Widget.plain) z.after))
++ [Widget.status size.columns "↑↓ move space done q quit"]
, cursor = Nothing
}
app : Widget.App (Zipper Todo) (List Todo)
app =
{ start =
{ before = []
, focus = todo "write the tutorial"
, after = [todo "read the reference", todo "test the examples"]
}
, step = step
, view = view
}

view draws a zipper: one row for each todo, the one under the cursor in inverse, and a line of help at the bottom. app puts the start, the step and the view together.

Running it

main : Co (Terminal.console <+> stdio) Unit
main = do
r <- Terminal.interact app
case r of
Just ts -> print (joinWith "\n" (List.map show ts))
Nothing -> pure MkUnit

Terminal.interact runs the app on the terminal: it draws the view, reads a key, steps, and draws again, until the app is Done. main then prints the todos.

Terminal window
$ heff run app/hello/main.hf