Fork of lines.love without drawings; useful starting point for further forks
I care a lot about being able to automatically check _any_ property about my
program before it ever runs. However, some things don't have tests yet, either
because I don't know how to test them or because I've been lazy. I'll at least
record those here.

First run: start up without a data directory, quit; app does not crash.

Initializing settings:
  - delete app settings, start; window opens running the note-taking app
  - quit while running the note-taking app, restart; window opens running the note-taking app in same position+dimensions
  - quit while editing source (color; no drawings; no selection), restart; window opens editing source in same position+dimensions
  - start out running the note-taking app, move window, press ctrl+w twice; window is running note-taking app in same position+dimensions
  - start out editing source, move window, press ctrl+w twice; window is editing source in same position+dimensions
  - no log file; switching to source works

  - run with an untested version of LÖVE. Error message pops up and waits for a key. The app attempts to continue, and doesn't receive the key.
  - run with a LÖVE v12 release candidate. No errors; it is a supported version. All tests pass.
  - create a couple of spuriously failing tests. Run with an untested version of LÖVE. Error message includes message about untested version.

Load directory:
  - clear save dir, run, you get an error message telling you to specify a directory for notes
    - quit works
  - create an empty config file, run, you get an error message telling you to migrate your existing notes directory
    - quit works
  - clear save dir, run with a dir argument that doesn't exist; app creates dir and opens up with no data
    - quit works
    - on quit, app creates 'config' file within the dir
  - clear save dir, run with a dir argument that exists and contains no 'config'; app opens up
    - quit works
    - on quit, app creates 'config' file within the dir
  - run with a dir argument of '/' (that you don't have access to)
    ?? app should either throw an error or save data you put in _somewhere_
  - run app from terminal with a dir argument; the app uses settings from 'config' inside that directory
    - quit works
  - run app from terminal with a dir argument, hit C-w; the app switches to source editor
  - app has a config with a `data_directory` key, run app from terminal with a dir argument; app opens up with new data directory
    - on quit, app saves the new data directory to the `data_directory` key

Code loading:
* run love with directory; note-taking app runs
* run love with zip file; note-taking app runs

* How the screen looks. Our tests use a level of indirection to check text and
  graphics printed to screen, but not the precise pixels they translate to.
    - where exactly the cursor is drawn to highlight a given character
    - analogously, how a shape precisely looks as you draw it

* Starting up from a blank slate, with an empty directory of files.

* Mouse click within pane can select text
* A single pane is colored distinctly and called the cursor pane
* Hitting ctrl+e toggles edit mode for the cursor pane
* There can never be more than one pane in edit mode
* Capture command edits a single note on screen
* Keypresses not sent to cursor pane if off screen
* open a file in a duplicate pane. edit the file using ctrl+e, insert a line. Other pane shows no change. Hit ctrl+e to stop editing. Other pane shows change.

Panning:
* Moving arrow keys and shift-arrow keys and pageup/pagedown normally pans surface around while keeping cursor on screen
  * Exception: When cursor pane is in edit mode, keys move the cursor as in lines.love
  * Scrolling within a cursor pane larger than screen height in edit mode also pans the surface around
  * Trying to scroll past edge of cursor pane in edit mode doesn't move the surface
* Mouse click outside pane can pan surface around
  * Cursor pane never changes
  * The pane in edit mode might go off screen
  * Cursor in pane in edit mode might go off screen
    * TODO: lines.love assumes cursor can never be above screen top. Do we want to violate this constraint in pensieve? Or move the cursor to satisfy it as needed? Or forbid movements that would violate it?
  * Dragging the mouse while panning leads to smooth panning
* hit pagedown. All columns scroll down by same amount.
* hit pageup. All columns scroll up by same amount.
* move mouse off any panes, press down and drag around. All columns pan by same amount.
* move mouse off any panes, press down and drag upwards. Panes don't flicker out before they go out of view.
* select a pane, hit ctrl+e, hit pagedown. All columns scroll down by same amount.
* select a pane, hit ctrl+e, hit pageup. All columns scroll up by same amount.
* select a pane, hit ctrl+e, move mouse off any panes, press down and drag around. All columns pan by same amount.
* select a pane, hit ctrl+e, hit pagedown, type some text. Surface does not pan.
* select a pane, hit ctrl+e, hit pagedown, move mouse off any panes, press down and drag around. All columns pan by same amount.
* select a pane that isn't topmost on column, hit ctrl+e, type a key. Surface does not pan.
* mouse wheel up/down with/without shift key pressed when editing/not editing a maximized/unmaximized pane

Height computations:
* select a pane, hit ctrl+e, hit enter. Pane below it goes down by one line.
* select a pane, hit ctrl+e, type in a long line. Pane below it goes down by one line immediately after line wraps.

* topmost pane from top, no line wrap
* second pane, no line wrap (exercises title, link height, padding between panes)

Command palette:
* hit ctrl+enter, run a command, hit ctrl+enter again. Palette opens up again.
* edit using ctrl+e (not the palette), exit using ctrl+e
* edit using command palette (ctrl+enter edit enter), exit using command palette (ctrl+enter exit enter)
* open command palette (ctrl+enter), type in a filename, hit enter. file shows up in a new column on screen

Maximize:
* maximize pane. Moves vertically to middle of screen.
* capture. Maximized pane has a visible cursor that you can type into.
* capture, type in some text, hit ctrl+e to stop editing. Pane remains maximized in middle of screen.
* maximize pane, click on a line of text -> nothing is selected
* maximize pane, edit using ctrl+e, exit using ctrl+e
* maximize pane, edit using command palette (ctrl+enter edit enter), exit using command palette (ctrl+enter exit enter)
* maximize pane, scroll using pagedown and pageup
* maximize pane, insert a few lines, unmaximize pane ('back to surface'). Pane height is updated on surface.
* capture note, exit -> pane bounds are correct, shows up in 'recently modified'
* capture note, close window -> no new file created
* capture note, exit, do other edits but not to the new pane, close window -> no new file created

Cursor management:
* Keypresses not sent to pane in edit mode if its cursor is off screen
* edit a pane that starts with a drawing. Cursor shows up on first line of text.
* select a pane, hit ctrl+e, hit pagedown. Cursor remains visible.
* select a pane, hit ctrl+e, hit pageup. Cursor remains visible.
* select a pane that isn't topmost on column, hit ctrl+e, hit pagedown. Cursor remains visible.
* select a pane, hit pagedown, hit ctrl+e. There should be some indication that the pane can be edited, and where, either on- or off-screen.
* select text. select some other text. first selection is no longer highlighted.
* select a pane that starts at or above top of and extends past bottom of viewport, hit ctrl+e, position cursor near bottom of pane, hit down arrow a few times. cursor continues to remain on screen, panning surface as necessary.
* select a pane that starts halfway down and extends past bottom of viewport, hit ctrl+e, position cursor near bottom of pane, hit down arrow a few times. cursor continues to remain on screen, panning surface as necessary.
* select a pane, hit ctrl+e, hit pagedown, hit up arrow a few times. cursor continues to remain on screen, panning surface as necessary.
* select a pane that extends past bottom of viewport. hit ctrl+e. Position cursor on pane near bottom of viewport. Start typing. cursor continues to remain on screen, panning surface as necessary.
* hit pagedown twice. Columns continue to scroll (even when cursor pane is off screen).
* use just the keyboard to position viewport so that top of cursor pane is above viewport. start editing. Cursor shows up on screen.
  (This test will be affected by mouse clicks that change the cursor position
  even when it's invisible.)
* cursor remains visible after undo/redo

Find on surface:
* press 'ctrl+f' and start typing. The text you type is highlighted on screen, if it exists, as you type. The pane containing the text becomes the cursor pane.
* press 'ctrl+f' and start typing. Press up/down. The previous/next occurrence of the text you type is highlighted.
* press 'ctrl+f' and start typing. The viewport moves to keep highlighted text.
* press 'ctrl+f', start typing to scroll viewport, press enter. Viewport and cursor pane remains unchanged on enter.
* press 'ctrl+f', start typing to scroll viewport, press backspace. Viewport remains unchanged on backspace, but highlight shrinks.
* press 'ctrl+f', start typing to scroll viewport, press escape. Viewport reverts to what it was at the start. Cursor pane also reverts.
* press 'ctrl+f', press C-v to paste clipboard into search box.
* highlight some text, then press 'ctrl+f'. Previous highlight is lost.
* press 'ctrl+f'. can still use mouse to pan around the surface.
* press 'ctrl+f'. can still use mouse to select text around the surface.
* press 'ctrl+f'. can still press C-c to copy text and paste it into the search box.

Text visibility:
* Click outside a pane and slowly drag the surface up. Text should appear
  smoothly from off screen without any artifacts around pane boundaries.

    This doesn't work perfectly at the moment. However, text shouldn't appear
    suddenly more than one line's height above the bottom.

* Start some way down the surface. Click outside a pane and slowly drag the
  surface down. Text should appear smoothly from off screen without any
  artifacts around pane boundaries.

Menu visibility:
* When cursor pane is out of viewport, the edit menu disappears.
* When cursor pane is editable and in viewport, the menu says "exit editing".

Mode interactions:
* Can show palette in maximized mode
* Can show palette when editing a pane
* Can show palette when editing a maximized pane (capture/focus mode)
* Can't show palette while searching
* Can't search while showing palette
* Can't search while editing

Source editing:
* start out in the note-taking app, press ctrl+w to edit source, make a change to the source, press ctrl+w twice to return to the source editor; the change should be preserved.
* run with an untested version of LÖVE. Error message pops up. Press a key. App comes up, and doesn't receive the key. Press ctrl+w. Error pops up. Press a key. Source editor opens up. Press ctrl+w. Error pops up. Press a key. App returns.

### Other compromises

Lua is dynamically typed. Tests can't patch over lack of type-checking.

* All strings are UTF-8. Bytes within them are not characters. I try to label
  byte offsets with the suffix `_offset`, and character positions as `_pos`.
  For example, `string.sub` should never use a `_pos`, only an `_offset`.

* Some ADT/interface support would be helpful in keeping per-line state in
  sync. Any change to line data should clear the derived line property
  `screen_line_starting_pos`.

* Some inputs get processed in love.textinput and some in love.keypressed.
  Several bugs have arisen due to destructive interference between the two for
  some key chord. I wish I could guarantee that the two sets are disjoint. But
  perhaps I'm not thinking about this right.

* Like any high-level language, it's easy to accidentally alias two non-scalar
  variables. I wish there was a way to require copy when assigning.

* I wish I could require pixel coordinates to be integers. The editor
  defensively converts input margins to integers.

* My test harness automatically runs `test_*` methods -- but only at the
  top-level. I wish there was a way to raise warnings if someone defines such
  a function inside a dict somewhere.