pub struct View { /* private fields */ }Expand description
Per-window view onto a Buffer.
View is the type the rest of hjkl-buffer — and all consumers —
use directly. It owns exactly the state that is local to one editor
window:
cursor— the charwise caret for this window.
All document-level state (text rope, dirty generation, folds) lives on
the inner Buffer and is accessed via Arc<Mutex<Buffer>>.
Two View instances that share the same Arc share text + folds
but carry independent cursors — the Helix Document+View model.
§Send + Sync
Arc<Mutex<Buffer>> is Send + Sync, so View remains Send.
The engine trait surface requires View: Send; this constraint
drove the choice of Mutex over RefCell. The mutex is never
contended in normal operation (single-threaded app loop), so the
lock cost is negligible (~5 ns uncontested).
§0.8.0 migration notes
The existing constructors (View::new, View::from_str,
View::replace_all, etc.) keep the same external signatures.
Callers that do not need multi-window sharing see no behaviour change.
Use View::new_view to create a second window onto the same
Buffer.
§Viewport
The rope invariant — at least one line, never empty — is preserved by
every mutation (ropey’s empty rope already reports len_lines() == 1).
The viewport itself (top_row, top_col, width, height, wrap, text_width)
lives on the engine Host adapter; methods that need it take a
&Viewport / &mut Viewport parameter so the rope-walking math stays
here while runtime state lives there.
Implementations§
Source§impl View
impl View
Sourcepub fn from_str(text: &str) -> Self
pub fn from_str(text: &str) -> Self
Build a buffer from a flat string. Splits on \n; a trailing
\n produces a trailing empty line (matches every text
editor’s behaviour and keeps from_text(buf.as_string()) an
identity round-trip in the common case).
Sourcepub fn new_view(content: Arc<Mutex<Buffer>>) -> Self
pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self
Create a second per-window view onto existing Buffer.
The new View shares text + folds with every other view on the
same Arc. Its cursor starts at (0, 0) independently. This is
the primary entry point for split-window features.
let a = View::from_str("hello\nworld");
let content = a.content_arc();
let mut b = View::new_view(Arc::clone(&content));
// Cursors are independent.
let mut a = View::new_view(Arc::clone(&content));
a.set_cursor(Position::new(1, 0));
assert_eq!(b.cursor(), Position::new(0, 0));Sourcepub fn content_arc(&self) -> Arc<Mutex<Buffer>>
pub fn content_arc(&self) -> Arc<Mutex<Buffer>>
Return a clone of the Arc<Mutex<Buffer>> so callers can
create additional views with View::new_view.
pub fn cursor(&self) -> Position
Sourcepub fn last_cursor(&self) -> (usize, usize)
pub fn last_cursor(&self) -> (usize, usize)
The last cursor (row, col) committed on the shared Buffer by any
view (see View::set_cursor). This is the “last-moved cursor across
all windows” the cross-session cursor store persists — not this view’s
own live cursor. Best-effort; read at write/close/exit.
pub fn dirty_gen(&self) -> u64
Sourcepub fn as_string(&self) -> String
pub fn as_string(&self) -> String
Concatenate the rows into a single String joined by \n.
Equivalent to rope.to_string() — ropey’s rope-to-string already
produces \n-joined content matching split('\n').join("\n").
Sourcepub fn set_cursor(&mut self, pos: Position)
pub fn set_cursor(&mut self, pos: Position)
Set cursor without scrolling. Clamps to valid positions.
The optional sticky column for j/k motions is not reset
by this call — it survives set_cursor intentionally.
Sourcepub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport)
pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport)
Bring the cursor into the visible Viewport, scrolling by the
minimum amount needed.
Sourcepub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize>
pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize>
Cursor’s screen row offset (0-based) from viewport.top_row.
Sourcepub fn screen_rows_between(
&self,
viewport: &Viewport,
start: usize,
end: usize,
) -> usize
pub fn screen_rows_between( &self, viewport: &Viewport, start: usize, end: usize, ) -> usize
Number of screen rows the doc range start..=end occupies.
Sourcepub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize
pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize
Earliest top_row such that screen_rows_between(top, last)
is at least height.
Sourcepub fn clamp_position(&self, pos: Position) -> Position
pub fn clamp_position(&self, pos: Position) -> Position
Clamp pos to the buffer’s content.
Sourcepub fn replace_all(&mut self, text: &str)
pub fn replace_all(&mut self, text: &str)
Replace the buffer’s full text in place. Cursor is clamped to the new content.
Sourcepub fn byte_len(&self) -> usize
pub fn byte_len(&self) -> usize
Canonical byte length of the document. Rope::len_bytes() is O(1)
and returns the same value as to_string().len() (i.e.
sum(line_bytes) + (n_lines-1) separators). Cached against
dirty_gen for API compatibility; the O(1) rope call makes the
cache essentially free but keeps the invalidation contract identical.
Sourcepub fn content_joined(&self) -> Arc<String>
pub fn content_joined(&self) -> Arc<String>
Return an Arc<String> of the full document, cached against
dirty_gen. Multiple per-tick consumers (syntax pipeline, LSP
notify, git signature, dirty hash) share the same Arc for the
same generation — first caller pays the rope.to_string() cost
(one alloc + one lock), the rest are O(1).
Cache invalidates automatically on every dirty_gen_bump and on
replace_all, so callers never need to manage invalidation.
Sourcepub fn rope(&self) -> Rope
pub fn rope(&self) -> Rope
Borrow the underlying rope. Hot-path consumers (tree-sitter
streaming parse, byte-range slicing) should use this instead of
content_joined() to avoid materializing the whole document as
a String.
ropey::Rope::clone is O(1) — it Arc-clones the root node.
The clone gives the caller a snapshot they can read without
holding the content mutex.
pub fn undo_stack_is_empty(&self) -> bool
pub fn redo_stack_is_empty(&self) -> bool
pub fn undo_stack_len(&self) -> usize
Sourcepub fn push_undo_entry(&self, entry: UndoEntry)
pub fn push_undo_entry(&self, entry: UndoEntry)
Commit the pre-edit LIVE state entry as an undo boundary: the current
node takes entry and a fresh child becomes current, with the forward
(redo) branch dropped — the tree form of undo_stack.push + clear_redo.
Sourcepub fn undo_step(
&self,
rope: Rope,
cursor: (usize, usize),
marks: MarkSnapshot,
) -> Option<UndoEntry>
pub fn undo_step( &self, rope: Rope, cursor: (usize, usize), marks: MarkSnapshot, ) -> Option<UndoEntry>
One undo move. live is the current buffer state (rope/cursor/marks) of
the node being left; returns the parent snapshot to restore, or None
at the root. See [crate::UndoTree::undo_step].
Sourcepub fn redo_step(
&self,
rope: Rope,
cursor: (usize, usize),
marks: MarkSnapshot,
) -> Option<UndoEntry>
pub fn redo_step( &self, rope: Rope, cursor: (usize, usize), marks: MarkSnapshot, ) -> Option<UndoEntry>
One redo move. Symmetric to Self::undo_step; returns the child
snapshot to restore, or None when there is no forward branch.
Sourcepub fn pop_committed(&self) -> bool
pub fn pop_committed(&self) -> bool
Discard the most-recent undo boundary without moving the live state
(undo_stack.pop()); false at the root. See
[crate::UndoTree::pop_committed].
Sourcepub fn seq_earlier_step(
&self,
rope: Rope,
cursor: (usize, usize),
marks: MarkSnapshot,
) -> Option<UndoEntry>
pub fn seq_earlier_step( &self, rope: Rope, cursor: (usize, usize), marks: MarkSnapshot, ) -> Option<UndoEntry>
One g- / :earlier step: move to the next-lower-seq state tree-wide,
returning its snapshot to restore, or None at the lowest state. live
is the current buffer state, stashed into the node being left. See
[crate::UndoTree::seq_earlier_step].
Sourcepub fn seq_later_step(
&self,
rope: Rope,
cursor: (usize, usize),
marks: MarkSnapshot,
) -> Option<UndoEntry>
pub fn seq_later_step( &self, rope: Rope, cursor: (usize, usize), marks: MarkSnapshot, ) -> Option<UndoEntry>
One g+ / :later step: move to the next-higher-seq state tree-wide.
Symmetric to Self::seq_earlier_step.
Sourcepub fn seq_earlier_timestamp(&self) -> Option<SystemTime>
pub fn seq_earlier_timestamp(&self) -> Option<SystemTime>
Timestamp of the next-lower-seq state (:earlier Ns predicate).
Sourcepub fn seq_later_timestamp(&self) -> Option<SystemTime>
pub fn seq_later_timestamp(&self) -> Option<SystemTime>
Timestamp of the next-higher-seq state (:later Ns predicate).
Sourcepub fn undo_leaves(&self) -> Vec<(u64, usize, SystemTime, bool)>
pub fn undo_leaves(&self) -> Vec<(u64, usize, SystemTime, bool)>
Undo-tree leaves for :undolist, each (seq, changes/depth, timestamp, is_current), sorted by seq. See [crate::UndoTree::leaves].
pub fn peek_undo_timestamp(&self) -> Option<SystemTime>
pub fn peek_redo_timestamp(&self) -> Option<SystemTime>
pub fn clear_undo_redo(&self)
Sourcepub fn undo_to_serializable(&self) -> (SerTree, u64)
pub fn undo_to_serializable(&self) -> (SerTree, u64)
Project this buffer’s undo tree into its serializable form for the
undofile, plus the current node’s seq. Syncs the current node to the
live buffer text first, so the on-disk tree’s current edge is exact
even when current is a fresh (still-stale) leaf.
Sourcepub fn install_undo_tree(&self, ser: &SerTree) -> bool
pub fn install_undo_tree(&self, ser: &SerTree) -> bool
Replace this buffer’s fresh single-node undo tree with one deserialized
from an undofile. Must run BEFORE the first user edit and after the
buffer text is populated (the tree’s current materializes to the loaded
content). Returns false — leaving the fresh tree untouched — if the
projection is structurally inconsistent.
Sourcepub fn install_recovered_undo_tree(
&self,
ser: &SerTree,
current_seq: u64,
) -> bool
pub fn install_recovered_undo_tree( &self, ser: &SerTree, current_seq: u64, ) -> bool
Install an undo tree recovered from a swap file (crash path),
verifying it against the just-recovered
buffer text before committing. Unlike Self::install_undo_tree (the
undofile / clean-close path, whose caller gates on a content hash), the
swap tail rides an unsaved buffer, so this re-checks consistency itself.
Returns false — leaving the fresh single-node tree seeded from the
recovered content untouched — when the projection is structurally
invalid, its current node’s seq differs from current_seq, or its
current node doesn’t materialize to the live buffer text. Must run AFTER
the recovered content is installed (which resets undo to a single node).
A trailing-newline difference is normalized away, matching how recovery
installs body.strip_suffix('\n').
pub fn clear_redo(&self)
Sourcepub fn undo_group_active(&self) -> bool
pub fn undo_group_active(&self) -> bool
Whether an undo group is currently open on this content (depth > 0).
Used by the engine’s push_undo to decide whether to coalesce.
Sourcepub fn undo_group_arm(&self) -> bool
pub fn undo_group_arm(&self) -> bool
Arm the open group’s single snapshot. See Buffer::undo_group_arm.
pub fn cap_undo(&self, cap: usize)
pub fn content_dirty(&self) -> bool
pub fn set_content_dirty(&self, v: bool)
pub fn mark_content_dirty(&self)
pub fn take_dirty(&self) -> bool
pub fn cached_editor_content(&self) -> Option<Arc<String>>
pub fn set_cached_editor_content(&self, arc: Arc<String>)
pub fn push_fold_op(&self, op: FoldOp)
pub fn take_fold_ops(&self) -> Vec<FoldOp>
pub fn extend_change_log(&self, edits: impl IntoIterator<Item = EngineEdit>)
pub fn take_change_log(&self) -> Vec<EngineEdit>
pub fn extend_pending_content_edits( &self, edits: impl IntoIterator<Item = ContentEdit>, )
pub fn push_pending_content_edit(&self, edit: ContentEdit)
pub fn take_pending_content_edits(&self) -> Vec<ContentEdit>
pub fn clear_pending_content_edits(&self)
pub fn pending_content_reset(&self) -> bool
pub fn set_pending_content_reset(&self, v: bool)
pub fn take_pending_content_reset(&self) -> bool
pub fn mark(&self, c: char) -> Option<(usize, usize)>
pub fn set_mark(&mut self, c: char, pos: (usize, usize))
pub fn clear_mark(&mut self, c: char)
pub fn marks_cloned(&self) -> BTreeMap<char, (usize, usize)>
pub fn set_marks(&mut self, marks: BTreeMap<char, (usize, usize)>)
Sourcepub fn rebase_marks(
&mut self,
edit_start: usize,
drop_end: usize,
shift_threshold: usize,
delta: isize,
)
pub fn rebase_marks( &mut self, edit_start: usize, drop_end: usize, shift_threshold: usize, delta: isize, )
Drop marks inside [edit_start, drop_end) and shift marks at/after
shift_threshold by delta rows (clamped to 0). Mirrors the engine’s
edit-coherence pass for the per-buffer mark map (#154).
pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)>
pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>)
Source§impl View
impl View
Sourcepub fn apply_edit(&mut self, edit: Edit) -> Edit
pub fn apply_edit(&mut self, edit: Edit) -> Edit
Apply edit and return the inverse. Pushing the inverse back
through apply_edit restores the previous state, making it the
single hook for undo-stack integration.
apply_edit is the only way to mutate buffer text.
§Post-conditions
After any Edit variant:
View::dirty_genis incremented exactly once.- The cursor is repositioned to a sensible place for the edit kind
(insert lands past the inserted content; delete lands at the
start). Callers that need to override the new cursor must call
View::set_cursorimmediately after. - All
Positionvalues the caller held from before the edit may be invalid. Re-derive from row / col deltas; do not cache.
Source§impl View
impl View
Sourcepub fn folds(&self) -> Vec<Fold>
pub fn folds(&self) -> Vec<Fold>
Returns a snapshot of all folds as an owned Vec<Fold>.
Owned rather than &[Fold] because a View is a per-window
view onto a shared Buffer; another view could mutate the folds vec
between when this returns and when the caller reads the slice.
Sourcepub fn add_fold(&mut self, start_row: usize, end_row: usize, closed: bool)
pub fn add_fold(&mut self, start_row: usize, end_row: usize, closed: bool)
Register a new fold. If an existing fold has the same
start_row, it’s replaced; otherwise the new one is inserted
in start-row order. Empty / inverted ranges are rejected.
Sourcepub fn set_auto_folds(
&mut self,
ranges: &[(usize, usize)],
default_closed: bool,
)
pub fn set_auto_folds( &mut self, ranges: &[(usize, usize)], default_closed: bool, )
Replace all auto-generated folds with a new set derived from
ranges, while leaving manual folds untouched.
§Algorithm (O(N) — bounded by ranges.len(), no unbounded growth)
- Snapshot
start_row → closedfor every existing auto fold so open/closed state survives a reparse. - Retain only manual folds (
auto_generated == false). - Insert one new
Foldper range, re-using the snapshotted closed state when the start_row existed before, elsedefault_closed.
Invariants preserved:
- Folds stay sorted by
start_row(same ordering asadd_fold). - Duplicate start_rows: the last range in
rangeswins (consistent withadd_fold’s replace-on-same-start-row semantics). In practice TS query ranges are already deduplicated. - Empty / inverted ranges (end_row < start_row) are silently skipped.
end_rowis clamped to the last valid row, same asadd_fold.
Sourcepub fn remove_fold_at(&mut self, row: usize) -> bool
pub fn remove_fold_at(&mut self, row: usize) -> bool
Drop the fold whose range covers row. Returns true when a
fold was actually removed.
Sourcepub fn open_fold_at(&mut self, row: usize) -> bool
pub fn open_fold_at(&mut self, row: usize) -> bool
Open the fold at row (no-op if already open or no fold).
Sourcepub fn close_fold_at(&mut self, row: usize) -> bool
pub fn close_fold_at(&mut self, row: usize) -> bool
Close the fold at row (no-op if already closed or no fold).
Sourcepub fn toggle_fold_at(&mut self, row: usize) -> bool
pub fn toggle_fold_at(&mut self, row: usize) -> bool
Flip the closed/open state of the fold containing row.
Sourcepub fn open_all_folds(&mut self)
pub fn open_all_folds(&mut self)
zR — open every fold.
Sourcepub fn clear_all_folds(&mut self)
pub fn clear_all_folds(&mut self)
zE — eliminate every fold.
Sourcepub fn close_all_folds(&mut self)
pub fn close_all_folds(&mut self)
zM — close every fold.
Sourcepub fn fold_at_row(&self, row: usize) -> Option<Fold>
pub fn fold_at_row(&self, row: usize) -> Option<Fold>
First fold whose range contains row. Useful for the host’s
za/zo/zc handlers.
True iff row is hidden by a closed fold (any fold).
Sourcepub fn reveal_row(&mut self, row: usize) -> bool
pub fn reveal_row(&mut self, row: usize) -> bool
Open every closed fold whose body hides row, so the row becomes
visible. Handles nested folds in a single pass — unlike
open_fold_at / FoldOp::OpenAt, which only act on the first fold
containing the row and so can never reach a nested inner fold.
Used by goto_line so a jump into a folded region reveals the
target line instead of stranding the cursor on a hidden row.
Returns true if any fold was opened.
Sourcepub fn next_visible_row(&self, row: usize) -> Option<usize>
pub fn next_visible_row(&self, row: usize) -> Option<usize>
First visible row strictly after row, skipping any rows hidden
by closed folds. Returns None past the end of the buffer.
Sourcepub fn prev_visible_row(&self, row: usize) -> Option<usize>
pub fn prev_visible_row(&self, row: usize) -> Option<usize>
First visible row strictly before row, skipping hidden rows.
Sourcepub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize)
pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize)
Drop every fold that touches [start_row, end_row].
Sourcepub fn rebase_folds(
&mut self,
edit_start: usize,
drop_end: usize,
shift_threshold: usize,
delta: isize,
)
pub fn rebase_folds( &mut self, edit_start: usize, drop_end: usize, shift_threshold: usize, delta: isize, )
Shift every buffer fold by an edit’s row-delta band. Mirrors
crate::buffer::View::rebase_marks for the shared fold storage —
see shift_folds_after_edit for the per-fold rules.
Sourcepub fn set_folds(&mut self, folds: &[Fold])
pub fn set_folds(&mut self, folds: &[Fold])
Replace the entire fold set wholesale. Used to install a per-window fold
snapshot into the shared buffer on focus change (window-level folds): the
app keeps each window’s open/closed state and swaps it in before dispatch,
so motions/render/z-ops operate on the focused window’s folds.