From 277b071336268e2fa241e535ed49ce87a0842d54 Mon Sep 17 00:00:00 2001 From: sharpchen Date: Wed, 23 Jul 2025 07:00:58 +0800 Subject: [PATCH] Fix that ViFindBrace doesn't search for brace when current char is not a brace --- PSReadLine/Movement.vi.cs | 23 ++++++++++++++++- test/MovementTest.VI.cs | 52 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/PSReadLine/Movement.vi.cs b/PSReadLine/Movement.vi.cs index 011c146d9..01bad815e 100644 --- a/PSReadLine/Movement.vi.cs +++ b/PSReadLine/Movement.vi.cs @@ -254,7 +254,28 @@ private int ViFindBrace(int i) case ')': return ViFindBackward(i, '(', withoutPassing: ')'); default: - return i; + ReadOnlySpan parenthese = stackalloc char[] { '{', '}', '(', ')', '[', ']' }; + int nextParen = i; + // find next of any kind of paren + for (; nextParen < _buffer.Length; nextParen++) + for (int idx = 0; idx < parenthese.Length; idx++) + if (parenthese[idx] == _buffer[nextParen]) goto Outer; + + Outer: + int match = _buffer[nextParen] switch + { + // if next is opening, find forward + '{' => ViFindForward(nextParen, '}', withoutPassing: '{'), + '[' => ViFindForward(nextParen, ']', withoutPassing: '['), + '(' => ViFindForward(nextParen, ')', withoutPassing: '('), + // if next is closing, find backward + '}' => ViFindBackward(nextParen, '{', withoutPassing: '}'), + ']' => ViFindBackward(nextParen, '[', withoutPassing: ']'), + ')' => ViFindBackward(nextParen, '(', withoutPassing: ')'), + _ => nextParen + }; + + return match == nextParen ? i : match; } } diff --git a/test/MovementTest.VI.cs b/test/MovementTest.VI.cs index 3c4573ebe..30d4d0e95 100644 --- a/test/MovementTest.VI.cs +++ b/test/MovementTest.VI.cs @@ -1,4 +1,5 @@ -using Xunit; +using System.Collections.Generic; +using Xunit; namespace Test { @@ -370,7 +371,7 @@ public void ViGlobMovement_EmptyBuffer_Defect1195() TestSetup(KeyMode.Vi); TestMustDing("", Keys( - _.Escape, "W" + _.Escape, "W" )); } @@ -454,6 +455,53 @@ public void ViGotoBrace() )); } + // tests when cursor not on any paren + foreach (var (opening, closing) in new[] { ('(', ')'), ('{', '}'), ('[', ']') }) + { + // closing paren with backward match + string input1 = $"0{opening}2{opening}4foo{closing}"; + Test(input1, Keys( + input1, + CheckThat(() => AssertCursorLeftIs(9)), + _.Escape, CheckThat(() => AssertCursorLeftIs(8)), + "0ff", CheckThat(() => AssertCursorLeftIs(5)), + _.Percent, CheckThat(() => AssertCursorLeftIs(3)), + _.Percent, CheckThat(() => AssertCursorLeftIs(8)) + )); + + // closing paren without backward match + string input2 = $"0]2)4foo{closing}"; + Test(input2, Keys( + input2, + CheckThat(() => AssertCursorLeftIs(9)), + _.Escape, CheckThat(() => AssertCursorLeftIs(8)), + "0ff", CheckThat(() => AssertCursorLeftIs(5)), + _.Percent, CheckThat(() => AssertCursorLeftIs(5)), // stay still + _.Percent, CheckThat(() => AssertCursorLeftIs(5)) + )); + + // opening paren with forward match + string input3 = $"0{opening}2foo6{closing}"; + Test(input3, Keys( + input3, + CheckThat(() => AssertCursorLeftIs(8)), + _.Escape, CheckThat(() => AssertCursorLeftIs(7)), + "0ff", CheckThat(() => AssertCursorLeftIs(3)), + _.Percent, CheckThat(() => AssertCursorLeftIs(1)), + _.Percent, CheckThat(() => AssertCursorLeftIs(7)) + )); + // opening paren without forward match + string input4 = $"0)2]4foo{opening}("; + Test(input4, Keys( + input4, + CheckThat(() => AssertCursorLeftIs(10)), + _.Escape, CheckThat(() => AssertCursorLeftIs(9)), + "0ff", CheckThat(() => AssertCursorLeftIs(5)), + _.Percent, CheckThat(() => AssertCursorLeftIs(5)), // stay still + _.Percent, CheckThat(() => AssertCursorLeftIs(5)) + )); + } + // <%> with empty text buffer should work fine. Test("", Keys( _.Escape, _.Percent,