From 0bc531696ef991ff13555a111cdba4cd98fab3aa Mon Sep 17 00:00:00 2001 From: wiru Date: Sat, 11 May 2024 21:43:36 -0300 Subject: [PATCH] feat: % go to the closing/opening token --- config/src/config.rs | 1 + config/src/default_config.rs | 2 +- reqtui/src/text_object/text_object.rs | 87 ++++++++++++++++++- tui/src/components/api_explorer/req_editor.rs | 8 ++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index df6106a..4cddec0 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -42,6 +42,7 @@ pub enum Action { InsertTab, InsertLine, DeletePreviousChar, + JumpToClosing, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/config/src/default_config.rs b/config/src/default_config.rs index bbefce8..fc78ce8 100644 --- a/config/src/default_config.rs +++ b/config/src/default_config.rs @@ -27,13 +27,13 @@ pub static DEFAULT_CONFIG: &str = r##" "S-O" = "InsertLineAbove" "p" = "PasteBelow" "a" = "InsertAhead" -#"/" = { EnterMode = "Search" } "i" = { EnterMode = "Insert" } "S-I" = ["MoveToLineStart", { EnterMode = "Insert" }] "S-A" = "InsertAtEOL" "S-B" = "MoveAfterWhitespaceReverse" "S-W" = "MoveAfterWhitespace" "S-X" = "DeletePreviousNonWrapping" +"%" = "JumpToClosing" [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 2517b91..29c2045 100644 --- a/reqtui/src/text_object/text_object.rs +++ b/reqtui/src/text_object/text_object.rs @@ -1,4 +1,7 @@ -use std::ops::{Add, Sub}; +use std::{ + collections::HashMap, + ops::{Add, Sub}, +}; use crate::text_object::cursor::Cursor; use ropey::Rope; @@ -329,6 +332,80 @@ impl TextObject { let curr_line = self.content.line_to_char(cursor.row()); self.content.insert(curr_line, &self.line_break.to_string()); } + + pub fn find_oposing_token(&mut self, cursor: &Cursor) -> (usize, usize) { + 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); + }); + + let mut look_forward = true; + let mut token_to_search = char::default(); + let mut curr_open = 0; + let mut walked = 0; + + if let Some(initial_char) = self.content.get_char(start_idx) { + match initial_char { + c if is_opening_token(c) => { + token_to_search = *combinations.get(&c).unwrap(); + curr_open = curr_open.add(1); + } + c if is_closing_token(c) => { + token_to_search = *combinations.get(&c).unwrap(); + curr_open = curr_open.add(1); + look_forward = false; + } + _ => {} + } + + 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; + } + } + } 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)); + + walked = walked.add(1); + + if curr_open.eq(&0) { + break; + } + } + } + } + + 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); + let curr_col = start_idx.add(walked).saturating_sub(curr_row_start); + (curr_col, curr_row) + } else { + let curr_row = self.content.char_to_line(start_idx.sub(walked)); + let curr_row_start = self.content.line_to_char(curr_row); + let curr_col = start_idx.sub(walked).sub(curr_row_start); + (curr_col, curr_row) + } + } } impl std::fmt::Display for TextObject { @@ -336,3 +413,11 @@ impl std::fmt::Display for TextObject { f.write_str(&self.content.to_string()) } } + +fn is_opening_token(char: char) -> bool { + matches!(char, '(' | '{' | '[' | '<') +} + +fn is_closing_token(char: char) -> bool { + matches!(char, ')' | '}' | ']' | '>') +} diff --git a/tui/src/components/api_explorer/req_editor.rs b/tui/src/components/api_explorer/req_editor.rs index 167054e..29f82ca 100644 --- a/tui/src/components/api_explorer/req_editor.rs +++ b/tui/src/components/api_explorer/req_editor.rs @@ -317,6 +317,7 @@ impl<'re> ReqEditor<'re> { Action::PreviousWord => self.move_to_prev_word(), Action::InsertLineBelow => self.insert_line_below(), Action::InsertLineAbove => self.insert_line_above(), + Action::JumpToClosing => self.jump_to_opposing_token(), Action::Undo => todo!(), Action::FindNext => todo!(), Action::FindPrevious => todo!(), @@ -361,6 +362,13 @@ impl<'re> ReqEditor<'re> { self.cursor.maybe_snap_to_col(line_len); } + fn jump_to_opposing_token(&mut self) { + let (new_col, new_row) = self.body.find_oposing_token(&self.cursor); + self.cursor.move_to_col(new_col); + self.cursor.move_to_row(new_row); + self.maybe_scroll_view(); + } + fn page_down(&mut self) { let half_height = self.layout.content_pane.height.saturating_sub(2).div(2); let len_lines = self.body.len_lines().saturating_sub(1);