1use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use crate::bookmarks::Bookmarks;
8use crate::fold::FoldStrategy;
9use crate::history::{History, Revision};
10use crate::text::Document;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
13pub struct BufferId(pub u64);
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub struct Indent {
19 pub use_spaces: bool,
21 pub width: usize,
23}
24
25impl Default for Indent {
26 fn default() -> Self {
27 Self {
28 use_spaces: false,
29 width: 4,
30 }
31 }
32}
33
34#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
36pub struct CommentSyntax {
37 pub line: Option<String>,
39 pub block: Option<(String, String)>,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum LineEnding {
45 #[default]
46 Lf,
47 CrLf,
48 Cr,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub struct FileMeta {
55 pub path: Option<PathBuf>,
56 pub encoding: String,
58 pub bom: bool,
59 pub line_ending: LineEnding,
60 pub mixed_line_endings: bool,
61}
62
63impl Default for FileMeta {
64 fn default() -> Self {
65 Self {
66 path: None,
67 encoding: "UTF-8".to_owned(),
68 bom: false,
69 line_ending: LineEnding::default(),
70 mixed_line_endings: false,
71 }
72 }
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub struct LineEdit {
81 pub start: usize,
82 pub old_end: usize,
83 pub new_end: usize,
84}
85
86impl LineEdit {
87 pub fn everything_from(start: usize) -> Self {
88 Self {
89 start,
90 old_end: usize::MAX,
91 new_end: usize::MAX,
92 }
93 }
94
95 pub fn is_everything(&self) -> bool {
96 self.old_end == usize::MAX
97 }
98}
99
100const MAX_PENDING_LINE_EDITS: usize = 1024;
101
102#[derive(Debug)]
103pub struct Buffer {
104 id: BufferId,
105 pub(crate) doc: Document,
106 pub(crate) history: History,
107 pub(crate) revision: Revision,
108 pub(crate) next_revision: Revision,
109 saved_revision: Revision,
110 pub language: Option<String>,
111 pub fold_strategy: FoldStrategy,
113 pub read_only: bool,
115 pub comments: Option<CommentSyntax>,
118 pub indent: Indent,
120 pub indent_set: bool,
123 pub indent_mixed: bool,
125 pub meta: FileMeta,
126 line_edits: Vec<LineEdit>,
128 pub(crate) bookmarks: Bookmarks,
130}
131
132impl Buffer {
133 pub(crate) fn new(id: BufferId, text: &str, meta: FileMeta) -> Self {
134 Self {
135 id,
136 doc: Document::new(text),
137 history: History::default(),
138 revision: 0,
139 next_revision: 1,
140 saved_revision: 0,
141 language: None,
142 fold_strategy: FoldStrategy::Indent,
143 read_only: false,
144 comments: None,
145 indent: Indent::default(),
146 indent_set: false,
147 indent_mixed: false,
148 meta,
149 line_edits: Vec::new(),
150 bookmarks: Bookmarks::default(),
151 }
152 }
153
154 pub fn id(&self) -> BufferId {
155 self.id
156 }
157
158 pub fn doc(&self) -> &Document {
159 &self.doc
160 }
161
162 pub fn history(&self) -> &History {
163 &self.history
164 }
165
166 pub fn bookmarks(&self) -> &Bookmarks {
167 &self.bookmarks
168 }
169
170 pub fn set_bookmarks(&mut self, lines: impl IntoIterator<Item = usize>) {
173 let count = self.doc.line_count();
174 self.bookmarks.set(lines, count);
175 }
176
177 pub fn toggle_bookmark(&mut self, line: usize) -> bool {
180 line < self.doc.line_count() && self.bookmarks.toggle(line)
181 }
182
183 pub fn revision(&self) -> Revision {
185 self.revision
186 }
187
188 pub fn is_dirty(&self) -> bool {
189 self.revision != self.saved_revision
190 }
191
192 pub fn mark_saved(&mut self) {
194 self.mark_saved_at(self.revision);
195 }
196
197 pub fn mark_saved_at(&mut self, revision: Revision) {
200 self.saved_revision = revision;
201 self.history.break_coalescing();
202 }
203
204 pub fn saved_history(&self, max_bytes: usize) -> crate::history::SavedHistory {
207 let (undo, redo) = self.history.newest(max_bytes);
208 crate::history::SavedHistory {
209 undo,
210 redo,
211 revision: self.revision,
212 next_revision: self.next_revision,
213 }
214 }
215
216 pub fn restore_history(
223 &mut self,
224 saved: crate::history::SavedHistory,
225 on_disk: bool,
226 ) -> Result<(), String> {
227 saved.check(&self.doc)?;
228 self.history.replace(saved.undo, saved.redo);
229 self.revision = saved.revision;
230 self.next_revision = self.next_revision.max(saved.next_revision);
231 if on_disk {
232 self.saved_revision = saved.revision;
233 } else {
234 self.mark_modified();
235 }
236 Ok(())
237 }
238
239 pub fn clear_history(&mut self) {
241 self.history.clear();
242 }
243
244 pub fn mark_modified(&mut self) {
247 self.saved_revision = Revision::MAX;
249 }
250
251 pub fn take_line_edits(&mut self) -> Vec<LineEdit> {
254 std::mem::take(&mut self.line_edits)
255 }
256
257 pub(crate) fn record_line_edit(&mut self, edit: LineEdit) {
258 if let Some(last) = self.line_edits.last_mut()
259 && last.old_end == usize::MAX
260 {
261 last.start = last.start.min(edit.start);
262 return;
263 }
264 if self.line_edits.len() >= MAX_PENDING_LINE_EDITS {
265 let start = self
268 .line_edits
269 .iter()
270 .map(|e| e.start)
271 .fold(edit.start, usize::min);
272 self.line_edits = vec![LineEdit::everything_from(start)];
273 return;
274 }
275 self.line_edits.push(edit);
276 }
277}