Skip to content

Commit 2ba2b7c

Browse files
committed
Improve testability using StringBuilder extensions classes.
1 parent 1f6c5c0 commit 2ba2b7c

8 files changed

+159
-22
lines changed

Diff for: PSReadLine/Position.cs

+1-11
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,12 @@ private static int GetFirstNonBlankOfLogicalLinePos(int current)
114114

115115
var newCurrent = beginningOfLine;
116116

117-
while (IsVisibleBlank(newCurrent))
117+
while (_singleton._buffer.IsVisibleBlank(newCurrent))
118118
{
119119
newCurrent++;
120120
}
121121

122122
return newCurrent;
123123
}
124-
125-
private static bool IsVisibleBlank(int newCurrent)
126-
{
127-
var c = _singleton._buffer[newCurrent];
128-
129-
// [:blank:] of vim's pattern matching behavior
130-
// defines blanks as SPACE and TAB characters.
131-
132-
return c == ' ' || c == '\t';
133-
}
134124
}
135125
}

Diff for: PSReadLine/Prediction.Views.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -743,12 +743,12 @@ internal int FindForwardSuggestionWordPoint(int currentIndex, string wordDelimit
743743
}
744744

745745
int i = currentIndex;
746-
if (!_singleton.InWord(_suggestionText[i], wordDelimiters))
746+
if (!Character.IsInWord(_suggestionText[i], wordDelimiters))
747747
{
748748
// Scan to end of current non-word region
749749
while (++i < _suggestionText.Length)
750750
{
751-
if (_singleton.InWord(_suggestionText[i], wordDelimiters))
751+
if (Character.IsInWord(_suggestionText[i], wordDelimiters))
752752
{
753753
break;
754754
}
@@ -759,7 +759,7 @@ internal int FindForwardSuggestionWordPoint(int currentIndex, string wordDelimit
759759
{
760760
while (++i < _suggestionText.Length)
761761
{
762-
if (!_singleton.InWord(_suggestionText[i], wordDelimiters))
762+
if (!Character.IsInWord(_suggestionText[i], wordDelimiters))
763763
{
764764
if (_suggestionText[i] == ' ')
765765
{

Diff for: PSReadLine/StringBuilderCharacterExtensions.cs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Text;
2+
3+
namespace Microsoft.PowerShell
4+
{
5+
internal static partial class StringBuilderExtensions
6+
{
7+
/// <summary>
8+
/// Returns true if the character at the specified position is a visible whitespace character.
9+
/// A blank character is defined as a SPACE or a TAB.
10+
/// </summary>
11+
/// <param name="buffer"></param>
12+
/// <param name="i"></param>
13+
/// <returns></returns>
14+
public static bool IsVisibleBlank(this StringBuilder buffer, int i)
15+
{
16+
var c = buffer[i];
17+
18+
// [:blank:] of vim's pattern matching behavior
19+
// defines blanks as SPACE and TAB characters.
20+
21+
return c == ' ' || c == '\t';
22+
}
23+
24+
/// <summary>
25+
/// Returns true if the character at the specified position is
26+
/// not present in a list of word-delimiter characters.
27+
/// </summary>
28+
/// <param name="buffer"></param>
29+
/// <param name="i"></param>
30+
/// <param name="wordDelimiters"></param>
31+
/// <returns></returns>
32+
public static bool InWord(this StringBuilder buffer, int i, string wordDelimiters)
33+
{
34+
return Character.IsInWord(buffer[i], wordDelimiters);
35+
}
36+
37+
/// <summary>
38+
/// Returns true if the character at the specified position is
39+
/// a unicode whitespace character.
40+
/// </summary>
41+
/// <param name="buffer"></param>
42+
/// <param name="i"></param>
43+
/// <returns></returns>
44+
public static bool IsWhiteSpace(this StringBuilder buffer, int i)
45+
{
46+
// Treat just beyond the end of buffer as whitespace because
47+
// it looks like whitespace to the user even though they haven't
48+
// entered a character yet.
49+
return i >= buffer.Length || char.IsWhiteSpace(buffer[i]);
50+
}
51+
}
52+
53+
public static class Character
54+
{
55+
/// <summary>
56+
/// Returns true if the character not present in a list of word-delimiter characters.
57+
/// </summary>
58+
/// <param name="c"></param>
59+
/// <param name="wordDelimiters"></param>
60+
/// <returns></returns>
61+
public static bool IsInWord(char c, string wordDelimiters)
62+
{
63+
return !char.IsWhiteSpace(c) && wordDelimiters.IndexOf(c) < 0;
64+
}
65+
}
66+
}

Diff for: PSReadLine/StringBuilderExtensions.cs renamed to PSReadLine/StringBuilderLinewiseExtensions.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal Range(int offset, int count)
1414
internal int Count { get; }
1515
}
1616

17-
internal static class StringBuilderLinewiseExtensions
17+
internal static partial class StringBuilderLinewiseExtensions
1818
{
1919
/// <summary>
2020
/// Determines the offset and the length of the fragment
@@ -72,6 +72,23 @@ internal static Range GetRange(this StringBuilder buffer, int lineIndex, int lin
7272
endPosition - startPosition + 1
7373
);
7474
}
75+
76+
/// <summary>
77+
/// Returns true if the specified position
78+
/// is on an empty logical line
79+
/// </summary>
80+
/// <param name="buffer"></param>
81+
/// <param name="cursor"></param>
82+
/// <returns></returns>
83+
public static bool IsLogigalLineEmpty(this StringBuilder buffer, int cursor)
84+
{
85+
return
86+
buffer.Length == 0 ||
87+
(cursor == buffer.Length && buffer[cursor - 1] == '\n') ||
88+
(cursor > 0 && buffer[cursor] == '\n') ||
89+
(cursor > 0 && buffer[cursor] == '\n' && cursor < buffer.Length - 1 && buffer[cursor - 1] == '\n')
90+
;
91+
}
7592
}
7693

7794
internal static class StringBuilderPredictionExtensions

Diff for: PSReadLine/Words.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,12 @@ private Token FindToken(int current, FindTokenMode mode)
9090

9191
private bool InWord(int index, string wordDelimiters)
9292
{
93-
char c = _buffer[index];
94-
return InWord(c, wordDelimiters);
93+
return _buffer.InWord(index, wordDelimiters);
9594
}
9695

9796
private bool InWord(char c, string wordDelimiters)
9897
{
99-
return !char.IsWhiteSpace(c) && wordDelimiters.IndexOf(c) < 0;
98+
return Character.IsInWord(c, wordDelimiters);
10099
}
101100

102101
/// <summary>

Diff for: PSReadLine/Words.vi.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Copyright (c) Microsoft Corporation. All rights reserved.
33
--********************************************************************/
44

5+
using System;
6+
57
namespace Microsoft.PowerShell
68
{
79
public partial class PSConsoleReadLine
@@ -106,10 +108,7 @@ private int ViFindNextWordFromWord(int i, string wordDelimiters)
106108
/// </summary>
107109
private bool IsWhiteSpace(int i)
108110
{
109-
// Treat just beyond the end of buffer as whitespace because
110-
// it looks like whitespace to the user even though they haven't
111-
// entered a character yet.
112-
return i >= _buffer.Length || char.IsWhiteSpace(_buffer[i]);
111+
return _buffer.IsWhiteSpace(i);
113112
}
114113

115114
/// <summary>

Diff for: PSReadLine/YankPaste.vi.cs

+20
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ private void SaveLinesToClipboard(int lineIndex, int lineCount)
7070
_clipboard.LinewiseRecord(_buffer.ToString(range.Offset, range.Count));
7171
}
7272

73+
/// <summary>
74+
/// Removes a portion of text from the buffer
75+
/// and saves it to the clipboard in order to support undo.
76+
/// </summary>
77+
/// <param name="start"></param>
78+
/// <param name="count"></param>
79+
/// <param name="instigator"></param>
80+
/// <param name="arg"></param>
81+
private void RemoveTextToClipboard(int start, int count, Action<ConsoleKeyInfo?, object> instigator = null, object arg = null)
82+
{
83+
_singleton.SaveToClipboard(start, count);
84+
_singleton.SaveEditItem(EditItemDelete.Create(
85+
_clipboard,
86+
start,
87+
instigator,
88+
arg
89+
));
90+
_singleton._buffer.Remove(start, count);
91+
}
92+
7393
/// <summary>
7494
/// Yank the entire buffer.
7595
/// </summary>

Diff for: test/StringBuilderCharacterExtensionsTests.cs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Microsoft.PowerShell;
2+
using System.Text;
3+
using Xunit;
4+
5+
namespace Test
6+
{
7+
public sealed class StringBuilderCharacterExtensionsTests
8+
{
9+
[Fact]
10+
public void StringBuilderCharacterExtensions_IsVisibleBlank()
11+
{
12+
var buffer = new StringBuilder(" \tn");
13+
14+
// system under test
15+
16+
Assert.True(buffer.IsVisibleBlank(0));
17+
Assert.True(buffer.IsVisibleBlank(1));
18+
Assert.False(buffer.IsVisibleBlank(2));
19+
}
20+
21+
[Fact]
22+
public void StringBuilderCharacterExtensions_InWord()
23+
{
24+
var buffer = new StringBuilder("hello, world!");
25+
const string wordDelimiters = " ";
26+
27+
// system under test
28+
29+
Assert.True(buffer.InWord(2, wordDelimiters));
30+
Assert.True(buffer.InWord(5, wordDelimiters));
31+
}
32+
33+
[Fact]
34+
public void StringBuilderCharacterExtensions_IsWhiteSpace()
35+
{
36+
var buffer = new StringBuilder("a c");
37+
38+
39+
// system under test
40+
41+
Assert.False(buffer.IsWhiteSpace(0));
42+
Assert.True(buffer.IsWhiteSpace(1));
43+
Assert.False(buffer.IsWhiteSpace(2));
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)