Documentation
¶
Overview ¶
Package text implements nem's buffer model: lines, positions, the two mutation primitives every edit composes from, and the undo log.
It has no dependency on a terminal or on any other nem package, so all of it is testable with plain table-driven tests.
Three coordinate spaces meet in this package and must never be conflated:
- RuneIdx an index into a line's []rune. Storage and editing.
- grapheme a cluster boundary; the only place a cursor may rest.
- ColIdx a display column on screen. Tabs jump to the next tab stop, CJK glyphs occupy two cells, combining marks occupy none.
RuneIdx and ColIdx are distinct types so the compiler rejects mixing them.
Index ¶
- Variables
- type Buffer
- func (b *Buffer) ActivateMark()
- func (b *Buffer) BeginUndoGroup()
- func (b *Buffer) BreakUndo()
- func (b *Buffer) ClampPos(p Pos) Pos
- func (b *Buffer) ClearMark()
- func (b *Buffer) DeactivateMark()
- func (b *Buffer) Delete(from, to Pos) error
- func (b *Buffer) End() Pos
- func (b *Buffer) EndUndoGroup()
- func (b *Buffer) HasMark() bool
- func (b *Buffer) Insert(at Pos, rs []rune) error
- func (b *Buffer) Line(i int) *Line
- func (b *Buffer) Mark() Pos
- func (b *Buffer) MarkActive() bool
- func (b *Buffer) Modified() bool
- func (b *Buffer) NumLines() int
- func (b *Buffer) Path() string
- func (b *Buffer) Redo() (Pos, bool)
- func (b *Buffer) Revision() uint64
- func (b *Buffer) Save() error
- func (b *Buffer) SaveAs(path string) error
- func (b *Buffer) SavePoint() Pos
- func (b *Buffer) SetMark(p Pos)
- func (b *Buffer) SetModified(m bool)
- func (b *Buffer) SetPath(p string)
- func (b *Buffer) SetSavePoint(p Pos)
- func (b *Buffer) String() string
- func (b *Buffer) TakeDirty() (from, delta int, rev uint64)
- func (b *Buffer) Text(from, to Pos) []rune
- func (b *Buffer) Undo() (Pos, bool)
- type Cluster
- type ColIdx
- type Line
- func (l *Line) Clusters() iter.Seq[Cluster]
- func (l *Line) DisplayCol(i RuneIdx) ColIdx
- func (l *Line) Len() RuneIdx
- func (l *Line) NextGrapheme(i RuneIdx) RuneIdx
- func (l *Line) PrevGrapheme(i RuneIdx) RuneIdx
- func (l *Line) RuneAt(c ColIdx) RuneIdx
- func (l *Line) Runes() []rune
- func (l *Line) String() string
- func (l *Line) Width() ColIdx
- type Pos
- type RuneIdx
- type UndoLog
Constants ¶
This section is empty.
Variables ¶
var ErrNoPath = errors.New("text: buffer has no file path")
ErrNoPath is returned by Save when the buffer has no associated file.
var ErrOutOfRange = errors.New("text: position out of range")
ErrOutOfRange is returned when a Pos does not name a location in the buffer.
Functions ¶
This section is empty.
Types ¶
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer is a sequence of lines plus the editing state that belongs to the text itself rather than to any view of it.
Point deliberately does not live here: several windows may show one buffer, each with its own cursor. Buffer keeps only savePt, the point restored when a window next visits it.
func LoadFile ¶
LoadFile reads path into a new buffer. A file that does not exist yields an empty buffer bound to that path rather than an error: that is find-file on a new file, not a failure. Invalid UTF-8 is replaced with U+FFFD.
func NewBuffer ¶
func NewBuffer() *Buffer
NewBuffer returns an empty buffer holding a single empty line.
func (*Buffer) ActivateMark ¶ added in v0.1.2
func (b *Buffer) ActivateMark()
ActivateMark makes the region live. With no mark set there is no region, so it does nothing.
func (*Buffer) BeginUndoGroup ¶
func (b *Buffer) BeginUndoGroup()
BeginUndoGroup starts a group: every Insert and Delete until the matching EndUndoGroup undoes and redoes as one unit.
A command that composes several primitives needs this. Moving a line is a delete plus an insert, so without grouping five presses of the move key would cost ten presses of undo.
Groups nest by depth, so a helper may group its own edits without splitting its caller's group. Pair it with defer, or with EndUndoGroup on every path.
func (*Buffer) BreakUndo ¶
func (b *Buffer) BreakUndo()
BreakUndo ends the current undo unit, so subsequent typing starts a new one. The editor calls it on movement, on any non-inserting command, and on save.
It also abandons an undo group left open by a command that bailed out. That bounds the damage to the one command: without it an unclosed group would keep absorbing later edits until a single undo reverted the whole session.
func (*Buffer) ClearMark ¶
func (b *Buffer) ClearMark()
ClearMark forgets the mark entirely, which also deactivates the region.
func (*Buffer) DeactivateMark ¶ added in v0.1.2
func (b *Buffer) DeactivateMark()
DeactivateMark ends the selection but keeps the mark, so C-x C-x can still return to it. This is what C-g does, and what any buffer-changing command does afterwards.
func (*Buffer) Delete ¶
Delete removes the runes between from and to, joining lines as needed. The endpoints may be given in either order. It is the other primitive.
func (*Buffer) EndUndoGroup ¶
func (b *Buffer) EndUndoGroup()
EndUndoGroup closes the outermost open group. Calling it with no group open is a no-op, so a stray call cannot corrupt the history.
func (*Buffer) HasMark ¶
HasMark reports whether a mark has been set in this buffer.
The zero Pos is a legitimate mark position, so a flag is the only way to tell "mark at the buffer start" from "no mark at all". Region commands must check this: emacs refuses to act on a region in a buffer with no mark, and without the distinction C-w would silently kill from the buffer start to point.
func (*Buffer) Insert ¶
Insert inserts rs at at, splitting the line at any newline in rs. It is one of the two primitives every edit composes from.
func (*Buffer) MarkActive ¶ added in v0.1.2
MarkActive reports whether the region is live: drawn as a selection, and replaced by typing, Backspace or a yank.
A mark can exist without the region being active, and the difference matters. When the two were conflated, every command that set a mark for navigation silently created a selection: M-> M-< then typing a character deleted the whole file, and typing after C-y deleted what had just been pasted. This is emacs's transient-mark-mode distinction between the mark and mark-active.
Region commands that consume the region themselves - C-w, M-w, C-x C-x - use HasMark instead, so C-y then C-w still kills what was yanked, as emacs does by default.
func (*Buffer) Revision ¶
Revision returns a counter that advances once per mutation.
It never resets, and a mutation that changes nothing does not advance it.
func (*Buffer) Save ¶
Save writes the buffer back to its file, preserving the line ending style and trailing-newline convention it was loaded with.
func (*Buffer) SavePoint ¶
SavePoint returns the point stored for when a window next visits this buffer.
func (*Buffer) SetMark ¶
SetMark sets the buffer's mark and records that a mark now exists.
It does NOT activate the region. Setting a mark and selecting text are different acts: yank sets the mark so C-x C-x can select what was yanked, and the buffer-edge jumps set it so C-x C-x can return, but in neither case is the text between mark and point a selection. Commands that mean to select call ActivateMark as well.
func (*Buffer) SetModified ¶
SetModified marks the buffer clean or dirty. Marking it clean records the current undo position, so undoing back to it clears the flag again.
func (*Buffer) SetSavePoint ¶
SetSavePoint stores the point for when a window next visits this buffer.
func (*Buffer) TakeDirty ¶
TakeDirty reports what has changed since the last call and clears the record.
It returns the lowest line touched, the net change in line count, and the current revision. When nothing has changed, from is >= NumLines and delta is zero.
It CONSUMES: the name says so because a second reader calling it would take the notification from the first, which would then never learn of the edit. nem attaches exactly one highlight cache to a buffer. A second reader is not a correctness problem even so - it sees the revision advance without a dirty line, which is a signal it cannot account for, and the cache treats that as "trust nothing" rather than as "nothing changed". The result is slower, not wrong.
type Cluster ¶
type Cluster struct {
// Runes is lent from the line, not copied. It is valid only until the line
// is edited, and must not be mutated or retained. Copy it if you need to
// keep it.
Runes []rune
// Start is the rune index where the cluster begins.
Start RuneIdx
// Col is the display column where the cluster begins.
Col ColIdx
// Width is how many display columns the cluster occupies. Zero for a
// combining mark that merged into the preceding cluster is impossible here,
// since such a mark is part of that cluster rather than one of its own.
Width ColIdx
}
Cluster is one grapheme cluster's placement within a line: the runes it is made of, where they start, and the screen columns they occupy.
A cluster is the smallest unit that occupies a cell, which is why it rather than the rune is what a renderer walks. "é" written as e+U+0301 is two runes in one cell; a ZWJ family emoji is seven runes in two cells; a tab is one rune spanning however many columns reach the next tab stop.
type ColIdx ¶
type ColIdx int
ColIdx is a display column on screen. It is not a rune index.
var TabWidth ColIdx = 8
TabWidth is the number of columns between tab stops. Changing it invalidates every line's cached layout lazily, on next use.
type Line ¶
type Line struct {
// contains filtered or unexported fields
}
Line is a single line of text plus a cached grapheme/column layout. The cache is built lazily and invalidated on edit.
Guaranteed: DisplayCol(Len()) == Width(). Renderers rely on it to size the last grapheme of a line, computing a cluster's width as the difference between its own start column and the next boundary's - and for the final cluster that next boundary is Len(). Anything changing DisplayCol must keep this true; text/guarantee_test.go pins it across every width class.
func (*Line) Clusters ¶
Clusters iterates the line's grapheme clusters in order, lending each one's runes.
This exists because the render path walks every visible line on every keystroke. Reconstructing cluster boundaries from NextGrapheme plus two DisplayCol lookups costs a binary search per lookup over the segment cache, and Runes() copies the whole line; iterating the cache directly does neither.
Breaking out of the loop stops the walk, which the render path relies on to abandon a line as soon as a cluster passes the right edge of the window.
func (*Line) DisplayCol ¶
DisplayCol returns the screen column at which the grapheme containing rune index i begins. Out-of-range indices clamp to the ends of the line, so DisplayCol(Len()) is the line's Width - a guarantee callers depend on to measure the final cluster without a special case.
func (*Line) NextGrapheme ¶
NextGrapheme returns the next grapheme boundary strictly after i, clamped to the end of the line.
func (*Line) PrevGrapheme ¶
PrevGrapheme returns the previous grapheme boundary strictly before i, clamped to the start of the line.
type Pos ¶
Pos is a location in a buffer: a line number and a rune index within it.
func OrderPos ¶
OrderPos returns p and q sorted, for normalizing a region whose endpoints may be in either order (point and mark, typically).