Skip to main content

fresh/model/
buffer_position.rs

1//! Pure conversions between byte offsets and 2D positions on a `Buffer`.
2//!
3//! These helpers consolidate the `byte_to_2d` / `pos_2d_to_byte` functions
4//! that previously existed as duplicates in `app/clipboard.rs` and
5//! `input/actions.rs`. They are free functions, not methods, to avoid
6//! growing `TextBuffer`'s API surface — both callers already have a
7//! `&Buffer` in hand.
8
9use crate::model::buffer::Buffer;
10use crate::model::cursor::Position2D;
11
12/// Convert a byte offset into a (line, column) position.
13///
14/// The column is measured in *bytes from the start of the line*, not
15/// visual columns — this matches the cursor position model used by the
16/// editing core.
17pub fn byte_to_2d(buffer: &Buffer, byte_pos: usize) -> Position2D {
18    let line = buffer.get_line_number(byte_pos);
19    let line_start = buffer.line_start_offset(line).unwrap_or(0);
20    let column = byte_pos.saturating_sub(line_start);
21    Position2D { line, column }
22}
23
24/// Convert a 2D position into a byte offset, clamping the column to the
25/// line's byte length (excluding the trailing newline, if any).
26pub fn pos_2d_to_byte(buffer: &Buffer, pos: Position2D) -> usize {
27    let line_start = buffer.line_start_offset(pos.line).unwrap_or(0);
28    let line_content = buffer.get_line(pos.line).unwrap_or_default();
29    let line_len = if line_content.last() == Some(&b'\n') {
30        line_content.len().saturating_sub(1)
31    } else {
32        line_content.len()
33    };
34    let clamped_col = pos.column.min(line_len);
35    line_start + clamped_col
36}