Skip to main content

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}