Skip to content

Commit 3a06f64

Browse files
committed
Move LastWordFinder to new file
1 parent 920bf3b commit 3a06f64

File tree

2 files changed

+191
-183
lines changed

2 files changed

+191
-183
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace System.Management.Automation
5+
{
6+
public partial class CommandCompletion
7+
{
8+
/// <summary>
9+
/// LastWordFinder implements the algorithm we use to search for the last word in a line of input taken from the console.
10+
/// This class exists for legacy purposes only - V3 and forward uses a slightly different interface.
11+
/// </summary>
12+
private sealed class LastWordFinder
13+
{
14+
internal static string FindLastWord(string sentence, out int replacementIndexOut, out char closingQuote)
15+
{
16+
return (new LastWordFinder(sentence)).FindLastWord(out replacementIndexOut, out closingQuote);
17+
}
18+
19+
private LastWordFinder(string sentence)
20+
{
21+
_replacementIndex = 0;
22+
Diagnostics.Assert(sentence != null, "need to provide an instance");
23+
_sentence = sentence;
24+
}
25+
26+
/// <summary>
27+
/// Locates the last "word" in a string of text. A word is a conguous sequence of characters that are not
28+
/// whitespace, or a contiguous set grouped by single or double quotes. Can be called by at most 1 thread at a time
29+
/// per LastWordFinder instance.
30+
/// </summary>
31+
/// <param name="replacementIndexOut">
32+
/// Receives the character index (from the front of the string) of the starting point of the located word, or 0 if
33+
/// the word starts at the beginning of the sentence.
34+
/// </param>
35+
/// <param name="closingQuote">
36+
/// Receives the quote character that would be needed to end the sentence with a balanced pair of quotes. For
37+
/// instance, if sentence is "foo then " is returned, if sentence if "foo" then nothing is returned, if sentence is
38+
/// 'foo then ' is returned, if sentence is 'foo' then nothing is returned.
39+
/// </param>
40+
/// <returns>The last word located, or the empty string if no word could be found.</returns>
41+
private string FindLastWord(out int replacementIndexOut, out char closingQuote)
42+
{
43+
bool inSingleQuote = false;
44+
bool inDoubleQuote = false;
45+
46+
ReplacementIndex = 0;
47+
48+
for (_sentenceIndex = 0; _sentenceIndex < _sentence.Length; ++_sentenceIndex)
49+
{
50+
Diagnostics.Assert(!(inSingleQuote && inDoubleQuote),
51+
"Can't be in both single and double quotes");
52+
53+
char c = _sentence[_sentenceIndex];
54+
55+
// there are 3 possibilities:
56+
// 1) a new sequence is starting,
57+
// 2) a sequence is ending, or
58+
// 3) a sequence is due to end on the next matching quote, end-of-sentence, or whitespace
59+
60+
if (c == '\'')
61+
{
62+
HandleQuote(ref inSingleQuote, ref inDoubleQuote, c);
63+
}
64+
else if (c == '"')
65+
{
66+
HandleQuote(ref inDoubleQuote, ref inSingleQuote, c);
67+
}
68+
else if (c == '`')
69+
{
70+
Consume(c);
71+
if (++_sentenceIndex < _sentence.Length)
72+
{
73+
Consume(_sentence[_sentenceIndex]);
74+
}
75+
}
76+
else if (IsWhitespace(c))
77+
{
78+
if (_sequenceDueToEnd)
79+
{
80+
// we skipped a quote earlier, now end that sequence
81+
82+
_sequenceDueToEnd = false;
83+
if (inSingleQuote)
84+
{
85+
inSingleQuote = false;
86+
}
87+
88+
if (inDoubleQuote)
89+
{
90+
inDoubleQuote = false;
91+
}
92+
93+
ReplacementIndex = _sentenceIndex + 1;
94+
}
95+
else if (inSingleQuote || inDoubleQuote)
96+
{
97+
// a sequence is started and we're in quotes
98+
99+
Consume(c);
100+
}
101+
else
102+
{
103+
// no sequence is started, so ignore c
104+
105+
ReplacementIndex = _sentenceIndex + 1;
106+
}
107+
}
108+
else
109+
{
110+
// a sequence is started and we're in it
111+
112+
Consume(c);
113+
}
114+
}
115+
116+
string result = new string(_wordBuffer, 0, _wordBufferIndex);
117+
118+
closingQuote = inSingleQuote ? '\'' : inDoubleQuote ? '"' : '\0';
119+
replacementIndexOut = ReplacementIndex;
120+
return result;
121+
}
122+
123+
private void HandleQuote(ref bool inQuote, ref bool inOppositeQuote, char c)
124+
{
125+
if (inOppositeQuote)
126+
{
127+
// a sequence is started, and we're in it.
128+
Consume(c);
129+
return;
130+
}
131+
132+
if (inQuote)
133+
{
134+
if (_sequenceDueToEnd)
135+
{
136+
// I've ended a sequence and am starting another; don't consume c, update replacementIndex
137+
ReplacementIndex = _sentenceIndex + 1;
138+
}
139+
140+
_sequenceDueToEnd = !_sequenceDueToEnd;
141+
}
142+
else
143+
{
144+
// I'm starting a sequence; don't consume c, update replacementIndex
145+
inQuote = true;
146+
ReplacementIndex = _sentenceIndex;
147+
}
148+
}
149+
150+
private void Consume(char c)
151+
{
152+
Diagnostics.Assert(_wordBuffer != null, "wordBuffer is not initialized");
153+
Diagnostics.Assert(_wordBufferIndex < _wordBuffer.Length, "wordBufferIndex is out of range");
154+
155+
_wordBuffer[_wordBufferIndex++] = c;
156+
}
157+
158+
private int ReplacementIndex
159+
{
160+
get
161+
{
162+
return _replacementIndex;
163+
}
164+
165+
set
166+
{
167+
Diagnostics.Assert(value >= 0 && value < _sentence.Length + 1, "value out of range");
168+
169+
// when we set the replacement index, that means we're also resetting our word buffer. we know wordBuffer
170+
// will never be longer than sentence.
171+
172+
_wordBuffer = new char[_sentence.Length];
173+
_wordBufferIndex = 0;
174+
_replacementIndex = value;
175+
}
176+
}
177+
178+
private static bool IsWhitespace(char c)
179+
{
180+
return (c == ' ') || (c == '\x0009');
181+
}
182+
183+
private readonly string _sentence;
184+
private char[] _wordBuffer;
185+
private int _wordBufferIndex;
186+
private int _replacementIndex;
187+
private int _sentenceIndex;
188+
private bool _sequenceDueToEnd;
189+
}
190+
}
191+
}

src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs

Lines changed: 0 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -666,189 +666,6 @@ private static List<CompletionResult> InvokeLegacyTabExpansion(PowerShell powers
666666
return results;
667667
}
668668

669-
/// <summary>
670-
/// LastWordFinder implements the algorithm we use to search for the last word in a line of input taken from the console.
671-
/// This class exists for legacy purposes only - V3 and forward uses a slightly different interface.
672-
/// </summary>
673-
private sealed class LastWordFinder
674-
{
675-
internal static string FindLastWord(string sentence, out int replacementIndexOut, out char closingQuote)
676-
{
677-
return (new LastWordFinder(sentence)).FindLastWord(out replacementIndexOut, out closingQuote);
678-
}
679-
680-
private LastWordFinder(string sentence)
681-
{
682-
_replacementIndex = 0;
683-
Diagnostics.Assert(sentence != null, "need to provide an instance");
684-
_sentence = sentence;
685-
}
686-
687-
/// <summary>
688-
/// Locates the last "word" in a string of text. A word is a conguous sequence of characters that are not
689-
/// whitespace, or a contiguous set grouped by single or double quotes. Can be called by at most 1 thread at a time
690-
/// per LastWordFinder instance.
691-
/// </summary>
692-
/// <param name="replacementIndexOut">
693-
/// Receives the character index (from the front of the string) of the starting point of the located word, or 0 if
694-
/// the word starts at the beginning of the sentence.
695-
/// </param>
696-
/// <param name="closingQuote">
697-
/// Receives the quote character that would be needed to end the sentence with a balanced pair of quotes. For
698-
/// instance, if sentence is "foo then " is returned, if sentence if "foo" then nothing is returned, if sentence is
699-
/// 'foo then ' is returned, if sentence is 'foo' then nothing is returned.
700-
/// </param>
701-
/// <returns>The last word located, or the empty string if no word could be found.</returns>
702-
private string FindLastWord(out int replacementIndexOut, out char closingQuote)
703-
{
704-
bool inSingleQuote = false;
705-
bool inDoubleQuote = false;
706-
707-
ReplacementIndex = 0;
708-
709-
for (_sentenceIndex = 0; _sentenceIndex < _sentence.Length; ++_sentenceIndex)
710-
{
711-
Diagnostics.Assert(!(inSingleQuote && inDoubleQuote),
712-
"Can't be in both single and double quotes");
713-
714-
char c = _sentence[_sentenceIndex];
715-
716-
// there are 3 possibilities:
717-
// 1) a new sequence is starting,
718-
// 2) a sequence is ending, or
719-
// 3) a sequence is due to end on the next matching quote, end-of-sentence, or whitespace
720-
721-
if (c == '\'')
722-
{
723-
HandleQuote(ref inSingleQuote, ref inDoubleQuote, c);
724-
}
725-
else if (c == '"')
726-
{
727-
HandleQuote(ref inDoubleQuote, ref inSingleQuote, c);
728-
}
729-
else if (c == '`')
730-
{
731-
Consume(c);
732-
if (++_sentenceIndex < _sentence.Length)
733-
{
734-
Consume(_sentence[_sentenceIndex]);
735-
}
736-
}
737-
else if (IsWhitespace(c))
738-
{
739-
if (_sequenceDueToEnd)
740-
{
741-
// we skipped a quote earlier, now end that sequence
742-
743-
_sequenceDueToEnd = false;
744-
if (inSingleQuote)
745-
{
746-
inSingleQuote = false;
747-
}
748-
749-
if (inDoubleQuote)
750-
{
751-
inDoubleQuote = false;
752-
}
753-
754-
ReplacementIndex = _sentenceIndex + 1;
755-
}
756-
else if (inSingleQuote || inDoubleQuote)
757-
{
758-
// a sequence is started and we're in quotes
759-
760-
Consume(c);
761-
}
762-
else
763-
{
764-
// no sequence is started, so ignore c
765-
766-
ReplacementIndex = _sentenceIndex + 1;
767-
}
768-
}
769-
else
770-
{
771-
// a sequence is started and we're in it
772-
773-
Consume(c);
774-
}
775-
}
776-
777-
string result = new string(_wordBuffer, 0, _wordBufferIndex);
778-
779-
closingQuote = inSingleQuote ? '\'' : inDoubleQuote ? '"' : '\0';
780-
replacementIndexOut = ReplacementIndex;
781-
return result;
782-
}
783-
784-
private void HandleQuote(ref bool inQuote, ref bool inOppositeQuote, char c)
785-
{
786-
if (inOppositeQuote)
787-
{
788-
// a sequence is started, and we're in it.
789-
Consume(c);
790-
return;
791-
}
792-
793-
if (inQuote)
794-
{
795-
if (_sequenceDueToEnd)
796-
{
797-
// I've ended a sequence and am starting another; don't consume c, update replacementIndex
798-
ReplacementIndex = _sentenceIndex + 1;
799-
}
800-
801-
_sequenceDueToEnd = !_sequenceDueToEnd;
802-
}
803-
else
804-
{
805-
// I'm starting a sequence; don't consume c, update replacementIndex
806-
inQuote = true;
807-
ReplacementIndex = _sentenceIndex;
808-
}
809-
}
810-
811-
private void Consume(char c)
812-
{
813-
Diagnostics.Assert(_wordBuffer != null, "wordBuffer is not initialized");
814-
Diagnostics.Assert(_wordBufferIndex < _wordBuffer.Length, "wordBufferIndex is out of range");
815-
816-
_wordBuffer[_wordBufferIndex++] = c;
817-
}
818-
819-
private int ReplacementIndex
820-
{
821-
get
822-
{
823-
return _replacementIndex;
824-
}
825-
826-
set
827-
{
828-
Diagnostics.Assert(value >= 0 && value < _sentence.Length + 1, "value out of range");
829-
830-
// when we set the replacement index, that means we're also resetting our word buffer. we know wordBuffer
831-
// will never be longer than sentence.
832-
833-
_wordBuffer = new char[_sentence.Length];
834-
_wordBufferIndex = 0;
835-
_replacementIndex = value;
836-
}
837-
}
838-
839-
private static bool IsWhitespace(char c)
840-
{
841-
return (c == ' ') || (c == '\x0009');
842-
}
843-
844-
private readonly string _sentence;
845-
private char[] _wordBuffer;
846-
private int _wordBufferIndex;
847-
private int _replacementIndex;
848-
private int _sentenceIndex;
849-
private bool _sequenceDueToEnd;
850-
}
851-
852669
#endregion private methods
853670
}
854671
}

0 commit comments

Comments
 (0)