|
3 | 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | 4 |
|
5 | 5 | using System; |
| 6 | +using System.Buffers; |
6 | 7 | using System.Collections.Generic; |
7 | 8 | using System.Collections.Immutable; |
8 | 9 | using System.Diagnostics; |
@@ -31,6 +32,9 @@ namespace CSharpRepl.PrettyPromptConfig; |
31 | 32 | /// </summary> |
32 | 33 | internal class CSharpReplPromptCallbacks : PromptCallbacks |
33 | 34 | { |
| 35 | + private const string lowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; |
| 36 | + private static SearchValues<char> lowercaseSearchValues = SearchValues.Create(lowercaseLetters); |
| 37 | + |
34 | 38 | private readonly IConsoleEx console; |
35 | 39 | private readonly RoslynServices roslyn; |
36 | 40 | private readonly Configuration configuration; |
@@ -90,12 +94,41 @@ protected override Task<bool> ShouldOpenCompletionWindowAsync(string text, int c |
90 | 94 |
|
91 | 95 | protected override async Task<IReadOnlyList<CompletionItem>> GetCompletionItemsAsync(string text, int caret, TextSpan spanToBeReplaced, CancellationToken cancellationToken) |
92 | 96 | { |
93 | | - var completions = await roslyn.CompleteAsync(text, caret).ConfigureAwait(false); |
94 | | - return completions |
95 | | - .OrderByDescending(i => i.Item.Rules.MatchPriority) |
96 | | - .ThenBy(i => i.Item.SortText) |
97 | | - .Select(CreatePrettyPromptCompletionItem) |
| 97 | + return await GetCompletionItemsCoreAsync(text, caret, cancellationToken).ConfigureAwait(false); |
| 98 | + } |
| 99 | + |
| 100 | + // Made internal for testing |
| 101 | + internal async Task<IReadOnlyList<CompletionItem>> GetCompletionItemsCoreAsync(string text, int caret, CancellationToken cancellationToken = default) |
| 102 | + { |
| 103 | + var replKeywordCompletions = GetReplKeywordCompletions(); |
| 104 | + |
| 105 | + var completions = await roslyn.CompleteAsync(text, caret, cancellationToken).ConfigureAwait(false); |
| 106 | + return replKeywordCompletions |
| 107 | + .Concat(completions |
| 108 | + .OrderByDescending(i => i.Item.Rules.MatchPriority) |
| 109 | + .ThenBy(i => i.Item.SortText) |
| 110 | + .Select(CreatePrettyPromptCompletionItem)) |
98 | 111 | .ToArray(); |
| 112 | + |
| 113 | + IEnumerable<CompletionItem> GetReplKeywordCompletions() |
| 114 | + { |
| 115 | + var trimmed = text.AsSpan().Trim(); |
| 116 | + const int largestKeywordLength = 5; |
| 117 | + if (trimmed.Length > largestKeywordLength) |
| 118 | + { |
| 119 | + return []; |
| 120 | + } |
| 121 | + |
| 122 | + Span<char> lowercaseBuffer = stackalloc char[largestKeywordLength]; |
| 123 | + trimmed.ToLowerInvariant(lowercaseBuffer); |
| 124 | + var lowercaseTrimmed = lowercaseBuffer.TrimEnd('\0'); |
| 125 | + if (lowercaseTrimmed.ContainsAnyExcept(lowercaseSearchValues)) |
| 126 | + { |
| 127 | + return []; |
| 128 | + } |
| 129 | + |
| 130 | + return ReplKeywordCompletionItems.AllItems; |
| 131 | + } |
99 | 132 | } |
100 | 133 |
|
101 | 134 | internal CompletionItem CreatePrettyPromptCompletionItem(CompletionItemWithDescription r) |
@@ -132,10 +165,40 @@ private static ImmutableArray<CharacterSetModificationRule> MergeCommitRules( |
132 | 165 |
|
133 | 166 | protected override async Task<IReadOnlyCollection<FormatSpan>> HighlightCallbackAsync(string text, CancellationToken cancellationToken) |
134 | 167 | { |
| 168 | + var replKeywordSpan = HighlightReplKeyword(text); |
| 169 | + if (replKeywordSpan is not null) |
| 170 | + { |
| 171 | + return [replKeywordSpan.Value]; |
| 172 | + } |
| 173 | + |
135 | 174 | var classifications = await roslyn.SyntaxHighlightAsync(text).ConfigureAwait(false); |
136 | 175 | return classifications.ToFormatSpans(); |
137 | 176 | } |
138 | 177 |
|
| 178 | + private static FormatSpan? HighlightReplKeyword(string text) |
| 179 | + { |
| 180 | + var trimmed = text.Trim().ToLowerInvariant(); |
| 181 | + switch (trimmed) |
| 182 | + { |
| 183 | + case ReadEvalPrintLoop.Keywords.HelpText: |
| 184 | + case "#help": |
| 185 | + return FullSpanWithColor(ReadEvalPrintLoop.Keywords.HelpInfo.Color); |
| 186 | + |
| 187 | + case ReadEvalPrintLoop.Keywords.ExitText: |
| 188 | + return FullSpanWithColor(ReadEvalPrintLoop.Keywords.ExitInfo.Color); |
| 189 | + |
| 190 | + case ReadEvalPrintLoop.Keywords.ClearText: |
| 191 | + return FullSpanWithColor(ReadEvalPrintLoop.Keywords.ClearInfo.Color); |
| 192 | + } |
| 193 | + |
| 194 | + return null; |
| 195 | + |
| 196 | + FormatSpan FullSpanWithColor(AnsiColor color) |
| 197 | + { |
| 198 | + return EntireWordFormatSpan(text, color); |
| 199 | + } |
| 200 | + } |
| 201 | + |
139 | 202 | protected override async Task<KeyPress> TransformKeyPressAsync(string text, int caret, KeyPress keyPress, CancellationToken cancellationToken) |
140 | 203 | { |
141 | 204 | // user submitted the prompt but it's incomplete. Insert a newline automatically with the correct level of indentation. |
@@ -286,6 +349,42 @@ protected override Task<bool> ConfirmCompletionCommit(string text, int caret, Ke |
286 | 349 |
|
287 | 350 | return null; |
288 | 351 | } |
| 352 | + |
| 353 | + private static FormatSpan EntireWordFormatSpan(ReadOnlySpan<char> word, AnsiColor color) |
| 354 | + { |
| 355 | + return new(0, word.Length, color); |
| 356 | + } |
| 357 | + |
| 358 | + private static FormattedString EntireWordFormatString(string word, AnsiColor color) |
| 359 | + { |
| 360 | + return new(word, EntireWordFormatSpan(word, color)); |
| 361 | + } |
| 362 | + |
| 363 | + private static FormattedString EntireWordFormatString(ReadEvalPrintLoop.Keywords.KeywordInfo keywordInfo) |
| 364 | + { |
| 365 | + return EntireWordFormatString(keywordInfo.Text, keywordInfo.Color); |
| 366 | + } |
| 367 | + |
| 368 | + private static class ReplKeywordCompletionItems |
| 369 | + { |
| 370 | + private static readonly FormattedString helpFormattedString = EntireWordFormatString(ReadEvalPrintLoop.Keywords.HelpInfo); |
| 371 | + private static readonly FormattedString exitFormattedString = EntireWordFormatString(ReadEvalPrintLoop.Keywords.ExitInfo); |
| 372 | + private static readonly FormattedString clearFormattedString = EntireWordFormatString(ReadEvalPrintLoop.Keywords.ClearInfo); |
| 373 | + |
| 374 | + public static CompletionItem Help { get; } = new( |
| 375 | + ReadEvalPrintLoop.Keywords.HelpText, |
| 376 | + displayText: helpFormattedString); |
| 377 | + |
| 378 | + public static CompletionItem Exit { get; } = new( |
| 379 | + ReadEvalPrintLoop.Keywords.ExitText, |
| 380 | + displayText: exitFormattedString); |
| 381 | + |
| 382 | + public static CompletionItem Clear { get; } = new( |
| 383 | + ReadEvalPrintLoop.Keywords.ClearText, |
| 384 | + displayText: clearFormattedString); |
| 385 | + |
| 386 | + public static IReadOnlyList<CompletionItem> AllItems = [Help, Exit, Clear]; |
| 387 | + } |
289 | 388 | } |
290 | 389 |
|
291 | 390 | /// <summary> |
|
0 commit comments