Skip to main content

redox_core/buffer/text_buffer/
core.rs

1//! Core `TextBuffer` definition and constructors.
2//!
3//! Note to self: 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;
12
13/// A Ropey-backed text buffer.
14///
15/// Invariants and conventions:
16/// - The backing store is a `ropey::Rope`.
17/// - Public APIs should generally speak in char indices (Unicode scalar
18///   value offsets) and logical positions (line/col in chars) because Ropey’s
19///   safe indexing APIs are char-based.
20/// - Byte indexing can be supported where needed, but should not be the primary
21///   index type for the editor core.
22///
23/// Higher-level editor state (modes, undo, viewports, etc.) should be built on
24/// top of this type rather than embedded inside it.
25#[derive(Debug, Clone)]
26pub struct TextBuffer {
27    pub(super) rope: Rope,
28}
29
30impl Default for TextBuffer {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl TextBuffer {
37    /// Create an empty buffer
38    #[inline]
39    pub fn new() -> Self {
40        Self { rope: Rope::new() }
41    }
42
43    /// Create a buffer from UTF-8 text
44    #[inline]
45    pub fn from_str(s: &str) -> Self {
46        Self {
47            rope: Rope::from_str(s),
48        }
49    }
50
51    /// Load a file as UTF-8 and create a buffer.
52    ///
53    /// This is intentionally simple for now. It just:
54    /// - reads the whole file into memory
55    /// - requires valid UTF-8
56    ///
57    /// NOTE: If/when I add encoding detection or incremental IO, those should likely
58    /// live in a separate IO-focused module.
59    pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
60        let path = path.as_ref();
61
62        let bytes = std::fs::read(path)
63            .with_context(|| format!("failed to read file: {}", path.to_string_lossy()))?;
64
65        let s = String::from_utf8(bytes)
66            .with_context(|| format!("file is not valid UTF-8: {}", path.to_string_lossy()))?;
67
68        Ok(Self::from_str(&s))
69    }
70
71    /// Access the underlying rope.
72    ///
73    /// Prefer higher-level APIs in other modules for most editor operations.
74    #[inline]
75    pub fn rope(&self) -> &Rope {
76        &self.rope
77    }
78
79    /// Mutable access to the underlying rope (use this sparingly!)
80    ///
81    /// Prefer dedicated editing APIs so invariants and future bookkeeping (eg.
82    /// undo/redo, marks, spans) remain easy to maintain.
83    #[inline]
84    pub fn rope_mut(&mut self) -> &mut Rope {
85        &mut self.rope
86    }
87
88    /// Total number of chars in the buffer.
89    ///
90    /// Kept here because it is a fundamental primitive used by most other modules.
91    #[inline]
92    pub fn len_chars(&self) -> usize {
93        self.rope.len_chars()
94    }
95
96    /// Whether or not the buffer contains zero characters (is empty).
97    #[inline]
98    pub fn is_empty(&self) -> bool {
99        self.rope.len_chars() == 0
100    }
101}