1use crate::edit_diff::{EditDiff, UndoRedo};
2use crate::error::Result;
3use crate::history::History;
4use crate::language::{Indent, Language};
5use crate::row::Row;
6use std::cmp;
7use std::fs::File;
8use std::io::{self, BufRead, Write};
9use std::path::{Path, PathBuf};
10use std::slice;
11
12pub struct FilePath {
14 pub path: PathBuf,
15 pub display: String,
16}
17
18impl FilePath {
19 fn from<P: AsRef<Path>>(path: P) -> Self {
20 let path = path.as_ref();
21 FilePath {
22 path: PathBuf::from(path),
23 display: path.to_string_lossy().to_string(),
24 }
25 }
26
27 fn from_string<S: Into<String>>(s: S) -> Self {
28 let display = s.into();
29 FilePath {
30 path: PathBuf::from(&display),
31 display,
32 }
33 }
34}
35
36#[derive(Clone, Copy, PartialEq)]
37pub enum CursorDir {
38 Left,
39 Right,
40 Up,
41 Down,
42}
43
44pub struct Lines<'a>(slice::Iter<'a, Row>);
45
46impl<'a> Iterator for Lines<'a> {
47 type Item = &'a str;
48
49 fn next(&mut self) -> Option<Self::Item> {
50 self.0.next().map(|r| r.buffer())
51 }
52
53 fn size_hint(&self) -> (usize, Option<usize>) {
54 let len = self.0.as_slice().len();
55 (len, Some(len))
56 }
57}
58
59impl<'a> ExactSizeIterator for Lines<'a> {}
60
61pub struct TextBuffer {
62 cx: usize,
64 cy: usize,
65 file: Option<FilePath>,
67 row: Vec<Row>,
69 undo_count: i32,
73 modified: bool,
77 lang: Language,
79 history: History,
81 inserted_undo: bool,
83 dirty_start: Option<usize>,
86}
87
88impl TextBuffer {
89 pub fn empty() -> Self {
90 Self {
91 cx: 0,
92 cy: 0,
93 file: None,
94 row: vec![Row::empty()], undo_count: 0,
96 modified: false,
97 lang: Language::Plain,
98 history: History::default(),
99 inserted_undo: false,
100 dirty_start: Some(0), }
102 }
103
104 pub fn with_lines<S: AsRef<str>, I: Iterator<Item = S>>(lines: I) -> Result<Self> {
105 Ok(Self {
106 cx: 0,
107 cy: 0,
108 file: None,
109 row: lines.map(|s| Row::new(s.as_ref())).collect::<Result<_>>()?,
110 undo_count: 0,
111 modified: false,
112 lang: Language::Plain,
113 history: History::default(),
114 inserted_undo: false,
115 dirty_start: Some(0), })
117 }
118
119 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
120 let path = path.as_ref();
121 let file = Some(FilePath::from(path));
122 if !path.exists() {
123 let mut buf = Self::empty();
125 buf.file = file;
126 buf.undo_count = 0;
127 buf.modified = false;
128 buf.lang = Language::detect(path);
129 return Ok(buf);
130 }
131
132 let row = io::BufReader::new(File::open(path)?)
133 .lines()
134 .map(|r| Row::new(r?))
135 .collect::<Result<_>>()?;
136
137 Ok(Self {
138 cx: 0,
139 cy: 0,
140 file,
141 row,
142 undo_count: 0,
143 modified: false,
144 lang: Language::detect(path),
145 history: History::default(),
146 inserted_undo: false,
147 dirty_start: Some(0),
148 })
149 }
150
151 fn set_dirty_start(&mut self, line: usize) {
152 if let Some(l) = self.dirty_start {
153 if l <= line {
154 return;
155 }
156 }
157 self.dirty_start = Some(line);
158 }
159
160 fn apply_diff(&mut self, diff: &EditDiff, which: UndoRedo) {
161 let (x, y) = diff.apply(&mut self.row, which);
162 self.set_cursor(x, y);
163 self.set_dirty_start(y);
164 }
165
166 fn new_diff(&mut self, diff: EditDiff) {
167 self.apply_diff(&diff, UndoRedo::Redo);
168 self.modified = true;
169 self.history.push(diff); }
171
172 fn insert_undo_point(&mut self) {
173 if !self.inserted_undo {
174 if self.history.finish_ongoing_edit() {
175 self.undo_count = self.undo_count.saturating_add(1);
176 }
177 self.modified = false;
178 self.inserted_undo = true;
179 }
180 }
181
182 pub fn finish_edit(&mut self) -> Option<usize> {
186 self.inserted_undo = false;
187 let dirty_start = self.dirty_start;
188 self.dirty_start = None;
189 dirty_start
190 }
191
192 pub fn insert_char(&mut self, ch: char) {
193 if self.cy == self.row.len() {
195 self.new_diff(EditDiff::Newline);
196 }
197 self.new_diff(EditDiff::InsertChar(self.cx, self.cy, ch));
198 }
199
200 pub fn insert_tab(&mut self) {
201 self.insert_undo_point();
202 match self.lang.indent() {
203 Indent::AsIs => self.insert_char('\t'),
204 Indent::Fixed(indent) => {
205 self.new_diff(EditDiff::Insert(self.cx, self.cy, indent.to_owned()));
206 }
207 }
208 }
209
210 fn concat_next_line(&mut self) {
211 let removed = self.row[self.cy + 1].buffer().to_owned();
213 self.new_diff(EditDiff::DeleteLine(self.cy + 1, removed.clone()));
214 self.new_diff(EditDiff::Append(self.cy, removed));
215 }
216
217 fn squash_to_previous_line(&mut self) {
218 self.cy -= 1;
220 self.cx = self.row[self.cy].len(); self.concat_next_line();
223 }
224
225 pub fn delete_char(&mut self) {
226 if self.cy == self.row.len() || self.cx == 0 && self.cy == 0 {
227 return;
228 }
229 self.insert_undo_point();
230 if self.cx > 0 {
231 let idx = self.cx - 1;
232 let deleted = self.row[self.cy].char_at(idx);
233 self.new_diff(EditDiff::DeleteChar(self.cx, self.cy, deleted));
234 } else {
235 self.squash_to_previous_line();
236 }
237 }
238
239 pub fn delete_until_end_of_line(&mut self) {
240 if self.cy == self.row.len() {
241 return;
242 }
243 self.insert_undo_point();
244 let row = &self.row[self.cy];
245 if self.cx == row.len() {
246 if self.cy == self.row.len() - 1 {
248 return;
249 }
250 self.concat_next_line();
251 } else if self.cx < row.buffer().len() {
252 let truncated = row[self.cx..].to_owned();
253 self.new_diff(EditDiff::Truncate(self.cy, truncated));
254 }
255 }
256
257 pub fn delete_until_head_of_line(&mut self) {
258 if self.cx == 0 && self.cy == 0 || self.cy == self.row.len() {
259 return;
260 }
261 self.insert_undo_point();
262 if self.cx == 0 {
263 self.squash_to_previous_line();
264 } else {
265 let removed = self.row[self.cy][..self.cx].to_owned();
266 self.new_diff(EditDiff::Remove(self.cx, self.cy, removed));
267 }
268 }
269
270 pub fn delete_word(&mut self) {
271 if self.cx == 0 || self.cy == self.row.len() {
272 return;
273 }
274 self.insert_undo_point();
275
276 let mut x = self.cx - 1;
277 let row = &self.row[self.cy];
278 while x > 0 && row.char_at(x).is_ascii_whitespace() {
279 x -= 1;
280 }
281 while x > 0 && !row.char_at(x - 1).is_ascii_whitespace() {
283 x -= 1;
284 }
285
286 let removed = self.row[self.cy][x..self.cx].to_owned();
287 self.new_diff(EditDiff::Remove(self.cx, self.cy, removed));
288 }
289
290 pub fn delete_right_char(&mut self) {
291 if self.cy == self.row.len()
292 || self.cy == self.row.len() - 1 && self.cx == self.row[self.cy].len()
293 {
294 return;
296 }
297 self.move_cursor_one(CursorDir::Right);
298 self.delete_char();
299 }
300
301 pub fn insert_line(&mut self) {
302 self.insert_undo_point();
303 if self.cy >= self.row.len() {
304 self.new_diff(EditDiff::Newline);
305 } else if self.cx >= self.row[self.cy].len() {
306 self.new_diff(EditDiff::InsertLine(self.cy + 1, "".to_string()));
307 } else if self.cx <= self.row[self.cy].buffer().len() {
308 let truncated = self.row[self.cy][self.cx..].to_owned();
309 self.new_diff(EditDiff::Truncate(self.cy, truncated.clone()));
310 self.new_diff(EditDiff::InsertLine(self.cy + 1, truncated));
311 }
312 }
313
314 pub fn move_cursor_one(&mut self, dir: CursorDir) {
315 match dir {
316 CursorDir::Up => self.cy = self.cy.saturating_sub(1),
317 CursorDir::Left => {
318 if self.cx > 0 {
319 self.cx -= 1;
320 } else if self.cy > 0 {
321 self.cy -= 1;
323 self.cx = self.row[self.cy].len();
324 }
325 }
326 CursorDir::Down => {
327 if self.cy < self.row.len() {
330 self.cy += 1;
331 }
332 }
333 CursorDir::Right => {
334 if self.cy < self.row.len() {
335 let len = self.row[self.cy].len();
336 if self.cx < len {
337 self.cx += 1;
340 } else if self.cx >= len {
341 self.cy += 1;
343 self.cx = 0;
344 }
345 }
346 }
347 };
348
349 let len = self.row.get(self.cy).map(Row::len).unwrap_or(0);
351 if self.cx > len {
352 self.cx = len;
353 }
354 }
355
356 pub fn move_cursor_page(&mut self, dir: CursorDir, rowoff: usize, num_rows: usize) {
357 self.cy = match dir {
358 CursorDir::Up => rowoff, CursorDir::Down => {
360 cmp::min(rowoff + num_rows - 1, self.row.len()) }
362 _ => unreachable!(),
363 };
364 for _ in 0..num_rows {
365 self.move_cursor_one(dir);
366 }
367 }
368
369 pub fn move_cursor_to_buffer_edge(&mut self, dir: CursorDir) {
370 match dir {
371 CursorDir::Left => self.cx = 0,
372 CursorDir::Right => {
373 if self.cy < self.row.len() {
374 self.cx = self.row[self.cy].len();
375 }
376 }
377 CursorDir::Up => self.cy = 0,
378 CursorDir::Down => self.cy = self.row.len(),
379 }
380 }
381
382 pub fn move_cursor_by_word(&mut self, dir: CursorDir) {
383 #[derive(PartialEq)]
384 enum CharKind {
385 Ident,
386 Punc,
387 Space,
388 }
389
390 impl CharKind {
391 fn new_at(rows: &[Row], x: usize, y: usize) -> Self {
392 rows.get(y)
393 .and_then(|r| r.char_at_checked(x))
394 .map(|c| {
395 if c.is_ascii_whitespace() {
396 CharKind::Space
397 } else if c == '_' || c.is_ascii_alphanumeric() {
398 CharKind::Ident
399 } else {
400 CharKind::Punc
401 }
402 })
403 .unwrap_or(CharKind::Space)
404 }
405 }
406
407 fn at_word_start(left: &CharKind, right: &CharKind) -> bool {
408 matches!(
409 (left, right),
410 (&CharKind::Space, &CharKind::Ident)
411 | (&CharKind::Space, &CharKind::Punc)
412 | (&CharKind::Punc, &CharKind::Ident)
413 | (&CharKind::Ident, &CharKind::Punc)
414 )
415 }
416
417 self.move_cursor_one(dir);
418 let mut prev = CharKind::new_at(&self.row, self.cx, self.cy);
419 self.move_cursor_one(dir);
420 let mut current = CharKind::new_at(&self.row, self.cx, self.cy);
421
422 loop {
423 if self.cy == 0 && self.cx == 0 || self.cy == self.row.len() {
424 return;
425 }
426
427 match dir {
428 CursorDir::Right if at_word_start(&prev, ¤t) => return,
429 CursorDir::Left if at_word_start(¤t, &prev) => {
430 self.move_cursor_one(CursorDir::Right); return;
432 }
433 _ => {}
434 }
435
436 prev = current;
437 self.move_cursor_one(dir);
438 current = CharKind::new_at(&self.row, self.cx, self.cy);
439 }
440 }
441
442 pub fn move_cursor_paragraph(&mut self, dir: CursorDir) {
443 debug_assert!(dir != CursorDir::Left && dir != CursorDir::Right);
444 loop {
445 self.move_cursor_one(dir);
446 if self.cy == 0
447 || self.cy == self.row.len()
448 || self.row[self.cy - 1].buffer().is_empty()
449 && !self.row[self.cy].buffer().is_empty()
450 {
451 break;
452 }
453 }
454 }
455
456 pub fn rows(&self) -> &[Row] {
457 &self.row
458 }
459
460 pub fn has_file(&self) -> bool {
461 self.file.is_some()
462 }
463
464 pub fn filename(&self) -> &str {
465 self.file
466 .as_ref()
467 .map(|f| f.display.as_str())
468 .unwrap_or("[No Name]")
469 }
470
471 pub fn modified(&self) -> bool {
472 self.undo_count != 0 || self.modified
473 }
474
475 pub fn lang(&self) -> Language {
476 self.lang
477 }
478
479 pub fn cy(&self) -> usize {
480 self.cy
481 }
482
483 pub fn lines(&self) -> Lines<'_> {
484 Lines(self.row.iter())
485 }
486
487 pub fn set_file<S: Into<String>>(&mut self, file_path: S) {
488 let file = FilePath::from_string(file_path);
489 self.lang = Language::detect(&file.path);
490 self.file = Some(file);
491 }
492
493 pub fn set_unnamed(&mut self) {
494 self.file = None;
495 }
496
497 pub fn set_lang(&mut self, lang: Language) {
498 self.lang = lang;
499 }
500
501 pub fn save(&mut self) -> std::result::Result<String, String> {
502 self.insert_undo_point();
503
504 let file = if let Some(file) = &self.file {
505 file
506 } else {
507 return Ok("".to_string()); };
509
510 let f = match File::create(&file.path) {
511 Ok(f) => f,
512 Err(e) => return Err(format!("Could not save: {}", e)),
513 };
514 let mut f = io::BufWriter::new(f);
515 let mut bytes = 0;
516 for line in self.row.iter() {
517 let b = line.buffer();
518 writeln!(f, "{}", b).map_err(|e| format!("Could not write to file: {}", e))?;
519 bytes += b.as_bytes().len() + 1;
520 }
521 f.flush()
522 .map_err(|e| format!("Could not flush to file: {}", e))?;
523
524 self.undo_count = 0;
525 self.modified = false;
526 Ok(format!("{} bytes written to {}", bytes, &file.display))
527 }
528
529 pub fn set_cursor(&mut self, x: usize, y: usize) {
530 self.cx = x;
531 self.cy = y;
532 }
533
534 pub fn cursor(&self) -> (usize, usize) {
535 (self.cx, self.cy)
536 }
537
538 fn after_undoredo(&mut self, state: Option<(usize, usize, usize, bool)>) -> bool {
539 match state {
540 Some((x, y, s, _)) => {
541 self.set_cursor(x, y);
542 self.set_dirty_start(s);
543 true
544 }
545 None => false,
546 }
547 }
548
549 pub fn undo(&mut self) -> bool {
550 let state = self.history.undo(&mut self.row);
551 if let Some((_, _, _, edited)) = state {
552 if !edited {
555 self.undo_count = self.undo_count.saturating_sub(1);
556 }
557 self.modified = false;
558 }
559 self.after_undoredo(state)
560 }
561
562 pub fn redo(&mut self) -> bool {
563 let state = self.history.redo(&mut self.row);
564 if let Some((_, _, _, edited)) = state {
565 if !edited {
569 self.undo_count = self.undo_count.saturating_add(1);
570 }
571 self.modified = false;
572 }
573 self.after_undoredo(state)
574 }
575
576 pub fn is_scratch(&self) -> bool {
577 self.file.is_none() && self.row.len() == 1 && self.row[0].len() == 0
578 }
579}