redox_core/buffer/text_buffer/core.rs
1//! Core `TextBuffer` definition and constructors.
2//!
3//! This file intentionally only does a couple of things:
4//! - It defines the `TextBuffer` type and its invariants.
5//! - It provides basic constructors and low-level rope access.
6//!
7//! Everything else (line indexing, movement, slicing, editing) should live in
8//! sibling modules as additional `impl TextBuffer` blocks.
9
10use anyhow::{Context as _, Result};
11use ropey::Rope;
12use std::fs::File;
13use std::io::BufReader;
14
15/// A Ropey-backed text buffer.
16///
17/// Invariants and conventions:
18/// - The backing store is a `ropey::Rope`.
19/// - Public APIs should generally speak in char indices (Unicode scalar
20/// value offsets) and logical positions (line/col in chars) because Ropey’s
21/// safe indexing APIs are char-based.
22/// - Byte indexing can be supported where needed, but should not be the primary
23/// index type for the editor core.
24///
25/// Higher-level editor state (modes, undo, viewports, etc.) should be built on
26/// top of this type rather than embedded inside it.
27#[derive(Debug, Clone)]
28pub struct TextBuffer {
29 pub(super) rope: Rope,
30}
31
32impl Default for TextBuffer {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl TextBuffer {
39 /// Create an empty buffer
40 #[inline]
41 pub fn new() -> Self {
42 Self { rope: Rope::new() }
43 }
44
45 /// Create a buffer from UTF-8 text
46 #[inline]
47 pub fn from_str(s: &str) -> Self {
48 Self {
49 rope: Rope::from_str(s),
50 }
51 }
52
53 /// Load a file as UTF-8 and create a buffer.
54 ///
55 /// This uses `ropey`'s streaming reader path and requires valid UTF-8.
56 pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
57 let path = path.as_ref();
58 let file = File::open(path)
59 .with_context(|| format!("failed to read file: {}", path.to_string_lossy()))?;
60 let reader = BufReader::new(file);
61 let rope = Rope::from_reader(reader)
62 .with_context(|| format!("file is not valid UTF-8: {}", path.to_string_lossy()))?;
63 Ok(Self { rope })
64 }
65
66 /// Access the underlying rope.
67 ///
68 /// Prefer higher-level APIs in other modules for most editor operations.
69 #[inline]
70 pub fn rope(&self) -> &Rope {
71 &self.rope
72 }
73
74 /// Mutable access to the underlying rope (use this sparingly!)
75 ///
76 /// Prefer dedicated editing APIs so invariants and bookkeeping remain easy to maintain.
77 #[inline]
78 pub fn rope_mut(&mut self) -> &mut Rope {
79 &mut self.rope
80 }
81
82 /// Total number of chars in the buffer.
83 ///
84 /// Kept here because it is a fundamental primitive used by most other modules.
85 #[inline]
86 pub fn len_chars(&self) -> usize {
87 self.rope.len_chars()
88 }
89
90 /// Whether or not the buffer contains zero characters (is empty).
91 #[inline]
92 pub fn is_empty(&self) -> bool {
93 self.rope.len_chars() == 0
94 }
95}