pub struct Buffer { /* private fields */ }Expand description
Per-window view onto a Content.
Buffer 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 Content and is accessed via Arc<Mutex<Content>>.
Two Buffer instances that share the same Arc share text + folds
but carry independent cursors — the Helix Document+View model.
§Send + Sync
Arc<Mutex<Content>> is Send + Sync, so Buffer remains Send.
The engine trait surface requires Buffer: 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 (Buffer::new, Buffer::from_str,
Buffer::replace_all, etc.) keep the same external signatures.
Callers that do not need multi-window sharing see no behaviour change.
Use Buffer::new_view to create a second window onto the same
Content.
§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 Buffer
impl Buffer
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<Content>>) -> Self
pub fn new_view(content: Arc<Mutex<Content>>) -> Self
Create a second per-window view onto existing Content.
The new Buffer 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 = Buffer::from_str("hello\nworld");
let content = a.content_arc();
let mut b = Buffer::new_view(Arc::clone(&content));
// Cursors are independent.
let mut a = Buffer::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<Content>>
pub fn content_arc(&self) -> Arc<Mutex<Content>>
Return a clone of the Arc<Mutex<Content>> so callers can
create additional views with Buffer::new_view.
pub fn cursor(&self) -> Position
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.
Source§impl Buffer
impl Buffer
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:
Buffer::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
Buffer::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 Buffer
impl Buffer
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 Buffer is a per-window
view onto a shared Content; 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 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 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].