diff --git a/reqtui/src/text_object/text_object.rs b/reqtui/src/text_object/text_object.rs index 582e84a..0c5b993 100644 --- a/reqtui/src/text_object/text_object.rs +++ b/reqtui/src/text_object/text_object.rs @@ -49,6 +49,17 @@ impl TextObject { self.content.insert_char(col_offset, c); } + pub fn erase_backwards_up_to_line_start(&mut self, cursor: &Cursor) { + if cursor.col().eq(&0) { + return; + } + let line = self.content.line_to_char(cursor.row()); + let col_offset = line + cursor.col(); + self.content + .try_remove(col_offset.saturating_sub(1)..col_offset) + .ok(); + } + pub fn erase_previous_char(&mut self, cursor: &Cursor) { let line = self.content.line_to_char(cursor.row()); let col_offset = line + cursor.col(); diff --git a/tui/src/components/api_explorer/api_explorer.rs b/tui/src/components/api_explorer/api_explorer.rs index 7b18fa3..dba8147 100644 --- a/tui/src/components/api_explorer/api_explorer.rs +++ b/tui/src/components/api_explorer/api_explorer.rs @@ -386,6 +386,9 @@ impl Eventful for ApiExplorer<'_> { (PaneFocus::Preview, None, KeyModifiers::NONE) => { self.focused_pane = PaneFocus::Sidebar } + (PaneFocus::Editor, Some(_), KeyModifiers::NONE) => { + self.editor.handle_key_event(key_event)?; + } (PaneFocus::Preview, Some(_), _) => { self.handle_preview_key_event(key_event)?; } diff --git a/tui/src/components/api_explorer/req_editor.rs b/tui/src/components/api_explorer/req_editor.rs index 2cf53c2..e6d613c 100644 --- a/tui/src/components/api_explorer/req_editor.rs +++ b/tui/src/components/api_explorer/req_editor.rs @@ -285,12 +285,10 @@ impl Eventful for ReqEditor<'_> { (EditorMode::Insert, KeyCode::Char(c), KeyModifiers::NONE) => { self.body.insert_char(c, &self.cursor); self.cursor.move_right(1); - self.tree = HIGHLIGHTER.write().unwrap().parse(&self.body.to_string()); } (EditorMode::Insert, KeyCode::Enter, KeyModifiers::NONE) => { self.body.insert_char('\n', &self.cursor); self.cursor.move_to_newline_start(); - self.tree = HIGHLIGHTER.write().unwrap().parse(&self.body.to_string()); } (EditorMode::Insert, KeyCode::Backspace, KeyModifiers::NONE) => { match (self.cursor.col(), self.cursor.row()) { @@ -306,16 +304,18 @@ impl Eventful for ReqEditor<'_> { self.cursor .move_to_col(current_line.len().saturating_sub(3)); - - self.tree = HIGHLIGHTER.write().unwrap().parse(&self.body.to_string()); } (_, _) => { self.body.erase_previous_char(&self.cursor); self.cursor.move_left(1); - self.tree = HIGHLIGHTER.write().unwrap().parse(&self.body.to_string()); } } } + (EditorMode::Insert, KeyCode::Tab, KeyModifiers::NONE) => { + self.body.insert_char(' ', &self.cursor); + self.body.insert_char(' ', &self.cursor); + self.cursor.move_right(2); + } (EditorMode::Insert, KeyCode::Esc, KeyModifiers::NONE) => { let current_line_len = self.body.line_len(self.cursor.row()); if self.cursor.col().ge(¤t_line_len) { @@ -366,6 +366,10 @@ impl Eventful for ReqEditor<'_> { (EditorMode::Normal, KeyCode::Char('x'), KeyModifiers::NONE) => { self.body.erase_current_char(&self.cursor); } + (EditorMode::Normal, KeyCode::Char('X'), KeyModifiers::SHIFT) => { + self.body.erase_backwards_up_to_line_start(&self.cursor); + self.cursor.move_left(1); + } (EditorMode::Normal, KeyCode::Char('a'), KeyModifiers::NONE) => { let current_line_len = self.body.line_len(self.cursor.row()); if current_line_len.gt(&0) { @@ -391,6 +395,7 @@ impl Eventful for ReqEditor<'_> { _ => {} }; + self.tree = HIGHLIGHTER.write().unwrap().parse(&self.body.to_string()); self.styled_display = build_styled_content(&self.body.to_string(), self.tree.as_ref(), self.colors); diff --git a/tui/src/utils.rs b/tui/src/utils.rs index 7e18ad1..d9e9da2 100644 --- a/tui/src/utils.rs +++ b/tui/src/utils.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use ratatui::{ style::Stylize, text::{Line, Span}, @@ -6,7 +8,7 @@ use reqtui::syntax::highlighter::HIGHLIGHTER; use tree_sitter::Tree; fn is_endline(c: char) -> bool { - matches!(c, '\n') + matches!(c, '\n' | '\r') } pub fn build_styled_content( @@ -24,7 +26,15 @@ pub fn build_styled_content( let mut current_token = String::default(); let mut current_capture = highlights.pop_front(); + // when handling CRLF line endings, we skip the second 'newline' to prevent an empty line + let mut skip_next = false; + for (i, c) in content.chars().enumerate() { + if skip_next { + skip_next = false; + continue; + } + if let Some(ref capture) = current_capture { if i == capture.start && current_token.is_empty() { current_token.push(c); @@ -37,12 +47,19 @@ pub fn build_styled_content( continue; } if i == capture.end && is_endline(c) { + current_token.push(c); current_line.push(Span::styled(current_token.clone(), capture.style)); styled_lines.push(current_line.clone().into()); current_token.clear(); current_line.clear(); current_capture = highlights.pop_front(); + + content + .chars() + .nth(i.add(1)) + .and_then(|next| is_endline(next).then(|| skip_next = true)); + continue; } @@ -55,11 +72,18 @@ pub fn build_styled_content( } if is_endline(c) { + current_token.push(c); current_line.push(Span::styled(current_token.clone(), capture.style)); styled_lines.push(current_line.clone().into()); current_token.clear(); current_line.clear(); + + content + .chars() + .nth(i.add(1)) + .and_then(|next| is_endline(next).then(|| skip_next = true)); + continue; } @@ -80,6 +104,12 @@ pub fn build_styled_content( current_token.clear(); current_line.clear(); + + content + .chars() + .nth(i.add(1)) + .and_then(|next| is_endline(next).then(|| skip_next = true)); + continue; }