From a329ce5f0899d3855e7c62671fe50d71eb0695a7 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 14 Sep 2020 15:16:11 +0200 Subject: [PATCH 1/3] Fix completion on part of existing string --- .../Services/Completion/CompletionService.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs index 187383eb8d..b674f2cb23 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs @@ -242,6 +242,19 @@ public async Task Handle(CompletionRequest request) break; } + // If the span we are using is re-using part of the typed text we just need to grab the completion an prefix it + // with the existing text. Such as Onenabled -> OnEnabled, this will re-use On of the typed text + if (typedSpan.Start < change.TextChange.Span.Start && typedSpan.Start < change.TextChange.Span.End && typedSpan.End == change.TextChange.Span.End) + { + var prefix = typedText.Substring(0, change.TextChange.Span.Start - typedSpan.Start); + + (insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0); + + insertText = prefix + insertText; + + break; + } + int additionalEditEndOffset; (additionalTextEdits, additionalEditEndOffset) = await GetAdditionalTextEdits(change, sourceText, (CSharpParseOptions)syntax!.Options, typedSpan, completion.DisplayText, providerName); From e21a232572a8b6833c484d9ddd98afa9a1614cd4 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Thu, 17 Sep 2020 22:45:02 -0700 Subject: [PATCH 2/3] Use text edit for main insert text Because I misread the LSP spec when imlementing completion, I didn't realize that InsertTextFormat applied to both InsertText, and to the NewText property of the main TextEdit of a completion item. This latter version is much more robust to casing issues than using InsertText directly, and ensures that matching of completion items in the completion list works better as well. Fixes https://github.com/OmniSharp/omnisharp-vscode/issues/4063. --- .../Models/v1/Completion/CompletionItem.cs | 9 ++- .../Services/Completion/CompletionService.cs | 12 ++- .../CompletionFacts.cs | 81 ++++++++++++------- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v1/Completion/CompletionItem.cs b/src/OmniSharp.Abstractions/Models/v1/Completion/CompletionItem.cs index aceb370b5a..1a659e4617 100644 --- a/src/OmniSharp.Abstractions/Models/v1/Completion/CompletionItem.cs +++ b/src/OmniSharp.Abstractions/Models/v1/Completion/CompletionItem.cs @@ -61,10 +61,17 @@ public class CompletionItem public string? InsertText { get; set; } /// - /// The format of . + /// The format of . This applies to both and + /// .. /// public InsertTextFormat? InsertTextFormat { get; set; } + /// + /// An edit which is applied to a document when selecting this completion. When an edit is provided the value of + /// is ignored. + /// + public LinePositionSpanTextChange? TextEdit { get; set; } + /// /// An optional set of characters that when pressed while this completion is active will accept it first and /// then type that character. diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs index b674f2cb23..88e9818e1d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs @@ -162,6 +162,9 @@ public async Task Handle(CompletionRequest request) bool expectingImportedItems = expandedItemsAvailable && _workspace.Options.GetOption(CompletionItemExtensions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp) == true; var syntax = await document.GetSyntaxTreeAsync(); + var typedSpanStartPosition = sourceText.Lines.GetLinePosition(typedSpan.Start); + var typedSpanEndPosition = sourceText.Lines.GetLinePosition(typedSpan.End); + for (int i = 0; i < completions.Items.Length; i++) { var completion = completions.Items[i]; @@ -284,7 +287,14 @@ public async Task Handle(CompletionRequest request) completionsBuilder.Add(new CompletionItem { Label = completion.DisplayTextPrefix + completion.DisplayText + completion.DisplayTextSuffix, - InsertText = insertText, + TextEdit = new LinePositionSpanTextChange + { + NewText = insertText, + StartLine = typedSpanStartPosition.Line, + StartColumn = typedSpanStartPosition.Character, + EndLine = typedSpanEndPosition.Line, + EndColumn = typedSpanEndPosition.Character + }, InsertTextFormat = insertTextFormat, AdditionalTextEdits = additionalTextEdits, // Ensure that unimported items are sorted after things already imported. diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs index 917fb0dec2..df02928cc2 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs @@ -44,7 +44,7 @@ public Class1() var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); Assert.Contains("Foo", completions.Items.Select(c => c.Label)); - Assert.Contains("Foo", completions.Items.Select(c => c.InsertText)); + Assert.Contains("Foo", completions.Items.Select(c => c.TextEdit.NewText)); } [Theory] @@ -63,7 +63,7 @@ public Class1() var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); Assert.Contains("foo", completions.Items.Select(c => c.Label)); - Assert.Contains("foo", completions.Items.Select(c => c.InsertText)); + Assert.Contains("foo", completions.Items.Select(c => c.TextEdit.NewText)); } [Theory] @@ -105,7 +105,7 @@ public Class1() }"; var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); - Assert.Contains("TryParse", completions.Items.Select(c => c.InsertText)); + Assert.Contains("TryParse", completions.Items.Select(c => c.TextEdit.NewText)); } [Theory] @@ -123,7 +123,7 @@ public Class1() var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); Assert.False(completions.IsIncomplete); - Assert.DoesNotContain("Guid", completions.Items.Select(c => c.InsertText)); + Assert.DoesNotContain("Guid", completions.Items.Select(c => c.TextEdit.NewText)); } [Theory] @@ -144,7 +144,7 @@ public Class1() // First completion request should kick off the task to update the completion cache. var completions = await FindCompletionsAsync(filename, input, host); Assert.True(completions.IsIncomplete); - Assert.DoesNotContain("Guid", completions.Items.Select(c => c.InsertText)); + Assert.DoesNotContain("Guid", completions.Items.Select(c => c.TextEdit.NewText)); // Populating the completion cache should take no more than a few ms, don't let it take too // long @@ -159,7 +159,7 @@ await Task.Run(async () => }, cts.Token); Assert.False(completions.IsIncomplete); - Assert.Contains("Guid", completions.Items.Select(c => c.InsertText)); + Assert.Contains("Guid", completions.Items.Select(c => c.TextEdit.NewText)); } [Theory] @@ -179,8 +179,8 @@ public Class1() using var host = GetImportCompletionHost(); var completions = await FindCompletionsWithImportedAsync(filename, input, host); - CompletionItem localCompletion = completions.Items.First(c => c.InsertText == "guid"); - CompletionItem typeCompletion = completions.Items.First(c => c.InsertText == "Guid"); + CompletionItem localCompletion = completions.Items.First(c => c.TextEdit.NewText == "guid"); + CompletionItem typeCompletion = completions.Items.First(c => c.TextEdit.NewText == "Guid"); Assert.True(localCompletion.Data < typeCompletion.Data); Assert.StartsWith("0", localCompletion.SortText); Assert.StartsWith("1", typeCompletion.SortText); @@ -215,7 +215,7 @@ public static void Test(this object o) using var host = GetImportCompletionHost(); var completions = await FindCompletionsWithImportedAsync(filename, input, host); - Assert.Contains("Test", completions.Items.Select(c => c.InsertText)); + Assert.Contains("Test", completions.Items.Select(c => c.TextEdit.NewText)); VerifySortOrders(completions.Items); } @@ -247,7 +247,7 @@ public static void Test(this object o) using var host = GetImportCompletionHost(); var completions = await FindCompletionsWithImportedAsync(filename, input, host); - var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.InsertText == "Test"), host); + var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.TextEdit.NewText == "Test"), host); Assert.Single(resolved.Item.AdditionalTextEdits); var additionalEdit = resolved.Item.AdditionalTextEdits[0]; @@ -288,7 +288,7 @@ public static void Test(this object o) using var host = GetImportCompletionHost(); var completions = await FindCompletionsWithImportedAsync(filename, input, host); - var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.InsertText == "Guid"), host); + var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.TextEdit.NewText == "Guid"), host); Assert.Single(resolved.Item.AdditionalTextEdits); var additionalEdit = resolved.Item.AdditionalTextEdits[0]; @@ -335,7 +335,7 @@ public class C3 using var host = GetImportCompletionHost(); var completions = await FindCompletionsWithImportedAsync(filename, input, host); - var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.InsertText == "C2"), host); + var resolved = await ResolveCompletionAsync(completions.Items.First(c => c.TextEdit.NewText == "C2"), host); Assert.Single(resolved.Item.AdditionalTextEdits); var additionalEdit = resolved.Item.AdditionalTextEdits[0]; @@ -500,7 +500,7 @@ public class Foo {}"; var completions = await FindCompletionsAsync(filename, source, SharedOmniSharpTestHost); Assert.Contains(completions.Items, c => c.Label == "Bar"); - Assert.Contains(completions.Items, c => c.InsertText == "Bar"); + Assert.Contains(completions.Items, c => c.TextEdit.NewText == "Bar"); Assert.All(completions.Items, c => { switch (c.Label) @@ -538,7 +538,7 @@ public MyClass2() var completions = await FindCompletionsAsync(filename, source, SharedOmniSharpTestHost); Assert.Single(completions.Items); Assert.Equal("Foo", completions.Items[0].Label); - Assert.Equal("Foo", completions.Items[0].InsertText); + Assert.Equal("Foo", completions.Items[0].TextEdit.NewText); } [Theory] @@ -564,7 +564,7 @@ public MyClass2() var completions = await FindCompletionsAsync(filename, source, SharedOmniSharpTestHost); var item = completions.Items.First(c => c.Label == "text:"); Assert.NotNull(item); - Assert.Equal("text", item.InsertText); + Assert.Equal("text", item.TextEdit.NewText); Assert.All(completions.Items, c => { switch (c.Label) @@ -624,7 +624,7 @@ class FooChild : Foo "Test(string text, string moreText)\n {\n base.Test(text, moreText);$0\n \\}", "ToString()\n {\n return base.ToString();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.Equal(new[] { "public override bool", "public override int", @@ -678,7 +678,7 @@ class CN3 : IN2 "GetN1()\n {\n throw new System.NotImplementedException();$0\n \\}", "ToString()\n {\n return base.ToString();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.Equal(new[] { "public override bool", "public override int", @@ -717,7 +717,7 @@ public override $$ "int GetHashCode()\n {\n return base.GetHashCode();$0\n \\}", "string ToString()\n {\n return base.ToString();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.All(completions.Items.Select(c => c.AdditionalTextEdits), a => Assert.Null(a)); Assert.All(completions.Items, c => Assert.Equal(InsertTextFormat.Snippet, c.InsertTextFormat)); @@ -739,7 +739,7 @@ public override bool $$ completions.Items.Select(c => c.Label)); Assert.Equal(new[] { "Equals(object obj)\n {\n return base.Equals(obj);$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.All(completions.Items.Select(c => c.AdditionalTextEdits), a => Assert.Null(a)); Assert.All(completions.Items, c => Assert.Equal(InsertTextFormat.Snippet, c.InsertTextFormat)); @@ -770,7 +770,7 @@ class Derived : Base "Test()\n {\n throw new System.NotImplementedException();$0\n \\}", "ToString()\n {\n return base.ToString();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.Equal(new[] { "public override bool", "public override int", @@ -817,7 +817,7 @@ public class Derived : Base Assert.Equal(0, item.AdditionalTextEdits[0].StartColumn); Assert.Equal(3, item.AdditionalTextEdits[0].EndLine); Assert.Equal(12, item.AdditionalTextEdits[0].EndColumn); - Assert.Equal("GetAction(Action a)\n {\n return base.GetAction(a);$0\n \\}", item.InsertText); + Assert.Equal("GetAction(Action a)\n {\n return base.GetAction(a);$0\n \\}", item.TextEdit.NewText); } [Fact] @@ -845,7 +845,7 @@ public class Derived : Base Assert.Equal(4, item.AdditionalTextEdits[0].StartColumn); Assert.Equal(9, item.AdditionalTextEdits[0].EndLine); Assert.Equal(12, item.AdditionalTextEdits[0].EndColumn); - Assert.Equal("M1(object param)\n {\n return base.M1(param);$0\n \\}", item.InsertText); + Assert.Equal("M1(object param)\n {\n return base.M1(param);$0\n \\}", item.TextEdit.NewText); } [Theory] @@ -869,7 +869,7 @@ partial class C completions.Items.Select(c => c.Label)); Assert.Equal(new[] { "void M1(string param)\n {\n throw new System.NotImplementedException();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.All(completions.Items.Select(c => c.AdditionalTextEdits), a => Assert.Null(a)); Assert.All(completions.Items, c => Assert.Equal(InsertTextFormat.Snippet, c.InsertTextFormat)); @@ -901,7 +901,7 @@ public partial class C Assert.Equal(0, item.AdditionalTextEdits[0].StartColumn); Assert.Equal(3, item.AdditionalTextEdits[0].EndLine); Assert.Equal(11, item.AdditionalTextEdits[0].EndColumn); - Assert.Equal("M(Action a)\n {\n throw new NotImplementedException();$0\n \\}", item.InsertText); + Assert.Equal("M(Action a)\n {\n throw new NotImplementedException();$0\n \\}", item.TextEdit.NewText); } [Fact] @@ -923,7 +923,7 @@ public partial class C var item = completions.Items.Single(c => c.Label.StartsWith("M1")); Assert.Equal("M1(object param)", item.Label); Assert.Null(item.AdditionalTextEdits); - Assert.Equal("void M1(object param)\n {\n throw new System.NotImplementedException();$0\n \\}", item.InsertText); + Assert.Equal("void M1(object param)\n {\n throw new System.NotImplementedException();$0\n \\}", item.TextEdit.NewText); } [Theory] @@ -945,7 +945,7 @@ override Ge$$ "GetHashCode()\n {\n return base.GetHashCode();$0\n \\}", "ToString()\n {\n return base.ToString();$0\n \\}" }, - completions.Items.Select(c => c.InsertText)); + completions.Items.Select(c => c.TextEdit.NewText)); Assert.Equal(new[] { "public override bool", "public override int", @@ -1030,8 +1030,8 @@ public class MyClass1 { "see cref=\"$0\"/>", "seealso cref=\"$0\"/>" }, - completions.Items.Select(c => c.InsertText)); - Assert.All(completions.Items, c => Assert.Equal(c.InsertText.Contains("$0"), c.InsertTextFormat == InsertTextFormat.Snippet)); + completions.Items.Select(c => c.TextEdit.NewText)); + Assert.All(completions.Items, c => Assert.Equal(c.TextEdit.NewText.Contains("$0"), c.InsertTextFormat == InsertTextFormat.Snippet)); } [Fact] @@ -1307,7 +1307,7 @@ public async Task InternalsVisibleToCompletion() var completions = await FindCompletionsAsync("dummy.cs", input, SharedOmniSharpTestHost); Assert.Single(completions.Items); Assert.Equal("AssemblyNameVal", completions.Items[0].Label); - Assert.Equal("AssemblyNameVal", completions.Items[0].InsertText); + Assert.Equal("AssemblyNameVal", completions.Items[0].TextEdit.NewText); } [Fact] @@ -1333,7 +1333,28 @@ public async Task InternalsVisibleToCompletionSkipsMiscProject() var completions = await FindCompletionsAsync("dummy.cs", input, SharedOmniSharpTestHost); Assert.Single(completions.Items); Assert.Equal("AssemblyNameVal", completions.Items[0].Label); - Assert.Equal("AssemblyNameVal", completions.Items[0].InsertText); + Assert.Equal("AssemblyNameVal", completions.Items[0].TextEdit.NewText); + } + + [Theory] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task ChangeSpanIsExpected(string filename) + { + const string input = +@"public class Base +{ + protected virtual void OnEnable() {} +} +public class Derived : Base +{ + protected override void On$$ +}"; + + var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); + var onEnable = completions.Items.Single(c => c.TextEdit.NewText.Contains("OnEnable")); + Assert.Equal(onEnable.TextEdit.StartLine, onEnable.TextEdit.EndLine); + Assert.Equal(2, onEnable.TextEdit.EndColumn - onEnable.TextEdit.StartColumn); } private CompletionService GetCompletionService(OmniSharpTestHost host) From 255a88314f2596052e760ede68a3f9fc332580b9 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Fri, 18 Sep 2020 00:04:10 -0700 Subject: [PATCH 3/3] Add more tests for override spans within the original typed text, comment with examples on vscode behavior for replacing ranges. --- .../Services/Completion/CompletionService.cs | 44 +++++++++-------- .../CompletionFacts.cs | 47 ++++++++++++++++++- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs index 88e9818e1d..4f8f6eaf2d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs @@ -162,8 +162,8 @@ public async Task Handle(CompletionRequest request) bool expectingImportedItems = expandedItemsAvailable && _workspace.Options.GetOption(CompletionItemExtensions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp) == true; var syntax = await document.GetSyntaxTreeAsync(); - var typedSpanStartPosition = sourceText.Lines.GetLinePosition(typedSpan.Start); - var typedSpanEndPosition = sourceText.Lines.GetLinePosition(typedSpan.End); + var replacingSpanStartPosition = sourceText.Lines.GetLinePosition(typedSpan.Start); + var replacingSpanEndPosition = sourceText.Lines.GetLinePosition(typedSpan.End); for (int i = 0; i < completions.Items.Length; i++) { @@ -236,25 +236,28 @@ public async Task Handle(CompletionRequest request) var change = await completionService.GetChangeAsync(document, completion); - // If the span we're using to key the completion off is the same as the replacement - // span, then we don't need to do anything special, just snippitize the text and - // exit if (typedSpan == change.TextChange.Span) { + // If the span we're using to key the completion off is the same as the replacement + // span, then we don't need to do anything special, just snippitize the text and + // exit (insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0); break; } - - // If the span we are using is re-using part of the typed text we just need to grab the completion an prefix it - // with the existing text. Such as Onenabled -> OnEnabled, this will re-use On of the typed text - if (typedSpan.Start < change.TextChange.Span.Start && typedSpan.Start < change.TextChange.Span.End && typedSpan.End == change.TextChange.Span.End) + if (change.TextChange.Span.Start > typedSpan.Start) { + // If the span we're using to key the replacement span within the original typed span + // span, we want to prepend the missing text from the original typed text to here. The + // reason is that some lsp clients, such as vscode, use the range from the text edit as + // the selector for what filter text to use. This can lead to odd scenarios where invoking + // completion and typing `EQ` will bring up the Equals override, but then dismissing and + // reinvoking completion will have a range that just replaces the Q. Vscode will then consider + // that capital Q to be the start of the filter text, and filter out the Equals overload + // leaving the user with no completion and no explanation + Debug.Assert(change.TextChange.Span.End == typedSpan.End); var prefix = typedText.Substring(0, change.TextChange.Span.Start - typedSpan.Start); - - (insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0); - - insertText = prefix + insertText; + (insertText, insertTextFormat) = getAdjustedInsertTextWithPosition(change, position, newOffset: 0, prefix); break; } @@ -290,10 +293,10 @@ public async Task Handle(CompletionRequest request) TextEdit = new LinePositionSpanTextChange { NewText = insertText, - StartLine = typedSpanStartPosition.Line, - StartColumn = typedSpanStartPosition.Character, - EndLine = typedSpanEndPosition.Line, - EndColumn = typedSpanEndPosition.Character + StartLine = replacingSpanStartPosition.Line, + StartColumn = replacingSpanStartPosition.Character, + EndLine = replacingSpanEndPosition.Line, + EndColumn = replacingSpanEndPosition.Character }, InsertTextFormat = insertTextFormat, AdditionalTextEdits = additionalTextEdits, @@ -382,7 +385,8 @@ static ImmutableArray buildCommitCharacters(CSharpCompletionList completio static (string, InsertTextFormat) getAdjustedInsertTextWithPosition( CompletionChange change, int originalPosition, - int newOffset) + int newOffset, + string? prependText = null) { // We often have to trim part of the given change off the front, but we // still want to turn the resulting change into a snippet and control @@ -398,7 +402,7 @@ static ImmutableArray buildCommitCharacters(CSharpCompletionList completio if (!(change.NewPosition is int newPosition) || newPosition >= (change.TextChange.Span.Start + newText.Length)) { - return (newText.Substring(newOffset), InsertTextFormat.PlainText); + return (prependText + newText.Substring(newOffset), InsertTextFormat.PlainText); } if (newPosition < (originalPosition + newOffset)) @@ -412,7 +416,7 @@ static ImmutableArray buildCommitCharacters(CSharpCompletionList completio // requested start to the new position, and from the new position to the end of the // string. int midpoint = newPosition - change.TextChange.Span.Start; - var beforeText = LspSnippetHelpers.Escape(newText.Substring(newOffset, midpoint - newOffset)); + var beforeText = LspSnippetHelpers.Escape(prependText + newText.Substring(newOffset, midpoint - newOffset)); var afterText = LspSnippetHelpers.Escape(newText.Substring(midpoint)); return (beforeText + "$0" + afterText, InsertTextFormat.Snippet); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs index df02928cc2..10ce20edcf 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs @@ -1339,7 +1339,7 @@ public async Task InternalsVisibleToCompletionSkipsMiscProject() [Theory] [InlineData("dummy.cs")] [InlineData("dummy.csx")] - public async Task ChangeSpanIsExpected(string filename) + public async Task PrefixHeaderIsFullyCorrect(string filename) { const string input = @"public class Base @@ -1355,6 +1355,51 @@ protected override void On$$ var onEnable = completions.Items.Single(c => c.TextEdit.NewText.Contains("OnEnable")); Assert.Equal(onEnable.TextEdit.StartLine, onEnable.TextEdit.EndLine); Assert.Equal(2, onEnable.TextEdit.EndColumn - onEnable.TextEdit.StartColumn); + Assert.Equal("OnEnable()\n {\n base.OnEnable();$0\n \\}", onEnable.TextEdit.NewText); + } + + [Theory] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task PrefixHeaderIsPartiallyCorrect_1(string filename) + { + const string input = +@"public class Base +{ + protected virtual void OnEnable() {} +} +public class Derived : Base +{ + protected override void ON$$ +}"; + + var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); + var onEnable = completions.Items.Single(c => c.TextEdit.NewText.Contains("OnEnable")); + Assert.Equal(onEnable.TextEdit.StartLine, onEnable.TextEdit.EndLine); + Assert.Equal(2, onEnable.TextEdit.EndColumn - onEnable.TextEdit.StartColumn); + Assert.Equal("OnEnable()\n {\n base.OnEnable();$0\n \\}", onEnable.TextEdit.NewText); + } + + [Theory] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task PrefixHeaderIsPartiallyCorrect_2(string filename) + { + const string input = +@"public class Base +{ + protected virtual void OnEnable() {} +} +public class Derived : Base +{ + protected override void on$$ +}"; + + var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); + var onEnable = completions.Items.Single(c => c.TextEdit.NewText.Contains("OnEnable")); + Assert.Equal(onEnable.TextEdit.StartLine, onEnable.TextEdit.EndLine); + Assert.Equal(2, onEnable.TextEdit.EndColumn - onEnable.TextEdit.StartColumn); + Assert.Equal("OnEnable()\n {\n base.OnEnable();$0\n \\}", onEnable.TextEdit.NewText); } private CompletionService GetCompletionService(OmniSharpTestHost host)