Skip to main content

View

Struct View 

Source
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

Source

pub fn new() -> Self

Construct an empty buffer with one empty row + cursor at (0, 0).

Source

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).

Source

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));
Source

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.

Source

pub fn cursor(&self) -> Position

Source

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.

Source

pub fn dirty_gen(&self) -> u64

Source

pub fn row_count(&self) -> usize

Number of rows in the buffer. Always >= 1.

Source

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").

Source

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.

Source

pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport)

Bring the cursor into the visible Viewport, scrolling by the minimum amount needed.

Source

pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize>

Cursor’s screen row offset (0-based) from viewport.top_row.

Source

pub fn screen_rows_between( &self, viewport: &Viewport, start: usize, end: usize, ) -> usize

Number of screen rows the doc range start..=end occupies.

Source

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.

Source

pub fn clamp_position(&self, pos: Position) -> Position

Clamp pos to the buffer’s content.

Source

pub fn replace_all(&mut self, text: &str)

Replace the buffer’s full text in place. Cursor is clamped to the new content.

Source

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.

Source

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.

Source

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.

Source

pub fn undo_stack_is_empty(&self) -> bool

Source

pub fn redo_stack_is_empty(&self) -> bool

Source

pub fn undo_stack_len(&self) -> usize

Source

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.

Source

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].

Source

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.

Source

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].

Source

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].

Source

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.

Source

pub fn seq_earlier_timestamp(&self) -> Option<SystemTime>

Timestamp of the next-lower-seq state (:earlier Ns predicate).

Source

pub fn seq_later_timestamp(&self) -> Option<SystemTime>

Timestamp of the next-higher-seq state (:later Ns predicate).

Source

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].

Source

pub fn peek_undo_timestamp(&self) -> Option<SystemTime>

Source

pub fn peek_redo_timestamp(&self) -> Option<SystemTime>

Source

pub fn clear_undo_redo(&self)

Source

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.

Source

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.

Source

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').

Source

pub fn clear_redo(&self)

Source

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.

Source

pub fn undo_group_arm(&self) -> bool

Arm the open group’s single snapshot. See Buffer::undo_group_arm.

Source

pub fn cap_undo(&self, cap: usize)

Source

pub fn content_dirty(&self) -> bool

Source

pub fn set_content_dirty(&self, v: bool)

Source

pub fn mark_content_dirty(&self)

Source

pub fn take_dirty(&self) -> bool

Source

pub fn cached_editor_content(&self) -> Option<Arc<String>>

Source

pub fn set_cached_editor_content(&self, arc: Arc<String>)

Source

pub fn push_fold_op(&self, op: FoldOp)

Source

pub fn take_fold_ops(&self) -> Vec<FoldOp>

Source

pub fn extend_change_log(&self, edits: impl IntoIterator<Item = EngineEdit>)

Source

pub fn take_change_log(&self) -> Vec<EngineEdit>

Source

pub fn extend_pending_content_edits( &self, edits: impl IntoIterator<Item = ContentEdit>, )

Source

pub fn push_pending_content_edit(&self, edit: ContentEdit)

Source

pub fn take_pending_content_edits(&self) -> Vec<ContentEdit>

Source

pub fn clear_pending_content_edits(&self)

Source

pub fn pending_content_reset(&self) -> bool

Source

pub fn set_pending_content_reset(&self, v: bool)

Source

pub fn take_pending_content_reset(&self) -> bool

Source

pub fn mark(&self, c: char) -> Option<(usize, usize)>

Source

pub fn set_mark(&mut self, c: char, pos: (usize, usize))

Source

pub fn clear_mark(&mut self, c: char)

Source

pub fn marks_cloned(&self) -> BTreeMap<char, (usize, usize)>

Source

pub fn set_marks(&mut self, marks: BTreeMap<char, (usize, usize)>)

Source

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).

Source

pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)>

Source

pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>)

Source§

impl View

Source

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_gen is 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_cursor immediately after.
  • All Position values the caller held from before the edit may be invalid. Re-derive from row / col deltas; do not cache.
Source§

impl View

Source

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.

Source

pub fn with_folds<T>(&self, f: impl FnOnce(&[Fold]) -> T) -> T

Run f against the fold list under a single content lock, with no clone. The borrow-style twin of Self::folds — prefer it for every read-only query (hides scans, is_empty, per-row loops); keep Self::folds only where an owned snapshot must outlive the lock (e.g. it is stored, or the buffer is re-borrowed mutably).

The closure runs with the content mutex held: it must not call back into any &self method of this View (they all re-lock, and the mutex is not re-entrant). Hoist such reads — row_count(), cursor(), … — above the call.

Source

pub fn has_folds(&self) -> bool

True when at least one fold is defined (open or closed). One lock, no clone — the cheap form of !folds().is_empty().

Source

pub fn fold_gen(&self) -> u64

Monotonic fold-mutation generation. Bumps only when a fold mutator actually changes the fold set; read-only queries and plain text edits leave it alone. Hosts caching a fold snapshot (hjkl’s per-window window_folds) compare this instead of re-cloning the fold Vec every keystroke.

Conservative in the same sense as Self::dirty_gen: “if it changed, the folds may have changed” (a rebase_folds whose row-shift happens to move nothing still bumps).

Source

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.

Source

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)
  1. Snapshot start_row → closed for every existing auto fold so open/closed state survives a reparse.
  2. Retain only manual folds (auto_generated == false).
  3. Insert one new Fold per range, re-using the snapshotted closed state when the start_row existed before, else default_closed.

Invariants preserved:

  • Folds stay sorted by start_row (same ordering as add_fold).
  • Duplicate start_rows: the last range in ranges wins (consistent with add_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_row is clamped to the last valid row, same as add_fold.
Source

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.

Source

pub fn open_fold_at(&mut self, row: usize) -> bool

Open the fold at row (no-op if already open or no fold).

Source

pub fn close_fold_at(&mut self, row: usize) -> bool

Close the fold at row (no-op if already closed or no fold).

Source

pub fn toggle_fold_at(&mut self, row: usize) -> bool

Flip the closed/open state of the fold containing row.

Source

pub fn open_all_folds(&mut self)

zR — open every fold.

Source

pub fn clear_all_folds(&mut self)

zE — eliminate every fold.

Source

pub fn close_all_folds(&mut self)

zM — close every fold.

Source

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.

Source

pub fn is_row_hidden(&self, row: usize) -> bool

True iff row is hidden by a closed fold (any fold).

Source

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.

Source

pub fn last_content_row(&self) -> usize

Last row index containing real content — skips vim’s single phantom trailing empty row. ropey’s len_lines() always synthesizes one extra empty final “line” when the buffer text ends in \n (vim treats that \n as a terminator, not a separator). Mirrors hjkl_engine::motions::move_bottom’s clamp (G) so vertical motions agree with G on where the buffer “ends”. A buffer whose real last line happens to be empty (e.g. "foo\n\n", row 1) is untouched — only a single trailing phantom row is ever skipped.

The emptiness test reads the last row’s byte length rather than materializing the row as a String: for the final rope line the two agree exactly (ropey never gives the last line a trailing \n, so rope_line_str has nothing to strip and rope_line_bytes(last) == 0 iff rope_line_str(last).is_empty()).

Source

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.

Takes the content lock once for the whole walk (via Self::with_folds) instead of once per skipped row — j over a long closed fold used to pay a lock + full Vec<Fold> clone per row. last_content_row() locks too, so it is resolved before the scan.

Source

pub fn prev_visible_row(&self, row: usize) -> Option<usize>

First visible row strictly before row, skipping hidden rows.

One lock for the whole walk, same as Self::next_visible_row.

Source

pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize)

Drop every fold that touches [start_row, end_row].

Source

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.

Source

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.

Trait Implementations§

Source§

impl Default for View

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for View

§

impl RefUnwindSafe for View

§

impl Send for View

§

impl Sync for View

§

impl Unpin for View

§

impl UnsafeUnpin for View

§

impl UnwindSafe for View

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.