From cbbf6f0d8fcc700c780772e5d4bf6a338f03f902 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 19 Dec 2023 11:04:33 -0700 Subject: [PATCH] Allow Editor to use reference or Arc of Buffer --- examples/editor-libcosmic/src/main.rs | 43 +- examples/editor-libcosmic/src/text_box.rs | 45 +- examples/editor-orbclient/src/main.rs | 41 +- examples/editor-test/src/main.rs | 18 +- examples/rich-text/src/main.rs | 20 +- src/edit/editor.rs | 743 +++++++++--------- src/edit/mod.rs | 138 ++-- src/edit/syntect.rs | 251 +++--- src/edit/vi.rs | 882 +++++++++++----------- 9 files changed, 1160 insertions(+), 1021 deletions(-) diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 0db3e72..1fa01e4 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -86,7 +86,7 @@ pub struct Window { path_opt: Option, attrs: Attrs<'static>, font_size: FontSize, - editor: Mutex>, + editor: Mutex>, } #[allow(dead_code)] @@ -185,10 +185,12 @@ impl Application for Window { if let Some(path) = &self.path_opt { let editor = self.editor.lock().unwrap(); let mut text = String::new(); - for line in editor.buffer().lines.iter() { - text.push_str(line.text()); - text.push('\n'); - } + editor.with_buffer(|buffer| { + for line in buffer.lines.iter() { + text.push_str(line.text()); + text.push('\n'); + } + }); match fs::write(path, text) { Ok(()) => { log::info!("saved '{}'", path.display()); @@ -234,15 +236,13 @@ impl Application for Window { let mut editor = self.editor.lock().unwrap(); editor .borrow_with(&mut FONT_SYSTEM.lock().unwrap()) - .buffer_mut() - .set_metrics(font_size.to_metrics()); + .with_buffer_mut(|buffer| buffer.set_metrics(font_size.to_metrics())); } Message::WrapChanged(wrap) => { let mut editor = self.editor.lock().unwrap(); editor .borrow_with(&mut FONT_SYSTEM.lock().unwrap()) - .buffer_mut() - .set_wrap(wrap); + .with_buffer_mut(|buffer| buffer.set_wrap(wrap)); } Message::AlignmentChanged(align) => { let mut editor = self.editor.lock().unwrap(); @@ -304,7 +304,7 @@ impl Application for Window { let editor = self.editor.lock().unwrap(); pick_list( WRAP_MODE, - Some(editor.buffer().wrap()), + Some(editor.with_buffer(|buffer| buffer.wrap())), Message::WrapChanged, ) }; @@ -373,20 +373,25 @@ impl Application for Window { } fn update_attrs(editor: &mut T, attrs: Attrs) { - editor.buffer_mut().lines.iter_mut().for_each(|line| { - line.set_attrs_list(AttrsList::new(attrs)); + editor.with_buffer_mut(|buffer| { + buffer.lines.iter_mut().for_each(|line| { + line.set_attrs_list(AttrsList::new(attrs)); + }); }); } fn update_alignment(editor: &mut T, align: Align) { let current_line = editor.cursor().line; - if let Some((start, end)) = editor.selection_bounds() { - if let Some(lines) = editor.buffer_mut().lines.get_mut(start.line..=end.line) { - for line in lines.iter_mut() { - line.set_align(Some(align)); + let selection_bounds_opt = editor.selection_bounds(); + editor.with_buffer_mut(|buffer| { + if let Some((start, end)) = selection_bounds_opt { + if let Some(lines) = buffer.lines.get_mut(start.line..=end.line) { + for line in lines.iter_mut() { + line.set_align(Some(align)); + } } + } else if let Some(line) = buffer.lines.get_mut(current_line) { + line.set_align(Some(align)); } - } else if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) { - line.set_align(Some(align)); - } + }); } diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index 8234d4c..c6c0ff8 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -31,13 +31,13 @@ impl StyleSheet for Theme { } } -pub struct TextBox<'a, 'editor> { - editor: &'a Mutex>, +pub struct TextBox<'a, 'editor, 'buffer> { + editor: &'a Mutex>, padding: Padding, } -impl<'a, 'editor> TextBox<'a, 'editor> { - pub fn new(editor: &'a Mutex>) -> Self { +impl<'a, 'editor, 'buffer> TextBox<'a, 'editor, 'buffer> { + pub fn new(editor: &'a Mutex>) -> Self { Self { editor, padding: Padding::new(0.), @@ -50,7 +50,9 @@ impl<'a, 'editor> TextBox<'a, 'editor> { } } -pub fn text_box<'a, 'editor>(editor: &'a Mutex>) -> TextBox<'a, 'editor> { +pub fn text_box<'a, 'editor, 'buffer>( + editor: &'a Mutex>, +) -> TextBox<'a, 'editor, 'buffer> { TextBox::new(editor) } @@ -103,7 +105,8 @@ fn draw_pixel( buffer[offset + 3] = (current >> 24) as u8; } -impl<'a, 'editor, Message, Renderer> Widget for TextBox<'a, 'editor> +impl<'a, 'editor, 'buffer, Message, Renderer> Widget + for TextBox<'a, 'editor, 'buffer> where Renderer: cosmic::iced_core::Renderer + image::Renderer, Renderer::Theme: StyleSheet, @@ -133,14 +136,17 @@ where .shape_as_needed(true); let mut layout_lines = 0; - for line in editor.buffer().lines.iter() { - match line.layout_opt() { - Some(layout) => layout_lines += layout.len(), - None => (), + editor.with_buffer(|buffer| { + for line in buffer.lines.iter() { + match line.layout_opt() { + Some(layout) => layout_lines += layout.len(), + None => (), + } } - } + }); - let height = layout_lines as f32 * editor.buffer().metrics().line_height; + let height = + layout_lines as f32 * editor.with_buffer(|buffer| buffer.metrics().line_height); let size = Size::new(limits.max().width, height); layout::Node::new(limits.resolve(size)) @@ -205,13 +211,11 @@ where let mut editor = editor.borrow_with(&mut font_system); // Scale metrics - let metrics = editor.buffer().metrics(); - editor - .buffer_mut() - .set_metrics(metrics.scale(SCALE_FACTOR as f32)); + let metrics = editor.with_buffer(|buffer| buffer.metrics()); + editor.with_buffer_mut(|buffer| buffer.set_metrics(metrics.scale(SCALE_FACTOR as f32))); // Set size - editor.buffer_mut().set_size(image_w as f32, image_h as f32); + editor.with_buffer_mut(|buffer| buffer.set_size(image_w as f32, image_h as f32)); // Shape and layout editor.shape_as_needed(true); @@ -228,7 +232,7 @@ where }); // Restore original metrics - editor.buffer_mut().set_metrics(metrics); + editor.with_buffer_mut(|buffer| buffer.set_metrics(metrics)); let handle = image::Handle::from_pixels(image_w as u32, image_h as u32, pixels); image::Renderer::draw( @@ -355,12 +359,13 @@ where } } -impl<'a, 'editor, Message, Renderer> From> for Element<'a, Message, Renderer> +impl<'a, 'editor, 'buffer, Message, Renderer> From> + for Element<'a, Message, Renderer> where Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet, { - fn from(text_box: TextBox<'a, 'editor>) -> Self { + fn from(text_box: TextBox<'a, 'editor, 'buffer>) -> Self { Self::new(text_box) } } diff --git a/examples/editor-orbclient/src/main.rs b/examples/editor-orbclient/src/main.rs index 21652f2..e4bfe0d 100644 --- a/examples/editor-orbclient/src/main.rs +++ b/examples/editor-orbclient/src/main.rs @@ -69,9 +69,9 @@ fn main() { let mut editor = editor.borrow_with(&mut font_system); - editor - .buffer_mut() - .set_size(window.width() as f32 - line_x * 2.0, window.height() as f32); + editor.with_buffer_mut(|buffer| { + buffer.set_size(window.width() as f32 - line_x * 2.0, window.height() as f32) + }); let attrs = Attrs::new().family(Family::Monospace); match editor.load_text(&path, attrs) { @@ -89,7 +89,7 @@ fn main() { let mut mouse_left = false; loop { editor.shape_as_needed(true); - if editor.buffer().redraw() { + if editor.redraw() { let instant = Instant::now(); let bg = editor.background_color(); @@ -109,15 +109,17 @@ fn main() { { let mut start_line_opt = None; let mut end_line = 0; - for run in editor.buffer().layout_runs() { - end_line = run.line_i; - if start_line_opt.is_none() { - start_line_opt = Some(end_line); + editor.with_buffer(|buffer| { + for run in buffer.layout_runs() { + end_line = run.line_i; + if start_line_opt.is_none() { + start_line_opt = Some(end_line); + } } - } + }); let start_line = start_line_opt.unwrap_or(end_line); - let lines = editor.buffer().lines.len(); + let lines = editor.with_buffer(|buffer| buffer.lines.len()); let start_y = (start_line * window.height() as usize) / lines; let end_y = (end_line * window.height() as usize) / lines; if end_y > start_y { @@ -133,7 +135,7 @@ fn main() { window.sync(); - editor.buffer_mut().set_redraw(false); + editor.set_redraw(false); log::debug!("redraw: {:?}", instant.elapsed()); } @@ -172,18 +174,23 @@ fn main() { orbclient::K_DEL if event.pressed => editor.action(Action::Delete), orbclient::K_0 if event.pressed && ctrl_pressed => { font_size_i = font_size_default; - editor.buffer_mut().set_metrics(font_sizes[font_size_i]); + editor + .with_buffer_mut(|buffer| buffer.set_metrics(font_sizes[font_size_i])); } orbclient::K_MINUS if event.pressed && ctrl_pressed => { if font_size_i > 0 { font_size_i -= 1; - editor.buffer_mut().set_metrics(font_sizes[font_size_i]); + editor.with_buffer_mut(|buffer| { + buffer.set_metrics(font_sizes[font_size_i]) + }); } } orbclient::K_EQUALS if event.pressed && ctrl_pressed => { if font_size_i + 1 < font_sizes.len() { font_size_i += 1; - editor.buffer_mut().set_metrics(font_sizes[font_size_i]); + editor.with_buffer_mut(|buffer| { + buffer.set_metrics(font_sizes[font_size_i]) + }); } } _ => (), @@ -224,9 +231,9 @@ fn main() { } } EventOption::Resize(event) => { - editor - .buffer_mut() - .set_size(event.width as f32 - line_x * 2.0, event.height as f32); + editor.with_buffer_mut(|buffer| { + buffer.set_size(event.width as f32 - line_x * 2.0, event.height as f32); + }); } EventOption::Scroll(event) => { editor.action(Action::Scroll { diff --git a/examples/editor-test/src/main.rs b/examples/editor-test/src/main.rs index 8ac8d6f..8a35afc 100644 --- a/examples/editor-test/src/main.rs +++ b/examples/editor-test/src/main.rs @@ -19,7 +19,7 @@ fn redraw( let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33); editor.shape_as_needed(true); - if editor.buffer().redraw() { + if editor.redraw() { let instant = Instant::now(); window.set(bg_color); @@ -36,7 +36,7 @@ fn redraw( window.sync(); - editor.buffer_mut().set_redraw(false); + editor.set_redraw(false); let duration = instant.elapsed(); log::debug!("redraw: {:?}", duration); @@ -159,13 +159,15 @@ fn main() { log::info!("Test completed in {:?}", test_elapsed); let mut wrong = 0; - for (line_i, line) in text.lines().enumerate() { - let buffer_line = &editor.buffer().lines[line_i]; - if buffer_line.text() != line { - log::error!("line {}: {:?} != {:?}", line_i, buffer_line.text(), line); - wrong += 1; + editor.with_buffer(|buffer| { + for (line_i, line) in text.lines().enumerate() { + let buffer_line = &buffer.lines[line_i]; + if buffer_line.text() != line { + log::error!("line {}: {:?} != {:?}", line_i, buffer_line.text(), line); + wrong += 1; + } } - } + }); if wrong == 0 { log::info!("All lines matched!"); process::exit(0); diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index 8da5168..bd16b20 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -42,9 +42,7 @@ fn main() { let mut editor = editor.borrow_with(&mut font_system); - editor - .buffer_mut() - .set_size(window.width() as f32, window.height() as f32); + editor.with_buffer_mut(|buffer| buffer.set_size(window.width() as f32, window.height() as f32)); let attrs = Attrs::new(); let serif_attrs = attrs.family(Family::Serif); @@ -117,9 +115,9 @@ fn main() { ), ]; - editor - .buffer_mut() - .set_rich_text(spans.iter().copied(), attrs, Shaping::Advanced); + editor.with_buffer_mut(|buffer| { + buffer.set_rich_text(spans.iter().copied(), attrs, Shaping::Advanced) + }); let mut swash_cache = SwashCache::new(); @@ -134,7 +132,7 @@ fn main() { let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33); editor.shape_as_needed(true); - if editor.buffer().redraw() { + if editor.redraw() { let instant = Instant::now(); window.set(bg_color); @@ -151,7 +149,7 @@ fn main() { window.sync(); - editor.buffer_mut().set_redraw(false); + editor.set_redraw(false); let duration = instant.elapsed(); log::debug!("redraw: {:?}", duration); @@ -206,9 +204,9 @@ fn main() { } } EventOption::Resize(resize) => { - editor - .buffer_mut() - .set_size(resize.width as f32, resize.height as f32); + editor.with_buffer_mut(|buffer| { + buffer.set_size(resize.width as f32, resize.height as f32) + }); } EventOption::Quit(_) => process::exit(0), _ => (), diff --git a/src/edit/editor.rs b/src/edit/editor.rs index 343c7c0..1de6ef5 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -2,6 +2,7 @@ #[cfg(not(feature = "std"))] use alloc::string::{String, ToString}; +use alloc::sync::Arc; use core::{cmp, iter::once}; use unicode_segmentation::UnicodeSegmentation; @@ -12,10 +13,35 @@ use crate::{ Edit, FontSystem, Selection, Shaping, }; +#[derive(Debug)] +pub enum BufferRef<'a> { + Owned(Buffer), + Borrowed(&'a mut Buffer), + Arc(Arc), +} + +impl<'a> From for BufferRef<'a> { + fn from(buffer: Buffer) -> Self { + Self::Owned(buffer) + } +} + +impl<'a> From<&'a mut Buffer> for BufferRef<'a> { + fn from(buffer: &'a mut Buffer) -> Self { + Self::Borrowed(buffer) + } +} + +impl<'a> From> for BufferRef<'a> { + fn from(arc: Arc) -> Self { + Self::Arc(arc) + } +} + /// A wrapper of [`Buffer`] for easy editing #[derive(Debug)] -pub struct Editor { - buffer: Buffer, +pub struct Editor<'a> { + buffer_ref: BufferRef<'a>, cursor: Cursor, cursor_x_opt: Option, selection: Selection, @@ -25,11 +51,11 @@ pub struct Editor { change: Option, } -impl Editor { +impl<'a> Editor<'a> { /// Create a new [`Editor`] with the provided [`Buffer`] - pub fn new(buffer: Buffer) -> Self { + pub fn new(buffer: impl Into>) -> Self { Self { - buffer, + buffer_ref: buffer.into(), cursor: Cursor::default(), cursor_x_opt: None, selection: Selection::None, @@ -53,174 +79,187 @@ impl Editor { ) where F: FnMut(i32, i32, u32, u32, Color), { - let line_height = self.buffer.metrics().line_height; + self.with_buffer(|buffer| { + let line_height = buffer.metrics().line_height; + for run in buffer.layout_runs() { + let line_i = run.line_i; + let line_y = run.line_y; + let line_top = run.line_top; - for run in self.buffer.layout_runs() { - let line_i = run.line_i; - let line_y = run.line_y; - let line_top = run.line_top; + let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> { + if cursor.line == line_i { + for (glyph_i, glyph) in run.glyphs.iter().enumerate() { + if cursor.index == glyph.start { + return Some((glyph_i, 0.0)); + } else if cursor.index > glyph.start && cursor.index < glyph.end { + // Guess x offset based on characters + let mut before = 0; + let mut total = 0; - let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> { - if cursor.line == line_i { - for (glyph_i, glyph) in run.glyphs.iter().enumerate() { - if cursor.index == glyph.start { - return Some((glyph_i, 0.0)); - } else if cursor.index > glyph.start && cursor.index < glyph.end { - // Guess x offset based on characters - let mut before = 0; - let mut total = 0; - - let cluster = &run.text[glyph.start..glyph.end]; - for (i, _) in cluster.grapheme_indices(true) { - if glyph.start + i < cursor.index { - before += 1; + let cluster = &run.text[glyph.start..glyph.end]; + for (i, _) in cluster.grapheme_indices(true) { + if glyph.start + i < cursor.index { + before += 1; + } + total += 1; } - total += 1; - } - let offset = glyph.w * (before as f32) / (total as f32); - return Some((glyph_i, offset)); + let offset = glyph.w * (before as f32) / (total as f32); + return Some((glyph_i, offset)); + } + } + match run.glyphs.last() { + Some(glyph) => { + if cursor.index == glyph.end { + return Some((run.glyphs.len(), 0.0)); + } + } + None => { + return Some((0, 0.0)); + } } } - match run.glyphs.last() { - Some(glyph) => { - if cursor.index == glyph.end { - return Some((run.glyphs.len(), 0.0)); + None + }; + + // Highlight selection (TODO: HIGHLIGHT COLOR!) + if let Some((start, end)) = self.selection_bounds() { + if line_i >= start.line && line_i <= end.line { + let mut range_opt = None; + for glyph in run.glyphs.iter() { + // Guess x offset based on characters + let cluster = &run.text[glyph.start..glyph.end]; + let total = cluster.grapheme_indices(true).count(); + let mut c_x = glyph.x; + let c_w = glyph.w / total as f32; + for (i, c) in cluster.grapheme_indices(true) { + let c_start = glyph.start + i; + let c_end = glyph.start + i + c.len(); + if (start.line != line_i || c_end > start.index) + && (end.line != line_i || c_start < end.index) + { + range_opt = match range_opt.take() { + Some((min, max)) => Some(( + cmp::min(min, c_x as i32), + cmp::max(max, (c_x + c_w) as i32), + )), + None => Some((c_x as i32, (c_x + c_w) as i32)), + }; + } else if let Some((min, max)) = range_opt.take() { + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + c_x += c_w; } } - None => { - return Some((0, 0.0)); + + if run.glyphs.is_empty() && end.line > line_i { + // Highlight all of internal empty lines + range_opt = Some((0, buffer.size().0 as i32)); + } + + if let Some((mut min, mut max)) = range_opt.take() { + if end.line > line_i { + // Draw to end of line + if run.rtl { + min = 0; + } else { + max = buffer.size().0 as i32; + } + } + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); } } } - None - }; - // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some((start, end)) = self.selection_bounds() { - if line_i >= start.line && line_i <= end.line { - let mut range_opt = None; - for glyph in run.glyphs.iter() { - // Guess x offset based on characters - let cluster = &run.text[glyph.start..glyph.end]; - let total = cluster.grapheme_indices(true).count(); - let mut c_x = glyph.x; - let c_w = glyph.w / total as f32; - for (i, c) in cluster.grapheme_indices(true) { - let c_start = glyph.start + i; - let c_end = glyph.start + i + c.len(); - if (start.line != line_i || c_end > start.index) - && (end.line != line_i || c_start < end.index) - { - range_opt = match range_opt.take() { - Some((min, max)) => Some(( - cmp::min(min, c_x as i32), - cmp::max(max, (c_x + c_w) as i32), - )), - None => Some((c_x as i32, (c_x + c_w) as i32)), - }; - } else if let Some((min, max)) = range_opt.take() { - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - selection_color, - ); - } - c_x += c_w; - } - } - - if run.glyphs.is_empty() && end.line > line_i { - // Highlight all of internal empty lines - range_opt = Some((0, self.buffer.size().0 as i32)); - } - - if let Some((mut min, mut max)) = range_opt.take() { - if end.line > line_i { - // Draw to end of line - if run.rtl { - min = 0; - } else { - max = self.buffer.size().0 as i32; - } - } - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - selection_color, - ); - } - } - } - - // Draw cursor - if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) { - let x = match run.glyphs.get(cursor_glyph) { - Some(glyph) => { - // Start of detected glyph - if glyph.level.is_rtl() { - (glyph.x + glyph.w - cursor_glyph_offset) as i32 - } else { - (glyph.x + cursor_glyph_offset) as i32 - } - } - None => match run.glyphs.last() { + // Draw cursor + if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) { + let x = match run.glyphs.get(cursor_glyph) { Some(glyph) => { - // End of last glyph + // Start of detected glyph if glyph.level.is_rtl() { - glyph.x as i32 + (glyph.x + glyph.w - cursor_glyph_offset) as i32 } else { - (glyph.x + glyph.w) as i32 + (glyph.x + cursor_glyph_offset) as i32 } } - None => { - // Start of empty line - 0 - } - }, - }; + None => match run.glyphs.last() { + Some(glyph) => { + // End of last glyph + if glyph.level.is_rtl() { + glyph.x as i32 + } else { + (glyph.x + glyph.w) as i32 + } + } + None => { + // Start of empty line + 0 + } + }, + }; - f(x, line_top as i32, 1, line_height as u32, cursor_color); + f(x, line_top as i32, 1, line_height as u32, cursor_color); + } + + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0., 0.), 1.0); + + let glyph_color = match glyph.color_opt { + Some(some) => some, + None => text_color, + }; + + cache.with_pixels( + font_system, + physical_glyph.cache_key, + glyph_color, + |x, y, color| { + f( + physical_glyph.x + x, + line_y as i32 + physical_glyph.y + y, + 1, + 1, + color, + ); + }, + ); + } } - - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((0., 0.), 1.0); - - let glyph_color = match glyph.color_opt { - Some(some) => some, - None => text_color, - }; - - cache.with_pixels( - font_system, - physical_glyph.cache_key, - glyph_color, - |x, y, color| { - f( - physical_glyph.x + x, - line_y as i32 + physical_glyph.y + y, - 1, - 1, - color, - ); - }, - ); - } - } + }); } } -impl Edit for Editor { - fn buffer(&self) -> &Buffer { - &self.buffer +impl<'a> Edit for Editor<'a> { + fn with_buffer T, T>(&self, f: F) -> T { + match &self.buffer_ref { + BufferRef::Owned(buffer) => f(buffer), + BufferRef::Borrowed(buffer) => f(buffer), + BufferRef::Arc(buffer) => f(buffer), + } } - fn buffer_mut(&mut self) -> &mut Buffer { - &mut self.buffer + fn with_buffer_mut T, T>(&mut self, f: F) -> T { + match &mut self.buffer_ref { + BufferRef::Owned(buffer) => f(buffer), + BufferRef::Borrowed(buffer) => f(buffer), + BufferRef::Arc(arc) => match Arc::get_mut(arc) { + Some(buffer) => f(buffer), + //TODO: use make_mut? + None => panic!("BufferRef::Arc cannot be accessed mutibly"), + }, + } } fn cursor(&self) -> Cursor { @@ -231,7 +270,7 @@ impl Edit for Editor { if self.cursor != cursor { self.cursor = cursor; self.cursor_moved = true; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } @@ -242,7 +281,7 @@ impl Edit for Editor { fn set_selection(&mut self, selection: Selection) { if self.selection != selection { self.selection = selection; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } @@ -265,76 +304,79 @@ impl Edit for Editor { } if self.tab_width != tab_width { self.tab_width = tab_width; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { if self.cursor_moved { - self.buffer - .shape_until_cursor(font_system, self.cursor, prune); + let cursor = self.cursor; + self.with_buffer_mut(|buffer| buffer.shape_until_cursor(font_system, cursor, prune)); self.cursor_moved = false; } else { - self.buffer.shape_until_scroll(font_system, prune); + self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, prune)); } } fn delete_range(&mut self, start: Cursor, end: Cursor) { - // Collect removed data for change tracking - let mut change_text = String::new(); + let change_item = self.with_buffer_mut(|buffer| { + // Collect removed data for change tracking + let mut change_text = String::new(); - // Delete the selection from the last line - let end_line_opt = if end.line > start.line { - // Get part of line after selection - let after = self.buffer.lines[end.line].split_off(end.index); + // Delete the selection from the last line + let end_line_opt = if end.line > start.line { + // Get part of line after selection + let after = buffer.lines[end.line].split_off(end.index); - // Remove end line - let removed = self.buffer.lines.remove(end.line); - change_text.insert_str(0, removed.text()); + // Remove end line + let removed = buffer.lines.remove(end.line); + change_text.insert_str(0, removed.text()); - Some(after) - } else { - None - }; - - // Delete interior lines (in reverse for safety) - for line_i in (start.line + 1..end.line).rev() { - let removed = self.buffer.lines.remove(line_i); - change_text.insert_str(0, removed.text()); - } - - // Delete the selection from the first line - { - // Get part after selection if start line is also end line - let after_opt = if start.line == end.line { - Some(self.buffer.lines[start.line].split_off(end.index)) + Some(after) } else { None }; - // Delete selected part of line - let removed = self.buffer.lines[start.line].split_off(start.index); - change_text.insert_str(0, removed.text()); - - // Re-add part of line after selection - if let Some(after) = after_opt { - self.buffer.lines[start.line].append(after); + // Delete interior lines (in reverse for safety) + for line_i in (start.line + 1..end.line).rev() { + let removed = buffer.lines.remove(line_i); + change_text.insert_str(0, removed.text()); } - // Re-add valid parts of end line - if let Some(end_line) = end_line_opt { - self.buffer.lines[start.line].append(end_line); - } - } + // Delete the selection from the first line + { + // Get part after selection if start line is also end line + let after_opt = if start.line == end.line { + Some(buffer.lines[start.line].split_off(end.index)) + } else { + None + }; - if let Some(ref mut change) = self.change { - let item = ChangeItem { + // Delete selected part of line + let removed = buffer.lines[start.line].split_off(start.index); + change_text.insert_str(0, removed.text()); + + // Re-add part of line after selection + if let Some(after) = after_opt { + buffer.lines[start.line].append(after); + } + + // Re-add valid parts of end line + if let Some(end_line) = end_line_opt { + buffer.lines[start.line].append(end_line); + } + } + + ChangeItem { start, end, text: change_text, insert: false, - }; - change.items.push(item); + } + }); + + if let Some(ref mut change) = self.change { + change.items.push(change_item); } } @@ -349,80 +391,83 @@ impl Edit for Editor { return cursor; } - // Save cursor for change tracking - let start = cursor; + let change_item = self.with_buffer_mut(|buffer| { + // Save cursor for change tracking + let start = cursor; - let line: &mut BufferLine = &mut self.buffer.lines[cursor.line]; - let insert_line = cursor.line + 1; + let line: &mut BufferLine = &mut buffer.lines[cursor.line]; + let insert_line = cursor.line + 1; - // Collect text after insertion as a line - let after: BufferLine = line.split_off(cursor.index); - let after_len = after.text().len(); + // Collect text after insertion as a line + let after: BufferLine = line.split_off(cursor.index); + let after_len = after.text().len(); - // Collect attributes - let mut final_attrs = attrs_list.unwrap_or_else(|| { - AttrsList::new(line.attrs_list().get_span(cursor.index.saturating_sub(1))) - }); + // Collect attributes + let mut final_attrs = attrs_list.unwrap_or_else(|| { + AttrsList::new(line.attrs_list().get_span(cursor.index.saturating_sub(1))) + }); - // Append the inserted text, line by line - // we want to see a blank entry if the string ends with a newline - let addendum = once("").filter(|_| data.ends_with('\n')); - let mut lines_iter = data.split_inclusive('\n').chain(addendum); - if let Some(data_line) = lines_iter.next() { - let mut these_attrs = final_attrs.split_off(data_line.len()); - remaining_split_len -= data_line.len(); - core::mem::swap(&mut these_attrs, &mut final_attrs); - line.append(BufferLine::new( - data_line - .strip_suffix(char::is_control) - .unwrap_or(data_line), - these_attrs, - Shaping::Advanced, - )); - } else { - panic!("str::lines() did not yield any elements"); - } - if let Some(data_line) = lines_iter.next_back() { - remaining_split_len -= data_line.len(); - let mut tmp = BufferLine::new( - data_line - .strip_suffix(char::is_control) - .unwrap_or(data_line), - final_attrs.split_off(remaining_split_len), - Shaping::Advanced, - ); - tmp.append(after); - self.buffer.lines.insert(insert_line, tmp); - cursor.line += 1; - } else { - line.append(after); - } - for data_line in lines_iter.rev() { - remaining_split_len -= data_line.len(); - let tmp = BufferLine::new( - data_line - .strip_suffix(char::is_control) - .unwrap_or(data_line), - final_attrs.split_off(remaining_split_len), - Shaping::Advanced, - ); - self.buffer.lines.insert(insert_line, tmp); - cursor.line += 1; - } + // Append the inserted text, line by line + // we want to see a blank entry if the string ends with a newline + let addendum = once("").filter(|_| data.ends_with('\n')); + let mut lines_iter = data.split_inclusive('\n').chain(addendum); + if let Some(data_line) = lines_iter.next() { + let mut these_attrs = final_attrs.split_off(data_line.len()); + remaining_split_len -= data_line.len(); + core::mem::swap(&mut these_attrs, &mut final_attrs); + line.append(BufferLine::new( + data_line + .strip_suffix(char::is_control) + .unwrap_or(data_line), + these_attrs, + Shaping::Advanced, + )); + } else { + panic!("str::lines() did not yield any elements"); + } + if let Some(data_line) = lines_iter.next_back() { + remaining_split_len -= data_line.len(); + let mut tmp = BufferLine::new( + data_line + .strip_suffix(char::is_control) + .unwrap_or(data_line), + final_attrs.split_off(remaining_split_len), + Shaping::Advanced, + ); + tmp.append(after); + buffer.lines.insert(insert_line, tmp); + cursor.line += 1; + } else { + line.append(after); + } + for data_line in lines_iter.rev() { + remaining_split_len -= data_line.len(); + let tmp = BufferLine::new( + data_line + .strip_suffix(char::is_control) + .unwrap_or(data_line), + final_attrs.split_off(remaining_split_len), + Shaping::Advanced, + ); + buffer.lines.insert(insert_line, tmp); + cursor.line += 1; + } - assert_eq!(remaining_split_len, 0); + assert_eq!(remaining_split_len, 0); - // Append the text after insertion - cursor.index = self.buffer.lines[cursor.line].text().len() - after_len; + // Append the text after insertion + cursor.index = buffer.lines[cursor.line].text().len() - after_len; - if let Some(ref mut change) = self.change { - let item = ChangeItem { + ChangeItem { start, end: cursor, text: data.to_string(), insert: true, - }; - change.items.push(item); + } + }); + + if let Some(ref mut change) = self.change { + change.items.push(change_item); } cursor @@ -430,32 +475,33 @@ impl Edit for Editor { fn copy_selection(&self) -> Option { let (start, end) = self.selection_bounds()?; + self.with_buffer(|buffer| { + let mut selection = String::new(); + // Take the selection from the first line + { + // Add selected part of line to string + if start.line == end.line { + selection.push_str(&buffer.lines[start.line].text()[start.index..end.index]); + } else { + selection.push_str(&buffer.lines[start.line].text()[start.index..]); + selection.push('\n'); + } + } - let mut selection = String::new(); - // Take the selection from the first line - { - // Add selected part of line to string - if start.line == end.line { - selection.push_str(&self.buffer.lines[start.line].text()[start.index..end.index]); - } else { - selection.push_str(&self.buffer.lines[start.line].text()[start.index..]); + // Take the selection from all interior lines (if they exist) + for line_i in start.line + 1..end.line { + selection.push_str(buffer.lines[line_i].text()); selection.push('\n'); } - } - // Take the selection from all interior lines (if they exist) - for line_i in start.line + 1..end.line { - selection.push_str(self.buffer.lines[line_i].text()); - selection.push('\n'); - } + // Take the selection from the last line + if end.line > start.line { + // Add selected part of line to string + selection.push_str(&buffer.lines[end.line].text()[..end.index]); + } - // Take the selection from the last line - if end.line > start.line { - // Add selected part of line to string - selection.push_str(&self.buffer.lines[end.line].text()[..end.index]); - } - - Some(selection) + Some(selection) + }) } fn delete_selection(&mut self) -> bool { @@ -517,18 +563,19 @@ impl Edit for Editor { match action { Action::Motion(motion) => { - if let Some((cursor, cursor_x_opt)) = - self.buffer - .cursor_motion(font_system, self.cursor, self.cursor_x_opt, motion) - { - self.cursor = cursor; - self.cursor_x_opt = cursor_x_opt; + let cursor = self.cursor; + let cursor_x_opt = self.cursor_x_opt; + if let Some((new_cursor, new_cursor_x_opt)) = self.with_buffer_mut(|buffer| { + buffer.cursor_motion(font_system, cursor, cursor_x_opt, motion) + }) { + self.cursor = new_cursor; + self.cursor_x_opt = new_cursor_x_opt; } } Action::Escape => { match self.selection { Selection::None => {} - _ => self.buffer.set_redraw(true), + _ => self.with_buffer_mut(|buffer| buffer.set_redraw(true)), } self.selection = Selection::None; } @@ -548,8 +595,8 @@ impl Edit for Editor { //TODO: what about indenting more after opening brackets or parentheses? if self.auto_indent { let mut string = String::from("\n"); - { - let line = &self.buffer.lines[self.cursor.line]; + self.with_buffer(|buffer| { + let line = &buffer.lines[self.cursor.line]; let text = line.text(); for c in text.chars() { if c.is_whitespace() { @@ -558,14 +605,17 @@ impl Edit for Editor { break; } } - } + }); self.insert_string(&string, None); } else { self.insert_string("\n", None); } // Ensure line is properly shaped and laid out (for potential immediate commands) - self.buffer.line_layout(font_system, self.cursor.line); + let line_i = self.cursor.line; + self.with_buffer_mut(|buffer| { + buffer.line_layout(font_system, line_i); + }); } Action::Backspace => { if self.delete_selection() { @@ -576,16 +626,17 @@ impl Edit for Editor { if self.cursor.index > 0 { // Move cursor to previous character index - let line = &self.buffer.lines[self.cursor.line]; - self.cursor.index = line.text()[..self.cursor.index] - .char_indices() - .next_back() - .map_or(0, |(i, _)| i); + self.cursor.index = self.with_buffer(|buffer| { + buffer.lines[self.cursor.line].text()[..self.cursor.index] + .char_indices() + .next_back() + .map_or(0, |(i, _)| i) + }); } else if self.cursor.line > 0 { // Move cursor to previous line self.cursor.line -= 1; - let line = &self.buffer.lines[self.cursor.line]; - self.cursor.index = line.text().len(); + self.cursor.index = + self.with_buffer(|buffer| buffer.lines[self.cursor.line].text().len()); } if self.cursor != end { @@ -598,30 +649,34 @@ impl Edit for Editor { if self.delete_selection() { // Deleted selection } else { - // Save current cursor as end + // Save current cursor as start and end + let mut start = self.cursor; let mut end = self.cursor; - if self.cursor.index < self.buffer.lines[self.cursor.line].text().len() { - let line = &self.buffer.lines[self.cursor.line]; + self.with_buffer(|buffer| { + if start.index < buffer.lines[start.line].text().len() { + let line = &buffer.lines[start.line]; - let range_opt = line - .text() - .grapheme_indices(true) - .take_while(|(i, _)| *i <= self.cursor.index) - .last() - .map(|(i, c)| i..(i + c.len())); + let range_opt = line + .text() + .grapheme_indices(true) + .take_while(|(i, _)| *i <= start.index) + .last() + .map(|(i, c)| i..(i + c.len())); - if let Some(range) = range_opt { - self.cursor.index = range.start; - end.index = range.end; + if let Some(range) = range_opt { + start.index = range.start; + end.index = range.end; + } + } else if start.line + 1 < buffer.lines.len() { + end.line += 1; + end.index = 0; } - } else if self.cursor.line + 1 < self.buffer.lines.len() { - end.line += 1; - end.index = 0; - } + }); - if self.cursor != end { - self.delete_range(self.cursor, end); + if start != end { + self.cursor = start; + self.delete_range(start, end); } } } @@ -636,10 +691,10 @@ impl Edit for Editor { let tab_width: usize = self.tab_width.into(); for line_i in start.line..=end.line { // Determine indexes of last indent and first character after whitespace - let mut after_whitespace; + let mut after_whitespace = 0; let mut required_indent = 0; - { - let line = &self.buffer.lines[line_i]; + self.with_buffer(|buffer| { + let line = &buffer.lines[line_i]; let text = line.text(); // Default to end of line if no non-whitespace found after_whitespace = text.len(); @@ -650,7 +705,7 @@ impl Edit for Editor { break; } } - } + }); // No indent required (not possible?) if required_indent == 0 { @@ -685,7 +740,7 @@ impl Edit for Editor { } // Request redraw - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } Action::Unindent => { @@ -700,9 +755,9 @@ impl Edit for Editor { for line_i in start.line..=end.line { // Determine indexes of last indent and first character after whitespace let mut last_indent = 0; - let mut after_whitespace; - { - let line = &self.buffer.lines[line_i]; + let mut after_whitespace = 0; + self.with_buffer(|buffer| { + let line = &buffer.lines[line_i]; let text = line.text(); // Default to end of line if no non-whitespace found after_whitespace = text.len(); @@ -715,7 +770,7 @@ impl Edit for Editor { last_indent = index; } } - } + }); // No de-indent required if last_indent == after_whitespace { @@ -746,65 +801,71 @@ impl Edit for Editor { } // Request redraw - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } Action::Click { x, y } => { self.set_selection(Selection::None); - if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { + if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32)) + { if new_cursor != self.cursor { self.cursor = new_cursor; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } } Action::DoubleClick { x, y } => { self.set_selection(Selection::None); - if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { + if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32)) + { if new_cursor != self.cursor { self.cursor = new_cursor; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } self.selection = Selection::Word(self.cursor); - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } Action::TripleClick { x, y } => { self.set_selection(Selection::None); - if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { + if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32)) + { if new_cursor != self.cursor { self.cursor = new_cursor; } self.selection = Selection::Line(self.cursor); - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } Action::Drag { x, y } => { if self.selection == Selection::None { self.selection = Selection::Normal(self.cursor); - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } - if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { + if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32)) + { if new_cursor != self.cursor { self.cursor = new_cursor; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } } Action::Scroll { lines } => { - let mut scroll = self.buffer.scroll(); - scroll.layout += lines; - self.buffer.set_scroll(scroll); + self.with_buffer_mut(|buffer| { + let mut scroll = buffer.scroll(); + scroll.layout += lines; + buffer.set_scroll(scroll); + }); } } if old_cursor != self.cursor { self.cursor_moved = true; - self.buffer.set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); /*TODO if let Some(glyph) = run.glyphs.get(new_cursor_glyph) { @@ -825,7 +886,7 @@ impl Edit for Editor { } } -impl<'a> BorrowedWithFontSystem<'a, Editor> { +impl<'a, 'b> BorrowedWithFontSystem<'a, Editor<'b>> { #[cfg(feature = "swash")] pub fn draw( &mut self, diff --git a/src/edit/mod.rs b/src/edit/mod.rs index aa6589e..f7600fa 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -131,10 +131,20 @@ pub trait Edit { } /// Get the internal [`Buffer`] - fn buffer(&self) -> &Buffer; + fn with_buffer T, T>(&self, f: F) -> T; /// Get the internal [`Buffer`], mutably - fn buffer_mut(&mut self) -> &mut Buffer; + fn with_buffer_mut T, T>(&mut self, f: F) -> T; + + /// Get the [`Buffer`] redraw flag + fn redraw(&self) -> bool { + self.with_buffer(|buffer| buffer.redraw()) + } + + /// Set the [`Buffer`] redraw flag + fn set_redraw(&mut self, redraw: bool) { + self.with_buffer_mut(|buffer| buffer.set_redraw(redraw)) + } /// Get the current cursor fn cursor(&self) -> Cursor; @@ -151,69 +161,71 @@ pub trait Edit { /// Get the bounds of the current selection //TODO: will not work with Block select fn selection_bounds(&self) -> Option<(Cursor, Cursor)> { - let cursor = self.cursor(); - match self.selection() { - Selection::None => None, - Selection::Normal(select) => match select.line.cmp(&cursor.line) { - cmp::Ordering::Greater => Some((cursor, select)), - cmp::Ordering::Less => Some((select, cursor)), - cmp::Ordering::Equal => { - /* select.line == cursor.line */ - if select.index < cursor.index { - Some((select, cursor)) - } else { - /* select.index >= cursor.index */ - Some((cursor, select)) - } - } - }, - Selection::Line(select) => { - let start_line = cmp::min(select.line, cursor.line); - let end_line = cmp::max(select.line, cursor.line); - let end_index = self.buffer().lines[end_line].text().len(); - Some((Cursor::new(start_line, 0), Cursor::new(end_line, end_index))) - } - Selection::Word(select) => { - let (mut start, mut end) = match select.line.cmp(&cursor.line) { - cmp::Ordering::Greater => (cursor, select), - cmp::Ordering::Less => (select, cursor), + self.with_buffer(|buffer| { + let cursor = self.cursor(); + match self.selection() { + Selection::None => None, + Selection::Normal(select) => match select.line.cmp(&cursor.line) { + cmp::Ordering::Greater => Some((cursor, select)), + cmp::Ordering::Less => Some((select, cursor)), cmp::Ordering::Equal => { /* select.line == cursor.line */ if select.index < cursor.index { - (select, cursor) + Some((select, cursor)) } else { /* select.index >= cursor.index */ - (cursor, select) + Some((cursor, select)) } } - }; - - // Move start to beginning of word - { - let line = &self.buffer().lines[start.line]; - start.index = line - .text() - .unicode_word_indices() - .rev() - .map(|(i, _)| i) - .find(|&i| i < start.index) - .unwrap_or(0); + }, + Selection::Line(select) => { + let start_line = cmp::min(select.line, cursor.line); + let end_line = cmp::max(select.line, cursor.line); + let end_index = buffer.lines[end_line].text().len(); + Some((Cursor::new(start_line, 0), Cursor::new(end_line, end_index))) } + Selection::Word(select) => { + let (mut start, mut end) = match select.line.cmp(&cursor.line) { + cmp::Ordering::Greater => (cursor, select), + cmp::Ordering::Less => (select, cursor), + cmp::Ordering::Equal => { + /* select.line == cursor.line */ + if select.index < cursor.index { + (select, cursor) + } else { + /* select.index >= cursor.index */ + (cursor, select) + } + } + }; - // Move end to end of word - { - let line = &self.buffer().lines[end.line]; - end.index = line - .text() - .unicode_word_indices() - .map(|(i, word)| i + word.len()) - .find(|&i| i > end.index) - .unwrap_or(line.text().len()); + // Move start to beginning of word + { + let line = &buffer.lines[start.line]; + start.index = line + .text() + .unicode_word_indices() + .rev() + .map(|(i, _)| i) + .find(|&i| i < start.index) + .unwrap_or(0); + } + + // Move end to end of word + { + let line = &buffer.lines[end.line]; + end.index = line + .text() + .unicode_word_indices() + .map(|(i, word)| i + word.len()) + .find(|&i| i > end.index) + .unwrap_or(line.text().len()); + } + + Some((start, end)) } - - Some((start, end)) } - } + }) } /// Get the current automatic indentation setting @@ -265,13 +277,19 @@ pub trait Edit { fn action(&mut self, font_system: &mut FontSystem, action: Action); } -impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> { +impl<'a, E: Edit> BorrowedWithFontSystem<'a, E> { /// Get the internal [`Buffer`], mutably - pub fn buffer_mut(&mut self) -> BorrowedWithFontSystem { - BorrowedWithFontSystem { - inner: self.inner.buffer_mut(), - font_system: self.font_system, - } + pub fn with_buffer_mut) -> T, T>( + &mut self, + f: F, + ) -> T { + self.inner.with_buffer_mut(|buffer| { + let mut borrowed = BorrowedWithFontSystem { + inner: buffer, + font_system: self.font_system, + }; + f(&mut borrowed) + }) } /// Shape lines until scroll, after adjusting scroll if the cursor moved diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index ee0321a..5d932c2 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -8,8 +8,8 @@ use syntect::highlighting::{ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; use crate::{ - Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, Editor, - FontSystem, Selection, Shaping, Style, Weight, + Action, AttrsList, BorrowedWithFontSystem, Buffer, BufferRef, Change, Color, Cursor, Edit, + Editor, FontSystem, Selection, Shaping, Style, Weight, }; pub use syntect::highlighting::Theme as SyntaxTheme; @@ -33,8 +33,8 @@ impl SyntaxSystem { /// A wrapper of [`Editor`] with syntax highlighting provided by [`SyntaxSystem`] #[derive(Debug)] -pub struct SyntaxEditor<'a> { - editor: Editor, +pub struct SyntaxEditor<'a, 'b> { + editor: Editor<'b>, syntax_system: &'a SyntaxSystem, syntax: &'a SyntaxReference, theme: &'a SyntaxTheme, @@ -42,13 +42,17 @@ pub struct SyntaxEditor<'a> { syntax_cache: Vec<(ParseState, ScopeStack)>, } -impl<'a> SyntaxEditor<'a> { +impl<'a, 'b> SyntaxEditor<'a, 'b> { /// Create a new [`SyntaxEditor`] with the provided [`Buffer`], [`SyntaxSystem`], and theme name. /// /// A good default theme name is "base16-eighties.dark". /// /// Returns None if theme not found - pub fn new(buffer: Buffer, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option { + pub fn new( + buffer: impl Into>, + syntax_system: &'a SyntaxSystem, + theme_name: &str, + ) -> Option { let editor = Editor::new(buffer); let syntax = syntax_system.syntax_set.find_syntax_plain_text(); let theme = syntax_system.theme_set.themes.get(theme_name)?; @@ -73,18 +77,20 @@ impl<'a> SyntaxEditor<'a> { self.syntax_cache.clear(); // Reset attrs to match default foreground and no highlighting - for line in self.editor.buffer_mut().lines.iter_mut() { - let mut attrs = line.attrs_list().defaults(); - if let Some(foreground) = self.theme.settings.foreground { - attrs = attrs.color(Color::rgba( - foreground.r, - foreground.g, - foreground.b, - foreground.a, - )); + self.with_buffer_mut(|buffer| { + for line in buffer.lines.iter_mut() { + let mut attrs = line.attrs_list().defaults(); + if let Some(foreground) = self.theme.settings.foreground { + attrs = attrs.color(Color::rgba( + foreground.r, + foreground.g, + foreground.b, + foreground.a, + )); + } + line.set_attrs_list(AttrsList::new(attrs)); } - line.set_attrs_list(AttrsList::new(attrs)); - } + }); } true @@ -118,9 +124,9 @@ impl<'a> SyntaxEditor<'a> { } let text = fs::read_to_string(path)?; - self.editor - .buffer_mut() - .set_text(font_system, &text, attrs, Shaping::Advanced); + self.editor.with_buffer_mut(|buffer| { + buffer.set_text(font_system, &text, attrs, Shaping::Advanced) + }); //TODO: re-use text self.syntax = match self.syntax_system.syntax_set.find_syntax_for_file(path) { @@ -194,7 +200,7 @@ impl<'a> SyntaxEditor<'a> { where F: FnMut(i32, i32, u32, u32, Color), { - let size = self.buffer().size(); + let size = self.with_buffer(|buffer| buffer.size()); f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); self.editor.draw( font_system, @@ -207,13 +213,13 @@ impl<'a> SyntaxEditor<'a> { } } -impl<'a> Edit for SyntaxEditor<'a> { - fn buffer(&self) -> &Buffer { - self.editor.buffer() +impl<'a, 'b> Edit for SyntaxEditor<'a, 'b> { + fn with_buffer T, T>(&self, f: F) -> T { + self.editor.with_buffer(f) } - fn buffer_mut(&mut self) -> &mut Buffer { - self.editor.buffer_mut() + fn with_buffer_mut T, T>(&mut self, f: F) -> T { + self.editor.with_buffer_mut(f) } fn cursor(&self) -> Cursor { @@ -253,23 +259,84 @@ impl<'a> Edit for SyntaxEditor<'a> { let now = std::time::Instant::now(); let cursor = self.cursor(); - let buffer = self.editor.buffer_mut(); - let visible_lines = buffer.visible_lines(); - let scroll = buffer.scroll(); - let scroll_end = scroll.layout + visible_lines; - let mut total_layout = 0; - let mut highlighted = 0; - for line_i in 0..buffer.lines.len() { - // Break out if we have reached the end of scroll and are past the cursor - if total_layout >= scroll_end && line_i > cursor.line { - break; - } + self.editor.with_buffer_mut(|buffer| { + let visible_lines = buffer.visible_lines(); + let scroll = buffer.scroll(); + let scroll_end = scroll.layout + visible_lines; + let mut total_layout = 0; + let mut highlighted = 0; + for line_i in 0..buffer.lines.len() { + // Break out if we have reached the end of scroll and are past the cursor + if total_layout >= scroll_end && line_i > cursor.line { + break; + } - let line = &mut buffer.lines[line_i]; - if line.metadata().is_some() && line_i < self.syntax_cache.len() { - //TODO: duplicated code! + let line = &mut buffer.lines[line_i]; + if line.metadata().is_some() && line_i < self.syntax_cache.len() { + //TODO: duplicated code! + if line_i >= scroll.line && total_layout < scroll_end { + // Perform shaping and layout of this line in order to count if we have reached scroll + match buffer.line_layout(font_system, line_i) { + Some(layout_lines) => { + total_layout += layout_lines.len() as i32; + } + None => { + //TODO: should this be possible? + } + } + } + continue; + } + highlighted += 1; + + let (mut parse_state, scope_stack) = + if line_i > 0 && line_i <= self.syntax_cache.len() { + self.syntax_cache[line_i - 1].clone() + } else { + (ParseState::new(self.syntax), ScopeStack::new()) + }; + let mut highlight_state = HighlightState::new(&self.highlighter, scope_stack); + let ops = parse_state + .parse_line(line.text(), &self.syntax_system.syntax_set) + .expect("failed to parse syntax"); + let ranges = RangedHighlightIterator::new( + &mut highlight_state, + &ops, + line.text(), + &self.highlighter, + ); + + let attrs = line.attrs_list().defaults(); + let mut attrs_list = AttrsList::new(attrs); + for (style, _, range) in ranges { + let span_attrs = attrs + .color(Color::rgba( + style.foreground.r, + style.foreground.g, + style.foreground.b, + style.foreground.a, + )) + //TODO: background + .style(if style.font_style.contains(FontStyle::ITALIC) { + Style::Italic + } else { + Style::Normal + }) + .weight(if style.font_style.contains(FontStyle::BOLD) { + Weight::BOLD + } else { + Weight::NORMAL + }); //TODO: underline + if span_attrs != attrs { + attrs_list.add_span(range, span_attrs); + } + } + + // Update line attributes. This operation only resets if the line changes + line.set_attrs_list(attrs_list); + + // Perform shaping and layout of this line in order to count if we have reached scroll if line_i >= scroll.line && total_layout < scroll_end { - // Perform shaping and layout of this line in order to count if we have reached scroll match buffer.line_layout(font_system, line_i) { Some(layout_lines) => { total_layout += layout_lines.len() as i32; @@ -279,91 +346,31 @@ impl<'a> Edit for SyntaxEditor<'a> { } } } - continue; - } - highlighted += 1; - let (mut parse_state, scope_stack) = if line_i > 0 && line_i <= self.syntax_cache.len() - { - self.syntax_cache[line_i - 1].clone() - } else { - (ParseState::new(self.syntax), ScopeStack::new()) - }; - let mut highlight_state = HighlightState::new(&self.highlighter, scope_stack); - let ops = parse_state - .parse_line(line.text(), &self.syntax_system.syntax_set) - .expect("failed to parse syntax"); - let ranges = RangedHighlightIterator::new( - &mut highlight_state, - &ops, - line.text(), - &self.highlighter, - ); - - let attrs = line.attrs_list().defaults(); - let mut attrs_list = AttrsList::new(attrs); - for (style, _, range) in ranges { - let span_attrs = attrs - .color(Color::rgba( - style.foreground.r, - style.foreground.g, - style.foreground.b, - style.foreground.a, - )) - //TODO: background - .style(if style.font_style.contains(FontStyle::ITALIC) { - Style::Italic - } else { - Style::Normal - }) - .weight(if style.font_style.contains(FontStyle::BOLD) { - Weight::BOLD - } else { - Weight::NORMAL - }); //TODO: underline - if span_attrs != attrs { - attrs_list.add_span(range, span_attrs); + let cache_item = (parse_state.clone(), highlight_state.path.clone()); + if line_i < self.syntax_cache.len() { + if self.syntax_cache[line_i] != cache_item { + self.syntax_cache[line_i] = cache_item; + if line_i + 1 < buffer.lines.len() { + buffer.lines[line_i + 1].reset(); + } + } + } else { + buffer.lines[line_i].set_metadata(self.syntax_cache.len()); + self.syntax_cache.push(cache_item); } } - // Update line attributes. This operation only resets if the line changes - line.set_attrs_list(attrs_list); - - // Perform shaping and layout of this line in order to count if we have reached scroll - if line_i >= scroll.line && total_layout < scroll_end { - match buffer.line_layout(font_system, line_i) { - Some(layout_lines) => { - total_layout += layout_lines.len() as i32; - } - None => { - //TODO: should this be possible? - } - } + if highlighted > 0 { + buffer.set_redraw(true); + #[cfg(feature = "std")] + log::debug!( + "Syntax highlighted {} lines in {:?}", + highlighted, + now.elapsed() + ); } - - let cache_item = (parse_state.clone(), highlight_state.path.clone()); - if line_i < self.syntax_cache.len() { - if self.syntax_cache[line_i] != cache_item { - self.syntax_cache[line_i] = cache_item; - if line_i + 1 < buffer.lines.len() { - buffer.lines[line_i + 1].reset(); - } - } - } else { - buffer.lines[line_i].set_metadata(self.syntax_cache.len()); - self.syntax_cache.push(cache_item); - } - } - - if highlighted > 0 { - buffer.set_redraw(true); - #[cfg(feature = "std")] - log::debug!( - "Syntax highlighted {} lines in {:?}", - highlighted, - now.elapsed() - ); - } + }); self.editor.shape_as_needed(font_system, prune); } @@ -401,7 +408,7 @@ impl<'a> Edit for SyntaxEditor<'a> { } } -impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> { +impl<'a, 'b, 'c> BorrowedWithFontSystem<'c, SyntaxEditor<'a, 'b>> { /// Load text from a file, and also set syntax to the best option /// /// ## Errors diff --git a/src/edit/vi.rs b/src/edit/vi.rs index d016698..bfdc58f 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -46,19 +46,20 @@ fn search(editor: &mut E, value: &str, forwards: bool) -> bool { let mut cursor = editor.cursor(); let start_line = cursor.line; if forwards { - while cursor.line < editor.buffer().lines.len() { - if let Some(index) = editor.buffer().lines[cursor.line] - .text() - .match_indices(value) - .filter_map(|(i, _)| { - if cursor.line != start_line || i > cursor.index { - Some(i) - } else { - None - } - }) - .next() - { + while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) { + if let Some(index) = editor.with_buffer(|buffer| { + buffer.lines[cursor.line] + .text() + .match_indices(value) + .filter_map(|(i, _)| { + if cursor.line != start_line || i > cursor.index { + Some(i) + } else { + None + } + }) + .next() + }) { cursor.index = index; editor.set_cursor(cursor); return true; @@ -71,18 +72,19 @@ fn search(editor: &mut E, value: &str, forwards: bool) -> bool { while cursor.line > 0 { cursor.line -= 1; - if let Some(index) = editor.buffer().lines[cursor.line] - .text() - .rmatch_indices(value) - .filter_map(|(i, _)| { - if cursor.line != start_line || i < cursor.index { - Some(i) - } else { - None - } - }) - .next() - { + if let Some(index) = editor.with_buffer(|buffer| { + buffer.lines[cursor.line] + .text() + .rmatch_indices(value) + .filter_map(|(i, _)| { + if cursor.line != start_line || i < cursor.index { + Some(i) + } else { + None + } + }) + .next() + }) { cursor.index = index; editor.set_cursor(cursor); return true; @@ -95,65 +97,67 @@ fn search(editor: &mut E, value: &str, forwards: bool) -> bool { fn select_in(editor: &mut E, start_c: char, end_c: char, include: bool) { // Find the largest encompasing object, or if there is none, find the next one. let cursor = editor.cursor(); - let buffer = editor.buffer(); - - // Search forwards for isolated end character, counting start and end characters found - let mut end = cursor; - let mut starts = 0; - let mut ends = 0; - 'find_end: loop { - let line = &buffer.lines[end.line]; - let text = line.text(); - for (i, c) in text[end.index..].char_indices() { - if c == end_c { - ends += 1; - } else if c == start_c { - starts += 1; + let (start, end) = editor.with_buffer(|buffer| { + // Search forwards for isolated end character, counting start and end characters found + let mut end = cursor; + let mut starts = 0; + let mut ends = 0; + 'find_end: loop { + let line = &buffer.lines[end.line]; + let text = line.text(); + for (i, c) in text[end.index..].char_indices() { + if c == end_c { + ends += 1; + } else if c == start_c { + starts += 1; + } + if ends > starts { + end.index += if include { i + c.len_utf8() } else { i }; + break 'find_end; + } } - if ends > starts { - end.index += if include { i + c.len_utf8() } else { i }; + if end.line + 1 < buffer.lines.len() { + end.line += 1; + end.index = 0; + } else { break 'find_end; } } - if end.line + 1 < buffer.lines.len() { - end.line += 1; - end.index = 0; - } else { - break 'find_end; - } - } - // Search backwards to resolve starts and ends - let mut start = cursor; - 'find_start: loop { - let line = &buffer.lines[start.line]; - let text = line.text(); - for (i, c) in text[..start.index].char_indices().rev() { - if c == start_c { - starts += 1; - } else if c == end_c { - ends += 1; + // Search backwards to resolve starts and ends + let mut start = cursor; + 'find_start: loop { + let line = &buffer.lines[start.line]; + let text = line.text(); + for (i, c) in text[..start.index].char_indices().rev() { + if c == start_c { + starts += 1; + } else if c == end_c { + ends += 1; + } + if starts >= ends { + start.index = if include { i } else { i + c.len_utf8() }; + break 'find_start; + } } - if starts >= ends { - start.index = if include { i } else { i + c.len_utf8() }; + if start.line > 0 { + start.line -= 1; + start.index = buffer.lines[start.line].text().len(); + } else { break 'find_start; } } - if start.line > 0 { - start.line -= 1; - start.index = buffer.lines[start.line].text().len(); - } else { - break 'find_start; - } - } + + (start, end) + }); editor.set_selection(Selection::Normal(start)); editor.set_cursor(end); } #[derive(Debug)] -pub struct ViEditor<'a> { - editor: SyntaxEditor<'a>, +pub struct ViEditor<'a, 'b> { + editor: SyntaxEditor<'a, 'b>, parser: ViParser, passthrough: bool, registers: BTreeMap, @@ -162,8 +166,8 @@ pub struct ViEditor<'a> { changed: bool, } -impl<'a> ViEditor<'a> { - pub fn new(editor: SyntaxEditor<'a>) -> Self { +impl<'a, 'b> ViEditor<'a, 'b> { + pub fn new(editor: SyntaxEditor<'a, 'b>) -> Self { Self { editor, parser: ViParser::new(), @@ -230,7 +234,7 @@ impl<'a> ViEditor<'a> { pub fn set_passthrough(&mut self, passthrough: bool) { if passthrough != self.passthrough { self.passthrough = passthrough; - self.buffer_mut().set_redraw(true); + self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } @@ -264,221 +268,222 @@ impl<'a> ViEditor<'a> { where F: FnMut(i32, i32, u32, u32, Color), { - let size = self.buffer().size(); - f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); - - let font_size = self.buffer().metrics().font_size; - let line_height = self.buffer().metrics().line_height; + let background_color = self.background_color(); let foreground_color = self.foreground_color(); let cursor_color = self.cursor_color(); let selection_color = self.selection_color(); + self.with_buffer(|buffer| { + let size = buffer.size(); + f(0, 0, size.0 as u32, size.1 as u32, background_color); + let font_size = buffer.metrics().font_size; + let line_height = buffer.metrics().line_height; + for run in buffer.layout_runs() { + let line_i = run.line_i; + let line_y = run.line_y; + let line_top = run.line_top; - for run in self.buffer().layout_runs() { - let line_i = run.line_i; - let line_y = run.line_y; - let line_top = run.line_top; + let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> { + //TODO: better calculation of width + let default_width = font_size / 2.0; + if cursor.line == line_i { + for (glyph_i, glyph) in run.glyphs.iter().enumerate() { + if cursor.index >= glyph.start && cursor.index < glyph.end { + // Guess x offset based on characters + let mut before = 0; + let mut total = 0; - let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> { - //TODO: better calculation of width - let default_width = font_size / 2.0; - if cursor.line == line_i { - for (glyph_i, glyph) in run.glyphs.iter().enumerate() { - if cursor.index >= glyph.start && cursor.index < glyph.end { - // Guess x offset based on characters - let mut before = 0; - let mut total = 0; - - let cluster = &run.text[glyph.start..glyph.end]; - for (i, _) in cluster.grapheme_indices(true) { - if glyph.start + i < cursor.index { - before += 1; + let cluster = &run.text[glyph.start..glyph.end]; + for (i, _) in cluster.grapheme_indices(true) { + if glyph.start + i < cursor.index { + before += 1; + } + total += 1; } - total += 1; - } - let width = glyph.w / (total as f32); - let offset = (before as f32) * width; - return Some((glyph_i, offset, width)); - } - } - match run.glyphs.last() { - Some(glyph) => { - if cursor.index == glyph.end { - return Some((run.glyphs.len(), 0.0, default_width)); + let width = glyph.w / (total as f32); + let offset = (before as f32) * width; + return Some((glyph_i, offset, width)); } } - None => { - return Some((0, 0.0, default_width)); - } - } - } - None - }; - - // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some((start, end)) = self.selection_bounds() { - if line_i >= start.line && line_i <= end.line { - let mut range_opt = None; - for glyph in run.glyphs.iter() { - // Guess x offset based on characters - let cluster = &run.text[glyph.start..glyph.end]; - let total = cluster.grapheme_indices(true).count(); - let mut c_x = glyph.x; - let c_w = glyph.w / total as f32; - for (i, c) in cluster.grapheme_indices(true) { - let c_start = glyph.start + i; - let c_end = glyph.start + i + c.len(); - if (start.line != line_i || c_end > start.index) - && (end.line != line_i || c_start < end.index) - { - range_opt = match range_opt.take() { - Some((min, max)) => Some(( - cmp::min(min, c_x as i32), - cmp::max(max, (c_x + c_w) as i32), - )), - None => Some((c_x as i32, (c_x + c_w) as i32)), - }; - } else if let Some((min, max)) = range_opt.take() { - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - selection_color, - ); + match run.glyphs.last() { + Some(glyph) => { + if cursor.index == glyph.end { + return Some((run.glyphs.len(), 0.0, default_width)); + } } - c_x += c_w; - } - } - - if run.glyphs.is_empty() && end.line > line_i { - // Highlight all of internal empty lines - range_opt = Some((0, self.buffer().size().0 as i32)); - } - - if let Some((mut min, mut max)) = range_opt.take() { - if end.line > line_i { - // Draw to end of line - if run.rtl { - min = 0; - } else { - max = self.buffer().size().0 as i32; + None => { + return Some((0, 0.0, default_width)); } } - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - selection_color, - ); - } - } - } - - // Draw cursor - if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = - cursor_glyph_opt(&self.cursor()) - { - let block_cursor = if self.passthrough { - false - } else { - match self.parser.mode { - ViMode::Insert | ViMode::Replace => false, - _ => true, /*TODO: determine block cursor in other modes*/ } + None }; - let (start_x, end_x) = match run.glyphs.get(cursor_glyph) { - Some(glyph) => { - // Start of detected glyph - if glyph.level.is_rtl() { - ( - (glyph.x + glyph.w - cursor_glyph_offset) as i32, - (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) - as i32, - ) - } else { - ( - (glyph.x + cursor_glyph_offset) as i32, - (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32, - ) + // Highlight selection (TODO: HIGHLIGHT COLOR!) + if let Some((start, end)) = self.selection_bounds() { + if line_i >= start.line && line_i <= end.line { + let mut range_opt = None; + for glyph in run.glyphs.iter() { + // Guess x offset based on characters + let cluster = &run.text[glyph.start..glyph.end]; + let total = cluster.grapheme_indices(true).count(); + let mut c_x = glyph.x; + let c_w = glyph.w / total as f32; + for (i, c) in cluster.grapheme_indices(true) { + let c_start = glyph.start + i; + let c_end = glyph.start + i + c.len(); + if (start.line != line_i || c_end > start.index) + && (end.line != line_i || c_start < end.index) + { + range_opt = match range_opt.take() { + Some((min, max)) => Some(( + cmp::min(min, c_x as i32), + cmp::max(max, (c_x + c_w) as i32), + )), + None => Some((c_x as i32, (c_x + c_w) as i32)), + }; + } else if let Some((min, max)) = range_opt.take() { + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + c_x += c_w; + } + } + + if run.glyphs.is_empty() && end.line > line_i { + // Highlight all of internal empty lines + range_opt = Some((0, buffer.size().0 as i32)); + } + + if let Some((mut min, mut max)) = range_opt.take() { + if end.line > line_i { + // Draw to end of line + if run.rtl { + min = 0; + } else { + max = buffer.size().0 as i32; + } + } + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); } } - None => match run.glyphs.last() { + } + + // Draw cursor + if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = + cursor_glyph_opt(&self.cursor()) + { + let block_cursor = if self.passthrough { + false + } else { + match self.parser.mode { + ViMode::Insert | ViMode::Replace => false, + _ => true, /*TODO: determine block cursor in other modes*/ + } + }; + + let (start_x, end_x) = match run.glyphs.get(cursor_glyph) { Some(glyph) => { - // End of last glyph + // Start of detected glyph if glyph.level.is_rtl() { - (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32) + ( + (glyph.x + glyph.w - cursor_glyph_offset) as i32, + (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) + as i32, + ) } else { ( - (glyph.x + glyph.w) as i32, - (glyph.x + glyph.w + cursor_glyph_width) as i32, + (glyph.x + cursor_glyph_offset) as i32, + (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32, ) } } - None => { - // Start of empty line - (0, cursor_glyph_width as i32) - } - }, - }; + None => match run.glyphs.last() { + Some(glyph) => { + // End of last glyph + if glyph.level.is_rtl() { + (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32) + } else { + ( + (glyph.x + glyph.w) as i32, + (glyph.x + glyph.w + cursor_glyph_width) as i32, + ) + } + } + None => { + // Start of empty line + (0, cursor_glyph_width as i32) + } + }, + }; - if block_cursor { - let left_x = cmp::min(start_x, end_x); - let right_x = cmp::max(start_x, end_x); - f( - left_x, - line_top as i32, - (right_x - left_x) as u32, - line_height as u32, - selection_color, - ); - } else { - f( - start_x, - line_top as i32, - 1, - line_height as u32, - cursor_color, + if block_cursor { + let left_x = cmp::min(start_x, end_x); + let right_x = cmp::max(start_x, end_x); + f( + left_x, + line_top as i32, + (right_x - left_x) as u32, + line_height as u32, + selection_color, + ); + } else { + f( + start_x, + line_top as i32, + 1, + line_height as u32, + cursor_color, + ); + } + } + + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0., 0.), 1.0); + + let glyph_color = match glyph.color_opt { + Some(some) => some, + None => foreground_color, + }; + + cache.with_pixels( + font_system, + physical_glyph.cache_key, + glyph_color, + |x, y, color| { + f( + physical_glyph.x + x, + line_y as i32 + physical_glyph.y + y, + 1, + 1, + color, + ); + }, ); } } - - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((0., 0.), 1.0); - - let glyph_color = match glyph.color_opt { - Some(some) => some, - None => foreground_color, - }; - - cache.with_pixels( - font_system, - physical_glyph.cache_key, - glyph_color, - |x, y, color| { - f( - physical_glyph.x + x, - line_y as i32 + physical_glyph.y + y, - 1, - 1, - color, - ); - }, - ); - } - } + }); } } -impl<'a> Edit for ViEditor<'a> { - fn buffer(&self) -> &Buffer { - self.editor.buffer() +impl<'a, 'b> Edit for ViEditor<'a, 'b> { + fn with_buffer T, T>(&self, f: F) -> T { + self.editor.with_buffer(f) } - fn buffer_mut(&mut self) -> &mut Buffer { - self.editor.buffer_mut() + fn with_buffer_mut T, T>(&mut self, f: F) -> T { + self.editor.with_buffer_mut(f) } fn cursor(&self) -> Cursor { @@ -618,8 +623,9 @@ impl<'a> Edit for ViEditor<'a> { Event::Delete => Action::Delete, Event::DeleteInLine => { let cursor = editor.cursor(); - let buffer = editor.buffer(); - if cursor.index < buffer.lines[cursor.line].text().len() { + if cursor.index + < editor.with_buffer(|buffer| buffer.lines[cursor.line].text().len()) + { Action::Delete } else { return; @@ -638,11 +644,12 @@ impl<'a> Edit for ViEditor<'a> { Selection::None | Selection::Normal(_) | Selection::Word(_) => { let mut cursor = editor.cursor(); if after { - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - if let Some(c) = text[cursor.index..].chars().next() { - cursor.index += c.len_utf8(); - } + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + if let Some(c) = text[cursor.index..].chars().next() { + cursor.index += c.len_utf8(); + } + }); editor.set_cursor(cursor); } editor.insert_at(cursor, data, None); @@ -682,7 +689,7 @@ impl<'a> Edit for ViEditor<'a> { return; } Event::Redraw => { - editor.buffer_mut().set_redraw(true); + editor.with_buffer_mut(|buffer| buffer.set_redraw(true)); return; } Event::SelectClear => { @@ -725,20 +732,21 @@ impl<'a> Edit for ViEditor<'a> { TextObject::Word(word) => { let mut cursor = editor.cursor(); let mut selection = editor.selection(); - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - match WordIter::new(text, word) - .find(|&(i, w)| i <= cursor.index && i + w.len() > cursor.index) - { - Some((i, w)) => { - cursor.index = i; - selection = Selection::Normal(cursor); - cursor.index += w.len(); + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + match WordIter::new(text, word) + .find(|&(i, w)| i <= cursor.index && i + w.len() > cursor.index) + { + Some((i, w)) => { + cursor.index = i; + selection = Selection::Normal(cursor); + cursor.index += w.len(); + } + None => { + //TODO + } } - None => { - //TODO - } - } + }); editor.set_selection(selection); editor.set_cursor(cursor); } @@ -782,7 +790,7 @@ impl<'a> Edit for ViEditor<'a> { Action::Motion(Motion::GotoLine(line.saturating_sub(1))) } modit::Motion::GotoEof => Action::Motion(Motion::GotoLine( - editor.buffer().lines.len().saturating_sub(1), + editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)), )), modit::Motion::Home => Action::Motion(Motion::Home), modit::Motion::Inside => { @@ -804,39 +812,41 @@ impl<'a> Edit for ViEditor<'a> { } modit::Motion::NextChar(find_c) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - if cursor.index < text.len() { - match text[cursor.index..] - .char_indices() - .filter(|&(i, c)| i > 0 && c == find_c) - .next() - { - Some((i, _)) => { - cursor.index += i; - editor.set_cursor(cursor); + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + if cursor.index < text.len() { + match text[cursor.index..] + .char_indices() + .filter(|&(i, c)| i > 0 && c == find_c) + .next() + { + Some((i, _)) => { + cursor.index += i; + } + None => {} } - None => {} } - } + }); + editor.set_cursor(cursor); return; } modit::Motion::NextCharTill(find_c) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - if cursor.index < text.len() { - let mut last_i = 0; - for (i, c) in text[cursor.index..].char_indices() { - if last_i > 0 && c == find_c { - cursor.index += last_i; - editor.set_cursor(cursor); - break; - } else { - last_i = i; + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + if cursor.index < text.len() { + let mut last_i = 0; + for (i, c) in text[cursor.index..].char_indices() { + if last_i > 0 && c == find_c { + cursor.index += last_i; + break; + } else { + last_i = i; + } } } - } + }); + editor.set_cursor(cursor); return; } modit::Motion::NextSearch => match &self.search_opt { @@ -848,53 +858,59 @@ impl<'a> Edit for ViEditor<'a> { }, modit::Motion::NextWordEnd(word) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - loop { - let text = buffer.lines[cursor.line].text(); - if cursor.index < text.len() { - cursor.index = WordIter::new(text, word) - .map(|(i, w)| { - i + w.char_indices().last().map(|(i, _)| i).unwrap_or(0) - }) - .find(|&i| i > cursor.index) - .unwrap_or(text.len()); - if cursor.index == text.len() { - // Try again, searching next line + editor.with_buffer(|buffer| { + loop { + let text = buffer.lines[cursor.line].text(); + if cursor.index < text.len() { + cursor.index = WordIter::new(text, word) + .map(|(i, w)| { + i + w + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap_or(0) + }) + .find(|&i| i > cursor.index) + .unwrap_or(text.len()); + if cursor.index == text.len() { + // Try again, searching next line + continue; + } + } else if cursor.line + 1 < buffer.lines.len() { + // Go to next line and rerun loop + cursor.line += 1; + cursor.index = 0; continue; } - } else if cursor.line + 1 < buffer.lines.len() { - // Go to next line and rerun loop - cursor.line += 1; - cursor.index = 0; - continue; + break; } - break; - } + }); editor.set_cursor(cursor); return; } modit::Motion::NextWordStart(word) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - loop { - let text = buffer.lines[cursor.line].text(); - if cursor.index < text.len() { - cursor.index = WordIter::new(text, word) - .map(|(i, _)| i) - .find(|&i| i > cursor.index) - .unwrap_or(text.len()); - if cursor.index == text.len() { - // Try again, searching next line + editor.with_buffer(|buffer| { + loop { + let text = buffer.lines[cursor.line].text(); + if cursor.index < text.len() { + cursor.index = WordIter::new(text, word) + .map(|(i, _)| i) + .find(|&i| i > cursor.index) + .unwrap_or(text.len()); + if cursor.index == text.len() { + // Try again, searching next line + continue; + } + } else if cursor.line + 1 < buffer.lines.len() { + // Go to next line and rerun loop + cursor.line += 1; + cursor.index = 0; continue; } - } else if cursor.line + 1 < buffer.lines.len() { - // Go to next line and rerun loop - cursor.line += 1; - cursor.index = 0; - continue; + break; } - break; - } + }); editor.set_cursor(cursor); return; } @@ -902,48 +918,50 @@ impl<'a> Edit for ViEditor<'a> { modit::Motion::PageUp => Action::Motion(Motion::PageUp), modit::Motion::PreviousChar(find_c) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - if cursor.index > 0 { - match text[..cursor.index] - .char_indices() - .filter(|&(_, c)| c == find_c) - .last() - { - Some((i, _)) => { - cursor.index = i; - editor.set_cursor(cursor); + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + if cursor.index > 0 { + match text[..cursor.index] + .char_indices() + .filter(|&(_, c)| c == find_c) + .last() + { + Some((i, _)) => { + cursor.index = i; + } + None => {} } - None => {} } - } + }); + editor.set_cursor(cursor); return; } modit::Motion::PreviousCharTill(find_c) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - let text = buffer.lines[cursor.line].text(); - if cursor.index > 0 { - match text[..cursor.index] - .char_indices() - .filter_map(|(i, c)| { - if c == find_c { - let end = i + c.len_utf8(); - if end < cursor.index { - return Some(end); + editor.with_buffer(|buffer| { + let text = buffer.lines[cursor.line].text(); + if cursor.index > 0 { + match text[..cursor.index] + .char_indices() + .filter_map(|(i, c)| { + if c == find_c { + let end = i + c.len_utf8(); + if end < cursor.index { + return Some(end); + } } + None + }) + .last() + { + Some(i) => { + cursor.index = i; } - None - }) - .last() - { - Some(i) => { - cursor.index = i; - editor.set_cursor(cursor); + None => {} } - None => {} } - } + }); + editor.set_cursor(cursor); return; } modit::Motion::PreviousSearch => match &self.search_opt { @@ -955,63 +973,71 @@ impl<'a> Edit for ViEditor<'a> { }, modit::Motion::PreviousWordEnd(word) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - loop { - let text = buffer.lines[cursor.line].text(); - if cursor.index > 0 { - cursor.index = WordIter::new(text, word) - .map(|(i, w)| { - i + w.char_indices().last().map(|(i, _)| i).unwrap_or(0) - }) - .filter(|&i| i < cursor.index) - .last() - .unwrap_or(0); - if cursor.index == 0 { - // Try again, searching previous line + editor.with_buffer(|buffer| { + loop { + let text = buffer.lines[cursor.line].text(); + if cursor.index > 0 { + cursor.index = WordIter::new(text, word) + .map(|(i, w)| { + i + w + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap_or(0) + }) + .filter(|&i| i < cursor.index) + .last() + .unwrap_or(0); + if cursor.index == 0 { + // Try again, searching previous line + continue; + } + } else if cursor.line > 0 { + // Go to previous line and rerun loop + cursor.line -= 1; + cursor.index = buffer.lines[cursor.line].text().len(); continue; } - } else if cursor.line > 0 { - // Go to previous line and rerun loop - cursor.line -= 1; - cursor.index = buffer.lines[cursor.line].text().len(); - continue; + break; } - break; - } + }); editor.set_cursor(cursor); return; } modit::Motion::PreviousWordStart(word) => { let mut cursor = editor.cursor(); - let buffer = editor.buffer(); - loop { - let text = buffer.lines[cursor.line].text(); - if cursor.index > 0 { - cursor.index = WordIter::new(text, word) - .map(|(i, _)| i) - .filter(|&i| i < cursor.index) - .last() - .unwrap_or(0); - if cursor.index == 0 { - // Try again, searching previous line + editor.with_buffer(|buffer| { + loop { + let text = buffer.lines[cursor.line].text(); + if cursor.index > 0 { + cursor.index = WordIter::new(text, word) + .map(|(i, _)| i) + .filter(|&i| i < cursor.index) + .last() + .unwrap_or(0); + if cursor.index == 0 { + // Try again, searching previous line + continue; + } + } else if cursor.line > 0 { + // Go to previous line and rerun loop + cursor.line -= 1; + cursor.index = buffer.lines[cursor.line].text().len(); continue; } - } else if cursor.line > 0 { - // Go to previous line and rerun loop - cursor.line -= 1; - cursor.index = buffer.lines[cursor.line].text().len(); - continue; + break; } - break; - } + }); editor.set_cursor(cursor); return; } modit::Motion::Right => Action::Motion(Motion::Right), modit::Motion::RightInLine => { let cursor = editor.cursor(); - let buffer = editor.buffer(); - if cursor.index < buffer.lines[cursor.line].text().len() { + if cursor.index + < editor + .with_buffer(|buffer| buffer.lines[cursor.line].text().len()) + { Action::Motion(Motion::Right) } else { return; @@ -1019,33 +1045,43 @@ impl<'a> Edit for ViEditor<'a> { } modit::Motion::ScreenHigh => { //TODO: is this efficient? - if let Some(first) = editor.buffer().layout_runs().next() { - Action::Motion(Motion::GotoLine(first.line_i)) + if let Some(line_i) = editor.with_buffer(|buffer| { + buffer.layout_runs().next().map(|first| first.line_i) + }) { + Action::Motion(Motion::GotoLine(line_i)) } else { return; } } modit::Motion::ScreenLow => { //TODO: is this efficient? - if let Some(last) = editor.buffer().layout_runs().last() { - Action::Motion(Motion::GotoLine(last.line_i)) + if let Some(line_i) = editor.with_buffer(|buffer| { + buffer.layout_runs().last().map(|last| last.line_i) + }) { + Action::Motion(Motion::GotoLine(line_i)) } else { return; } } modit::Motion::ScreenMiddle => { //TODO: is this efficient? - let mut layout_runs = editor.buffer().layout_runs(); - if let Some(first) = layout_runs.next() { - if let Some(last) = layout_runs.last() { - Action::Motion(Motion::GotoLine( - (last.line_i + first.line_i) / 2, - )) + let action_opt = editor.with_buffer(|buffer| { + let mut layout_runs = buffer.layout_runs(); + if let Some(first) = layout_runs.next() { + if let Some(last) = layout_runs.last() { + Some(Action::Motion(Motion::GotoLine( + (last.line_i + first.line_i) / 2, + ))) + } else { + None + } } else { - return; + None } - } else { - return; + }); + match action_opt { + Some(action) => action, + None => return, } } modit::Motion::Selection => { @@ -1062,7 +1098,7 @@ impl<'a> Edit for ViEditor<'a> { } } -impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> { +impl<'a, 'b, 'c> BorrowedWithFontSystem<'c, ViEditor<'a, 'b>> { /// Load text from a file, and also set syntax to the best option #[cfg(feature = "std")] pub fn load_text>(