Cairn · module

Cairn.Text.Field

lean/Cairn/Text/Field.lean

Single-line text editing

Field is the editing model behind every text field: the text, caret, anchor, IME marked range, undo and redo stacks, and placeholder. Field.apply gives the meaning of every TextOp; Field.view turns a field into the FieldView the view layer draws.

The text is a List Char, so every index is a Unicode scalar position, the same unit as String.length and TextMetrics.offsets. Lists are used rather than String because every edit is a splice (take a ++ s ++ drop b), and core Lean's List lemmas about take, drop and length make the proofs short; String's byte-indexed API would put a UTF-8 decoding step between every theorem and its statement. The view converts with String.ofList (String.length_ofList keeps the indices meaning the same thing).

The behaviour is specified, op by op and against macOS's NSTextView, in docs/text.md. Theorems are in Cairn.Text.Proofs.

Imports Cairn.Input, Cairn.UI.Node

Order

Structures 4

structure

Cairn.Text.Snapshot

lean/Cairn/Text/Field.lean:78
Snapshot : Type

What an undo step restores: the text and the selection.

Fields
text : List Char
caret : Nat
anchor : Nat
Instances

Repr BEq Inhabited

structure

Cairn.Text.Field

lean/Cairn/Text/Field.lean:84
Field : Type
Fields
text : List Char

Unicode scalars; never contains a line break (SingleLine).

caret : Nat

Where the insertion point is, and the end of the selection that moves.

anchor : Nat

The fixed end of the selection. The selection is [min caret anchor, max caret anchor).

marked : Option (Nat × Nat)

IME composition in progress, [start, end) of text.

undoStack : List Snapshot

Most recent first. Each entry is the state before an undoable edit.

redoStack : List Snapshot

Most recent first. Cleared by every edit that changes the text.

typing : Bool

The last op was a typing insert; another one at a collapsed caret joins its undo step.

placeholder : String
Instances

Repr Inhabited BEq

structure

Cairn.Text.Field.WF

lean/Cairn/Text/Field.lean:267
WF (f : Field) : Prop

Every index is a position in the text, and the text is a single line.

Fields
caret_le : f.caret ≤ f.text.length
anchor_le : f.anchor ≤ f.text.length
marked_le : a ≤ b ∧ b ≤ f.text.length
single : SingleLine f.text
undo_wf : s.WF
redo_wf : s.WF
structure

Cairn.Text.SecureField

lean/Cairn/Text/Field.lean:294
SecureField : Type

A password field: a Field whose history is always empty.

Fields
field : Field
Instances

BEq Repr Inhabited

Definitions 45

def

Cairn.Text.isLineBreak

lean/Cairn/Text/Field.lean:30
isLineBreak (c : Char) : Bool

Scalars that would start a new line. A single-line field never contains them: inserted and pasted text has each one replaced by a space, as NSTextField does in single-line mode.

def

Cairn.Text.SingleLine

lean/Cairn/Text/Field.lean:35
SingleLine (t : List Char) : Prop

The text a field may contain: no line breaks.

def

Cairn.Text.flatten

lean/Cairn/Text/Field.lean:38
flatten (c : Char) : Char

One scalar as a single-line field stores it: a line break becomes a space.

def

Cairn.Text.clean

lean/Cairn/Text/Field.lean:41
clean (s : String) : List Char

s as scalars, each line break replaced by a space. Same length as s.

def

Cairn.Text.isWordChar

lean/Cairn/Text/Field.lean:47
isWordChar (c : Char) : Bool

A word is a maximal run of word characters: ASCII letters, digits and _, and every non-ASCII scalar except the Unicode spaces below. Everything else (ASCII punctuation and spaces) separates words. This is deliberately simpler than NSTextView, which uses ICU word boundaries and a dictionary for scripts without spaces; see docs/text.md.

def

Cairn.Text.span

lean/Cairn/Text/Field.lean:53
span (p : Char → Bool) : List Char → Nat

The length of the longest prefix of l whose scalars satisfy p.

def

Cairn.Text.wordLeft

lean/Cairn/Text/Field.lean:58
wordLeft (t : List Char) (i : Nat) : Nat

Where Option-Left goes from i: back over separators, then back over a word.

def

Cairn.Text.wordRight

lean/Cairn/Text/Field.lean:65
wordRight (t : List Char) (i : Nat) : Nat

Where Option-Right goes from i: forward over separators, then forward over a word.

def

Cairn.Text.splice

lean/Cairn/Text/Field.lean:72
splice (t : List Char) (a b : Nat) (s : List Char) : List Char

t with [a, b) replaced by s.

def

Cairn.Text.Field.empty

lean/Cairn/Text/Field.lean:105
empty (placeholder : String := "") : Field

An empty field.

def

Cairn.Text.Field.ofString

lean/Cairn/Text/Field.lean:108
ofString (s : String) (placeholder : String := "") : Field

A field holding s (line breaks removed) with the caret at the end.

def

Cairn.Text.Field.lo

lean/Cairn/Text/Field.lean:112
lo (f : Field) : Nat
def

Cairn.Text.Field.hi

lean/Cairn/Text/Field.lean:113
hi (f : Field) : Nat
def

Cairn.Text.Field.len

lean/Cairn/Text/Field.lean:114
len (f : Field) : Nat
def

Cairn.Text.Field.selected

lean/Cairn/Text/Field.lean:117
selected (f : Field) : List Char

The selected scalars, [lo, hi).

def

Cairn.Text.Field.snapshot

lean/Cairn/Text/Field.lean:119
snapshot (f : Field) : Snapshot
def

Cairn.Text.Field.textString

lean/Cairn/Text/Field.lean:121
textString (f : Field) : String
def

Cairn.Text.Field.target

lean/Cairn/Text/Field.lean:125
target (f : Field) : Nat × Nat

The range an insertion or composition replaces: the marked text if there is any, else the selection.

def

Cairn.Text.Field.targetOr

lean/Cairn/Text/Field.lean:130
targetOr (f : Field) : Option (Nat × Nat) → Nat × Nat

The range an input-method edit replaces: the explicit replace range when the input system gives one (press-and-hold accents, reconversion, Korean IMEs that rewrite the last syllable), clamped into the text, else target.

def

Cairn.Text.Field.edit

lean/Cairn/Text/Field.lean:139
edit (f : Field) (text : List Char) (caret anchor : Nat)
  (push typing : Bool) : Field

Set the text and selection after an edit. When the text really changes, the old state is pushed for undo (if push) and redo is cleared; when it doesn't, only the selection moves and nothing is recorded.

def

Cairn.Text.Field.moveTo

lean/Cairn/Text/Field.lean:148
moveTo (f : Field) (i : Nat) (extend : Bool) : Field

Move the caret to i, keeping the anchor when extending. Ends any composition.

def

Cairn.Text.Field.deleteRange

lean/Cairn/Text/Field.lean:153
deleteRange (f : Field) (a b : Nat) (coalesce : Bool := false) : Field

Delete [a, b). With coalesce, the deletion joins an open typing step and keeps it open (Backspace while typing); otherwise it is its own undo step.

def

Cairn.Text.Field.deleteSelOr

lean/Cairn/Text/Field.lean:157
deleteSelOr (f : Field) (a b : Nat) (coalesce : Bool := false) : Field

Delete the selection if there is one (its own undo step), else [a, b).

def

Cairn.Text.Field.insert

lean/Cairn/Text/Field.lean:165
insert (f : Field) (s : String) (replace : Option (Nat × Nat) := none) :
  Field

Typed or committed text replaces the marked text, else the selection. Consecutive typing at a collapsed caret is one undo step; committing a composition joins the step its first setMarked opened. replace is the input system's replacement range (see targetOr).

def

Cairn.Text.Field.paste

lean/Cairn/Text/Field.lean:173
paste (f : Field) (s : String) : Field

Like insert, but always its own undo step (unless it commits a composition).

def

Cairn.Text.Field.setMarked

lean/Cairn/Text/Field.lean:183
setMarked (f : Field) (s : String) (selStart selLen : Nat)
  (replace : Option (Nat × Nat) := none) : Field

IME composition: replace the marked text (or, when composition starts, the selection) with s, mark it, and put the selection at [selStart, selStart + selLen) within it, clamped. Starting a composition opens an undo step; the whole composition, through its commit, is that one step. An empty composition ends it. replace is as for insert.

def

Cairn.Text.Field.unmark

lean/Cairn/Text/Field.lean:200
unmark (f : Field) : Field

Commit the composition as it stands; the text and selection stay.

def

Cairn.Text.Field.undo

lean/Cairn/Text/Field.lean:202
undo (f : Field) : Field × List Effect
def

Cairn.Text.Field.redo

lean/Cairn/Text/Field.lean:209
redo (f : Field) : Field × List Effect
def

Cairn.Text.Field.clipboard

lean/Cairn/Text/Field.lean:216
clipboard (f : Field) : List Effect
def

Cairn.Text.Field.apply

lean/Cairn/Text/Field.lean:220
apply : TextOp → Field → Field × List Effect

The meaning of every TextOp. See docs/text.md.

def

Cairn.Text.Field.applyAll

lean/Cairn/Text/Field.lean:248
applyAll (f : Field) (ops : List TextOp) : Field

Apply ops in order, dropping effects.

def

Cairn.Text.Field.iterate

lean/Cairn/Text/Field.lean:252
iterate (op : TextOp) : Nat → Field → Field

Apply op n times.

def

Cairn.Text.Field.view

lean/Cairn/Text/Field.lean:257
view (f : Field) (focused : Bool) : FieldView

What the view layer draws.

def

Cairn.Text.Snapshot.WF

lean/Cairn/Text/Field.lean:263
WF (s : Snapshot) : Prop
def

Cairn.Text.bullet

lean/Cairn/Text/Field.lean:288
bullet : Char

What a secure field draws for each scalar.

def

Cairn.Text.bullets

lean/Cairn/Text/Field.lean:291
bullets (n : Nat) : String

n bullets.

def

Cairn.Text.SecureField.empty

lean/Cairn/Text/Field.lean:301
empty (placeholder : String := "") : SecureField

An empty secure field.

def

Cairn.Text.SecureField.text

lean/Cairn/Text/Field.lean:304
text (f : SecureField) : String

What was typed. Only for the one request that sends it (with redact := true).

def

Cairn.Text.SecureField.len

lean/Cairn/Text/Field.lean:306
len (f : SecureField) : Nat
def

Cairn.Text.SecureField.forget

lean/Cairn/Text/Field.lean:309
forget (f : Field) : Field

The field without undo or redo history.

def

Cairn.Text.SecureField.apply

lean/Cairn/Text/Field.lean:313
apply : TextOp → SecureField → SecureField × List Effect

Like Field.apply, except that copy, cut, undo and redo beep and change nothing, and no history is kept.

def

Cairn.Text.SecureField.applyAll

lean/Cairn/Text/Field.lean:318
applyAll (f : SecureField) (ops : List TextOp) : SecureField

Apply ops in order, dropping effects.

def

Cairn.Text.SecureField.view

lean/Cairn/Text/Field.lean:323
view (f : SecureField) (focused : Bool) : FieldView

What the view layer draws: a bullet per scalar, the field's selection and composition, and secure.

def

Cairn.Text.SecureField.Agree

lean/Cairn/Text/Field.lean:329
Agree (f g : SecureField) : Prop

Two secure fields that show the same: the same number of scalars, selection, composition and placeholder, whatever the scalars are.

Theorems 2

theorem

Cairn.Text.SecureField.view_text

provedlean/Cairn/Text/Field.lean:335
view_text (f : SecureField) (b : Bool) :
  (f.view b).text.toList = List.replicate f.field.text.length bullet ∧
    (f.view b).secure = true

Bullets: the view's text is one bullet per scalar typed.

theorem

Cairn.Text.SecureField.view_agree

provedlean/Cairn/Text/Field.lean:342
view_agree {f g : SecureField} (h : f.Agree g) (b : Bool) :
  f.view b = g.view b

The view hides the text: two secure fields that agree on everything but which scalars were typed have the same view, so nothing built from it (layout, the frame, the accessibility mirror, a dump) can tell them apart.

Instances 12

instance

Cairn.Text.instReprSnapshot

lean/Cairn/Text/Field.lean:82
instReprSnapshot : Repr Snapshot
instance

Cairn.Text.instBEqSnapshot

lean/Cairn/Text/Field.lean:82
instBEqSnapshot : BEq Snapshot
instance

Cairn.Text.instInhabitedSnapshot

lean/Cairn/Text/Field.lean:82
instInhabitedSnapshot : Inhabited Snapshot
instance

Cairn.Text.instDecidableEqSnapshot

lean/Cairn/Text/Field.lean:82
instDecidableEqSnapshot : DecidableEq Snapshot
instance

Cairn.Text.instDecidableEqField

lean/Cairn/Text/Field.lean:100
instDecidableEqField : DecidableEq Field
instance

Cairn.Text.instReprField

lean/Cairn/Text/Field.lean:100
instReprField : Repr Field
instance

Cairn.Text.instInhabitedField

lean/Cairn/Text/Field.lean:100
instInhabitedField : Inhabited Field
instance

Cairn.Text.instBEqField

lean/Cairn/Text/Field.lean:100
instBEqField : BEq Field
instance

Cairn.Text.instDecidableEqSecureField

lean/Cairn/Text/Field.lean:296
instDecidableEqSecureField : DecidableEq SecureField
instance

Cairn.Text.instBEqSecureField

lean/Cairn/Text/Field.lean:296
instBEqSecureField : BEq SecureField
instance

Cairn.Text.instReprSecureField

lean/Cairn/Text/Field.lean:296
instReprSecureField : Repr SecureField
instance

Cairn.Text.instInhabitedSecureField

lean/Cairn/Text/Field.lean:296
instInhabitedSecureField : Inhabited SecureField