From 7855dce09d457ea6d516f621fdaafaa7ce78d9d4 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 1 Nov 2023 13:31:53 -0600 Subject: [PATCH] Add indent action and tab width --- src/edit/editor.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++ src/edit/mod.rs | 24 +++++- src/edit/syntect.rs | 8 ++ src/edit/vi.rs | 8 ++ 4 files changed, 210 insertions(+), 3 deletions(-) diff --git a/src/edit/editor.rs b/src/edit/editor.rs index 44d0ed0838..f1c60748db 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -23,6 +23,7 @@ pub struct Editor { cursor_x_opt: Option, select_opt: Option, cursor_moved: bool, + tab_width: usize, } impl Editor { @@ -34,6 +35,7 @@ impl Editor { cursor_x_opt: None, select_opt: None, cursor_moved: false, + tab_width: 4, } } @@ -104,6 +106,21 @@ impl Edit for Editor { } } + fn tab_width(&self) -> usize { + self.tab_width + } + + fn set_tab_width(&mut self, tab_width: usize) { + // A tab width of 0 is not allowed + if tab_width == 0 { + return; + } + if self.tab_width != tab_width { + self.tab_width = tab_width; + self.buffer.set_redraw(true); + } + } + fn shape_as_needed(&mut self, font_system: &mut FontSystem) { if self.cursor_moved { self.buffer.shape_until_cursor(font_system, self.cursor); @@ -576,6 +593,162 @@ impl Edit for Editor { self.buffer.lines[self.cursor.line].append(old_line); } } + Action::Indent => { + // Get start and end of selection + let (start, end) = match self.select_opt { + Some(select) => match select.line.cmp(&self.cursor.line) { + cmp::Ordering::Greater => (self.cursor, select), + cmp::Ordering::Less => (select, self.cursor), + cmp::Ordering::Equal => { + /* select.line == self.cursor.line */ + if select.index < self.cursor.index { + (select, self.cursor) + } else { + /* select.index >= self.cursor.index */ + (self.cursor, select) + } + } + }, + None => (self.cursor, self.cursor), + }; + + // For every line in selection + for line_i in start.line..=end.line { + let line = &mut self.buffer.lines[line_i]; + + // Determine indexes of last indent and first character after whitespace + let mut after_whitespace = 0; + let mut required_indent = 0; + { + let text = line.text(); + for (count, (index, c)) in text.char_indices().enumerate() { + if !c.is_whitespace() { + after_whitespace = index; + required_indent = self.tab_width - (count % self.tab_width); + break; + } + } + } + + // No indent required (not possible?) + if required_indent == 0 { + continue; + } + + // Save line after last whitespace + let after = line.split_off(after_whitespace); + + // Add required indent + line.append(BufferLine::new( + " ".repeat(required_indent), + AttrsList::new(line.attrs_list().defaults()), + Shaping::Advanced, + )); + + // Re-add line after last whitespace + line.append(after); + + // Adjust cursor + if self.cursor.line == line_i { + if self.cursor.index >= after_whitespace { + self.cursor.index += required_indent; + self.cursor_moved = true; + } + } + + // Adjust selection + match self.select_opt { + Some(ref mut select) => { + if select.line == line_i { + if select.index >= after_whitespace { + select.index += required_indent; + } + } + } + None => {} + } + + // Request redraw + self.buffer.set_redraw(true); + } + } + Action::Unindent => { + // Get start and end of selection + let (start, end) = match self.select_opt { + Some(select) => match select.line.cmp(&self.cursor.line) { + cmp::Ordering::Greater => (self.cursor, select), + cmp::Ordering::Less => (select, self.cursor), + cmp::Ordering::Equal => { + /* select.line == self.cursor.line */ + if select.index < self.cursor.index { + (select, self.cursor) + } else { + /* select.index >= self.cursor.index */ + (self.cursor, select) + } + } + }, + None => (self.cursor, self.cursor), + }; + + // For every line in selection + for line_i in start.line..=end.line { + let line = &mut self.buffer.lines[line_i]; + + // Determine indexes of last indent and first character after whitespace + let mut last_indent = 0; + let mut after_whitespace = 0; + { + let text = line.text(); + for (count, (index, c)) in text.char_indices().enumerate() { + if !c.is_whitespace() { + after_whitespace = index; + break; + } + if count % self.tab_width == 0 { + last_indent = index; + } + } + } + + // No de-indent required + if last_indent == after_whitespace { + continue; + } + + // Save line after last whitespace + let after = line.split_off(after_whitespace); + + // Drop part of line after last indent + line.split_off(last_indent); + + // Re-add line after last whitespace + line.append(after); + + // Adjust cursor + if self.cursor.line == line_i { + if self.cursor.index > last_indent { + self.cursor.index -= after_whitespace - last_indent; + self.cursor_moved = true; + } + } + + // Adjust selection + match self.select_opt { + Some(ref mut select) => { + if select.line == line_i { + if select.index > last_indent { + select.index -= after_whitespace - last_indent; + } + } + } + None => {} + } + + // Request redraw + self.buffer.set_redraw(true); + } + } Action::Click { x, y } => { self.select_opt = None; diff --git a/src/edit/mod.rs b/src/edit/mod.rs index a3173a73e4..3a8041b76b 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -59,12 +59,24 @@ pub enum Action { Backspace, /// Delete text in front of cursor Delete, + // Indent text (typically Tab) + Indent, + // Unindent text (typically Shift+Tab) + Unindent, /// Mouse click at specified position - Click { x: i32, y: i32 }, + Click { + x: i32, + y: i32, + }, /// Mouse drag to specified position - Drag { x: i32, y: i32 }, + Drag { + x: i32, + y: i32, + }, /// Scroll specified number of lines - Scroll { lines: i32 }, + Scroll { + lines: i32, + }, /// Move cursor to previous word boundary PreviousWord, /// Move cursor to next word boundary @@ -113,6 +125,12 @@ pub trait Edit { /// Set the current selection position fn set_select_opt(&mut self, select_opt: Option); + /// Get the current tab width + fn tab_width(&self) -> usize; + + /// Set the current tab width. A tab_width of 0 is not allowed, and will be ignored + fn set_tab_width(&mut self, tab_width: usize); + /// Shape lines until scroll, after adjusting scroll if the cursor moved fn shape_as_needed(&mut self, font_system: &mut FontSystem); diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index 8eda3913c1..684af710b3 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -157,6 +157,14 @@ impl<'a> Edit for SyntaxEditor<'a> { self.editor.set_select_opt(select_opt); } + fn tab_width(&self) -> usize { + self.editor.tab_width() + } + + fn set_tab_width(&mut self, tab_width: usize) { + self.editor.set_tab_width(tab_width); + } + fn shape_as_needed(&mut self, font_system: &mut FontSystem) { #[cfg(feature = "std")] let now = std::time::Instant::now(); diff --git a/src/edit/vi.rs b/src/edit/vi.rs index f11ec1c80b..8733869bc9 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -156,6 +156,14 @@ impl<'a> Edit for ViEditor<'a> { self.editor.set_select_opt(select_opt); } + fn tab_width(&self) -> usize { + self.editor.tab_width() + } + + fn set_tab_width(&mut self, tab_width: usize) { + self.editor.set_tab_width(tab_width); + } + fn shape_as_needed(&mut self, font_system: &mut FontSystem) { self.editor.shape_as_needed(font_system); }