Skip to main content

oxihuman_core/
text_buffer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! A growable text buffer supporting append, line-level access, and search.
6
7/// A mutable text buffer backed by a String.
8#[allow(dead_code)]
9pub struct TextBuffer {
10    buf: String,
11    append_count: u64,
12}
13
14#[allow(dead_code)]
15impl TextBuffer {
16    pub fn new() -> Self {
17        Self {
18            buf: String::new(),
19            append_count: 0,
20        }
21    }
22
23    pub fn with_capacity(cap: usize) -> Self {
24        Self {
25            buf: String::with_capacity(cap),
26            append_count: 0,
27        }
28    }
29
30    /// Append a string slice.
31    pub fn append(&mut self, s: &str) {
32        self.buf.push_str(s);
33        self.append_count += 1;
34    }
35
36    /// Append a line (adds newline at end).
37    pub fn append_line(&mut self, s: &str) {
38        self.buf.push_str(s);
39        self.buf.push('\n');
40        self.append_count += 1;
41    }
42
43    /// Total character count.
44    pub fn char_count(&self) -> usize {
45        self.buf.chars().count()
46    }
47
48    /// Total byte count.
49    pub fn byte_len(&self) -> usize {
50        self.buf.len()
51    }
52
53    pub fn is_empty(&self) -> bool {
54        self.buf.is_empty()
55    }
56
57    /// Returns the buffer as a string slice.
58    pub fn as_str(&self) -> &str {
59        &self.buf
60    }
61
62    /// Returns the number of lines.
63    pub fn line_count(&self) -> usize {
64        if self.buf.is_empty() {
65            return 0;
66        }
67        self.buf.lines().count()
68    }
69
70    /// Returns the nth line (0-indexed).
71    pub fn line(&self, n: usize) -> Option<&str> {
72        self.buf.lines().nth(n)
73    }
74
75    /// Returns all lines as a vector.
76    pub fn lines_vec(&self) -> Vec<&str> {
77        self.buf.lines().collect()
78    }
79
80    /// Search for a substring; returns the byte offset of the first match.
81    pub fn find(&self, needle: &str) -> Option<usize> {
82        self.buf.find(needle)
83    }
84
85    /// Count occurrences of a substring.
86    pub fn count_occurrences(&self, needle: &str) -> usize {
87        if needle.is_empty() {
88            return 0;
89        }
90        let mut count = 0;
91        let mut start = 0;
92        while let Some(pos) = self.buf[start..].find(needle) {
93            count += 1;
94            start += pos + needle.len();
95        }
96        count
97    }
98
99    /// Replace all occurrences of `from` with `to`.
100    pub fn replace_all(&mut self, from: &str, to: &str) {
101        self.buf = self.buf.replace(from, to);
102    }
103
104    /// Clear the buffer.
105    pub fn clear(&mut self) {
106        self.buf.clear();
107    }
108
109    /// Truncate to the first `n` bytes (must be on a char boundary).
110    pub fn truncate(&mut self, n: usize) {
111        if n < self.buf.len() {
112            // Find the last valid char boundary at or before n
113            let n_safe = (0..=n)
114                .rev()
115                .find(|&i| self.buf.is_char_boundary(i))
116                .unwrap_or(0);
117            self.buf.truncate(n_safe);
118        }
119    }
120
121    pub fn append_count(&self) -> u64 {
122        self.append_count
123    }
124}
125
126impl Default for TextBuffer {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132pub fn new_text_buffer() -> TextBuffer {
133    TextBuffer::new()
134}
135
136pub fn tb_append(buf: &mut TextBuffer, s: &str) {
137    buf.append(s);
138}
139
140pub fn tb_append_line(buf: &mut TextBuffer, s: &str) {
141    buf.append_line(s);
142}
143
144pub fn tb_as_str(buf: &TextBuffer) -> &str {
145    buf.as_str()
146}
147
148pub fn tb_line_count(buf: &TextBuffer) -> usize {
149    buf.line_count()
150}
151
152pub fn tb_find(buf: &TextBuffer, needle: &str) -> Option<usize> {
153    buf.find(needle)
154}
155
156pub fn tb_clear(buf: &mut TextBuffer) {
157    buf.clear();
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn empty_on_creation() {
166        let b = new_text_buffer();
167        assert!(b.is_empty());
168        assert_eq!(b.byte_len(), 0);
169    }
170
171    #[test]
172    fn append_and_as_str() {
173        let mut b = new_text_buffer();
174        tb_append(&mut b, "hello");
175        tb_append(&mut b, " world");
176        assert_eq!(tb_as_str(&b), "hello world");
177    }
178
179    #[test]
180    fn append_line_adds_newline() {
181        let mut b = new_text_buffer();
182        tb_append_line(&mut b, "line1");
183        tb_append_line(&mut b, "line2");
184        assert_eq!(tb_line_count(&b), 2);
185    }
186
187    #[test]
188    fn line_access() {
189        let mut b = new_text_buffer();
190        tb_append_line(&mut b, "alpha");
191        tb_append_line(&mut b, "beta");
192        assert_eq!(b.line(0), Some("alpha"));
193        assert_eq!(b.line(1), Some("beta"));
194        assert_eq!(b.line(2), None);
195    }
196
197    #[test]
198    fn find_substring() {
199        let mut b = new_text_buffer();
200        tb_append(&mut b, "abcdef");
201        assert_eq!(tb_find(&b, "cde"), Some(2));
202        assert_eq!(tb_find(&b, "xyz"), None);
203    }
204
205    #[test]
206    fn count_occurrences() {
207        let mut b = new_text_buffer();
208        tb_append(&mut b, "aababab");
209        assert_eq!(b.count_occurrences("ab"), 3);
210    }
211
212    #[test]
213    fn replace_all() {
214        let mut b = new_text_buffer();
215        tb_append(&mut b, "foo bar foo");
216        b.replace_all("foo", "baz");
217        assert_eq!(tb_as_str(&b), "baz bar baz");
218    }
219
220    #[test]
221    fn clear_resets() {
222        let mut b = new_text_buffer();
223        tb_append(&mut b, "data");
224        tb_clear(&mut b);
225        assert!(b.is_empty());
226    }
227
228    #[test]
229    fn truncate() {
230        let mut b = new_text_buffer();
231        tb_append(&mut b, "hello world");
232        b.truncate(5);
233        assert_eq!(tb_as_str(&b), "hello");
234    }
235
236    #[test]
237    fn append_count() {
238        let mut b = new_text_buffer();
239        tb_append(&mut b, "a");
240        tb_append_line(&mut b, "b");
241        assert_eq!(b.append_count(), 2);
242    }
243}