hjkl_buffer/content.rs
1//! Per-document text content. Arc-shareable across multiple [`crate::Buffer`]
2//! views.
3//!
4//! [`Content`] owns everything that belongs to the document itself:
5//!
6//! - The `lines` rope (text content).
7//! - The `dirty_gen` render-cache generation counter.
8//! - Manual folds (`folds`).
9//!
10//! [`crate::Buffer`] is the per-window wrapper. It holds an
11//! `Arc<Mutex<Content>>` plus the per-window cursor. Two `Buffer`
12//! instances that share one `Content` see the same text and folds, but
13//! each moves its cursor independently.
14//!
15//! ## Concurrency
16//!
17//! Held inside `Arc<Mutex<Content>>` so multiple `Buffer` views can share
18//! one document safely. `Mutex` (not `RefCell`) because the engine's
19//! `Cursor`, `Query`, `BufferEdit`, and `Search` traits require `Send`,
20//! and `RefCell` is `!Send`. Lock contention is near-zero in the
21//! single-threaded app loop; the Mutex is essentially a free `Send`
22//! adapter.
23
24use crate::folds::Fold;
25
26/// Per-document state shared across all [`crate::Buffer`] views of the
27/// same file. Wrap in `Arc<Mutex<Content>>` and pass to
28/// [`crate::Buffer::new_view`] to create an additional window onto the
29/// same content.
30///
31/// Fields intentionally parallel [`crate::Buffer`]'s pre-0.8 layout so
32/// the diff stays mechanical: `lines`, `dirty_gen`, and `folds` moved
33/// here; `cursor` stayed on `Buffer`.
34pub struct Content {
35 /// One entry per logical row. Always non-empty: a freshly
36 /// constructed `Content` holds a single empty `String` so cursor
37 /// positions never need an "is the buffer empty?" branch.
38 pub(crate) lines: Vec<String>,
39 /// Bumps on every mutation; render cache keys against this so a
40 /// per-row `Line` gets recomputed when its source row changes.
41 pub(crate) dirty_gen: u64,
42 /// Manual folds — closed ranges hide rows in the render path.
43 /// `pub(crate)` so the [`crate::folds`] module can read/write
44 /// directly (same visibility as before the split).
45 pub(crate) folds: Vec<Fold>,
46}
47
48impl Default for Content {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl Content {
55 /// New empty content with one empty row.
56 pub fn new() -> Self {
57 Self {
58 lines: vec![String::new()],
59 dirty_gen: 0,
60 folds: Vec::new(),
61 }
62 }
63
64 /// Build content from a flat string. Splits on `\n`; a trailing
65 /// `\n` produces a trailing empty line.
66 #[allow(clippy::should_implement_trait)]
67 pub fn from_str(text: &str) -> Self {
68 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
69 if lines.is_empty() {
70 lines.push(String::new());
71 }
72 Self {
73 lines,
74 dirty_gen: 0,
75 folds: Vec::new(),
76 }
77 }
78}