Skip to content

Commit 2984546

Browse files
authored
Fix ViFindBrace to search for brace the same as vim when current char is not a brace (#4862)
1 parent caa53f8 commit 2984546

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

PSReadLine/Movement.vi.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,32 @@ private int ViFindBrace(int i)
254254
case ')':
255255
return ViFindBackward(i, '(', withoutPassing: ')');
256256
default:
257-
return i;
257+
ReadOnlySpan<char> parentheses = stackalloc char[] { '{', '}', '(', ')', '[', ']' };
258+
int nextParen = i;
259+
// find next of any kind of paren
260+
for (; nextParen < _buffer.Length; nextParen++)
261+
for (int idx = 0; idx < parentheses.Length; idx++)
262+
if (parentheses[idx] == _buffer[nextParen]) goto Outer;
263+
264+
// if not found, nextParen could exceed the range
265+
if (nextParen >= _buffer.Length)
266+
return i;
267+
268+
Outer:
269+
int match = _buffer[nextParen] switch
270+
{
271+
// if next is opening, find forward
272+
'{' => ViFindForward(nextParen, '}', withoutPassing: '{'),
273+
'[' => ViFindForward(nextParen, ']', withoutPassing: '['),
274+
'(' => ViFindForward(nextParen, ')', withoutPassing: '('),
275+
// if next is closing, find backward
276+
'}' => ViFindBackward(nextParen, '{', withoutPassing: '}'),
277+
']' => ViFindBackward(nextParen, '[', withoutPassing: ']'),
278+
')' => ViFindBackward(nextParen, '(', withoutPassing: ')'),
279+
_ => nextParen
280+
};
281+
282+
return match == nextParen ? i : match;
258283
}
259284
}
260285

test/MovementTest.VI.cs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ public void ViGlobMovement_EmptyBuffer_Defect1195()
370370
TestSetup(KeyMode.Vi);
371371

372372
TestMustDing("", Keys(
373-
_.Escape, "W"
373+
_.Escape, "W"
374374
));
375375
}
376376

@@ -423,6 +423,11 @@ public void ViCursorMovement()
423423
[SkippableFact]
424424
public void ViGotoBrace()
425425
{
426+
// NOTE: When the input has unmatched braces, in order to avoid an
427+
// exception caused by AcceptLineImpl waiting for incomplete input,
428+
// the test needs to end with the Vi command "ddi" and assert that
429+
// the result is an empty string.
430+
426431
TestSetup(KeyMode.Vi);
427432

428433
Test("0[2(4{6]8)a}c", Keys(
@@ -450,10 +455,60 @@ public void ViGotoBrace()
450455
CheckThat(() => AssertCursorLeftIs(4)),
451456
_.Percent,
452457
CheckThat(() => AssertCursorLeftIs(4)),
453-
"ddi"
458+
"ddi" // Unmatched brace
454459
));
455460
}
456461

462+
// Tests when the cursor is not on any paren
463+
foreach (var (opening, closing) in new[] { ('(', ')'), ('{', '}'), ('[', ']') })
464+
{
465+
// Closing paren with backward match
466+
string input1 = $"0{opening}2{opening}4foo{closing}";
467+
Test("", Keys(
468+
input1,
469+
CheckThat(() => AssertCursorLeftIs(9)),
470+
_.Escape, CheckThat(() => AssertCursorLeftIs(8)),
471+
"0ff", CheckThat(() => AssertCursorLeftIs(5)),
472+
_.Percent, CheckThat(() => AssertCursorLeftIs(3)),
473+
_.Percent, CheckThat(() => AssertCursorLeftIs(8)),
474+
"ddi" // Unmatched closing brace
475+
));
476+
477+
// Closing paren without backward match
478+
string input2 = $"0]2)4foo{closing}";
479+
TestMustDing(input2, Keys(
480+
input2,
481+
CheckThat(() => AssertCursorLeftIs(9)),
482+
_.Escape, CheckThat(() => AssertCursorLeftIs(8)),
483+
"0ff", CheckThat(() => AssertCursorLeftIs(5)),
484+
_.Percent, CheckThat(() => AssertCursorLeftIs(5)), // stay still
485+
_.Percent, CheckThat(() => AssertCursorLeftIs(5))
486+
));
487+
488+
// Opening paren with forward match
489+
string input3 = $"0{opening}2foo6{closing}";
490+
Test(input3, Keys(
491+
input3,
492+
CheckThat(() => AssertCursorLeftIs(8)),
493+
_.Escape, CheckThat(() => AssertCursorLeftIs(7)),
494+
"0ff", CheckThat(() => AssertCursorLeftIs(3)),
495+
_.Percent, CheckThat(() => AssertCursorLeftIs(1)),
496+
_.Percent, CheckThat(() => AssertCursorLeftIs(7))
497+
));
498+
499+
// Opening paren without forward match
500+
string input4 = $"0)2]4foo{opening}(";
501+
TestMustDing("", Keys(
502+
input4,
503+
CheckThat(() => AssertCursorLeftIs(10)),
504+
_.Escape, CheckThat(() => AssertCursorLeftIs(9)),
505+
"0ff", CheckThat(() => AssertCursorLeftIs(5)),
506+
_.Percent, CheckThat(() => AssertCursorLeftIs(5)), // stay still
507+
_.Percent, CheckThat(() => AssertCursorLeftIs(5)),
508+
"ddi" // Unmatched brace
509+
));
510+
}
511+
457512
// <%> with empty text buffer should work fine.
458513
Test("", Keys(
459514
_.Escape, _.Percent,

0 commit comments

Comments
 (0)