Skip to main content

Buffer

Struct Buffer 

Source
pub struct Buffer { /* private fields */ }
Expand description

A rope-backed text buffer: the raw text storage under every Document.

Addressing is by [Position] — a 0-based line plus a 0-based UTF-8 byte offset within that line (not a char or column index). Every mutation goes through Buffer::apply_edit, which returns an AppliedEdit carrying both the inverse (for undo) and the tree-sitter-shaped [EditDelta] (for incremental reparse).

Buffer knows nothing about versions, undo, dirtiness or cursors — that bookkeeping is Document’s. Reach for Buffer directly only when you need text without the document around it.

Cloning is cheap: ropey::Rope shares its chunks by Arc.

§Examples

use lattice_core::Buffer;
use lattice_core::protocol::edit::Edit;
use lattice_core::protocol::position::{Position, Range};

let mut buf = Buffer::from_text("hello\nworld\n");

// Replace "world" (line 1, bytes 0..5) with "there".
let range = Range::new(Position::new(1, 0), Position::new(1, 5));
let applied = buf.apply_edit(&Edit::replace(range, "there"))?;

assert_eq!(buf.as_string(), "hello\nthere\n");
assert_eq!(applied.replaced_text, "world"); // what undo re-inserts
assert_eq!(buf.line(1).as_deref(), Some("there"));

// Two lines as a user counts them; ropey counts the empty tail too.
assert_eq!(buf.content_line_count(), 2);
assert_eq!(buf.rope_line_count(), 3);

Implementations§

Source§

impl Buffer

Source

pub fn empty() -> Self

An empty buffer: zero bytes, one (empty) line.

Source

pub fn from_text(text: &str) -> Self

A buffer holding a copy of text. No line-ending normalisation is done: \r\n stays two bytes.

Source

pub fn rope_line_count(&self) -> u32

ropey’s raw line count: the number of line starts, so a buffer ending in \n reports one extra (empty) line and an empty buffer reports 1. Saturates at u32::MAX.

This is the right bound for [Position::line] validity — the empty line after a trailing newline is addressable (it is where an append at end-of-file lands). For “how many lines does this file have”, use Self::content_line_count.

§Examples
use lattice_core::Buffer;

assert_eq!(Buffer::empty().rope_line_count(), 1);
assert_eq!(Buffer::from_text("a\nb").rope_line_count(), 2);
assert_eq!(Buffer::from_text("a\nb\n").rope_line_count(), 3);
assert_eq!(Buffer::from_text("a\nb\n").content_line_count(), 2);
Source

pub fn content_line_count(&self) -> u32

Lines the document actually has, in the sense every editor and every user means: "a\nb\n" is two lines, and so is "a\nb". A trailing newline terminates the last line rather than starting a new one.

This is the count for anything that lays out, addresses or counts document content — display rows, G, :$, % progress. Self::rope_line_count is ropey’s raw count and reports one more for any rope ending in \n; feeding that to a layout or coverage range is what puts a phantom empty row at the end of every normal file (CV.2).

Never zero: an empty buffer is one empty line, matching Self::rope_line_count and vim.

Source

pub fn byte_len(&self) -> u64

Total length of the buffer in bytes, newlines included.

Source

pub fn as_string(&self) -> String

The whole buffer as one String. O(n) allocation — never call this on a per-frame or per-keystroke path; use Self::line, Self::slice or Self::to_rope instead.

Source

pub fn to_rope(&self) -> Rope

Clone the underlying rope. Rope::clone is Arc-share of the underlying chunks (no deep copy), so the cost is one refcount bump per chunk. Used by the diff subsystem’s BufferTextProvider impl to hand a snapshot rope to BufferSource from inside the worker’s spawn_blocking body. Exposes only Rope (not the internal Rope field), so the abstraction barrier the pub(crate) rope() method protects stays in place.

Slice: D.3.a (2026-05-29).

Source

pub fn line(&self, line: u32) -> Option<String>

Materialise one logical line (without its trailing \n). Returns None if line is past the end. Locating the line is O(log n); copying its bytes is O(line_len).

Why this exists: the renderer’s hot path needs only the visible window of lines per frame. Calling Self::as_string followed by split('\n').collect::<Vec<_>>() once per frame allocates O(buffer size) bytes; a 100MB log blows the §8.2 frame budget on every paint. Iterating one line at a time keeps the per-frame work proportional to the viewport, not the document.

Source

pub fn line_shapes_from<'a>( &'a self, start: u32, unit: &'a IndentUnit, ) -> impl Iterator<Item = LineShape> + 'a

Allocation-free LineShapes from start to the end of the rope — the indent-guide builder’s read path.

Two properties, and the guide layer needs both:

  • Allocation-free. Self::line allocates a String per call, and the guide layer is rebuilt over its whole covered range on every keystroke — below WINDOW_CAP_LINES that is the whole document. Streaming the rope slice’s chars instead keeps the pass allocation-free, and LineShape::from_chars stops reading each line as soon as the answer cannot change.
  • Sequential. Asking for line i costs a O(log n) B-tree descent; asking for n lines one index at a time costs n of them. Lines is a cursor that walks chunk by chunk, so the whole covered range costs one descent plus a linear walk — which is what the builder’s forward pass wants. Callers that genuinely need one line probe with line_shapes_from(i).next() and pay the single descent knowingly.

start past the end of the rope yields nothing rather than panicking, so an out-of-range probe reads as “no such line”.

Source

pub fn line_byte_len(&self, line: u32) -> u32

Byte length of one line excluding the trailing newline. O(log n). Cheap helper for renderers that want to know a line’s width without materialising its text.

Source

pub fn slice(&self, range: Range) -> CoreResult<String>

Copy the text in the half-open range ([start, end)).

Both endpoints are validated like Self::position_to_byte and then rounded down to a UTF-8 char boundary, so a mid-codepoint offset never panics. A range may span lines; the newlines between are included.

§Errors

[ProtocolError::PositionOutOfBounds] if either endpoint is out of bounds, [ProtocolError::InvalidRange] if end precedes start (both wrapped in CoreError::Protocol).

§Examples
use lattice_core::Buffer;
use lattice_core::protocol::position::{Position, Range};

let buf = Buffer::from_text("héllo\nworld");
// "é" is two bytes, so "llo" starts at byte 3.
let r = Range::new(Position::new(0, 3), Position::new(1, 2));
assert_eq!(buf.slice(r).ok().as_deref(), Some("llo\nwo"));

// Line 5 does not exist.
let bad = Range::new(Position::new(0, 0), Position::new(5, 0));
assert!(buf.slice(bad).is_err());
Source

pub fn apply_edit(&mut self, edit: &Edit) -> CoreResult<AppliedEdit>

Apply an edit and return what was applied. The returned AppliedEdit::replaced_text is exactly what the caller needs to push onto the undo stack as the inverse.

The range endpoints are validated and snapped down to UTF-8 char boundaries exactly as in Self::slice. On error the buffer is left untouched. Note that AppliedEdit::original_range and the delta’s positions echo the edit’s range as given, before snapping.

§Errors

Same as Self::slice: an out-of-bounds endpoint or end < start.

Source

pub fn position_to_byte(&self, pos: Position) -> CoreResult<usize>

Convert a [Position] to an absolute byte offset from the start of the buffer.

pos.line must be < rope_line_count(). pos.byte may be anywhere up to and including the line’s full length with its trailing \n — so the offset just past the newline is accepted and equals the next line’s start. The offset is not snapped to a char boundary.

§Errors

[ProtocolError::PositionOutOfBounds] if the line does not exist or the byte offset is past the end of the line.

§Examples
use lattice_core::Buffer;
use lattice_core::protocol::position::Position;

let buf = Buffer::from_text("ab\ncd");
assert_eq!(buf.position_to_byte(Position::new(1, 1)).ok(), Some(4));
assert!(buf.position_to_byte(Position::new(1, 3)).is_err());
assert_eq!(
    buf.byte_to_position(4).ok(),
    Some(Position::new(1, 1)),
);
Source

pub fn byte_to_position(&self, byte: usize) -> CoreResult<Position>

Convert an absolute byte offset into a [Position] (line + byte within that line). The inverse of Self::position_to_byte.

§Panics

Despite the Result return type this never returns Err: a byte greater than Self::byte_len panics inside ropey. Callers pass offsets derived from the buffer itself.

Trait Implementations§

Source§

impl Clone for Buffer

Source§

fn clone(&self) -> Buffer

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Buffer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Buffer

Source§

fn default() -> Self

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

Auto Trait Implementations§

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.