From d0111a524d682f5d1ee222ef0ebce93e894dedca Mon Sep 17 00:00:00 2001 From: wiru Date: Sun, 12 May 2024 00:23:11 -0300 Subject: [PATCH] feat: support {} to jump to paragraphs --- config/src/config.rs | 2 + config/src/default_config.rs | 2 + reqtui/src/text_object/text_object.rs | 91 +++++++++++++------ tui/src/components/api_explorer/req_editor.rs | 18 ++++ 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index 4cddec0..b75ca8e 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -43,6 +43,8 @@ pub enum Action { InsertLine, DeletePreviousChar, JumpToClosing, + JumpToEmptyLineBelow, + JumpToEmptyLineAbove, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/config/src/default_config.rs b/config/src/default_config.rs index fc78ce8..6fb32e5 100644 --- a/config/src/default_config.rs +++ b/config/src/default_config.rs @@ -34,6 +34,8 @@ pub static DEFAULT_CONFIG: &str = r##" "S-W" = "MoveAfterWhitespace" "S-X" = "DeletePreviousNonWrapping" "%" = "JumpToClosing" +"{" = "JumpToEmptyLineAbove" +"}" = "JumpToEmptyLineBelow" [editor_keys.normal.d] "w" = "DeleteWord" diff --git a/reqtui/src/text_object/text_object.rs b/reqtui/src/text_object/text_object.rs index 29c2045..9afdebe 100644 --- a/reqtui/src/text_object/text_object.rs +++ b/reqtui/src/text_object/text_object.rs @@ -256,6 +256,37 @@ impl TextObject { (curr_col, curr_row) } + pub fn find_empty_line_above(&self, cursor: &Cursor) -> usize { + let mut new_row = cursor.row().saturating_sub(1); + + while let Some(line) = self.content.get_line(new_row) { + if line.to_string().eq(&self.line_break.to_string()) { + break; + } + + if new_row.eq(&0) { + break; + } + new_row = new_row.saturating_sub(1); + } + + new_row + } + + pub fn find_empty_line_below(&self, cursor: &Cursor) -> usize { + let mut new_row = cursor.row().add(1); + let len_lines = self.len_lines(); + + while let Some(line) = self.content.get_line(new_row) { + if line.to_string().eq(&self.line_break.to_string()) { + break; + } + new_row = new_row.add(1); + } + + usize::min(new_row, len_lines.saturating_sub(1)) + } + pub fn len_lines(&self) -> usize { self.content.len_lines() } @@ -337,7 +368,6 @@ impl TextObject { let start_idx = self.content.line_to_char(cursor.row()).add(cursor.col()); let mut combinations = HashMap::new(); let pairs = [('<', '>'), ('(', ')'), ('[', ']'), ('{', '}')]; - pairs.iter().for_each(|pair| { combinations.insert(pair.0, pair.1); combinations.insert(pair.1, pair.0); @@ -345,8 +375,7 @@ impl TextObject { let mut look_forward = true; let mut token_to_search = char::default(); - let mut curr_open = 0; - let mut walked = 0; + let (mut curr_open, mut walked) = (0, 0); if let Some(initial_char) = self.content.get_char(start_idx) { match initial_char { @@ -362,38 +391,48 @@ impl TextObject { _ => {} } - if look_forward { - for char in self.content.chars_at(start_idx.add(1)) { - char.eq(combinations.get(&token_to_search).unwrap()) - .then(|| curr_open = curr_open.add(1)); - - char.eq(&token_to_search) - .then(|| curr_open = curr_open.sub(1)); - - walked = walked.add(1); - - if curr_open.eq(&0) { - break; + let range = if look_forward { + start_idx.add(1)..self.content.len_chars() + } else { + 0..start_idx + }; + + for i in range { + let char = self + .content + .get_char(if look_forward { + i + } else { + start_idx - walked - 1 + }) + .unwrap_or_default(); + + if token_to_search.eq(&char::default()) { + if !is_opening_token(char) { + walked = walked.add(1); + continue; } + token_to_search = *combinations.get(&char).unwrap(); } - } else { - for _ in (0..start_idx.saturating_sub(1)).rev() { - let char = self.content.char(start_idx.saturating_sub(walked)); - char.eq(combinations.get(&token_to_search).unwrap()) - .then(|| curr_open = curr_open.add(1)); - char.eq(&token_to_search) - .then(|| curr_open = curr_open.sub(1)); + char.eq(combinations.get(&token_to_search).unwrap()) + .then(|| curr_open = curr_open.add(1)); - walked = walked.add(1); + char.eq(&token_to_search) + .then(|| curr_open = curr_open.sub(1)); - if curr_open.eq(&0) { - break; - } + walked = walked.add(1); + + if curr_open.eq(&0) { + break; } } } + if curr_open.gt(&0) { + return (cursor.col(), cursor.row()); + } + if look_forward { let curr_row = self.content.char_to_line(start_idx.add(walked)); let curr_row_start = self.content.line_to_char(curr_row); diff --git a/tui/src/components/api_explorer/req_editor.rs b/tui/src/components/api_explorer/req_editor.rs index 29f82ca..d4bf005 100644 --- a/tui/src/components/api_explorer/req_editor.rs +++ b/tui/src/components/api_explorer/req_editor.rs @@ -318,6 +318,8 @@ impl<'re> ReqEditor<'re> { Action::InsertLineBelow => self.insert_line_below(), Action::InsertLineAbove => self.insert_line_above(), Action::JumpToClosing => self.jump_to_opposing_token(), + Action::JumpToEmptyLineBelow => self.jump_to_empty_line_below(), + Action::JumpToEmptyLineAbove => self.jump_to_empty_line_above(), Action::Undo => todo!(), Action::FindNext => todo!(), Action::FindPrevious => todo!(), @@ -354,6 +356,22 @@ impl<'re> ReqEditor<'re> { .then(|| self.col_scroll = self.col_scroll.saturating_sub(1)); } + fn jump_to_empty_line_below(&mut self) { + let new_row = self.body.find_empty_line_below(&self.cursor); + self.cursor.move_to_row(new_row); + self.maybe_scroll_view(); + let line_len = self.body.line_len(self.cursor.row()); + self.cursor.maybe_snap_to_col(line_len); + } + + fn jump_to_empty_line_above(&mut self) { + let new_row = self.body.find_empty_line_above(&self.cursor); + self.cursor.move_to_row(new_row); + self.maybe_scroll_view(); + let line_len = self.body.line_len(self.cursor.row()); + self.cursor.maybe_snap_to_col(line_len); + } + fn page_up(&mut self) { let half_height = self.layout.content_pane.height.saturating_sub(2).div(2); self.cursor.move_up(half_height.into());