diff options
6 files changed, 192 insertions, 108 deletions
diff --git a/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs b/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs index 72bd1d2..6a91623 100644 --- a/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs +++ b/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs @@ -18,14 +18,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation internal class AsyncCompletionBroker : IAsyncCompletionBroker { [Import(AllowDefault = true)] - internal ILoggingServiceInternal Logger { get; set; } - - // This may be used to GetExtentOfWord, but it doesn't fully work yet, so we're not using it. - [Import(AllowDefault = true)] - internal ITextDocumentFactoryService TextDocumentFactoryService { get; set; } + private ILoggingServiceInternal Logger; [Import] - internal IGuardedOperations GuardedOperations { get; set; } + private IGuardedOperations GuardedOperations; [Import] private JoinableTaskContext JoinableTaskContext; @@ -33,6 +29,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation [Import] private IContentTypeRegistryService ContentTypeRegistryService; + // Used exclusively for legacy telemetry + [Import(AllowDefault = true)] + private ITextDocumentFactoryService TextDocumentFactoryService; + [ImportMany] private IEnumerable<Lazy<ICompletionPresenterProvider, IOrderableContentTypeMetadata>> UnorderedPresenterProviders; @@ -43,15 +43,15 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation private IEnumerable<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeMetadata>> UnorderedCompletionServiceProviders; private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeMetadata>> _orderedPresenterProviders; - internal IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeMetadata>> OrderedPresenterProviders + private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeMetadata>> OrderedPresenterProviders => _orderedPresenterProviders ?? (_orderedPresenterProviders = Orderer.Order(UnorderedPresenterProviders)); private IList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeMetadata>> _orderedCompletionItemSourceProviders; - internal IList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeMetadata>> OrderedCompletionItemSourceProviders + private IList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeMetadata>> OrderedCompletionItemSourceProviders => _orderedCompletionItemSourceProviders ?? (_orderedCompletionItemSourceProviders = Orderer.Order(UnorderedCompletionItemSourceProviders)); private IList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeMetadata>> _orderedCompletionServiceProviders; - internal IList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeMetadata>> OrderedCompletionServiceProviders + private IList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeMetadata>> OrderedCompletionServiceProviders => _orderedCompletionServiceProviders ?? (_orderedCompletionServiceProviders = Orderer.Order(UnorderedCompletionServiceProviders)); private ImmutableDictionary<IContentType, ImmutableSortedSet<char>> _commitCharacters = ImmutableDictionary<IContentType, ImmutableSortedSet<char>>.Empty; @@ -60,12 +60,26 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation private ImmutableDictionary<IContentType, ICompletionPresenterProvider> _cachedPresenterProviders = ImmutableDictionary<IContentType, ICompletionPresenterProvider>.Empty; private bool firstRun = true; // used only for diagnostics private bool _firstInvocationReported; // used for "time to code" - private StableContentTypeComparer _contentTypeComparer; + private const string IsCompletionAvailableProperty = "IsCompletionAvailable"; - internal void DismissSession(IAsyncCompletionSession session) + private Dictionary<IContentType, bool> FeatureAvailabilityByContentType = new Dictionary<IContentType, bool>(); + + bool IAsyncCompletionBroker.IsCompletionSupported(IContentType contentType) { - session.TextView.Properties.RemoveProperty(typeof(IAsyncCompletionSession)); + bool featureIsAvailable; + if (FeatureAvailabilityByContentType.TryGetValue(contentType, out featureIsAvailable)) + { + return featureIsAvailable; + } + + featureIsAvailable = UnorderedCompletionItemSourceProviders + .Any(n => n.Metadata.ContentTypes.Any(ct => contentType.IsOfType(ct))); + featureIsAvailable &= UnorderedCompletionServiceProviders + .Any(n => n.Metadata.ContentTypes.Any(ct => contentType.IsOfType(ct))); + + FeatureAvailabilityByContentType[contentType] = featureIsAvailable; + return featureIsAvailable; } IAsyncCompletionSession IAsyncCompletionBroker.TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, char typedChar) @@ -77,9 +91,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } var sourcesWithData = MetadataUtilities<IAsyncCompletionItemSourceProvider>.GetBuffersAndImports(textView, triggerLocation, GetCompletionItemSourceProviders); + var cachedData = new CompletionSourcesWithData(sourcesWithData); foreach (var sourceWithData in sourcesWithData) { - // TODO: pass the sources to TriggerCompletion var sourceProvider = GuardedOperations.InstantiateExtension(this, sourceWithData.import); // TODO: consider caching this var source = sourceProvider.GetOrCreate(textView); var candidateSpan = GuardedOperations.CallExtensionPoint( @@ -92,22 +106,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { var mappingSpan = textView.BufferGraph.CreateMappingSpan(candidateSpan.Value, SpanTrackingMode.EdgeInclusive); var applicableSpan = mappingSpan.GetSpans(textView.TextBuffer)[0]; - return TriggerCompletion(textView, triggerLocation, applicableSpan); + return TriggerCompletion(textView, triggerLocation, applicableSpan, cachedData); } } return null; } - private IAsyncCompletionSession TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan) - {// TODO: If we are in virtual space, create real space. Look at CompletionTrigger.cs + private IAsyncCompletionSession TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CompletionSourcesWithData sources) + { var session = GetSession(textView); if (session != null) { return session; } - var sourcesWithData = MetadataUtilities<IAsyncCompletionItemSourceProvider>.GetBuffersAndImports(textView, triggerLocation, GetCompletionItemSourceProviders); - if (!sourcesWithData.Any()) + if (!sources.Data.Any()) { // There is no completion source available for this buffer return null; @@ -116,9 +129,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation //var sourcesWithLocations = new Dictionary<IAsyncCompletionItemSource, SnapshotPoint>(); var potentialCommitCharsBuilder = ImmutableArray.CreateBuilder<char>(); var sourcesWithLocations = new Dictionary<IAsyncCompletionItemSource, SnapshotPoint>(); - foreach (var sourceWithData in sourcesWithData) + foreach (var sourceWithData in sources.Data) { - // Unfortunately we can't use for loop here because IDictionary.Keys is ICollection which can't be accessed with an indexer var sourceProvider = GuardedOperations.InstantiateExtension(this, sourceWithData.import); // TODO: consider caching this GuardedOperations.CallExtensionPoint( errorSource: sourceProvider, @@ -154,7 +166,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } var telemetry = GetOrCreateTelemetry(textView); - session = new AsyncCompletionSession(applicableSpan, potentialCommitCharsBuilder.ToImmutable(), JoinableTaskContext.Factory, presenterProvider, sourcesWithLocations, service, this, textView, telemetry); + session = new AsyncCompletionSession(applicableSpan, potentialCommitCharsBuilder.ToImmutable(), JoinableTaskContext.Factory, presenterProvider, sourcesWithLocations, service, this, textView, telemetry, GuardedOperations); textView.Properties.AddProperty(typeof(IAsyncCompletionSession), session); textView.Closed += TextView_Closed; @@ -285,6 +297,29 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation return null; } + /// <summary> + /// This method is used by <see cref="IAsyncCompletionSession"/> to inform the broker that it should forget about the session. Used when dismissing. + /// This method does not dismiss the session! + /// </summary> + /// <param name="session">Session being dismissed</param> + internal void ForgetSession(IAsyncCompletionSession session) + { + session.TextView.Properties.RemoveProperty(typeof(IAsyncCompletionSession)); + } + + /// <summary> + /// Wrapper around complex parameters. This is a candidate for refactoring. + /// </summary> + private struct CompletionSourcesWithData + { + internal IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeMetadata> import)> Data; + + public CompletionSourcesWithData(IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeMetadata> import)> data) + { + Data = data; + } + } + // Helper methods for telemetry: private CompletionTelemetryHost GetOrCreateTelemetry(ITextView textView) { @@ -341,22 +376,5 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation ("VS.Editor.IntellisenseFirstRun.Opened.ContentType", reportedContentType), ("VS.Editor.IntellisenseFirstRun.Opened.FileExtension", fileExtension)); } - - const string IsCompletionAvailableProperty = "IsCompletionAvailable"; - bool IAsyncCompletionBroker.IsCompletionSupported(ITextView textView) - { - bool featureIsAvailable; - if (textView.Properties.TryGetProperty(IsCompletionAvailableProperty, out featureIsAvailable)) - { - return featureIsAvailable; - } - - featureIsAvailable = UnorderedCompletionItemSourceProviders - .Any(n => n.Metadata.ContentTypes.Any(ct => textView.TextBuffer.ContentType.IsOfType(ct))); - featureIsAvailable &= UnorderedCompletionServiceProviders - .Any(n => n.Metadata.ContentTypes.Any(ct => textView.TextBuffer.ContentType.IsOfType(ct))); - textView.Properties.AddProperty(IsCompletionAvailableProperty, featureIsAvailable); - return featureIsAvailable; - } } } diff --git a/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs b/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs index 7933064..0469423 100644 --- a/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs +++ b/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs @@ -28,7 +28,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation private readonly JoinableTaskFactory _jtf; private readonly ICompletionPresenterProvider _presenterProvider; private readonly AsyncCompletionBroker _broker; - private readonly ITextView _view; + private readonly ITextView _textView; private readonly IGuardedOperations _guardedOperations; private readonly ImmutableArray<char> _potentialCommitChars; @@ -62,18 +62,19 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation // Facilitate experience when there are no items to display private bool _selectionModeBeforeNoResultFallback; private bool _inNoResultFallback; + private bool _ignoreCaretMovement; public event EventHandler<CompletionItemEventArgs> ItemCommitted; public event EventHandler Dismissed; public event EventHandler<CompletionItemsWithHighlightEventArgs> ItemsUpdated; - public ITextView TextView => _view; + public ITextView TextView => _textView; public bool IsDismissed => _isDismissed; public AsyncCompletionSession(SnapshotSpan applicableSpan, ImmutableArray<char> potentialCommitChars, JoinableTaskFactory jtf, ICompletionPresenterProvider presenterProvider, IDictionary<IAsyncCompletionItemSource, SnapshotPoint> completionSources, - IAsyncCompletionService completionService, AsyncCompletionBroker broker, ITextView view, CompletionTelemetryHost telemetryHost) + IAsyncCompletionService completionService, AsyncCompletionBroker broker, ITextView view, CompletionTelemetryHost telemetryHost, IGuardedOperations guardedOperations) { _initialApplicableSpan = applicableSpan; _potentialCommitChars = potentialCommitChars; @@ -82,11 +83,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation _broker = broker; _completionSources = completionSources; _completionService = completionService; - _view = view; - _guardedOperations = broker.GuardedOperations; + _textView = view; + _guardedOperations = guardedOperations; _telemetry = new CompletionSessionTelemetry(telemetryHost, completionService, presenterProvider); PageStepSize = presenterProvider?.ResultsPerPage ?? 1; - _view.Caret.PositionChanged += OnCaretPositionChanged; + _textView.Caret.PositionChanged += OnCaretPositionChanged; } bool IAsyncCompletionSession.CommitIfUnique(CancellationToken token) @@ -120,10 +121,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation /// <param name="token">Command handler infrastructure provides a token that we should pass to the language service's custom commit method.</param> /// <param name="typedChar">It is default(char) when commit was requested by an explcit command (e.g. hitting Tab, Enter or clicking) /// and it is not default(char) when commit happens as a result of typing a commit character.</param> - CustomCommitBehavior IAsyncCompletionSession.Commit(CancellationToken token, char typedChar) + CommitBehavior IAsyncCompletionSession.Commit(CancellationToken token, char typedChar) { if (_isDismissed) - return CustomCommitBehavior.None; + return CommitBehavior.None; var lastModel = _computation.WaitAndGetResult(); @@ -131,12 +132,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { // In soft selection mode, user commits explicitly (click, tab, e.g. not tied to a text change). Otherwise, we dismiss the session ((IAsyncCompletionSession)this).Dismiss(); - return CustomCommitBehavior.None; + return CommitBehavior.None; } else if (lastModel.SelectSuggestionMode && string.IsNullOrWhiteSpace(lastModel.SuggestionModeItem?.InsertText)) { // In suggestion mode, don't commit empty suggestion (suggestion item temporarily shows description of suggestion mode) - return CustomCommitBehavior.None; + return CommitBehavior.None; } else if (lastModel.SelectSuggestionMode) { @@ -147,7 +148,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { // There is nothing to commit Dismiss(); - return CustomCommitBehavior.None; + return CommitBehavior.None; } else { @@ -156,9 +157,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } } - private CustomCommitBehavior Commit(char typedChar, CompletionItem itemToCommit, CancellationToken token) + private CommitBehavior Commit(char typedChar, CompletionItem itemToCommit, CancellationToken token) { - CustomCommitBehavior result = CustomCommitBehavior.None; + CommitBehavior result = CommitBehavior.None; if (_isDismissed) return result; @@ -168,18 +169,24 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation throw new InvalidOperationException($"{nameof(IAsyncCompletionSession)}.{nameof(IAsyncCompletionSession.Commit)} must be callled from UI thread."); UiStopwatch.Restart(); + + // Pass appropriate buffer to the item's provider + var buffer = _completionSources[itemToCommit.Source].Snapshot.TextBuffer; if (itemToCommit.UseCustomCommit) { - // Pass appropriate buffer to the item's provider - var buffer = _completionSources[itemToCommit.Source].Snapshot.TextBuffer; result = _guardedOperations.CallExtensionPoint( errorSource: itemToCommit.Source, - call: () => itemToCommit.Source.CustomCommit(_view, buffer, itemToCommit, lastModel.ApplicableSpan, typedChar, token), - valueOnThrow: CustomCommitBehavior.None); + call: () => itemToCommit.Source.CustomCommit(_textView, buffer, itemToCommit, lastModel.ApplicableSpan, typedChar, token), + valueOnThrow: CommitBehavior.None); } else { - InsertIntoBuffer(_view, lastModel, itemToCommit.InsertText, typedChar); + result = _guardedOperations.CallExtensionPoint( + errorSource: itemToCommit.Source, + call: () => itemToCommit.Source.GetDefaultCommitBehavior(_textView, buffer, itemToCommit, lastModel.ApplicableSpan, typedChar, token), + valueOnThrow: CommitBehavior.None); + + InsertIntoBuffer(_textView, lastModel, itemToCommit.InsertText, typedChar); } UiStopwatch.Stop(); _telemetry.RecordCommitted(UiStopwatch.ElapsedMilliseconds, itemToCommit); @@ -205,10 +212,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation return; _isDismissed = true; - _broker.DismissSession(this); + _broker.ForgetSession(this); _guardedOperations.RaiseEvent(this, Dismissed); - _view.Caret.PositionChanged -= OnCaretPositionChanged; - _computation = null; + _textView.Caret.PositionChanged -= OnCaretPositionChanged; _computationCancellation.Cancel(); if (_gui != null) @@ -229,7 +235,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } } - void IAsyncCompletionSession.OpenOrUpdate(ITextView view, CompletionTrigger trigger, SnapshotPoint triggerLocation) + void IAsyncCompletionSession.OpenOrUpdate(CompletionTrigger trigger, SnapshotPoint triggerLocation) { if (_isDismissed) return; @@ -237,14 +243,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation if (_computation == null) { _computation = new ModelComputation<CompletionModel>(PrioritizedTaskScheduler.AboveNormalInstance, _computationCancellation.Token, _guardedOperations, this); - _computation.Enqueue((model, token) => GetInitialModel(view, trigger, triggerLocation, token), updateUi: false); + _computation.Enqueue((model, token) => GetInitialModel(_textView, trigger, triggerLocation, token), updateUi: false); } var taskId = Interlocked.Increment(ref _lastFilteringTaskId); _computation.Enqueue((model, token) => UpdateSnapshot(model, trigger, FromCompletionTriggerReason(trigger.Reason), triggerLocation, token, taskId), updateUi: true); } - internal void InvokeAndCommitIfUnique(ITextView view, CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token) + internal void InvokeAndCommitIfUnique(CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token) { if (_isDismissed) return; @@ -252,7 +258,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation if (_computation == null) { // Do not recompute, since this may change the selection. - ((IAsyncCompletionSession)this).OpenOrUpdate(view, trigger, triggerLocation); + ((IAsyncCompletionSession)this).OpenOrUpdate(trigger, triggerLocation); } if (((IAsyncCompletionSession)this).CommitIfUnique(token)) @@ -336,14 +342,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation /// and return whether any source would like to commit completion. /// </summary> /// <remarks>This method must run on UI thread because of mapping the point across buffers.</remarks> - bool IAsyncCompletionSession.ShouldCommit(ITextView view, char typeChar, SnapshotPoint triggerLocation) + bool IAsyncCompletionSession.ShouldCommit(char typeChar, SnapshotPoint triggerLocation) { if (!_jtf.Context.IsOnMainThread) throw new InvalidOperationException($"This method must be callled on the UI thread."); if (_potentialCommitChars.Contains(typeChar)) { - var mappingPoint = view.BufferGraph.CreateMappingPoint(triggerLocation, PointTrackingMode.Negative); + var mappingPoint = _textView.BufferGraph.CreateMappingPoint(triggerLocation, PointTrackingMode.Negative); return _completionSources .Select(p => (p, mappingPoint.GetPoint(p.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor))) .Where(n => n.Item2.HasValue) @@ -356,17 +362,30 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } /// <summary> - /// Monitors when user scrolled outside of the completion area. - /// Note that this event is not raised during regular typing - /// Also note that typing stretches the completion area + /// Monitors when user scrolled outside of the applicable span. Note that: + /// * This event is not raised during regular typing. + /// * This event is raised by brace completion. + /// * Typing stretches the applicable span /// </summary> private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) { // http://source.roslyn.io/#Microsoft.CodeAnalysis.EditorFeatures/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs,40 - // enqueue a task to see if the caret is still within the broundary + if (_ignoreCaretMovement) + return; + _computation.Enqueue((model, token) => HandleCaretPositionChanged(model, e.NewPosition), updateUi: true); } + internal void IgnoreCaretMovement(bool ignore) + { + _ignoreCaretMovement = ignore; + if (!ignore) + { + // Don't let the session exist in invalid state: ensure that the location of the session is still valid + _computation?.Enqueue((model, token) => HandleCaretPositionChanged(model, _textView.Caret.Position), updateUi: true); + } + } + async Task ICompletionComputationCallbackHandler<CompletionModel>.UpdateUi(CompletionModel model) { if (_presenterProvider == null) return; @@ -388,14 +407,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation UiStopwatch.Restart(); if (_gui == null) { - _gui = _guardedOperations.CallExtensionPoint(errorSource: _presenterProvider, call: () => _presenterProvider.GetOrCreate(_view), valueOnThrow: null); + _gui = _guardedOperations.CallExtensionPoint(errorSource: _presenterProvider, call: () => _presenterProvider.GetOrCreate(_textView), valueOnThrow: null); if (_gui != null) { _guardedOperations.CallExtensionPoint( errorSource: _gui, call: () => { - _gui = _presenterProvider.GetOrCreate(_view); + _gui = _presenterProvider.GetOrCreate(_textView); _gui.Open(new CompletionPresentationViewModel(model.PresentedItems, model.Filters, model.ApplicableSpan, model.UseSoftSelection, model.DisplaySuggestionMode, model.SelectSuggestionMode, model.SelectedIndex, model.SuggestionModeItem, model.SuggestionModeDescription)); _gui.FiltersChanged += OnFiltersChanged; @@ -461,7 +480,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation ComputationStopwatch.Restart(); var sortedList = await _guardedOperations.CallExtensionPointAsync( errorSource: _completionService, - asyncCall: () => _completionService.SortCompletionListAsync(initialCompletionItems, trigger.Reason, triggerLocation.Snapshot, applicableSpan, _view, token), + asyncCall: () => _completionService.SortCompletionListAsync(initialCompletionItems, trigger.Reason, triggerLocation.Snapshot, applicableSpan, _textView, token), valueOnThrow: initialCompletionItems); ComputationStopwatch.Stop(); _telemetry.RecordProcessing(ComputationStopwatch.ElapsedMilliseconds, initialCompletionItems.Length); @@ -536,7 +555,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation triggerLocation.Snapshot, model.ApplicableSpan, model.Filters, - _view, + _textView, token), valueOnThrow: null); @@ -620,7 +639,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation model.Snapshot, model.ApplicableSpan, newFilters, - _view, + _textView, token), valueOnThrow: null); diff --git a/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs b/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs index ed7aecd..45694ad 100644 --- a/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs +++ b/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs @@ -56,10 +56,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation /// </summary> /// <param name="view"></param> /// <returns></returns> - private CommandState Available(ITextView view) + private CommandState Available(IContentType contentType) { return ModernCompletionFeature.GetFeatureState(ExperimentationService) - && Broker.IsCompletionSupported(view) + && Broker.IsCompletionSupported(contentType) ? CommandState.Available : CommandState.Unspecified; } @@ -95,7 +95,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { var trigger = new CompletionTrigger(CompletionTriggerReason.Deletion); var location = args.TextView.Caret.Position.BufferPosition; - session.OpenOrUpdate(args.TextView, trigger, location); + session.OpenOrUpdate(trigger, location); } } @@ -114,40 +114,56 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } CommandState ICommandHandler<InvokeCompletionListCommandArgs>.GetCommandState(InvokeCompletionListCommandArgs args) - => Available(args.TextView); + => Available(args.SubjectBuffer.ContentType); bool ICommandHandler<InvokeCompletionListCommandArgs>.ExecuteCommand(InvokeCompletionListCommandArgs args, CommandExecutionContext executionContext) { + // If the caret is buried in virtual space, we should realize this virtual space before triggering the session. + if (args.TextView.Caret.InVirtualSpace) + { + IEditorOperations editorOperations = EditorOperationsFactoryService.GetEditorOperations(args.TextView); + // We can realize virtual space by inserting nothing through the editor operations. + editorOperations?.InsertText(""); + } + var trigger = new CompletionTrigger(CompletionTriggerReason.Invoke); var location = args.TextView.Caret.Position.BufferPosition; var session = Broker.TriggerCompletion(args.TextView, location, default(char)); if (session != null) { - session.OpenOrUpdate(args.TextView, trigger, location); + session.OpenOrUpdate(trigger, location); return true; } return false; } CommandState ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.GetCommandState(CommitUniqueCompletionListItemCommandArgs args) - => Available(args.TextView); + => Available(args.SubjectBuffer.ContentType); bool ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.ExecuteCommand(CommitUniqueCompletionListItemCommandArgs args, CommandExecutionContext executionContext) { + // If the caret is buried in virtual space, we should realize this virtual space before triggering the session. + if (args.TextView.Caret.InVirtualSpace) + { + IEditorOperations editorOperations = EditorOperationsFactoryService.GetEditorOperations(args.TextView); + // We can realize virtual space by inserting nothing through the editor operations. + editorOperations?.InsertText(""); + } + var trigger = new CompletionTrigger(CompletionTriggerReason.InvokeAndCommitIfUnique); var location = args.TextView.Caret.Position.BufferPosition; var session = Broker.TriggerCompletion(args.TextView, location, default(char)); if (session != null) { var sessionInternal = session as AsyncCompletionSession; - sessionInternal?.InvokeAndCommitIfUnique(args.TextView, trigger, location, executionContext.OperationContext.UserCancellationToken); + sessionInternal?.InvokeAndCommitIfUnique(trigger, location, executionContext.OperationContext.UserCancellationToken); return true; } return false; } CommandState ICommandHandler<InsertSnippetCommandArgs>.GetCommandState(InsertSnippetCommandArgs args) - => Available(args.TextView); + => Available(args.SubjectBuffer.ContentType); bool ICommandHandler<InsertSnippetCommandArgs>.ExecuteCommand(InsertSnippetCommandArgs args, CommandExecutionContext executionContext) { @@ -156,7 +172,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } CommandState ICommandHandler<ToggleCompletionModeCommandArgs>.GetCommandState(ToggleCompletionModeCommandArgs args) - => Available(args.TextView); + => Available(args.SubjectBuffer.ContentType); bool ICommandHandler<ToggleCompletionModeCommandArgs>.ExecuteCommand(ToggleCompletionModeCommandArgs args, CommandExecutionContext executionContext) { @@ -186,7 +202,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { var trigger = new CompletionTrigger(CompletionTriggerReason.Deletion); var location = args.TextView.Caret.Position.BufferPosition; - session.OpenOrUpdate(args.TextView, trigger, location); + session.OpenOrUpdate(trigger, location); } } @@ -263,35 +279,50 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } CommandState IChainedCommandHandler<TypeCharCommandArgs>.GetCommandState(TypeCharCommandArgs args, Func<CommandState> nextCommandHandler) - => Available(args.TextView); + => Available(args.SubjectBuffer.ContentType); void IChainedCommandHandler<TypeCharCommandArgs>.ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) { var initialTextSnapshot = args.TextView.TextSnapshot; var view = args.TextView; + var location = view.Caret.Position.BufferPosition; + + // Note regarding undo: When completion and brace completion happen together, completion should be first on the undo stack. + // Effectively, we want to first undo the completion, leaving brace completion intact. Second undo should undo brace completion. + // To achieve this, we create a transaction in which we commit and reapply brace completion (via nextCommandHandler). + // Please read "Note regarding undo" comments in this method that explain the implementation choices. + // Hopefully an upcoming upgrade of the undo mechanism will allow us to undo out of order and vastly simplify this method. + + // Note regarding undo: In a corner case of typing closing brace over existing closing brace, + // Roslyn brace completion does not perform an edit. It moves the caret outside of session's applicable span, + // which dismisses the session. Put the session in a state where it will not dismiss when caret leaves the applicable span. + /* + // Unfortunately, preserving this session means that ultimately replaying the key will insert second brace instead of overtyping. + // Actually, even obtaining session before nextCommandHandler messes this up. I don't understand this yet and for now I will keep this code commented out. Completing over closing brace is now a known bug. + if (sessionToCommit != null && args.TextView.TextBuffer == args.SubjectBuffer) + { + ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: true); + } + */ + // Execute other commands in the chain to see the change in the buffer. This includes brace completion. + // Note regarding undo: This will be undone second + nextCommandHandler(); if (args.TextView.TextBuffer != args.SubjectBuffer) { // We are only inteterested in the top buffer. Currently, commanding implementation calls us multiple times, once per each buffer. - // Allow other command handlers to act. - nextCommandHandler(); return; } - var location = view.Caret.Position.BufferPosition; - // Call ShouldCommit before nextCommandHandler so that extenders + // Pass location from before calling nextCommandHandler so that extenders // get the same view of the buffer in both ShouldCommit and Commit var sessionToCommit = Broker.GetSession(args.TextView); - var shouldCommit = sessionToCommit?.ShouldCommit(view, args.TypedChar, location); - - if (shouldCommit == true) + if (sessionToCommit?.ShouldCommit(args.TypedChar, location) == true) { - // Execute other commands in the chain to see the change in the buffer. - nextCommandHandler(); - // Buffer has changed, update the snapshot location = view.Caret.Position.BufferPosition; + // Note regarding undo: this transaction will be undone first using (var undoTransaction = new CaretPreservingEditTransaction("Completion", view, UndoHistoryRegistry, EditorOperationsFactoryService)) { UndoUtilities.RollbackToBeforeTypeChar(initialTextSnapshot, args.SubjectBuffer); @@ -299,18 +330,19 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation var customBehavior = sessionToCommit.Commit(executionContext.OperationContext.UserCancellationToken, args.TypedChar); - if ((customBehavior & CustomCommitBehavior.SuppressFurtherCommandHandlers) == 0) + if ((customBehavior & CommitBehavior.SuppressFurtherCommandHandlers) == 0) nextCommandHandler(); // Replay the key, so that we get brace completion. // Complete the transaction before stopping it. undoTransaction.Complete(); } } - else + /* + if (sessionToCommit != null) { - nextCommandHandler(); + ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: false); } - + */ // Buffer might have changed. Update it for when we try to trigger new session. location = view.Caret.Position.BufferPosition; @@ -318,14 +350,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation var session = Broker.GetSession(args.TextView); if (session != null) { - session.OpenOrUpdate(view, trigger, location); + session.OpenOrUpdate(trigger, location); } else { var newSession = Broker.TriggerCompletion(args.TextView, location, args.TypedChar); if (newSession != null) { - newSession?.OpenOrUpdate(view, trigger, location); + newSession?.OpenOrUpdate(trigger, location); } } } diff --git a/src/Language/Impl/Language/Completion/DefaultCompletionService.cs b/src/Language/Impl/Language/Completion/DefaultCompletionService.cs index 50f3728..5b7066f 100644 --- a/src/Language/Impl/Language/Completion/DefaultCompletionService.cs +++ b/src/Language/Impl/Language/Completion/DefaultCompletionService.cs @@ -161,6 +161,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation return CustomCommitBehavior.None; } + CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) + { + return CommitBehavior.None; + } + async Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan triggerLocation, CancellationToken token) { return await Task.FromResult(new CompletionContext( @@ -229,9 +234,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation { private static readonly ImmutableArray<char> commitCharacters = ImmutableArray.Create(' ', '>', '='); - CustomCommitBehavior IAsyncCompletionItemSource.CustomCommit(Text.Editor.ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) + CommitBehavior IAsyncCompletionItemSource.CustomCommit(Text.Editor.ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) { - return CustomCommitBehavior.None; + return CommitBehavior.None; + } + + CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) + { + return CommitBehavior.None; } async Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CancellationToken token) diff --git a/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs b/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs index a5b34e4..c86589c 100644 --- a/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs +++ b/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs @@ -23,9 +23,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation } } - CustomCommitBehavior IAsyncCompletionItemSource.CustomCommit(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) + CommitBehavior IAsyncCompletionItemSource.CustomCommit(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) { - return CustomCommitBehavior.None; + return CommitBehavior.None; + } + + CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token) + { + return CommitBehavior.None; } Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CancellationToken token) diff --git a/src/Microsoft.VisualStudio.Text.Implementation.csproj b/src/Microsoft.VisualStudio.Text.Implementation.csproj index 2ef1691..27cd98c 100644 --- a/src/Microsoft.VisualStudio.Text.Implementation.csproj +++ b/src/Microsoft.VisualStudio.Text.Implementation.csproj @@ -5,10 +5,10 @@ <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile> <DelaySign>false</DelaySign> - <Version>15.0.20-pre</Version> + <Version>15.0.21-pre</Version> <AssemblyVersion>15.0.0.0</AssemblyVersion> - <NuGetVersionEditor>15.7.124-preview-ge536746498</NuGetVersionEditor> - <NuGetVersionLanguage>15.7.124-preview-ge536746498</NuGetVersionLanguage> + <NuGetVersionEditor>15.7.140-preview-gc7d2848231</NuGetVersionEditor> + <NuGetVersionLanguage>15.7.140-preview-gc7d2848231</NuGetVersionLanguage> </PropertyGroup> <PropertyGroup> |