-
Notifications
You must be signed in to change notification settings - Fork 420
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1986 from 333fred/async-completion
Add async completion support
- Loading branch information
Showing
19 changed files
with
1,498 additions
and
390 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using System.ComponentModel; | ||
|
||
namespace System.Runtime.CompilerServices | ||
{ | ||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
internal sealed class IsExternalInit { } | ||
} |
12 changes: 12 additions & 0 deletions
12
src/OmniSharp.Abstractions/Models/v1/Completion/CompletionAfterInsertRequest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#nullable enable | ||
|
||
using OmniSharp.Mef; | ||
|
||
namespace OmniSharp.Models.v1.Completion | ||
{ | ||
[OmniSharpEndpoint(OmniSharpEndpoints.CompletionAfterInsert, typeof(CompletionAfterInsertRequest), typeof(CompletionAfterInsertResponse))] | ||
public class CompletionAfterInsertRequest : IRequest | ||
{ | ||
public CompletionItem Item { get; set; } = null!; | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/OmniSharp.Abstractions/Models/v1/Completion/CompletionAfterInsertResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#nullable enable | ||
|
||
using Newtonsoft.Json; | ||
using System.Collections.Generic; | ||
|
||
namespace OmniSharp.Models.v1.Completion | ||
{ | ||
public class CompletionAfterInsertResponse | ||
{ | ||
/// <summary> | ||
/// Text changes to be applied to the document. These need to be applied in batch, all with reference to | ||
/// the same original document. | ||
/// </summary> | ||
public IReadOnlyList<LinePositionSpanTextChange>? Changes { get; set; } | ||
/// <summary> | ||
/// Line to position the cursor on after applying <see cref="Changes"/>. | ||
/// </summary> | ||
[JsonConverter(typeof(ZeroBasedIndexConverter))] | ||
public int? Line { get; set; } | ||
/// <summary> | ||
/// Column to position the cursor on after applying <see cref="Changes"/>. | ||
/// </summary> | ||
[JsonConverter(typeof(ZeroBasedIndexConverter))] | ||
public int? Column { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
#nullable enable | ||
|
||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Completion; | ||
using Microsoft.CodeAnalysis.Tags; | ||
using Microsoft.CodeAnalysis.Text; | ||
using OmniSharp.Models; | ||
using OmniSharp.Models.v1.Completion; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using CompletionItem = OmniSharp.Models.v1.Completion.CompletionItem; | ||
using CSharpCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList; | ||
using CSharpCompletionService = Microsoft.CodeAnalysis.Completion.CompletionService; | ||
|
||
namespace OmniSharp.Roslyn.CSharp.Services.Completion | ||
{ | ||
internal static partial class CompletionListBuilder | ||
{ | ||
private static readonly Dictionary<string, CompletionItemKind> s_roslynTagToCompletionItemKind = new() | ||
{ | ||
{ WellKnownTags.Public, CompletionItemKind.Keyword }, | ||
{ WellKnownTags.Protected, CompletionItemKind.Keyword }, | ||
{ WellKnownTags.Private, CompletionItemKind.Keyword }, | ||
{ WellKnownTags.Internal, CompletionItemKind.Keyword }, | ||
{ WellKnownTags.File, CompletionItemKind.File }, | ||
{ WellKnownTags.Project, CompletionItemKind.File }, | ||
{ WellKnownTags.Folder, CompletionItemKind.Folder }, | ||
{ WellKnownTags.Assembly, CompletionItemKind.File }, | ||
{ WellKnownTags.Class, CompletionItemKind.Class }, | ||
{ WellKnownTags.Constant, CompletionItemKind.Constant }, | ||
{ WellKnownTags.Delegate, CompletionItemKind.Function }, | ||
{ WellKnownTags.Enum, CompletionItemKind.Enum }, | ||
{ WellKnownTags.EnumMember, CompletionItemKind.EnumMember }, | ||
{ WellKnownTags.Event, CompletionItemKind.Event }, | ||
{ WellKnownTags.ExtensionMethod, CompletionItemKind.Method }, | ||
{ WellKnownTags.Field, CompletionItemKind.Field }, | ||
{ WellKnownTags.Interface, CompletionItemKind.Interface }, | ||
{ WellKnownTags.Intrinsic, CompletionItemKind.Text }, | ||
{ WellKnownTags.Keyword, CompletionItemKind.Keyword }, | ||
{ WellKnownTags.Label, CompletionItemKind.Text }, | ||
{ WellKnownTags.Local, CompletionItemKind.Variable }, | ||
{ WellKnownTags.Namespace, CompletionItemKind.Module }, | ||
{ WellKnownTags.Method, CompletionItemKind.Method }, | ||
{ WellKnownTags.Module, CompletionItemKind.Module }, | ||
{ WellKnownTags.Operator, CompletionItemKind.Operator }, | ||
{ WellKnownTags.Parameter, CompletionItemKind.Variable }, | ||
{ WellKnownTags.Property, CompletionItemKind.Property }, | ||
{ WellKnownTags.RangeVariable, CompletionItemKind.Variable }, | ||
{ WellKnownTags.Reference, CompletionItemKind.Reference }, | ||
{ WellKnownTags.Structure, CompletionItemKind.Struct }, | ||
{ WellKnownTags.TypeParameter, CompletionItemKind.TypeParameter }, | ||
{ WellKnownTags.Snippet, CompletionItemKind.Snippet }, | ||
{ WellKnownTags.Error, CompletionItemKind.Text }, | ||
{ WellKnownTags.Warning, CompletionItemKind.Text }, | ||
}; | ||
|
||
// VS has a more complex concept of a commit mode vs suggestion mode for intellisense. | ||
// LSP doesn't have this, so mock it as best we can by removing space ` ` from the list | ||
// of commit characters if we're in suggestion mode. | ||
private static readonly IReadOnlyList<char> DefaultRulesWithoutSpace = CompletionRules.Default.DefaultCommitCharacters.Where(c => c != ' ').ToList(); | ||
|
||
internal static async Task<(IReadOnlyList<CompletionItem>, bool)> BuildCompletionItems( | ||
Document document, | ||
SourceText sourceText, | ||
long cacheId, | ||
int position, | ||
CSharpCompletionService completionService, | ||
CSharpCompletionList completions, | ||
TextSpan typedSpan, | ||
bool expectingImportedItems, | ||
bool isSuggestionMode, bool enableAsyncCompletion) | ||
=> enableAsyncCompletion | ||
? await BuildCompletionItemsAsync(document, sourceText, cacheId, position, completionService, completions, typedSpan, expectingImportedItems, isSuggestionMode) | ||
: await BuildCompletionItemsSync(document, sourceText, cacheId, position, completionService, completions, typedSpan, expectingImportedItems, isSuggestionMode); | ||
|
||
internal static LinePositionSpanTextChange GetChangeForTextAndSpan(string? insertText, TextSpan changeSpan, SourceText sourceText) | ||
{ | ||
var changeLinePositionSpan = sourceText.Lines.GetLinePositionSpan(changeSpan); | ||
return new() | ||
{ | ||
NewText = insertText ?? "", | ||
StartLine = changeLinePositionSpan.Start.Line, | ||
StartColumn = changeLinePositionSpan.Start.Character, | ||
EndLine = changeLinePositionSpan.End.Line, | ||
EndColumn = changeLinePositionSpan.End.Character | ||
}; | ||
} | ||
|
||
private static IReadOnlyList<char>? BuildCommitCharacters(ImmutableArray<CharacterSetModificationRule> characterRules, bool isSuggestionMode, Dictionary<ImmutableArray<CharacterSetModificationRule>, IReadOnlyList<char>> commitCharacterRulesCache, HashSet<char> commitCharactersBuilder) | ||
{ | ||
if (characterRules.IsEmpty) | ||
{ | ||
// Use defaults | ||
return isSuggestionMode ? DefaultRulesWithoutSpace : CompletionRules.Default.DefaultCommitCharacters; | ||
} | ||
|
||
if (commitCharacterRulesCache.TryGetValue(characterRules, out var cachedRules)) | ||
{ | ||
return cachedRules; | ||
} | ||
|
||
addAllCharacters(CompletionRules.Default.DefaultCommitCharacters); | ||
|
||
foreach (var modifiedRule in characterRules) | ||
{ | ||
switch (modifiedRule.Kind) | ||
{ | ||
case CharacterSetModificationKind.Add: | ||
commitCharactersBuilder.UnionWith(modifiedRule.Characters); | ||
break; | ||
|
||
case CharacterSetModificationKind.Remove: | ||
commitCharactersBuilder.ExceptWith(modifiedRule.Characters); | ||
break; | ||
|
||
case CharacterSetModificationKind.Replace: | ||
commitCharactersBuilder.Clear(); | ||
addAllCharacters(modifiedRule.Characters); | ||
break; | ||
} | ||
} | ||
|
||
// VS has a more complex concept of a commit mode vs suggestion mode for intellisense. | ||
// LSP doesn't have this, so mock it as best we can by removing space ` ` from the list | ||
// of commit characters if we're in suggestion mode. | ||
if (isSuggestionMode) | ||
{ | ||
commitCharactersBuilder.Remove(' '); | ||
} | ||
|
||
var finalCharacters = commitCharactersBuilder.ToList(); | ||
commitCharactersBuilder.Clear(); | ||
|
||
commitCharacterRulesCache.Add(characterRules, finalCharacters); | ||
|
||
return finalCharacters; | ||
|
||
void addAllCharacters(ImmutableArray<char> characters) | ||
{ | ||
foreach (var @char in characters) | ||
{ | ||
commitCharactersBuilder.Add(@char); | ||
} | ||
} | ||
} | ||
|
||
private static CompletionItemKind GetCompletionItemKind(ImmutableArray<string> tags) | ||
{ | ||
foreach (var tag in tags) | ||
{ | ||
if (s_roslynTagToCompletionItemKind.TryGetValue(tag, out var itemKind)) | ||
{ | ||
return itemKind; | ||
} | ||
} | ||
|
||
return CompletionItemKind.Text; | ||
} | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#nullable enable | ||
|
||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Completion; | ||
using Microsoft.CodeAnalysis.Text; | ||
using OmniSharp.Models; | ||
using OmniSharp.Models.v1.Completion; | ||
using OmniSharp.Roslyn.CSharp.Services.Intellisense; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using System.Threading.Tasks; | ||
using CompletionItem = OmniSharp.Models.v1.Completion.CompletionItem; | ||
using CSharpCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList; | ||
using CSharpCompletionService = Microsoft.CodeAnalysis.Completion.CompletionService; | ||
|
||
namespace OmniSharp.Roslyn.CSharp.Services.Completion | ||
{ | ||
internal static partial class CompletionListBuilder | ||
{ | ||
internal static async Task<(IReadOnlyList<CompletionItem>, bool)> BuildCompletionItemsAsync( | ||
Document document, | ||
SourceText sourceText, | ||
long cacheId, | ||
int position, | ||
CSharpCompletionService completionService, | ||
CSharpCompletionList completions, | ||
TextSpan typedSpan, | ||
bool expectingImportedItems, bool isSuggestionMode) | ||
{ | ||
var completionsBuilder = new List<CompletionItem>(completions.Items.Length); | ||
var seenUnimportedCompletions = false; | ||
var commitCharacterRuleCache = new Dictionary<ImmutableArray<CharacterSetModificationRule>, IReadOnlyList<char>>(); | ||
var commitCharacterRuleBuilder = new HashSet<char>(); | ||
var isOverrideOrPartialCompletion = completions.Items.Length > 0 | ||
&& completions.Items[0].GetProviderName() is CompletionItemExtensions.OverrideCompletionProvider or CompletionItemExtensions.PartialMethodCompletionProvider; | ||
|
||
for (int i = 0; i < completions.Items.Length; i++) | ||
{ | ||
var completion = completions.Items[i]; | ||
string labelText = completion.DisplayTextPrefix + completion.DisplayText + completion.DisplayTextSuffix; | ||
string? insertText; | ||
string? filterText = null; | ||
List<LinePositionSpanTextChange>? additionalTextEdits = null; | ||
InsertTextFormat insertTextFormat = InsertTextFormat.PlainText; | ||
TextSpan changeSpan; | ||
string? sortText; | ||
bool hasAfterInsertStep = false; | ||
if (completion.IsComplexTextEdit) | ||
{ | ||
// The completion is somehow expensive. Currently, this one of two categories: import completion, or override/partial | ||
// completion. | ||
Debug.Assert(completion.GetProviderName() is CompletionItemExtensions.OverrideCompletionProvider or CompletionItemExtensions.PartialMethodCompletionProvider | ||
or CompletionItemExtensions.TypeImportCompletionProvider or CompletionItemExtensions.ExtensionMethodImportCompletionProvider); | ||
|
||
changeSpan = typedSpan; | ||
|
||
if (isOverrideOrPartialCompletion) | ||
{ | ||
// For override and partial completion, we don't want to use the DisplayText as the insert text because they contain | ||
// characters that will affect our ability to asynchronously resolve the change later. | ||
insertText = completion.FilterText; | ||
sortText = GetSortText(completion, labelText, expectingImportedItems); | ||
hasAfterInsertStep = true; | ||
} | ||
else | ||
{ | ||
insertText = completion.DisplayText; | ||
sortText = '1' + completion.SortText; | ||
seenUnimportedCompletions = true; | ||
} | ||
} | ||
else | ||
{ | ||
// For non-complex completions, just await the text edit. It's cheap enough that it doesn't impact our ability | ||
// to pop completions quickly | ||
|
||
// If the completion item is the misc project name, skip it. | ||
if (completion.DisplayText == Configuration.OmniSharpMiscProjectName) continue; | ||
|
||
GetCompletionInfo( | ||
sourceText, | ||
position, | ||
completion, | ||
await completionService.GetChangeAsync(document, completion), | ||
typedSpan, | ||
labelText, | ||
expectingImportedItems, | ||
out insertText, out filterText, out sortText, out insertTextFormat, out changeSpan, out additionalTextEdits); | ||
} | ||
|
||
var commitCharacters = BuildCommitCharacters(completion.Rules.CommitCharacterRules, isSuggestionMode, commitCharacterRuleCache, commitCharacterRuleBuilder); | ||
|
||
completionsBuilder.Add(new CompletionItem | ||
{ | ||
Label = labelText, | ||
TextEdit = GetChangeForTextAndSpan(insertText!, changeSpan, sourceText), | ||
InsertTextFormat = insertTextFormat, | ||
AdditionalTextEdits = additionalTextEdits, | ||
SortText = sortText, | ||
FilterText = filterText, | ||
Kind = GetCompletionItemKind(completion.Tags), | ||
Detail = completion.InlineDescription, | ||
Data = (cacheId, i), | ||
Preselect = completion.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, | ||
CommitCharacters = commitCharacters, | ||
HasAfterInsertStep = hasAfterInsertStep, | ||
}); | ||
} | ||
|
||
return (completionsBuilder, seenUnimportedCompletions); | ||
} | ||
} | ||
} |
Oops, something went wrong.