Skip to content

Commit 252f79a

Browse files
authored
Merge pull request #1 from 333fred/textedit-completion
Use text edit for main insert text
2 parents a329ce5 + 255a883 commit 252f79a

3 files changed

Lines changed: 133 additions & 46 deletions

File tree

src/OmniSharp.Abstractions/Models/v1/Completion/CompletionItem.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,17 @@ public class CompletionItem
6161
public string? InsertText { get; set; }
6262

6363
/// <summary>
64-
/// The format of <see cref="InsertText"/>.
64+
/// The format of <see cref="InsertText"/>. This applies to both <see cref="InsertText"/> and
65+
/// <see cref="TextEdit"/>.<see cref="LinePositionSpanTextChange.NewText"/>.
6566
/// </summary>
6667
public InsertTextFormat? InsertTextFormat { get; set; }
6768

69+
/// <summary>
70+
/// An edit which is applied to a document when selecting this completion. When an edit is provided the value of
71+
/// <see cref="InsertText"/> is ignored.
72+
/// </summary>
73+
public LinePositionSpanTextChange? TextEdit { get; set; }
74+
6875
/// <summary>
6976
/// An optional set of characters that when pressed while this completion is active will accept it first and
7077
/// then type that character.

src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ public async Task<CompletionResponse> Handle(CompletionRequest request)
162162
bool expectingImportedItems = expandedItemsAvailable && _workspace.Options.GetOption(CompletionItemExtensions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp) == true;
163163
var syntax = await document.GetSyntaxTreeAsync();
164164

165+
var replacingSpanStartPosition = sourceText.Lines.GetLinePosition(typedSpan.Start);
166+
var replacingSpanEndPosition = sourceText.Lines.GetLinePosition(typedSpan.End);
167+
165168
for (int i = 0; i < completions.Items.Length; i++)
166169
{
167170
var completion = completions.Items[i];
@@ -233,25 +236,28 @@ public async Task<CompletionResponse> Handle(CompletionRequest request)
233236

234237
var change = await completionService.GetChangeAsync(document, completion);
235238

236-
// If the span we're using to key the completion off is the same as the replacement
237-
// span, then we don't need to do anything special, just snippitize the text and
238-
// exit
239239
if (typedSpan == change.TextChange.Span)
240240
{
241+
// If the span we're using to key the completion off is the same as the replacement
242+
// span, then we don't need to do anything special, just snippitize the text and
243+
// exit
241244
(insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0);
242245
break;
243246
}
244-
245-
// If the span we are using is re-using part of the typed text we just need to grab the completion an prefix it
246-
// with the existing text. Such as Onenabled -> OnEnabled, this will re-use On of the typed text
247-
if (typedSpan.Start < change.TextChange.Span.Start && typedSpan.Start < change.TextChange.Span.End && typedSpan.End == change.TextChange.Span.End)
247+
if (change.TextChange.Span.Start > typedSpan.Start)
248248
{
249+
// If the span we're using to key the replacement span within the original typed span
250+
// span, we want to prepend the missing text from the original typed text to here. The
251+
// reason is that some lsp clients, such as vscode, use the range from the text edit as
252+
// the selector for what filter text to use. This can lead to odd scenarios where invoking
253+
// completion and typing `EQ` will bring up the Equals override, but then dismissing and
254+
// reinvoking completion will have a range that just replaces the Q. Vscode will then consider
255+
// that capital Q to be the start of the filter text, and filter out the Equals overload
256+
// leaving the user with no completion and no explanation
257+
Debug.Assert(change.TextChange.Span.End == typedSpan.End);
249258
var prefix = typedText.Substring(0, change.TextChange.Span.Start - typedSpan.Start);
250-
251-
(insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0);
252-
253-
insertText = prefix + insertText;
254259

260+
(insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0, prefix);
255261
break;
256262
}
257263

@@ -284,7 +290,14 @@ public async Task<CompletionResponse> Handle(CompletionRequest request)
284290
completionsBuilder.Add(new CompletionItem
285291
{
286292
Label = completion.DisplayTextPrefix + completion.DisplayText + completion.DisplayTextSuffix,
287-
InsertText = insertText,
293+
TextEdit = new LinePositionSpanTextChange
294+
{
295+
NewText = insertText,
296+
StartLine = replacingSpanStartPosition.Line,
297+
StartColumn = replacingSpanStartPosition.Character,
298+
EndLine = replacingSpanEndPosition.Line,
299+
EndColumn = replacingSpanEndPosition.Character
300+
},
288301
InsertTextFormat = insertTextFormat,
289302
AdditionalTextEdits = additionalTextEdits,
290303
// Ensure that unimported items are sorted after things already imported.
@@ -372,7 +385,8 @@ static ImmutableArray<char> buildCommitCharacters(CSharpCompletionList completio
372385
static (string, InsertTextFormat) getAdjustedInsertTextWithPosition(
373386
CompletionChange change,
374387
int originalPosition,
375-
int newOffset)
388+
int newOffset,
389+
string? prependText = null)
376390
{
377391
// We often have to trim part of the given change off the front, but we
378392
// still want to turn the resulting change into a snippet and control
@@ -388,7 +402,7 @@ static ImmutableArray<char> buildCommitCharacters(CSharpCompletionList completio
388402
if (!(change.NewPosition is int newPosition)
389403
|| newPosition >= (change.TextChange.Span.Start + newText.Length))
390404
{
391-
return (newText.Substring(newOffset), InsertTextFormat.PlainText);
405+
return (prependText + newText.Substring(newOffset), InsertTextFormat.PlainText);
392406
}
393407

394408
if (newPosition < (originalPosition + newOffset))
@@ -402,7 +416,7 @@ static ImmutableArray<char> buildCommitCharacters(CSharpCompletionList completio
402416
// requested start to the new position, and from the new position to the end of the
403417
// string.
404418
int midpoint = newPosition - change.TextChange.Span.Start;
405-
var beforeText = LspSnippetHelpers.Escape(newText.Substring(newOffset, midpoint - newOffset));
419+
var beforeText = LspSnippetHelpers.Escape(prependText + newText.Substring(newOffset, midpoint - newOffset));
406420
var afterText = LspSnippetHelpers.Escape(newText.Substring(midpoint));
407421

408422
return (beforeText + "$0" + afterText, InsertTextFormat.Snippet);

0 commit comments

Comments
 (0)