From 6a7967554407fd014b44e356990f452558194873 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 27 Jun 2016 18:17:57 -0700 Subject: [PATCH] Refactor completion API. --- .../OverrideCompletionProviderTests.cs | 4 +- .../Completion/CompletionHelper.cs | 1 + .../Controller.Session_ComputeModel.cs | 1 + ...Controller.Session_SetModelBuilderState.cs | 2 +- .../IntelliSense/Completion/Controller.cs | 7 +- .../Completion/Controller_Commit.cs | 277 ++++++------------ ...ntroller_CommitUniqueCompletionListItem.cs | 5 +- .../Completion/Controller_ReturnKey.cs | 2 +- .../Completion/Controller_TabKey.cs | 2 +- .../Completion/Controller_TypeChar.cs | 73 +++-- .../DescriptionModifyingPresentationItem.cs | 11 +- .../IntelliSense/Completion/Model.cs | 110 +++---- .../AbstractCompletionProviderTests.cs | 17 +- .../CSharpCompletionCommandHandlerTests.vb | 3 - ...isualBasicCompletionCommandHandlerTests.vb | 2 +- ...tributeNamedParameterCompletionProvider.cs | 2 +- .../CrefCompletionProvider.cs | 2 +- .../ExplicitInterfaceCompletionProvider.cs | 5 +- .../NamedParameterCompletionProvider.cs | 4 +- .../OverrideCompletionProvider.cs | 2 +- .../SymbolCompletionProvider.cs | 19 +- .../XmlDocCommentCompletionProvider.cs | 4 +- .../Completion/CommonCompletionProvider.cs | 10 +- .../Portable/Completion/CompletionChange.cs | 54 +++- .../Completion/CompletionItemRules.cs | 10 + .../Portable/Completion/CompletionProvider.cs | 2 +- .../Portable/Completion/CompletionService.cs | 22 +- .../CompletionServiceWithProviders.cs | 2 +- .../AbstractDocCommentCompletionProvider.cs | 9 +- .../AbstractKeywordCompletionProvider.cs | 19 +- ...stractMemberInsertingCompletionProvider.cs | 48 ++- .../AbstractOverrideCompletionProvider.cs | 2 +- .../Core/Portable/PublicAPI.Unshipped.txt | 5 +- .../AbstractSpellCheckCodeFixProvider.cs | 10 +- .../CompletionUtilities.vb | 5 +- .../KeywordCompletionProvider.vb | 4 +- .../NamedParameterCompletionProvider.vb | 12 +- .../PartialTypeCompletionProvider.vb | 5 +- .../LoadDirectiveCompletionProvider.cs | 6 +- .../Snippets/SnippetCompletionProvider.vb | 6 +- 40 files changed, 397 insertions(+), 389 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index 52ded5fef0a..8af8e9450c8 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -2556,11 +2556,11 @@ static void Main(string[] args) var oldTree = await document.GetSyntaxTreeAsync(); var commit = await provider.GetChangeAsync(document, completionList.Items.First(i => i.DisplayText == "ToString()"), ' '); - var changes = commit.TextChanges; + var change = commit.TextChange; // If we left the trailing trivia of the close curly of Main alone, // there should only be one change: the replacement of "override " with a method. - Assert.Equal(changes.Single().Span, TextSpan.FromBounds(136, 145)); + Assert.Equal(change.Span, TextSpan.FromBounds(136, 145)); } } } diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs index e1a346b1fb3..7b799b29c9c 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs @@ -7,6 +7,7 @@ using System.Globalization; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_ComputeModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_ComputeModel.cs index e14098ea4d3..0449e4a3c8f 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_ComputeModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_ComputeModel.cs @@ -107,6 +107,7 @@ private async Task DoInBackgroundAsync(CancellationToken cancellationToke } return Model.CreateModel( + _documentOpt, _disconnectedBufferGraph, completionList, selectedItem: completionList.Items.First(), diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_SetModelBuilderState.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_SetModelBuilderState.cs index 8401fe24100..2f88018d246 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_SetModelBuilderState.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_SetModelBuilderState.cs @@ -35,7 +35,7 @@ public void SetModelBuilderState(bool includeBuilder) .WithHardSelection(!softSelect); } - return model.WithHardSelection(!softSelect).WithUseSuggestionCompletionMode(includeBuilder); + return model.WithHardSelection(!softSelect).WithUseSuggestionMode(includeBuilder); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs index 3ffcfb67810..3da3b4c89d2 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs @@ -235,7 +235,12 @@ private void CommitItem(PresentationItem item) return; } - this.Commit(item, this.sessionOpt.Computation.InitialUnfilteredModel, commitChar: null); + this.Commit( + item, this.sessionOpt.Computation.InitialUnfilteredModel, + initialTextSnapshot: this.SubjectBuffer.CurrentSnapshot, + initialCaretPositionInView: this.TextView.Caret.Position.VirtualBufferPosition, + commitChar: null, + nextHandler: null); } private const int MaxMRUSize = 10; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Commit.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Commit.cs index e6f70516f01..7f742940d34 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Commit.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Commit.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -9,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Operations; @@ -30,7 +32,20 @@ private CompletionProvider GetCompletionProvider(CompletionItem item) return null; } - private void Commit(PresentationItem item, Model model, char? commitChar) + private void CommitOnNonTypeChar( + PresentationItem item, Model model) + { + Commit(item, model, + commitChar: null, + initialTextSnapshot: null, + initialCaretPositionInView: default(VirtualSnapshotPoint), + nextHandler: null); + } + + private void Commit( + PresentationItem item, Model model, char? commitChar, + ITextSnapshot initialTextSnapshot, VirtualSnapshotPoint initialCaretPositionInView, + Action nextHandler) { AssertIsForeground(); @@ -47,13 +62,6 @@ private void Commit(PresentationItem item, Model model, char? commitChar) // into us. However, for now, we just hope that no such craziness will occur. this.StopModelComputation(); - Commit(item, model, commitChar, CancellationToken.None); - } - - private void Commit(PresentationItem item, Model model, char? commitChar, CancellationToken cancellationToken) - { - var textChanges = ImmutableArray.Empty; - // NOTE(cyrusn): It is intentional that we get the undo history for the // surface buffer and not the subject buffer. // There have been some watsons where the ViewBuffer hadn't been registered, @@ -61,6 +69,7 @@ private void Commit(PresentationItem item, Model model, char? commitChar, Cancel ITextUndoHistory undoHistory; _undoHistoryRegistry.TryGetHistory(this.TextView.TextBuffer, out undoHistory); + CompletionChange completionChange; using (var transaction = undoHistory?.CreateTransaction(EditorFeaturesResources.IntelliSense)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) @@ -69,203 +78,117 @@ private void Commit(PresentationItem item, Model model, char? commitChar, Cancel transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; } - // Check if the provider wants to perform custom commit itself. Otherwise we will - // handle things. var provider = GetCompletionProvider(item.Item) as ICustomCommitCompletionProvider; - if (provider == null) + if (provider != null) { - var viewBuffer = this.TextView.TextBuffer; - var commitDocument = this.SubjectBuffer.CurrentSnapshot.AsText().GetDocumentWithFrozenPartialSemanticsAsync(cancellationToken).WaitAndGetResult(cancellationToken); - - // adjust commit item span foward to match current document that is passed to GetChangeAsync below - var commitItem = item.Item; - var currentItemSpan = GetCurrentItemSpan(commitItem, model); - commitItem = commitItem.WithSpan(currentItemSpan); - - var completionService = CompletionService.GetService(commitDocument); - var commitChange = completionService.GetChangeAsync(commitDocument, commitItem, commitChar, cancellationToken).WaitAndGetResult(cancellationToken); - textChanges = commitChange.TextChanges; - - // Use character based diffing here to avoid overwriting the commit character placed into the editor. - var editOptions = new EditOptions(new StringDifferenceOptions - { - DifferenceType = StringDifferenceTypes.Character, - IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace - }); - - // edit subject buffer (not view) because text changes are in terms of current document. - using (var textEdit = this.SubjectBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) + provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); + } + else + { + // Right before calling Commit, we may have passed the commitChar through to the + // editor. That was so that undoing completion will get us back to the state we + // we would be in if completion had done nothing. However, now that we're going + // to actually commit, we want to roll back to where we were before we pushed + // commit character into the buffer. This has multiple benefits: + // + // 1) the buffer is in a state we expect it to be in. i.e. we don't have to + // worry about what might have happened (like brace-completion) when the + // commit char was inserted. + // 2) after we commit the item, we'll pass the commit character again into the + // buffer (unless the items asks us not to). By doing this, we can make sure + // that things like brace-completion or formatting trigger as we expect them + // to. + var currentSnapshot = this.SubjectBuffer.CurrentSnapshot; + var characterWasSentIntoBuffer = commitChar != null && + initialTextSnapshot.Version.VersionNumber != currentSnapshot.Version.VersionNumber; + if (characterWasSentIntoBuffer) { - for (int iChange = 0; iChange < textChanges.Length; iChange++) - { - var textChange = textChanges[iChange]; - var isFirst = iChange == 0; - var isLast = iChange == textChanges.Length - 1; - - // add commit char to end of last change if not already included - if (isLast && !commitChange.IncludesCommitCharacter && commitChar.HasValue) - { - textChange = new TextChange(textChange.Span, textChange.NewText + commitChar.Value); - } - - var currentSpan = new SnapshotSpan(this.SubjectBuffer.CurrentSnapshot, new Span(textChange.Span.Start, textChange.Span.Length)); + // Get all the versions from the initial text snapshot (before we passed the + // commit character down) to the current snapshot we're at. + var versions = GetVersions(initialTextSnapshot, currentSnapshot).ToList(); - // In order to play nicely with automatic brace completion, we need to - // not touch the opening paren. We'll check our span and textchange - // for ( and adjust them accordingly if we find them. - - // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves. - // due to that, existing brace completion engine in editor that should take care of interaction between brace completion - // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely. - bool textChanged; - string newText = textChange.NewText; - - if (isFirst) - { - newText = AdjustFirstText(textChange); - } - - if (isLast) - { - newText = AdjustLastText(newText, commitChar.GetValueOrDefault(), out textChanged); - currentSpan = AdjustLastSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged); - } - - var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); - var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer); - - if (caretPoint.HasValue && virtualCaretPoint.HasValue) - { - // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view. - this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value)); - } - - caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); - - // Now that we're doing character level diffing, we need to move the caret to the end of - // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with - // M|ain, since character based diffing makes that quite legit. - if (caretPoint.HasValue) + // Un-apply the edits. + for (var i = versions.Count - 1; i >= 0; i--) + { + var version = versions[i]; + using (var textEdit = this.SubjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null)) { - var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor); - if (caretPoint.Value < endInSubjectBuffer) + foreach (var change in version.Changes) { - this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position)); + textEdit.Replace(change.NewSpan, change.OldText); } - } - textEdit.Replace(currentSpan, newText); + textEdit.Apply(); + } } - textEdit.Apply(); + // Move the caret back to where it was prior to committing the item. + TextView.Caret.MoveTo(new VirtualSnapshotPoint( + new SnapshotPoint(this.TextView.TextSnapshot, initialCaretPositionInView.Position.Position), + initialCaretPositionInView.VirtualSpaces)); } - // adjust the caret position if requested by completion service - if (commitChange.NewPosition != null) - { - var target = new SnapshotPoint(this.SubjectBuffer.CurrentSnapshot, commitChange.NewPosition.Value); - this.TextView.TryMoveCaretToAndEnsureVisible(target); - } + // Now, get the change the item wants to make. Note that the change will be relative + // to the initial snapshot/document the item was triggered from. We'll map that change + // forward, then apply it to our current snapshot. + var triggerDocument = model.TriggerDocument; + var triggerSnapshot = model.TriggerSnapshot; - // We've manipulated the caret position in order to generate the correct edit. However, - // if the insertion is long enough, the caret will scroll out of the visible area. - // Re-center the view. - using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) - { - var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); - if (caretPoint.HasValue) - { - this.TextView.Caret.EnsureVisible(); - } - } + var completionService = CompletionService.GetService(triggerDocument); + completionChange = completionService.GetChangeAsync( + triggerDocument, item.Item, commitChar, CancellationToken.None).WaitAndGetResult(CancellationToken.None); + var textChange = completionChange.TextChange; - transaction?.Complete(); - } - else - { - // Let the provider handle this. - provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); - transaction?.Complete(); - } - } - - var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var formattingService = document.GetLanguageService(); - - var commitCharTriggersFormatting = commitChar != null && - (formattingService?.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()) - ?? false); + var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); + var mappedSpan = triggerSnapshotSpan.TranslateTo( + this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - if (formattingService != null && (item.Item.Rules.FormatOnCommit || commitCharTriggersFormatting)) - { - // Formatting the completion item affected span is done as a separate transaction because this gives the user - // the flexibility to undo the formatting but retain the changes associated with the completion item - using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSenseCommitFormatting)) - { - var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); - IList changes = null; - - if (commitCharTriggersFormatting && caretPoint.HasValue) + // Now actually make the text change to the document. + using (var textEdit = this.SubjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null)) { - // if the commit character is supported by formatting service, then let the formatting service - // find the appropriate range to format. - changes = formattingService.GetFormattingChangesAsync(document, commitChar.Value, caretPoint.Value.Position, cancellationToken).WaitAndGetResult(cancellationToken); + var adjustedNewText = AdjustForVirtualSpace(textChange); + + textEdit.Replace(mappedSpan.Span, adjustedNewText); + textEdit.Apply(); } - else if (textChanges.Length > 0) + + if (completionChange.NewPosition != null) { - // if this is not a supported trigger character for formatting service (space or tab etc.) - // then format the span of the textchange. - var totalSpan = TextSpan.FromBounds(textChanges.Min(c => c.Span.Start), textChanges.Max(c => c.Span.End)); - changes = formattingService.GetFormattingChangesAsync(document, totalSpan, cancellationToken).WaitAndGetResult(cancellationToken); + TextView.Caret.MoveTo(new SnapshotPoint( + this.SubjectBuffer.CurrentSnapshot, completionChange.NewPosition.Value)); } - if (changes != null && !changes.IsEmpty()) + // Now, pass along the commit character unless the completion item said not to + if (characterWasSentIntoBuffer && !completionChange.IncludesCommitCharacter) { - document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); + nextHandler(); } - formattingTransaction.Complete(); + // If the insertion is long enough, the caret will scroll out of the visible area. + // Re-center the view. + this.TextView.Caret.EnsureVisible(); } + + transaction?.Complete(); } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(item.Item.DisplayText); } - private TextSpan GetCurrentItemSpan(CompletionItem item, Model model) - { - var originalSpanInView = model.GetViewBufferSpan(item.Span); - var currentSpanInView = model.GetCurrentSpanInSnapshot(originalSpanInView, this.TextView.TextBuffer.CurrentSnapshot); - var newStart = item.Span.Start + (currentSpanInView.Span.Start - originalSpanInView.TextSpan.Start); - return new TextSpan(newStart, currentSpanInView.Length); - } - - private SnapshotSpan AdjustLastSpan(SnapshotSpan currentSpan, char commitChar, bool textChanged) + private IEnumerable GetVersions( + ITextSnapshot initialTextSnapshot, ITextSnapshot currentSnapshot) { - var currentSpanText = currentSpan.GetText(); - if (currentSpan.Length > 0 && this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion)) + var version = initialTextSnapshot.Version; + while (version != null && version.VersionNumber != currentSnapshot.Version.VersionNumber) { - if (currentSpanText[currentSpanText.Length - 1] == commitChar) - { - return new SnapshotSpan(currentSpan.Start, currentSpan.Length - 1); - } - - // looks like auto insertion happened. find right span to replace - if (textChanged) - { - var index = currentSpanText.LastIndexOf(commitChar); - if (index >= 0) - { - return new SnapshotSpan(currentSpan.Start, index); - } - } + yield return version; + version = version.Next; } - - return currentSpan; } - private string AdjustFirstText(TextChange textChange) - { + private string AdjustForVirtualSpace(TextChange textChange) + { var newText = textChange.NewText; var caretPoint = this.TextView.Caret.Position.BufferPosition; @@ -285,17 +208,5 @@ private string AdjustFirstText(TextChange textChange) return newText; } - - private string AdjustLastText(string text, char commitChar, out bool textAdjusted) - { - var finaltText = this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion) - ? text.TrimEnd(commitChar) - : text; - - // set whether text has changed or not - textAdjusted = finaltText != text; - - return finaltText; - } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs index 62d8d3e3355..588fa60774a 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs @@ -13,7 +13,8 @@ CommandState ICommandHandler.GetComma return nextHandler(); } - void ICommandHandler.ExecuteCommand(CommitUniqueCompletionListItemCommandArgs args, Action nextHandler) + void ICommandHandler.ExecuteCommand( + CommitUniqueCompletionListItemCommandArgs args, Action nextHandler) { AssertIsForeground(); @@ -51,7 +52,7 @@ void ICommandHandler.ExecuteCommand(C if (model.IsUnique) { // We had a unique item in the list. Commit it and dismiss this session. - this.Commit(model.SelectedItem, model, commitChar: null); + this.CommitOnNonTypeChar(model.SelectedItem, model); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs index 6722c6ff603..1f90e91c2d3 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs @@ -95,7 +95,7 @@ private void CommitOnEnter(out bool sendThrough, out bool committed) service.GetRules(), model.SelectedItem.Item, textTypedSoFar); } - this.Commit(model.SelectedItem, model, commitChar: null); + this.CommitOnNonTypeChar(model.SelectedItem, model); committed = true; } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs index 7bb4adc39c4..f9a9ba96f2d 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs @@ -172,7 +172,7 @@ private void CommitOnTab(out bool committed) return; } - Commit(model.SelectedItem, model, commitChar: null); + CommitOnNonTypeChar(model.SelectedItem, model); committed = true; } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs index bfce469e3a1..a456c42dada 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs @@ -31,6 +31,9 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg AssertIsForeground(); + var initialTextSnapshot = this.SubjectBuffer.CurrentSnapshot; + var initialVirtualCaretPosition = this.TextView.Caret.Position.VirtualBufferPosition; + var initialCaretPosition = GetCaretPointInViewBuffer(); // When a character is typed it is *always* sent through to the editor. This way the @@ -85,7 +88,8 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg { Trace.WriteLine("typechar was on seam and a commit char, cannot have a completion session."); - this.CommitOnTypeChar(args.TypedChar); + this.CommitOnTypeChar( + args.TypedChar, initialTextSnapshot, initialVirtualCaretPosition, nextHandler); return; } else if (_autoBraceCompletionChars.Contains(args.TypedChar) && @@ -96,7 +100,8 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg // I don't think there is any better way than this. if typed char is one of auto brace completion char, // we don't do multiple buffer change check - this.CommitOnTypeChar(args.TypedChar); + this.CommitOnTypeChar( + args.TypedChar, initialTextSnapshot, initialVirtualCaretPosition, nextHandler); return; } else @@ -216,7 +221,8 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg // Known to be a commit character for the currently selected item. So just // commit the session. - this.CommitOnTypeChar(args.TypedChar); + this.CommitOnTypeChar( + args.TypedChar, initialTextSnapshot, initialVirtualCaretPosition, nextHandler); } else { @@ -311,23 +317,18 @@ private bool IsCommitCharacter(char ch) } var completionService = GetCompletionService(); - var filterText = GetCurrentFilterText(model, model.SelectedItem.Item); + var textTypedSoFar = GetTextTypedSoFar(model, model.SelectedItem.Item); return IsCommitCharacter( - completionService.GetRules(), model.SelectedItem.Item, ch, filterText); + completionService.GetRules(), model.SelectedItem.Item, ch, textTypedSoFar); } /// /// Internal for testing purposes only. /// internal static bool IsCommitCharacter( - CompletionRules completionRules, CompletionItem item, char ch, string filterText) + CompletionRules completionRules, CompletionItem item, char ch, string textTypedSoFar) { - // general rule: if the filtering text exactly matches the start of the item then it must be a filter character - if (TextTypedSoFarMatchesItem(item, ch, textTypedSoFar: filterText)) - { - return false; - } - + // First see if the item has any specifc commit rules it wants followed. foreach (var rule in item.Rules.CommitCharacterRules) { switch (rule.Kind) @@ -351,6 +352,12 @@ private bool IsCommitCharacter(char ch) } } + // general rule: if the filtering text exactly matches the start of the item then it must be a filter character + if (TextTypedSoFarMatchesItem(item, ch, textTypedSoFar)) + { + return false; + } + // Fall back to the default rules for this language's completion service. return completionRules.DefaultCommitCharacters.IndexOf(ch) >= 0; } @@ -371,28 +378,24 @@ private bool IsFilterCharacter(char ch) return char.IsLetterOrDigit(ch); } - var filterText = GetCurrentFilterText(model, model.SelectedItem.Item); - return IsFilterCharacter(model.SelectedItem.Item, ch, filterText); + var textTypedSoFar = GetTextTypedSoFar(model, model.SelectedItem.Item); + return IsFilterCharacter(model.SelectedItem.Item, ch, textTypedSoFar); } private static bool TextTypedSoFarMatchesItem(CompletionItem item, char ch, string textTypedSoFar) { - var textTypedWithChar = textTypedSoFar + ch; - return item.DisplayText.StartsWith(textTypedWithChar, StringComparison.CurrentCultureIgnoreCase) || - item.FilterText.StartsWith(textTypedWithChar, StringComparison.CurrentCultureIgnoreCase); - } - - /// - /// Internal for testing purposes only. - /// - internal static bool IsFilterCharacter(CompletionItem item, char ch, string filterText) - { - // general rule: if the filtering text exactly matches the start of the item then it must be a filter character - if (TextTypedSoFarMatchesItem(item, ch, textTypedSoFar: filterText)) + if (textTypedSoFar.Length > 0) { - return false; + return item.DisplayText.StartsWith(textTypedSoFar, StringComparison.CurrentCultureIgnoreCase) || + item.FilterText.StartsWith(textTypedSoFar, StringComparison.CurrentCultureIgnoreCase); } + return false; + } + + private static bool IsFilterCharacter(CompletionItem item, char ch, string textTypedSoFar) + { + // First see if the item has any specific filter rules it wants followed. foreach (var rule in item.Rules.FilterCharacterRules) { switch (rule.Kind) @@ -416,10 +419,16 @@ internal static bool IsFilterCharacter(CompletionItem item, char ch, string filt } } + // general rule: if the filtering text exactly matches the start of the item then it must be a filter character + if (TextTypedSoFarMatchesItem(item, ch, textTypedSoFar)) + { + return true; + } + return false; } - private string GetCurrentFilterText(Model model, CompletionItem selectedItem) + private string GetTextTypedSoFar(Model model, CompletionItem selectedItem) { var textSnapshot = this.TextView.TextSnapshot; var viewSpan = model.GetViewBufferSpan(selectedItem.Span); @@ -428,7 +437,10 @@ private string GetCurrentFilterText(Model model, CompletionItem selectedItem) return filterText; } - private void CommitOnTypeChar(char ch) + private void CommitOnTypeChar( + char ch, ITextSnapshot initialTextSnapshot, + VirtualSnapshotPoint initialCaretPointInView, + Action nextHandler) { AssertIsForeground(); @@ -440,7 +452,8 @@ private void CommitOnTypeChar(char ch) // was commit character if we had a selected item. Contract.ThrowIfNull(model); - this.Commit(model.SelectedItem, model, ch); + this.Commit(model.SelectedItem, model, ch, + initialTextSnapshot, initialCaretPointInView, nextHandler); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs index 4be06883600..5e599bfe32d 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs @@ -67,14 +67,7 @@ public override async Task GetDescriptionAsync(Document d var change = await service.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); // normally the items that produce multiple changes are not expecting to trigger the behaviors that rely on looking at the text - if (change.TextChanges.Length == 1) - { - return change.TextChanges[0]; - } - else - { - return new TextChange(item.Span, item.DisplayText); - } + return change.TextChange; } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Model.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Model.cs index d67178501a0..eaed8497977 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Model.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Model.cs @@ -17,6 +17,8 @@ internal class Model private readonly DisconnectedBufferGraph _disconnectedBufferGraph; public ITextSnapshot TriggerSnapshot { get { return _disconnectedBufferGraph.SubjectBufferSnapshot; } } + public Document TriggerDocument { get; } + public CompletionList OriginalList { get; } public ImmutableArray TotalItems { get; } public ImmutableArray FilteredItems { get; } @@ -45,6 +47,7 @@ internal class Model public bool DismissIfEmpty { get; } private Model( + Document triggerDocument, DisconnectedBufferGraph disconnectedBufferGraph, CompletionList originalList, ImmutableArray totalItems, @@ -64,6 +67,7 @@ internal class Model { Contract.ThrowIfFalse(totalItems.Length != 0, "Must have at least one item."); + this.TriggerDocument = triggerDocument; _disconnectedBufferGraph = disconnectedBufferGraph; this.OriginalList = originalList; this.TotalItems = totalItems; @@ -83,6 +87,7 @@ internal class Model } public static Model CreateModel( + Document triggerDocument, DisconnectedBufferGraph disconnectedBufferGraph, CompletionList originalList, CompletionItem selectedItem, @@ -120,7 +125,7 @@ internal class Model // By default we do not filter anything out. ImmutableDictionary filterState = null; - + if (completionService != null && workspace != null && workspace.Kind != WorkspaceKind.Interactive && // TODO (https://github.com/dotnet/roslyn/issues/5107): support in interactive @@ -152,9 +157,10 @@ internal class Model var selectedPresentationItem = totalItems.FirstOrDefault(it => it.Item == selectedItem); - var completionItemToFilterText= new Dictionary(); + var completionItemToFilterText = new Dictionary(); return new Model( + triggerDocument, disconnectedBufferGraph, originalList, totalItems, @@ -196,86 +202,90 @@ public bool IsSoftSelection } } + private Model With( + Optional> filteredItems = default(Optional>), + Optional selectedItem = default(Optional), + Optional> filterState = default(Optional>), + Optional> completionItemToFilterText = default(Optional>), + Optional isHardSelection = default(Optional), + Optional isUnique = default(Optional), + Optional useSuggestionMode = default(Optional), + Optional suggestionModeItem = default(Optional), + Optional commitTrackingSpanEndPoint = default(Optional)) + { + var newFilteredItems = filteredItems.HasValue ? filteredItems.Value : FilteredItems; + var newSelectedItem = selectedItem.HasValue ? selectedItem.Value : SelectedItem; + var newFilterState = filterState.HasValue ? filterState.Value : FilterState; + var newCompletionItemToFilterText = completionItemToFilterText.HasValue ? completionItemToFilterText.Value : CompletionItemToFilterText; + var newIsHardSelection = isHardSelection.HasValue ? isHardSelection.Value : IsHardSelection; + var newIsUnique = isUnique.HasValue ? isUnique.Value : IsUnique; + var newUseSuggestionMode = useSuggestionMode.HasValue ? useSuggestionMode.Value : UseSuggestionMode; + var newSuggestionModeItem = suggestionModeItem.HasValue ? suggestionModeItem.Value : SuggestionModeItem; + var newCommitTrackingSpanEndPoint = commitTrackingSpanEndPoint.HasValue ? commitTrackingSpanEndPoint.Value : CommitTrackingSpanEndPoint; + + if (newFilteredItems == FilteredItems && + newSelectedItem == SelectedItem && + newFilterState == FilterState && + newCompletionItemToFilterText == CompletionItemToFilterText && + newIsHardSelection == IsHardSelection && + newIsUnique == IsUnique && + newUseSuggestionMode == UseSuggestionMode && + newSuggestionModeItem == SuggestionModeItem && + newCommitTrackingSpanEndPoint == CommitTrackingSpanEndPoint) + { + return this; + } + + return new Model( + TriggerDocument, _disconnectedBufferGraph, OriginalList, TotalItems, newFilteredItems, + newSelectedItem, CompletionItemFilters, newFilterState, newCompletionItemToFilterText, + newIsHardSelection, newIsUnique, newUseSuggestionMode, newSuggestionModeItem, + DefaultSuggestionModeItem, Trigger, newCommitTrackingSpanEndPoint, DismissIfEmpty); + } + public Model WithFilteredItems(ImmutableArray filteredItems) { - return new Model(_disconnectedBufferGraph, OriginalList, TotalItems, filteredItems, - filteredItems.FirstOrDefault(), CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, - IsUnique, UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, - Trigger, CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(filteredItems: filteredItems, selectedItem: filteredItems.FirstOrDefault()); } public Model WithSelectedItem(PresentationItem selectedItem) { - return selectedItem == this.SelectedItem - ? this - : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - selectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(selectedItem: selectedItem); } public Model WithHardSelection(bool isHardSelection) { - return isHardSelection == this.IsHardSelection - ? this - : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, isHardSelection, IsUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(isHardSelection: isHardSelection); } public Model WithIsUnique(bool isUnique) { - return isUnique == this.IsUnique - ? this - : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, isUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(isUnique: isUnique); } public Model WithSuggestionModeItem(PresentationItem suggestionModeItem) { - return suggestionModeItem == this.SuggestionModeItem - ? this - : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, - UseSuggestionMode, suggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(suggestionModeItem: suggestionModeItem); } - public Model WithUseSuggestionCompletionMode(bool useSuggestionCompletionMode) + public Model WithUseSuggestionMode(bool useSuggestionMode) { - return useSuggestionCompletionMode == this.UseSuggestionMode - ? this - : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, - useSuggestionCompletionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(useSuggestionMode: useSuggestionMode); } internal Model WithTrackingSpanEnd(ITrackingPoint trackingSpanEnd) { - return new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - trackingSpanEnd, DismissIfEmpty); + return With(commitTrackingSpanEndPoint: new Optional(trackingSpanEnd)); } internal Model WithFilterState(ImmutableDictionary filterState) { - return new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, filterState, CompletionItemToFilterText, IsHardSelection, IsUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(filterState: filterState); } internal Model WithCompletionItemToFilterText(IReadOnlyDictionary completionItemToFilterText) { - return new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, - SelectedItem, CompletionItemFilters, FilterState, completionItemToFilterText, IsHardSelection, IsUnique, - UseSuggestionMode, SuggestionModeItem, DefaultSuggestionModeItem, Trigger, - CommitTrackingSpanEndPoint, DismissIfEmpty); + return With(completionItemToFilterText: new Optional>(completionItemToFilterText)); } internal SnapshotSpan GetCurrentSpanInSnapshot(ViewTextSpan originalSpan, ITextSnapshot textSnapshot) @@ -325,4 +335,4 @@ internal ViewTextSpan GetViewBufferSpan(TextSpan subjectBufferSpan) return _disconnectedBufferGraph.GetSubjectBufferTextSpanInViewBuffer(subjectBufferSpan); } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs index b7a29ec6ccf..1f8b40ee7d3 100644 --- a/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs @@ -346,7 +346,8 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document string actualExpectedCode = null; MarkupTestFile.GetPosition(expectedCodeAfterCommit, out actualExpectedCode, out expectedCaretPosition); - if (commitChar.HasValue && !Controller.IsCommitCharacter(service.GetRules(), completionItem, commitChar.Value, string.Empty)) + if (commitChar.HasValue && + !Controller.IsCommitCharacter(service.GetRules(), completionItem, commitChar.Value, commitChar.Value.ToString())) { Assert.Equal(codeBeforeCommit, actualExpectedCode); return; @@ -355,7 +356,7 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); var text = await document.GetTextAsync(); - var newText = text.WithChanges(commit.TextChanges); + var newText = text.WithChanges(commit.TextChange); var newDoc = document.WithText(newText); document.Project.Solution.Workspace.TryApplyChanges(newDoc.Project.Solution); @@ -384,7 +385,8 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document string actualExpectedCode = null; MarkupTestFile.GetPosition(expectedCodeAfterCommit, out actualExpectedCode, out expectedCaretPosition); - if (commitChar.HasValue && !Controller.IsCommitCharacter(service.GetRules(), completionItem, commitChar.Value, string.Empty)) + if (commitChar.HasValue && + !Controller.IsCommitCharacter(service.GetRules(), completionItem, commitChar.Value, commitChar.Value.ToString())) { Assert.Equal(codeBeforeCommit, actualExpectedCode); return; @@ -435,7 +437,8 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document var text = await document.GetTextAsync(); - if (commitChar == '\t' || Controller.IsCommitCharacter(service.GetRules(), firstItem, commitChar, textTypedSoFar)) + if (commitChar == '\t' || + Controller.IsCommitCharacter(service.GetRules(), firstItem, commitChar, textTypedSoFar + commitChar)) { var textChange = await DescriptionModifyingPresentationItem.GetTextChangeAsync( service, document, firstItem, commitChar); @@ -804,12 +807,14 @@ protected async Task VerifyCommitCharactersAsync(string initialMarkup, string te foreach (var ch in validChars) { - Assert.True(Controller.IsCommitCharacter(service.GetRules(), item, ch, textTypedSoFar), $"Expected '{ch}' to be a commit character"); + Assert.True(Controller.IsCommitCharacter( + service.GetRules(), item, ch, textTypedSoFar + ch), $"Expected '{ch}' to be a commit character"); } foreach (var ch in invalidChars) { - Assert.False(Controller.IsCommitCharacter(service.GetRules(), item, ch, textTypedSoFar), $"Expected '{ch}' NOT to be a commit character"); + Assert.False(Controller.IsCommitCharacter( + service.GetRules(), item, ch, textTypedSoFar + ch), $"Expected '{ch}' NOT to be a commit character"); } } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index e5302f1364b..b40cca12c74 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -1586,9 +1586,6 @@ $$]]>, extraExportedTypes:={GetType(CSharpEditorFormattingService)}.T state.AssertMatchesTextStartingAtLine(6, " doodle;") state.SendUndo() Await state.WaitForAsynchronousOperationsAsync() - state.AssertMatchesTextStartingAtLine(6, "doodle;") - state.SendUndo() - Await state.WaitForAsynchronousOperationsAsync() state.AssertMatchesTextStartingAtLine(6, "doo;") End Using End Function diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb index 407c37e1270..a25ac68b9f5 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb @@ -28,7 +28,7 @@ End Class Await state.AssertSelectedCompletionItem("On Error GoTo", description:=String.Format(FeaturesResources.Keyword, "On Error GoTo") + vbCrLf + VBFeaturesResources.OnErrorGotoKeywordToolTip) state.SendTypeChars(" ") Await state.WaitForAsynchronousOperationsAsync() - Await state.AssertSelectedCompletionItem("Error GoTo", description:=String.Format(FeaturesResources.Keyword, "Error GoTo") + vbCrLf + VBFeaturesResources.OnErrorGotoKeywordToolTip) + Await state.AssertSelectedCompletionItem("On Error GoTo", description:=String.Format(FeaturesResources.Keyword, "On Error GoTo") + vbCrLf + VBFeaturesResources.OnErrorGotoKeywordToolTip) End Using End Function diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs index 5cfaaa64bab..951024f0953 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs @@ -226,7 +226,7 @@ private ISet GetExistingNamedParameters(AttributeArgumentListSyntax argu return attributeType.GetAttributeNamedParameters(semanticModel.Compilation, within); } - public override Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) + protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) { return Task.FromResult(GetTextChange(selectedItem, ch)); } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs index 7a603b2f11b..313ac6fabd0 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs @@ -338,7 +338,7 @@ private CompletionItemRules GetRules(string displayText) private static readonly string InsertionTextProperty = "insertionText"; - public override Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) + protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) { string insertionText; if (!selectedItem.Properties.TryGetValue(InsertionTextProperty, out insertionText)) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceCompletionProvider.cs index e0e45846c6c..5e46326584e 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceCompletionProvider.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; @@ -126,4 +123,4 @@ public override Task GetDescriptionAsync(Document documen return new TextChange(selectedItem.Span, selectedItem.DisplayText); } } -} +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs index 9fd21a85440..baf0a21df23 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs @@ -235,11 +235,11 @@ int IEqualityComparer.GetHashCode(IParameterSymbol obj) return obj.Name.GetHashCode(); } - public override Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) + protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) { return Task.FromResult(new TextChange( selectedItem.Span, selectedItem.DisplayText.Substring(0, selectedItem.DisplayText.Length - ColonString.Length))); } } -} +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs index 0c654c1a470..d1970ec4ced 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OverrideCompletionProvider.cs @@ -220,4 +220,4 @@ protected override int GetTargetCaretPosition(SyntaxNode caretTarget) } } } -} +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs index 147eccec45d..afeb0d108f6 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -102,16 +101,16 @@ protected override async Task CreateContext(Document docu private static CompletionItemRules s_importDirectiveRules = CompletionItemRules.Create(commitCharacterRules: ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, '.', ';'))); + // '<' should not filter the completion list, even though it's in generic items like IList<> + private static readonly CompletionItemRules s_itemRules = CompletionItemRules.Default. + WithFilterCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, '<')). + WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Add, '<')); + protected override CompletionItemRules GetCompletionItemRules(IReadOnlyList symbols, AbstractSyntaxContext context) { - if (context.IsInImportsDirective) - { - return s_importDirectiveRules; - } - else - { - return CompletionItemRules.Default; - } + return context.IsInImportsDirective + ? s_importDirectiveRules + : s_itemRules; } } -} +} \ No newline at end of file diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index 07de3d95d6d..2feabb709e4 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -24,7 +24,9 @@ internal override bool IsInsertionTrigger(SourceText text, int characterPosition return text[characterPosition] == '<'; } - protected override async Task> GetItemsWorkerAsync(Document document, int position, TextSpan span, CompletionTrigger trigger, CancellationToken cancellationToken) + protected override async Task> GetItemsWorkerAsync( + Document document, int position, TextSpan span, + CompletionTrigger trigger, CancellationToken cancellationToken) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); diff --git a/src/Features/Core/Portable/Completion/CommonCompletionProvider.cs b/src/Features/Core/Portable/Completion/CommonCompletionProvider.cs index 0b53707f0d0..c0df74f363e 100644 --- a/src/Features/Core/Portable/Completion/CommonCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/CommonCompletionProvider.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.Text; using System.Collections.Immutable; using System.Threading; +using System; namespace Microsoft.CodeAnalysis.Completion { @@ -44,12 +45,17 @@ public override async Task GetChangeAsync(Document document, C { var change = (await GetTextChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false)) ?? new TextChange(item.Span, item.DisplayText); - return CompletionChange.Create(ImmutableArray.Create(change)); + return CompletionChange.Create(change); } public virtual Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) + { + return GetTextChangeAsync(selectedItem, ch, cancellationToken); + } + + protected virtual Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) { return Task.FromResult(null); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index ce94792313f..30ca26f9233 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Completion @@ -12,8 +14,11 @@ namespace Microsoft.CodeAnalysis.Completion public sealed class CompletionChange { /// - /// The text changes to be applied to the document. + /// The text change to be applied to the document. /// + public TextChange TextChange { get; } + + [Obsolete("Use TextChange instead")] public ImmutableArray TextChanges { get; } /// @@ -23,16 +28,25 @@ public sealed class CompletionChange public int? NewPosition { get; } /// - /// True if the changes include the typed character that caused the to be committed. - /// If false the completion host will determine if and where the commit character is inserted into the document. + /// True if the changes include the typed character that caused the + /// to be committed. If false the completion host will determine if and where the commit + /// character is inserted into the document. /// public bool IncludesCommitCharacter { get; } - private CompletionChange(ImmutableArray textChanges, int? newPosition, bool includesCommit) + private CompletionChange(ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) + : this(textChanges.Single(), newPosition, includesCommitCharacter) { - this.TextChanges = textChanges.IsDefault ? ImmutableArray.Empty : textChanges; - this.NewPosition = newPosition; - this.IncludesCommitCharacter = includesCommit; + } + + private CompletionChange(TextChange textChange, int? newPosition, bool includesCommitCharacter) + { + TextChange = textChange; +#pragma warning disable CS0618 // Type or member is obsolete + TextChanges = ImmutableArray.Create(textChange); +#pragma warning restore CS0618 // Type or member is obsolete + NewPosition = newPosition; + IncludesCommitCharacter = includesCommitCharacter; } /// @@ -44,25 +58,45 @@ private CompletionChange(ImmutableArray textChanges, int? newPositio /// True if the changes include the typed character that caused the to be committed. /// If false, the completion host will determine if and where the commit character is inserted into the document. /// - public static CompletionChange Create(ImmutableArray textChanges, int? newPosition = null, bool includesCommitCharacter = false) + [Obsolete("Use Create overload that only takes a single TextChange")] + public static CompletionChange Create( + ImmutableArray textChanges, + int? newPosition, + bool includesCommitCharacter) { return new CompletionChange(textChanges, newPosition, includesCommitCharacter); } +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + public static CompletionChange Create( +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + TextChange textChange, + int? newPosition = null, + bool includesCommitCharacter = false) + { + return new CompletionChange(textChange, newPosition, includesCommitCharacter); + } + /// /// Creates a copy of this with the property changed. /// + [Obsolete("Use WithTextChange instead")] public CompletionChange WithTextChanges(ImmutableArray textChanges) { return new CompletionChange(textChanges, this.NewPosition, this.IncludesCommitCharacter); } + public CompletionChange WithTextChange(TextChange textChange) + { + return new CompletionChange(textChange, this.NewPosition, this.IncludesCommitCharacter); + } + /// /// Creates a copy of this with the property changed. /// public CompletionChange WithNewPosition(int? newPostion) { - return new CompletionChange(this.TextChanges, newPostion, this.IncludesCommitCharacter); + return new CompletionChange(this.TextChange, newPostion, this.IncludesCommitCharacter); } /// @@ -70,7 +104,7 @@ public CompletionChange WithNewPosition(int? newPostion) /// public CompletionChange WithIncludesCommitCharacter(bool includesCommitCharacter) { - return new CompletionChange(this.TextChanges, this.NewPosition, includesCommitCharacter); + return new CompletionChange(this.TextChange, this.NewPosition, includesCommitCharacter); } } } \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionItemRules.cs b/src/Features/Core/Portable/Completion/CompletionItemRules.cs index 223aac6dfb9..3e31bee5489 100644 --- a/src/Features/Core/Portable/Completion/CompletionItemRules.cs +++ b/src/Features/Core/Portable/Completion/CompletionItemRules.cs @@ -210,6 +210,16 @@ public CompletionItemRules WithFilterCharacterRules(ImmutableArray /// Creates a copy of this with the property changed. /// diff --git a/src/Features/Core/Portable/Completion/CompletionProvider.cs b/src/Features/Core/Portable/Completion/CompletionProvider.cs index 51034f10bf0..2ac4554b005 100644 --- a/src/Features/Core/Portable/Completion/CompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/CompletionProvider.cs @@ -54,7 +54,7 @@ public virtual Task GetDescriptionAsync(Document document /// public virtual Task GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) { - return Task.FromResult(CompletionChange.Create(ImmutableArray.Create(new TextChange(item.Span, item.DisplayText)))); + return Task.FromResult(CompletionChange.Create(new TextChange(item.Span, item.DisplayText))); } /// diff --git a/src/Features/Core/Portable/Completion/CompletionService.cs b/src/Features/Core/Portable/Completion/CompletionService.cs index ef394416072..6a5d4324f6f 100644 --- a/src/Features/Core/Portable/Completion/CompletionService.cs +++ b/src/Features/Core/Portable/Completion/CompletionService.cs @@ -49,10 +49,10 @@ public virtual CompletionRules GetRules() /// This API uses SourceText instead of Document so implementations can only be based on text, not syntax or semantics. /// public virtual bool ShouldTriggerCompletion( - SourceText text, - int caretPosition, - CompletionTrigger trigger, - ImmutableHashSet roles = null, + SourceText text, + int caretPosition, + CompletionTrigger trigger, + ImmutableHashSet roles = null, OptionSet options = null) { return false; @@ -93,8 +93,8 @@ public virtual TextSpan GetDefaultItemSpan(SourceText text, int caretPosition) /// The item to get the description for. /// public virtual Task GetDescriptionAsync( - Document document, - CompletionItem item, + Document document, + CompletionItem item, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(CompletionDescription.Empty); @@ -110,12 +110,12 @@ public virtual TextSpan GetDefaultItemSpan(SourceText text, int caretPosition) /// This value is null when the commit was caused by the [TAB] or [ENTER] keys. /// public virtual Task GetChangeAsync( - Document document, - CompletionItem item, - char? commitCharacter = null, + Document document, + CompletionItem item, + char? commitCharacter = null, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(CompletionChange.Create(ImmutableArray.Create(new TextChange(item.Span, item.DisplayText)))); + return Task.FromResult(CompletionChange.Create(new TextChange(item.Span, item.DisplayText))); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs index ae4411d1aad..8881f878efe 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs @@ -452,7 +452,7 @@ internal virtual bool SupportsTriggerOnDeletion(OptionSet options) } else { - return CompletionChange.Create(ImmutableArray.Create(new TextChange(item.Span, item.DisplayText))); + return CompletionChange.Create(new TextChange(item.Span, item.DisplayText)); } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index cf497e72a11..59753c375b7 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using System.Collections.Immutable; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.Completion.Providers { @@ -83,7 +84,9 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) return; } - var items = await GetItemsWorkerAsync(context.Document, context.Position, context.DefaultItemSpan, context.Trigger, context.CancellationToken).ConfigureAwait(false); + var items = await GetItemsWorkerAsync( + context.Document, context.Position, context.DefaultItemSpan, + context.Trigger, context.CancellationToken).ConfigureAwait(false); if (items != null) { context.AddItems(items); @@ -212,7 +215,9 @@ public override async Task GetChangeAsync(Document document, C replacementText += afterCaretText; - return CompletionChange.Create(ImmutableArray.Create(new TextChange(replacementSpan, replacementText)), newPosition, includesCommitCharacter: true); + return CompletionChange.Create( + new TextChange(replacementSpan, replacementText), + newPosition, includesCommitCharacter: true); } private TextSpan ComputeReplacementSpan(CompletionItem completionItem, SourceText text) diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs index 780c22c9040..0454c9097c1 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using System; namespace Microsoft.CodeAnalysis.Completion.Providers { @@ -105,21 +104,9 @@ protected virtual CompletionItem CreateItem(RecommendedKeyword keyword, TextSpan return set; } - public override async Task GetTextChangeAsync(Document document, CompletionItem item, char? ch, CancellationToken cancellationToken) + public override Task GetTextChangeAsync(Document document, CompletionItem item, char? ch, CancellationToken cancellationToken) { - var insertionText = item.DisplayText; - if (ch == ' ') - { - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var textTypedSoFar = text.GetSubText(item.Span).ToString(); - - if (textTypedSoFar.Length > 0 && insertionText.StartsWith(textTypedSoFar, StringComparison.OrdinalIgnoreCase)) - { - insertionText = insertionText.Substring(0, textTypedSoFar.Length - 1); - } - } - - return new TextChange(item.Span, insertionText); + return Task.FromResult((TextChange?)new TextChange(item.Span, item.DisplayText)); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs index 50ccd31bf63..a7c31d8cea9 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using System.Collections.Immutable; +using System.Collections.Generic; namespace Microsoft.CodeAnalysis.Completion.Providers { @@ -57,14 +58,42 @@ public override async Task GetChangeAsync(Document document, C } var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - return CompletionChange.Create(ImmutableArray.CreateRange(changes), newPosition, includesCommitCharacter: true); + var change = Collapse(newText, changes.ToList()); + + return CompletionChange.Create(change, newPosition, includesCommitCharacter: true); + } + + private TextChange Collapse(SourceText newText, List changes) + { + if (changes.Count == 0) + { + return new TextChange(new TextSpan(0, 0), ""); + } + else if (changes.Count == 1) + { + return changes[0]; + } + + // The span we want to replace goes from the start of the first span to the end of + // the last span. + var totalOldSpan = TextSpan.FromBounds(changes.First().Span.Start, changes.Last().Span.End); + + // We figure out the text we're replacing with by actually just figuring out the + // new span in the newText and grabbing the text out of that. The newSpan will + // start from the same position as the oldSpan, but it's length will be the old + // span's length + all the deltas we accumulate through each text change. i.e. + // if the first change adds 2 characters and the second change adds 4, then + // the newSpan will be 2+4=6 characters longer than the old span. + var sumOfDeltas = changes.Sum(c => c.NewText.Length - c.Span.Length); + var totalNewSpan = new TextSpan(totalOldSpan.Start, totalOldSpan.Length + sumOfDeltas); + + return new TextChange(totalOldSpan, newText.ToString(totalNewSpan)); } private async Task DetermineNewDocumentAsync(CompletionItem completionItem, SourceText sourceText, CancellationToken cancellationToken) { // The span we're going to replace var line = sourceText.Lines[MemberInsertionCompletionItem.GetLine(completionItem)]; - //var line = textSnapshot.GetLineFromLineNumber(MemberInsertionCompletionItem.GetLine(completionItem)); //var sourceText = textSnapshot.AsText(); var document = sourceText.GetOpenDocumentInCurrentContextWithChanges(); @@ -210,22 +239,23 @@ private TextSpan ComputeDestinationSpan(SyntaxNode insertionRoot, string inserti private static readonly ImmutableArray s_commitRules = ImmutableArray.Create( CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, '(')); + private static readonly ImmutableArray s_filterRules = ImmutableArray.Create( + CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, '(')); + private static readonly CompletionItemRules s_defaultRules = - CompletionItemRules.Create(commitCharacterRules: s_commitRules, enterKeyRule: EnterKeyRule.Never); + CompletionItemRules.Create( + commitCharacterRules: s_commitRules, + filterCharacterRules: s_filterRules, + enterKeyRule: EnterKeyRule.Never); internal virtual CompletionItemRules GetRules() { return s_defaultRules; } - public override Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) - { - return Task.FromResult(null); - } - public override Task GetDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) { return MemberInsertionCompletionItem.GetDescriptionAsync(item, document, cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractOverrideCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractOverrideCompletionProvider.cs index 3d03710c51e..d0097ea0c2f 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractOverrideCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractOverrideCompletionProvider.cs @@ -116,4 +116,4 @@ protected ITypeSymbol GetReturnType(ISymbol symbol) } } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index a68f2cb16b6..7fba3f668ed 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -8,9 +8,11 @@ Microsoft.CodeAnalysis.Completion.CharacterSetModificationRule.Kind.get -> Micro Microsoft.CodeAnalysis.Completion.CompletionChange Microsoft.CodeAnalysis.Completion.CompletionChange.IncludesCommitCharacter.get -> bool Microsoft.CodeAnalysis.Completion.CompletionChange.NewPosition.get -> int? +Microsoft.CodeAnalysis.Completion.CompletionChange.TextChange.get -> Microsoft.CodeAnalysis.Text.TextChange Microsoft.CodeAnalysis.Completion.CompletionChange.TextChanges.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Completion.CompletionChange.WithIncludesCommitCharacter(bool includesCommitCharacter) -> Microsoft.CodeAnalysis.Completion.CompletionChange Microsoft.CodeAnalysis.Completion.CompletionChange.WithNewPosition(int? newPostion) -> Microsoft.CodeAnalysis.Completion.CompletionChange +Microsoft.CodeAnalysis.Completion.CompletionChange.WithTextChange(Microsoft.CodeAnalysis.Text.TextChange textChange) -> Microsoft.CodeAnalysis.Completion.CompletionChange Microsoft.CodeAnalysis.Completion.CompletionChange.WithTextChanges(System.Collections.Immutable.ImmutableArray textChanges) -> Microsoft.CodeAnalysis.Completion.CompletionChange Microsoft.CodeAnalysis.Completion.CompletionContext Microsoft.CodeAnalysis.Completion.CompletionContext.AddItem(Microsoft.CodeAnalysis.Completion.CompletionItem item) -> void @@ -197,7 +199,8 @@ override Microsoft.CodeAnalysis.Completion.CompletionServiceWithProviders.Should override Microsoft.CodeAnalysis.TaggedText.ToString() -> string static Microsoft.CodeAnalysis.Completion.CharacterSetModificationRule.Create(Microsoft.CodeAnalysis.Completion.CharacterSetModificationKind kind, System.Collections.Immutable.ImmutableArray characters) -> Microsoft.CodeAnalysis.Completion.CharacterSetModificationRule static Microsoft.CodeAnalysis.Completion.CharacterSetModificationRule.Create(Microsoft.CodeAnalysis.Completion.CharacterSetModificationKind kind, params char[] characters) -> Microsoft.CodeAnalysis.Completion.CharacterSetModificationRule -static Microsoft.CodeAnalysis.Completion.CompletionChange.Create(System.Collections.Immutable.ImmutableArray textChanges, int? newPosition = null, bool includesCommitCharacter = false) -> Microsoft.CodeAnalysis.Completion.CompletionChange +static Microsoft.CodeAnalysis.Completion.CompletionChange.Create(Microsoft.CodeAnalysis.Text.TextChange textChange, int? newPosition = null, bool includesCommitCharacter = false) -> Microsoft.CodeAnalysis.Completion.CompletionChange +static Microsoft.CodeAnalysis.Completion.CompletionChange.Create(System.Collections.Immutable.ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) -> Microsoft.CodeAnalysis.Completion.CompletionChange static Microsoft.CodeAnalysis.Completion.CompletionDescription.Create(System.Collections.Immutable.ImmutableArray taggedParts) -> Microsoft.CodeAnalysis.Completion.CompletionDescription static Microsoft.CodeAnalysis.Completion.CompletionDescription.FromText(string text) -> Microsoft.CodeAnalysis.Completion.CompletionDescription static Microsoft.CodeAnalysis.Completion.CompletionItem.Create(string displayText, string filterText = null, string sortText = null, Microsoft.CodeAnalysis.Text.TextSpan span = default(Microsoft.CodeAnalysis.Text.TextSpan), System.Collections.Immutable.ImmutableDictionary properties = null, System.Collections.Immutable.ImmutableArray tags = default(System.Collections.Immutable.ImmutableArray), Microsoft.CodeAnalysis.Completion.CompletionItemRules rules = null) -> Microsoft.CodeAnalysis.Completion.CompletionItem diff --git a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs index 8f994d9f6ca..f2e5be433f0 100644 --- a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs +++ b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs @@ -97,15 +97,7 @@ private async Task GetInsertionTextAsync(Document document, CompletionIt var service = CompletionService.GetService(document); var change = await service.GetChangeAsync(document, item, null, cancellationToken).ConfigureAwait(false); - // normally the items that produce multiple changes are not expecting to trigger the behaviors that rely on looking at the text - if (change.TextChanges.Length == 1) - { - return change.TextChanges[0].NewText; - } - else - { - return item.DisplayText; - } + return change.TextChange.NewText; } private SpellCheckCodeAction CreateCodeAction(TSimpleName nameNode, string oldName, string newName, Document document) diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/CompletionUtilities.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/CompletionUtilities.vb index 3c70214e66e..961f27e0b9a 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/CompletionUtilities.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/CompletionUtilities.vb @@ -127,7 +127,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Private Function GetOfText(symbol As ISymbol, typedChar As Char) As String If symbol.Kind = SymbolKind.NamedType Then If typedChar = "("c Then - Return "(" + Return "" Else Return "(Of" End If @@ -148,6 +148,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return GetInsertionText(name, symbol, context.IsRightOfNameSeparator, DirectCast(context, VisualBasicSyntaxContext).WithinAsyncMethod, ch) End Function - End Module -End Namespace +End Namespace \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/KeywordCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/KeywordCompletionProvider.vb index 745b98896ee..57159e1c160 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/KeywordCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/KeywordCompletionProvider.vb @@ -6,6 +6,7 @@ Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery +Imports Microsoft.CodeAnalysis.Completion Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Friend Class KeywordCompletionProvider @@ -170,6 +171,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers New KeywordRecommenders.Types.BuiltInTypesKeywordRecommender() }.ToImmutableArray() End Function - End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb index 5300ae768e3..35a73de8007 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.vb @@ -75,10 +75,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers descriptionPosition:=position, contextPosition:=position, isArgumentName:=True, - rules:=CompletionItemRules.Default)) + rules:=s_itemRules)) Next End Function + ' Typing : or = should not filter the list, but they should commit the list. + Private Shared s_itemRules As CompletionItemRules = CompletionItemRules.Default. + WithFilterCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ":"c, "="c)). + WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Add, ":"c, "="c)) + Public Overrides Function GetDescriptionAsync(document As Document, item As CompletionItem, cancellationToken As CancellationToken) As Task(Of CompletionDescription) Return SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken) End Function @@ -199,7 +204,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers argumentList = Nothing End Sub - Public Overrides Function GetTextChangeAsync(document As Document, selectedItem As CompletionItem, ch As Char?, cancellationToken As CancellationToken) As Task(Of TextChange?) + Protected Overrides Function GetTextChangeAsync(selectedItem As CompletionItem, ch As Char?, cancellationToken As CancellationToken) As Task(Of TextChange?) Dim symbolItem = selectedItem Dim insertionText = SymbolCompletionItem.GetInsertionText(selectedItem) Dim change As TextChange @@ -212,6 +217,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers End If Return Task.FromResult(Of TextChange?)(change) End Function - End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.vb index 2591e9569ee..e3144ee52a7 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.vb @@ -142,10 +142,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return Not s.DeclaringSyntaxReferences.Select(Function(r) r.GetSyntax()).All(Function(a) a.Span.IntersectsWith(token.Span)) End Function - Public Overrides Function GetTextChangeAsync(document As Document, selectedItem As CompletionItem, ch As Char?, cancellationToken As CancellationToken) As Task(Of TextChange?) + Protected Overrides Function GetTextChangeAsync(selectedItem As CompletionItem, ch As Char?, cancellationToken As CancellationToken) As Task(Of TextChange?) Dim insertionText = SymbolCompletionItem.GetInsertionText(selectedItem) Return Task.FromResult(Of TextChange?)(New TextChange(selectedItem.Span, insertionText)) End Function - End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs index 2c6c5f9b640..8c3c2e7c59a 100644 --- a/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs +++ b/src/Interactive/EditorFeatures/CSharp/Completion/FileSystem/LoadDirectiveCompletionProvider.cs @@ -110,7 +110,7 @@ private ImmutableArray GetItems(SourceText text, Document docume return helper.GetItems(pathThroughLastSlash, documentPath: null); } - public override Task GetTextChangeAsync(Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) + protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) { // When we commit "\\" when the user types \ we have to adjust for the fact that the // controller will automatically append \ after we commit. Because of that, we don't @@ -121,7 +121,7 @@ private ImmutableArray GetItems(SourceText text, Document docume return Task.FromResult(new TextChange(selectedItem.Span, "\\")); } - return base.GetTextChangeAsync(document, selectedItem, ch, cancellationToken); + return base.GetTextChangeAsync(selectedItem, ch, cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetCompletionProvider.vb b/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetCompletionProvider.vb index 71a1e88251f..36c583d87cc 100644 --- a/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetCompletionProvider.vb +++ b/src/VisualStudio/VisualBasic/Impl/Snippets/SnippetCompletionProvider.vb @@ -78,7 +78,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Snippets options.GetOption(CompletionOptions.TriggerOnTypingLetters, LanguageNames.VisualBasic) End Function - Public Sub Commit(completionItem As CompletionItem, textView As ITextView, subjectBuffer As ITextBuffer, triggerSnapshot As ITextSnapshot, commitChar As Char?) Implements ICustomCommitCompletionProvider.Commit + Public Sub Commit(completionItem As CompletionItem, + textView As ITextView, + subjectBuffer As ITextBuffer, + triggerSnapshot As ITextSnapshot, + commitChar As Char?) Implements ICustomCommitCompletionProvider.Commit Dim snippetClient = SnippetExpansionClient.GetSnippetExpansionClient(textView, subjectBuffer, _editorAdaptersFactoryService) Dim trackingSpan = triggerSnapshot.CreateTrackingSpan(completionItem.Span.ToSpan(), SpanTrackingMode.EdgeInclusive) -- GitLab