Text Editing
textAreaedits aVar Text. Use it for short text: notes, descriptions, commit messages.textAreaDocumentedits aVar TextDocument, which shares unchanged lines between edits. Use it for files and code buffers.
textArea for short text
notes = do
draft <- useVar ""
textArea draft
Every edit builds a new Text, which is cheap for a few kilobytes.
textAreaDocument for documents
editorView :: Var WidgetId -> Var TextDocument -> NanoUI ()
editorView editorId docVar = do
resp <- textAreaDocumentWith' (grow . minW 320 . minH 200) docVar
when (respChanged resp) markDirty
writeVar editorId (respId resp)
Only the edited lines are copied; unchanged lines are shared. documentText materializes the whole text; call it when saving instead of on every edit.
save :: FilePath -> TextDocument -> NanoUI ()
save path doc = liftIO (BS.writeFile path (TE.encodeUtf8 (documentText doc)))
documentLineCount is cheap enough for a status bar.
One undo history
Keyboard edits, mouse selection, the right-click menu, and programmatic commands all dispatch TextCommands, so they share one undo history:
-- | Editing, navigation, selection, history, and clipboard operations on a
-- text field. Mutating commands record an undo step and replace any active
-- selection.
data TextCommand
= -- | Replace the selection with text (typing, a snippet).
InsertText !Text
| -- | Delete the selection, or from the cursor to where the motion lands:
-- @Delete CharLeft@ is Backspace, @Delete WordRight@ Ctrl+Delete.
Delete !TextMotion
| -- | Move the cursor, extending the selection when the flag is set.
-- Without it, 'CharLeft' and 'CharRight' over a selection go to its start
-- and its end.
Move !TextMotion !Bool
| SelectAll
| -- | Select from the first position (the anchor) to the second (the
-- cursor), clamped into the document.
Select !Cursor !Cursor
| -- | Replace the text between two positions, leaving the cursor after it.
Replace !Cursor !Cursor !Text
| -- | Replace the whole document as one undoable edit.
ReplaceAll !Text
| Undo
| Redo
| Cut
| Copy
| Paste
deriving (Eq, Show)
Run a command against a field by its WidgetId:
editMenu :: WidgetId -> Text -> NanoUI ()
editMenu editor today = do
whenM (menuItem "Undo") (runTextCommand editor Undo)
whenM (menuItem "Insert date") (runTextCommand editor (InsertText today))
Query history state for menu enablement:
undoState :: Var WidgetId -> NanoUI (Bool, Bool)
undoState editorId = do
editor <- readVar editorId
canUndo <- textCanUndo editor
canRedo <- textCanRedo editor
pure (canUndo, canRedo)
A complete Edit menu built this way is in Build a Notepad.
Tip
Directly overwriting the
Var TextorVar TextDocumentfrom outside resets the undo stack. To make a programmatic edit undoable, issue aTextCommand(InsertText,ReplaceAll, and so on) instead of writing theVar.
In the primed variants’ Response, respChanged is True after an edit and False after a caret move alone, so use it to mark a document dirty. respId names the editor for commands and focus.