Skip to main content

ochrepad_core/
buffer.rs

1//! `Buffer`: one document's text, undo history, revisions and file metadata.
2
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use crate::fold::FoldStrategy;
8use crate::history::{History, Revision};
9use crate::text::Document;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
12pub struct BufferId(pub u64);
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
15pub enum LineEnding {
16    #[default]
17    Lf,
18    CrLf,
19    Cr,
20}
21
22/// How the file was stored on disk. Filled in and used only by `fileio`; core
23/// never touches the filesystem.
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
25pub struct FileMeta {
26    pub path: Option<PathBuf>,
27    /// WHATWG encoding label, e.g. "UTF-8", "UTF-16LE", "windows-1252".
28    pub encoding: String,
29    pub bom: bool,
30    pub line_ending: LineEnding,
31    pub mixed_line_endings: bool,
32}
33
34impl Default for FileMeta {
35    fn default() -> Self {
36        Self {
37            path: None,
38            encoding: "UTF-8".to_owned(),
39            bom: false,
40            line_ending: LineEnding::default(),
41            mixed_line_endings: false,
42        }
43    }
44}
45
46/// How one edit changed the lines: lines `start..=old_end` were replaced by
47/// lines `start..=new_end`, and every later line moved by `new_end - old_end`.
48/// `old_end == new_end == usize::MAX` means "everything from `start` on may
49/// have changed" (the log overflowed).
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct LineEdit {
52    pub start: usize,
53    pub old_end: usize,
54    pub new_end: usize,
55}
56
57impl LineEdit {
58    pub fn everything_from(start: usize) -> Self {
59        Self {
60            start,
61            old_end: usize::MAX,
62            new_end: usize::MAX,
63        }
64    }
65
66    pub fn is_everything(&self) -> bool {
67        self.old_end == usize::MAX
68    }
69}
70
71const MAX_PENDING_LINE_EDITS: usize = 1024;
72
73#[derive(Debug)]
74pub struct Buffer {
75    id: BufferId,
76    pub(crate) doc: Document,
77    pub(crate) history: History,
78    pub(crate) revision: Revision,
79    pub(crate) next_revision: Revision,
80    saved_revision: Revision,
81    pub language: Option<String>,
82    /// How fold regions are found; the app sets it from the language.
83    pub fold_strategy: FoldStrategy,
84    /// Edits are refused (a compare view's copy of a file on disk).
85    pub read_only: bool,
86    pub meta: FileMeta,
87    /// Line edits since the last `take_line_edits`, for the highlighter.
88    line_edits: Vec<LineEdit>,
89}
90
91impl Buffer {
92    pub(crate) fn new(id: BufferId, text: &str, meta: FileMeta) -> Self {
93        Self {
94            id,
95            doc: Document::new(text),
96            history: History::default(),
97            revision: 0,
98            next_revision: 1,
99            saved_revision: 0,
100            language: None,
101            fold_strategy: FoldStrategy::Indent,
102            read_only: false,
103            meta,
104            line_edits: Vec::new(),
105        }
106    }
107
108    pub fn id(&self) -> BufferId {
109        self.id
110    }
111
112    pub fn doc(&self) -> &Document {
113        &self.doc
114    }
115
116    pub fn history(&self) -> &History {
117        &self.history
118    }
119
120    /// Identifies the current text state; returns to an earlier value on undo.
121    pub fn revision(&self) -> Revision {
122        self.revision
123    }
124
125    pub fn is_dirty(&self) -> bool {
126        self.revision != self.saved_revision
127    }
128
129    /// Records that the current state is what is on disk.
130    pub fn mark_saved(&mut self) {
131        self.mark_saved_at(self.revision);
132    }
133
134    /// Records that `revision` is what is on disk (a background save of an
135    /// earlier state finished; the buffer stays dirty if it changed since).
136    pub fn mark_saved_at(&mut self, revision: Revision) {
137        self.saved_revision = revision;
138        self.history.break_coalescing();
139    }
140
141    /// Marks the buffer as differing from disk, e.g. when its text was
142    /// restored from a backup snapshot.
143    pub fn mark_modified(&mut self) {
144        // No reachable revision has this value.
145        self.saved_revision = Revision::MAX;
146    }
147
148    /// How the lines changed since the last call, in the order the edits
149    /// were applied (for incremental highlighting).
150    pub fn take_line_edits(&mut self) -> Vec<LineEdit> {
151        std::mem::take(&mut self.line_edits)
152    }
153
154    pub(crate) fn record_line_edit(&mut self, edit: LineEdit) {
155        if let Some(last) = self.line_edits.last_mut()
156            && last.old_end == usize::MAX
157        {
158            last.start = last.start.min(edit.start);
159            return;
160        }
161        if self.line_edits.len() >= MAX_PENDING_LINE_EDITS {
162            // Nobody is draining the log; keep one conservative entry. The
163            // smallest start is never after the earliest changed line.
164            let start = self
165                .line_edits
166                .iter()
167                .map(|e| e.start)
168                .fold(edit.start, usize::min);
169            self.line_edits = vec![LineEdit::everything_from(start)];
170            return;
171        }
172        self.line_edits.push(edit);
173    }
174}