From c8ac92eab9bb6c1081dc8999208b1c49ed7f9673 Mon Sep 17 00:00:00 2001 From: lunargon Date: Fri, 20 Jun 2025 12:09:23 +0700 Subject: [PATCH 1/4] handle logic to auto open and delete brackets --- src/tui.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index 67f675c4582a..1df6edea8b49 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -547,7 +547,7 @@ impl Tui { // This causes us to ignore the keyboard input here. We need a way to inform the caller over // how much of the input text we actually processed in a single frame. Or perhaps we could use // the needs_settling logic? - if text.len() == 1 { + if !matches!(text, "{" | "[" | "(") && text.len() == 1 { let ch = text.as_bytes()[0]; input_keyboard = InputKey::from_ascii(ch as char) } @@ -2308,9 +2308,31 @@ impl<'a> Context<'a, '_> { } let mut write: &[u8] = &[]; - + if let Some(input) = &self.input_text { - write = input.as_bytes(); + // Handle auto-closing brackets + match *input { + "{" => { + self.handle_auto_closing_brackets(tb, "{", "}"); + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } + "[" => { + self.handle_auto_closing_brackets(tb, "[", "]"); + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } + "(" => { + self.handle_auto_closing_brackets(tb, "(", ")"); + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } + _ => { + write = input.as_bytes(); + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } + } } else if let Some(input) = &self.input_keyboard { let key = input.key(); let modifiers = input.modifiers(); @@ -2324,7 +2346,13 @@ impl<'a> Context<'a, '_> { } else { CursorMovement::Grapheme }; - tb.delete(granularity, -1); + + // Check if we should delete both brackets when cursor is between them + if matches!(granularity, CursorMovement::Grapheme) && self.should_delete_both_brackets(tb) { + self.delete_both_brackets(tb); + } else { + tb.delete(granularity, -1); + } } vk::TAB => { if single_line { @@ -3320,8 +3348,54 @@ impl<'a> Context<'a, '_> { self.block_begin("shortcut"); self.block_end(); } + self.attr_padding(Rect { left: 2, top: 0, right: 2, bottom: 0 }); } + + /// Handles auto-closing brackets by inserting both opening and closing brackets + /// and positioning the cursor between them. + fn handle_auto_closing_brackets(&mut self, tb: &mut TextBuffer, opening: &str, closing: &str) { + // Insert the opening bracket + tb.write_raw(opening.as_bytes()); + + // Insert the closing bracket + tb.write_raw(closing.as_bytes()); + + // Move cursor back to be between the brackets + // We need to move back by the length of the closing bracket + let new_offset = tb.cursor_logical_pos(); + tb.cursor_move_to_logical(Point { x: new_offset.x - closing.len() as CoordType, y: new_offset.y }); + } + + /// Checks if we should delete both opening and closing brackets + /// Returns true if we should delete both brackets, false for normal deletion + fn should_delete_both_brackets(&mut self, tb: &mut TextBuffer) -> bool { + // Get the text before and after the cursor + let text_before = tb.read_backward(1); + let text_after = tb.read_forward(1); + + // Check for common bracket pairs + if text_before.ends_with(b"(") && text_after.starts_with(b")") { + return true; + } + if text_before.ends_with(b"[") && text_after.starts_with(b"]") { + return true; + } + if text_before.ends_with(b"{") && text_after.starts_with(b"}") { + return true; + } + + false + } + + /// Deletes both opening and closing brackets when cursor is between them + fn delete_both_brackets(&mut self, tb: &mut TextBuffer) { + // Delete the closing bracket first (forward) + tb.delete(CursorMovement::Grapheme, 1); + // Then delete the opening bracket (backward) + tb.delete(CursorMovement::Grapheme, -1); + } + } /// See [`Tree::visit_all`]. From 2e95f2e5b8394ce166b7214456dd5978b1c991cf Mon Sep 17 00:00:00 2001 From: lunargon Date: Fri, 20 Jun 2025 12:17:55 +0700 Subject: [PATCH 2/4] remove check not useful --- src/tui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui.rs b/src/tui.rs index 1df6edea8b49..0fc3f16cf4e2 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -547,7 +547,7 @@ impl Tui { // This causes us to ignore the keyboard input here. We need a way to inform the caller over // how much of the input text we actually processed in a single frame. Or perhaps we could use // the needs_settling logic? - if !matches!(text, "{" | "[" | "(") && text.len() == 1 { + if text.len() == 1 { let ch = text.as_bytes()[0]; input_keyboard = InputKey::from_ascii(ch as char) } From 23518f583692eb956769f996a8ed28678a13792c Mon Sep 17 00:00:00 2001 From: lunargon Date: Fri, 20 Jun 2025 12:29:50 +0700 Subject: [PATCH 3/4] improve auto delete both open and close --- src/tui.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index 0fc3f16cf4e2..3e738e34cf54 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -3368,11 +3368,15 @@ impl<'a> Context<'a, '_> { } /// Checks if we should delete both opening and closing brackets + /// /// Returns true if we should delete both brackets, false for normal deletion fn should_delete_both_brackets(&mut self, tb: &mut TextBuffer) -> bool { - // Get the text before and after the cursor - let text_before = tb.read_backward(1); - let text_after = tb.read_forward(1); + // Get the current cursor offset + let cursor_pos = tb.cursor_logical_pos(); + + // Read text before cursor (try to get at least 1 character) + let text_before = tb.read_backward(cursor_pos.x as usize); + let text_after = tb.read_forward(cursor_pos.x as usize); // Check for common bracket pairs if text_before.ends_with(b"(") && text_after.starts_with(b")") { From 41b94086e3de78cc1a96f9c55f6a47b3fe011bf5 Mon Sep 17 00:00:00 2001 From: lunargon Date: Wed, 25 Jun 2025 10:29:15 +0700 Subject: [PATCH 4/4] clear part not use --- src/tui.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index 3e738e34cf54..e39d0d022229 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -2315,22 +2315,18 @@ impl<'a> Context<'a, '_> { "{" => { self.handle_auto_closing_brackets(tb, "{", "}"); tc.preferred_column = tb.cursor_visual_pos().x; - make_cursor_visible = true; } "[" => { self.handle_auto_closing_brackets(tb, "[", "]"); tc.preferred_column = tb.cursor_visual_pos().x; - make_cursor_visible = true; } "(" => { self.handle_auto_closing_brackets(tb, "(", ")"); tc.preferred_column = tb.cursor_visual_pos().x; - make_cursor_visible = true; } _ => { write = input.as_bytes(); tc.preferred_column = tb.cursor_visual_pos().x; - make_cursor_visible = true; } } } else if let Some(input) = &self.input_keyboard {