From c065beda2529a8e25317f3ca0a2cd237b52adf5c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 14:33:53 -0800 Subject: [PATCH 01/11] Make the IDocumentNavigationSerivice entirely async. --- .../InteractiveDocumentNavigationService.cs | 30 ++---- .../NavigateTo/NavigateToItemDisplay.cs | 13 ++- .../NavigateToItemDisplayFactory.cs | 14 ++- .../NavigateTo/NavigateToItemProvider.cs | 2 +- .../Core.Wpf/Peek/PeekableItemSource.cs | 97 ++++++++++--------- .../Peek/PeekableItemSourceProvider.cs | 10 +- .../AbstractEditorNavigationBarItemService.cs | 14 +-- .../VSTypeScriptNavigationBarItemService.cs | 3 +- .../AbstractGoToDefinitionService.cs | 5 +- .../CodeActionEditHandlerService.cs | 54 ++++++----- .../AbstractExtractInterfaceCommandHandler.cs | 3 +- .../InlineRename/InlineRenameService.cs | 9 +- .../WorkspaceThreadingServiceProvider.cs | 25 +++++ ...NavigationBarItemService_CodeGeneration.vb | 4 +- .../Portable/Common/NavigationOperation.cs | 6 +- ...eScriptDocumentNavigationServiceWrapper.cs | 26 +++-- .../DefinitionItem.DefaultDefinitionItem.cs | 3 +- .../DefaultDocumentNavigationService.cs | 22 ++--- .../DefaultSymbolNavigationService.cs | 4 +- .../Navigation/IDocumentNavigationService.cs | 44 +++------ .../Navigation/ISymbolNavigationService.cs | 2 +- .../FSharpDocumentNavigationService.cs | 26 +++-- .../IFSharpDocumentNavigationService.cs | 12 +-- .../CallHierarchy/CallHierarchyDetail.cs | 8 +- .../CallHierarchy/CallHierarchyItem.cs | 14 ++- .../CallHierarchy/CallHierarchyProvider.cs | 18 ++-- .../Finders/AbstractCallFinder.cs | 9 +- .../Progression/GraphNavigatorExtension.cs | 35 ++----- .../AbstractTableEntriesSnapshot.cs | 5 +- .../VisualStudioDocumentNavigationService.cs | 39 +++----- .../VisualStudioSymbolNavigationService.cs | 14 +-- .../StackTraceExplorer/StackFrameViewModel.cs | 3 +- .../Def/ValueTracking/TreeItemViewModel.cs | 4 +- .../ValueTrackedTreeItemViewModel.cs | 3 +- .../SourceGeneratedFileItem.cs | 13 ++- .../SourceGeneratedFileItemSource.cs | 3 +- .../MockDocumentNavigationServiceFactory.cs | 12 +-- .../Utilities/IWorkspaceThreadingService.cs | 6 ++ 38 files changed, 338 insertions(+), 276 deletions(-) create mode 100644 src/EditorFeatures/Core/Shared/Utilities/WorkspaceThreadingServiceProvider.cs diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs index 041b4a2452056..6b3e672a6e6ca 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs @@ -10,10 +10,10 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Interactive { @@ -26,32 +26,20 @@ public InteractiveDocumentNavigationService(IThreadingContext threadingContext) _threadingContext = threadingContext; } - public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) + public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) { - // This switch is technically not needed as the call to CanNavigateToSpan just returns 'true'. - // However, this abides by the contract that CanNavigateToSpan only be called on the UI thread. - // It also means if we ever update that method, this code will stay corrrect. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return CanNavigateToSpan(workspace, documentId, textSpan, cancellationToken); + return SpecializedTasks.True; } - public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) - => true; - - public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) - => false; + public Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) + => SpecializedTasks.False; - public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => false; + public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + => SpecializedTasks.False; public async Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return TryNavigateToSpan(workspace, documentId, textSpan, options, allowInvalidSpan, cancellationToken); - } - - public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) - { if (workspace is not InteractiveWindowWorkspace interactiveWorkspace) { Debug.Fail("InteractiveDocumentNavigationService called with incorrect workspace!"); @@ -95,10 +83,10 @@ public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp return true; } - public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) + public Task TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) => throw new NotSupportedException(); - public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) + public Task TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) => throw new NotSupportedException(); } } diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs index 294f8ff7b0a72..486ba4c5c92c4 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs @@ -8,6 +8,7 @@ using System.Collections.ObjectModel; using System.Drawing; using System.Threading; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Navigation; @@ -21,11 +22,15 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo { internal sealed class NavigateToItemDisplay : INavigateToItemDisplay3 { + private readonly IThreadingContext _threadingContext; private readonly INavigateToSearchResult _searchResult; private ReadOnlyCollection _descriptionItems; - public NavigateToItemDisplay(INavigateToSearchResult searchResult) - => _searchResult = searchResult; + public NavigateToItemDisplay(IThreadingContext threadingContext, INavigateToSearchResult searchResult) + { + _threadingContext = threadingContext; + _searchResult = searchResult; + } public string AdditionalInformation => _searchResult.AdditionalInformation; @@ -112,13 +117,13 @@ public void NavigateTo() // // TODO: Get the platform to use and pass us an operation context, or create one // ourselves. - navigationService.TryNavigateToSpan( + _threadingContext.JoinableTaskFactory.Run(() => navigationService.TryNavigateToSpanAsync( workspace, document.Id, _searchResult.NavigableItem.SourceSpan, NavigationOptions.Default, allowInvalidSpan: _searchResult.NavigableItem.IsStale, - CancellationToken.None); + CancellationToken.None)); } public int GetProvisionalViewingStatus() diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplayFactory.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplayFactory.cs index 75e3678cd458a..9c333514ce34c 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplayFactory.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplayFactory.cs @@ -2,21 +2,25 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo { internal sealed class NavigateToItemDisplayFactory : INavigateToItemDisplayFactory { + private readonly IThreadingContext _threadingContext; + + public NavigateToItemDisplayFactory(IThreadingContext threadingContext) + { + _threadingContext = threadingContext; + } + public INavigateToItemDisplay CreateItemDisplay(NavigateToItem item) { var searchResult = (INavigateToSearchResult)item.Tag; - return new NavigateToItemDisplay(searchResult); + return new NavigateToItemDisplay(_threadingContext, searchResult); } } } diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index 08ba4018943c4..0a6f456fc81bf 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -36,7 +36,7 @@ public NavigateToItemProvider( _workspace = workspace; _asyncListener = asyncListener; - _displayFactory = new NavigateToItemDisplayFactory(); + _displayFactory = new NavigateToItemDisplayFactory(threadingContext); _threadingContext = threadingContext; } diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs index 5506fe5818283..d9e5977c0257d 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Peek; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -23,17 +26,20 @@ internal sealed class PeekableItemSource : IPeekableItemSource private readonly ITextBuffer _textBuffer; private readonly IPeekableItemFactory _peekableItemFactory; private readonly IPeekResultFactory _peekResultFactory; + private readonly IThreadingContext _threadingContext; private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; public PeekableItemSource( ITextBuffer textBuffer, IPeekableItemFactory peekableItemFactory, IPeekResultFactory peekResultFactory, + IThreadingContext threadingContext, IUIThreadOperationExecutor uiThreadOperationExecutor) { _textBuffer = textBuffer; _peekableItemFactory = peekableItemFactory; _peekResultFactory = peekResultFactory; + _threadingContext = threadingContext; _uiThreadOperationExecutor = uiThreadOperationExecutor; } @@ -58,63 +64,65 @@ public void AugmentPeekSession(IPeekSession session, IList peekab _uiThreadOperationExecutor.Execute(EditorFeaturesResources.Peek, EditorFeaturesResources.Loading_Peek_information, allowCancellation: true, showProgress: false, action: context => { - var cancellationToken = context.UserCancellationToken; - var services = document.Project.Solution.Workspace.Services; + _threadingContext.JoinableTaskFactory.Run(() => AugumentPeekSessionAsync(peekableItems, context, triggerPoint.Value, document)); + }); + } - IEnumerable results; + private async Task AugumentPeekSessionAsync( + IList peekableItems, IUIThreadOperationContext context, SnapshotPoint triggerPoint, Document document) + { + var cancellationToken = context.UserCancellationToken; + var services = document.Project.Solution.Workspace.Services; - if (!document.SupportsSemanticModel) + if (!document.SupportsSemanticModel) + { + // For documents without semantic models, just try to use the goto-def service + // as a reasonable place to peek at. + var goToDefinitionService = document.GetLanguageService(); + if (goToDefinitionService == null) { - // For documents without semantic models, just try to use the goto-def service - // as a reasonable place to peek at. - var goToDefinitionService = document.GetLanguageService(); - if (goToDefinitionService == null) - { - return; - } - - var navigableItems = goToDefinitionService.FindDefinitionsAsync(document, triggerPoint.Value.Position, cancellationToken) - .WaitAndGetResult(cancellationToken); + return; + } - results = GetPeekableItemsForNavigableItems(navigableItems, document.Project, _peekResultFactory, cancellationToken); + var navigableItems = await goToDefinitionService.FindDefinitionsAsync(document, triggerPoint.Position, cancellationToken).ConfigureAwait(false); + await foreach (var item in GetPeekableItemsForNavigableItemsAsync( + navigableItems, document.Project, _peekResultFactory, cancellationToken).ConfigureAwait(false)) + { + peekableItems.Add(item); } - else + } + else + { + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticInfo = await SymbolFinder.GetSemanticInfoAtPositionAsync( + semanticModel, + triggerPoint.Position, + services, + cancellationToken).ConfigureAwait(false); + var symbol = semanticInfo.GetAnySymbol(includeType: true); + if (symbol == null) { - var semanticModel = document.GetRequiredSemanticModelAsync(cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - var symbol = SymbolFinder.GetSemanticInfoAtPositionAsync( - semanticModel, - triggerPoint.Value.Position, - services, - cancellationToken).WaitAndGetResult(cancellationToken) - .GetAnySymbol(includeType: true); - - if (symbol == null) - { - return; - } - - symbol = symbol.GetOriginalUnreducedDefinition(); + return; + } - // Get the symbol back from the originating workspace - var symbolMappingService = services.GetRequiredService(); + symbol = symbol.GetOriginalUnreducedDefinition(); - var mappingResult = symbolMappingService.MapSymbolAsync(document, symbol, cancellationToken) - .WaitAndGetResult(cancellationToken); + // Get the symbol back from the originating workspace + var symbolMappingService = services.GetRequiredService(); - mappingResult ??= new SymbolMappingResult(document.Project, symbol); + var mappingResult = await symbolMappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false); - results = _peekableItemFactory.GetPeekableItemsAsync(mappingResult.Symbol, mappingResult.Project, _peekResultFactory, cancellationToken) - .WaitAndGetResult(cancellationToken); - } + mappingResult ??= new SymbolMappingResult(document.Project, symbol); - peekableItems.AddRange(results); - }); + peekableItems.AddRange(await _peekableItemFactory.GetPeekableItemsAsync( + mappingResult.Symbol, mappingResult.Project, _peekResultFactory, cancellationToken).ConfigureAwait(false)); + } } - private static IEnumerable GetPeekableItemsForNavigableItems( + private static async IAsyncEnumerable GetPeekableItemsForNavigableItemsAsync( IEnumerable? navigableItems, Project project, IPeekResultFactory peekResultFactory, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { if (navigableItems != null) { @@ -124,9 +132,10 @@ private static IEnumerable GetPeekableItemsForNavigableItems( foreach (var item in navigableItems) { var document = item.Document; - if (navigationService.CanNavigateToPosition(workspace, document.Id, item.SourceSpan.Start, cancellationToken)) + if (await navigationService.CanNavigateToPositionAsync( + workspace, document.Id, item.SourceSpan.Start, cancellationToken).ConfigureAwait(false)) { - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var linePositionSpan = text.Lines.GetLinePositionSpan(item.SourceSpan); if (document.FilePath != null) { diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs index dca8d5dd1efb9..a23229acb4a8f 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.ComponentModel.Composition; -using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Peek; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; @@ -24,6 +22,7 @@ internal sealed class PeekableItemSourceProvider : IPeekableItemSourceProvider { private readonly IPeekableItemFactory _peekableItemFactory; private readonly IPeekResultFactory _peekResultFactory; + private readonly IThreadingContext _threadingContext; private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; [ImportingConstructor] @@ -31,14 +30,17 @@ internal sealed class PeekableItemSourceProvider : IPeekableItemSourceProvider public PeekableItemSourceProvider( IPeekableItemFactory peekableItemFactory, IPeekResultFactory peekResultFactory, + IThreadingContext threadingContext, IUIThreadOperationExecutor uiThreadOperationExecutor) { _peekableItemFactory = peekableItemFactory; _peekResultFactory = peekResultFactory; + _threadingContext = threadingContext; _uiThreadOperationExecutor = uiThreadOperationExecutor; } public IPeekableItemSource TryCreatePeekableItemSource(ITextBuffer textBuffer) - => textBuffer.Properties.GetOrCreateSingletonProperty(() => new PeekableItemSource(textBuffer, _peekableItemFactory, _peekResultFactory, _uiThreadOperationExecutor)); + => textBuffer.Properties.GetOrCreateSingletonProperty(() => + new PeekableItemSource(textBuffer, _peekableItemFactory, _peekResultFactory, _threadingContext, _uiThreadOperationExecutor)); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index 523837e1e7f4d..772a184f8ba99 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -44,21 +44,21 @@ protected async Task NavigateToSymbolItemAsync( var (documentId, position, virtualSpace) = await GetNavigationLocationAsync( document, item, symbolItem, textVersion, cancellationToken).ConfigureAwait(false); - // Ensure we're back on the UI thread before either navigating or showing a failure message. - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - NavigateToPosition(workspace, documentId, position, virtualSpace, cancellationToken); + await NavigateToPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false); } - protected void NavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + protected async Task NavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) { - this.AssertIsForeground(); var navigationService = workspace.Services.GetRequiredService(); - if (navigationService.CanNavigateToPosition(workspace, documentId, position, virtualSpace, cancellationToken)) + if (await navigationService.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false)) { - navigationService.TryNavigateToPosition(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken); + await navigationService.TryNavigateToPositionAsync( + workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken).ConfigureAwait(false); } else { + // Ensure we're back on the UI thread before showing a failure message. + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var notificationService = workspace.Services.GetRequiredService(); notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error); } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs index 9830629f086e6..40a9bb90615b1 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -54,7 +54,8 @@ public async Task TryNavigateToItemAsync( var workspace = document.Project.Solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); - navigationService.TryNavigateToPosition(workspace, document.Id, navigationSpan.Start, virtualSpace: 0, NavigationOptions.Default, cancellationToken); + await navigationService.TryNavigateToPositionAsync( + workspace, document.Id, navigationSpan.Start, virtualSpace: 0, NavigationOptions.Default, cancellationToken).ConfigureAwait(false); return true; } diff --git a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs index fe6e15bc65e30..0e7a6ec28af1a 100644 --- a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs +++ b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs @@ -42,14 +42,15 @@ protected AbstractGoToDefinitionService( async Task?> IGoToDefinitionService.FindDefinitionsAsync(Document document, int position, CancellationToken cancellationToken) => await FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); - private static bool TryNavigateToSpan(Document document, int position, CancellationToken cancellationToken) + private bool TryNavigateToSpan(Document document, int position, CancellationToken cancellationToken) { var solution = document.Project.Solution; var workspace = solution.Workspace; var service = workspace.Services.GetRequiredService(); var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: true); - return service.TryNavigateToPosition(workspace, document.Id, position, virtualSpace: 0, options, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.TryNavigateToPositionAsync(workspace, document.Id, position, virtualSpace: 0, options, cancellationToken)); } public bool TryGoToDefinition(Document document, int position, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs b/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs index b605c92aa5a3c..10e8485fdd503 100644 --- a/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs +++ b/src/EditorFeatures/Core/Implementation/CodeActions/CodeActionEditHandlerService.cs @@ -184,7 +184,8 @@ public async Task ApplyAsync( } var updatedSolution = operations.OfType().FirstOrDefault()?.ChangedSolution ?? oldSolution; - TryNavigateToLocationOrStartRenameSession(workspace, oldSolution, updatedSolution, cancellationToken); + await TryNavigateToLocationOrStartRenameSessionAsync( + workspace, oldSolution, updatedSolution, cancellationToken).ConfigureAwait(false); return applied; } @@ -288,7 +289,7 @@ private async Task ProcessOperationsAsync( return applied; } - private void TryNavigateToLocationOrStartRenameSession(Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken) + private async Task TryNavigateToLocationOrStartRenameSessionAsync(Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken) { var changedDocuments = newSolution.GetChangedDocuments(oldSolution); foreach (var documentId in changedDocuments) @@ -299,14 +300,15 @@ private void TryNavigateToLocationOrStartRenameSession(Workspace workspace, Solu continue; } - var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var navigationTokenOpt = root.GetAnnotatedTokens(NavigationAnnotation.Kind) .FirstOrNull(); if (navigationTokenOpt.HasValue) { var navigationService = workspace.Services.GetRequiredService(); - navigationService.TryNavigateToPosition(workspace, documentId, navigationTokenOpt.Value.SpanStart, cancellationToken); + await navigationService.TryNavigateToPositionAsync( + workspace, documentId, navigationTokenOpt.Value.SpanStart, cancellationToken).ConfigureAwait(false); return; } @@ -323,30 +325,36 @@ private void TryNavigateToLocationOrStartRenameSession(Workspace workspace, Solu var pathToRenameToken = new SyntaxPath(renameTokenOpt.Value); var latestDocument = workspace.CurrentSolution.GetDocument(documentId); - var latestRoot = latestDocument?.GetSyntaxRootSynchronously(cancellationToken); - if (pathToRenameToken.TryResolve(latestRoot, out var resolvedRenameToken) && - resolvedRenameToken.IsToken) + if (latestDocument != null) { - var editorWorkspace = workspace; - var navigationService = editorWorkspace.Services.GetRequiredService(); - if (navigationService.TryNavigateToSpan(editorWorkspace, documentId, resolvedRenameToken.Span, cancellationToken)) + var latestRoot = await latestDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (pathToRenameToken.TryResolve(latestRoot, out var resolvedRenameToken) && + resolvedRenameToken.IsToken) { - var openDocument = workspace.CurrentSolution.GetRequiredDocument(documentId); - var openRoot = openDocument.GetSyntaxRootSynchronously(cancellationToken); + var editorWorkspace = workspace; + var navigationService = editorWorkspace.Services.GetRequiredService(); + if (await navigationService.TryNavigateToSpanAsync( + editorWorkspace, documentId, resolvedRenameToken.Span, cancellationToken).ConfigureAwait(false)) + { + var openDocument = workspace.CurrentSolution.GetRequiredDocument(documentId); + var openRoot = await openDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - // NOTE: We need to resolve the syntax path again in case VB line commit kicked in - // due to the navigation. + // NOTE: We need to resolve the syntax path again in case VB line commit kicked in + // due to the navigation. - // TODO(DustinCa): We still have a potential problem here with VB line commit, - // because it can insert tokens and all sorts of other business, which could - // wind up with us not being able to resolve the token. - if (pathToRenameToken.TryResolve(openRoot, out resolvedRenameToken) && - resolvedRenameToken.IsToken) - { - var snapshot = openDocument.GetTextSynchronously(cancellationToken).FindCorrespondingEditorTextSnapshot(); - if (snapshot != null) + // TODO(DustinCa): We still have a potential problem here with VB line commit, + // because it can insert tokens and all sorts of other business, which could + // wind up with us not being able to resolve the token. + if (pathToRenameToken.TryResolve(openRoot, out resolvedRenameToken) && + resolvedRenameToken.IsToken) { - _renameService.StartInlineSession(openDocument, resolvedRenameToken.AsToken().Span, cancellationToken); + var text = await openDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + var snapshot = text.FindCorrespondingEditorTextSnapshot(); + if (snapshot != null) + { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + _renameService.StartInlineSession(openDocument, resolvedRenameToken.AsToken().Span, cancellationToken); + } } } } diff --git a/src/EditorFeatures/Core/Implementation/ExtractInterface/AbstractExtractInterfaceCommandHandler.cs b/src/EditorFeatures/Core/Implementation/ExtractInterface/AbstractExtractInterfaceCommandHandler.cs index f2eb91e950422..4a5bee5aaa40e 100644 --- a/src/EditorFeatures/Core/Implementation/ExtractInterface/AbstractExtractInterfaceCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/ExtractInterface/AbstractExtractInterfaceCommandHandler.cs @@ -78,7 +78,8 @@ public bool ExecuteCommand(ExtractInterfaceCommandArgs args, CommandExecutionCon // TODO: Use a threaded-wait-dialog here so we can cancel navigation. var navigationService = workspace.Services.GetService(); - navigationService.TryNavigateToPosition(workspace, result.NavigationDocumentId, 0, CancellationToken.None); + _threadingContext.JoinableTaskFactory.Run(() => + navigationService.TryNavigateToPositionAsync(workspace, result.NavigationDocumentId, 0, CancellationToken.None)); return true; } diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs index 41d1fa0766be5..d704aec4c43f9 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs @@ -72,7 +72,7 @@ public InlineRenameSessionInfo StartInlineSession( var editorRenameService = document.GetRequiredLanguageService(); var renameInfo = editorRenameService.GetRenameInfoAsync(document, textSpan.Start, cancellationToken).WaitAndGetResult(cancellationToken); - var readOnlyOrCannotNavigateToSpanSessionInfo = IsReadOnlyOrCannotNavigateToSpan(renameInfo, document, cancellationToken); + var readOnlyOrCannotNavigateToSpanSessionInfo = IsReadOnlyOrCannotNavigateToSpan(_threadingContext, renameInfo, document, cancellationToken); if (readOnlyOrCannotNavigateToSpanSessionInfo != null) { return readOnlyOrCannotNavigateToSpanSessionInfo; @@ -111,7 +111,8 @@ public InlineRenameSessionInfo StartInlineSession( return new InlineRenameSessionInfo(ActiveSession); - static InlineRenameSessionInfo? IsReadOnlyOrCannotNavigateToSpan(IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) + static InlineRenameSessionInfo? IsReadOnlyOrCannotNavigateToSpan( + IThreadingContext threadingContext, IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) { if (renameInfo is IInlineRenameInfo inlineRenameInfo && inlineRenameInfo.DefinitionLocations != default) { @@ -134,7 +135,9 @@ public InlineRenameSessionInfo StartInlineSession( } } - if (!navigationService.CanNavigateToSpan(workspace, document.Id, documentSpan.SourceSpan, cancellationToken)) + var canNavigate = threadingContext.JoinableTaskFactory.Run(() => + navigationService.CanNavigateToSpanAsync(workspace, document.Id, documentSpan.SourceSpan, cancellationToken)); + if (!canNavigate) { return new InlineRenameSessionInfo(EditorFeaturesResources.You_cannot_rename_this_element_because_it_is_in_a_location_that_cannot_be_navigated_to); } diff --git a/src/EditorFeatures/Core/Shared/Utilities/WorkspaceThreadingServiceProvider.cs b/src/EditorFeatures/Core/Shared/Utilities/WorkspaceThreadingServiceProvider.cs new file mode 100644 index 0000000000000..2f54aa74de508 --- /dev/null +++ b/src/EditorFeatures/Core/Shared/Utilities/WorkspaceThreadingServiceProvider.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities +{ + [ExportWorkspaceService(typeof(IWorkspaceThreadingServiceProvider)), Shared] + internal sealed class WorkspaceThreadingServiceProvider : IWorkspaceThreadingServiceProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public WorkspaceThreadingServiceProvider( + IWorkspaceThreadingService service) + { + Service = service; + } + + public IWorkspaceThreadingService Service { get; } + } +} diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb index 0599324081b8b..fc3cc823452f7 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb @@ -39,9 +39,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar newDocument.Project.Solution.Workspace.ApplyDocumentChanges(newDocument, cancellationToken) Dim solution = newDocument.Project.Solution - NavigateToPosition( + Await NavigateToPositionAsync( solution.Workspace, solution.GetRequiredDocument(navigationPoint.Tree).Id, - navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken) + navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken).ConfigureAwait(True) transaction.Complete() End Using diff --git a/src/Features/Core/Portable/Common/NavigationOperation.cs b/src/Features/Core/Portable/Common/NavigationOperation.cs index 66b8ff183ffd8..23389de7a3005 100644 --- a/src/Features/Core/Portable/Common/NavigationOperation.cs +++ b/src/Features/Core/Portable/Common/NavigationOperation.cs @@ -7,6 +7,7 @@ using System; using System.Threading; using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.CodeActions { @@ -34,7 +35,10 @@ public override void Apply(Workspace workspace, CancellationToken cancellationTo if (workspace.CanOpenDocuments) { var navigationService = workspace.Services.GetService(); - navigationService.TryNavigateToPosition(workspace, _documentId, _position, cancellationToken); + var threadingService = workspace.Services.GetService(); + + threadingService.Service.Run( + () => navigationService.TryNavigateToPositionAsync(workspace, _documentId, _position, cancellationToken)); } } } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs index bb666062967b7..85a93ae77860a 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs @@ -6,18 +6,26 @@ using System.Threading; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api { internal readonly struct VSTypeScriptDocumentNavigationServiceWrapper { private readonly IDocumentNavigationService _underlyingObject; + private readonly IWorkspaceThreadingServiceProvider _threadingProvider; - public VSTypeScriptDocumentNavigationServiceWrapper(IDocumentNavigationService underlyingObject) - => _underlyingObject = underlyingObject; + public VSTypeScriptDocumentNavigationServiceWrapper( + IDocumentNavigationService underlyingObject, + IWorkspaceThreadingServiceProvider threadingProvider) + { + _underlyingObject = underlyingObject; + _threadingProvider = threadingProvider; + } public static VSTypeScriptDocumentNavigationServiceWrapper Create(Workspace workspace) - => new(workspace.Services.GetRequiredService()); + => new(workspace.Services.GetRequiredService(), + workspace.Services.GetRequiredService()); [Obsolete("Call overload that takes a CancellationToken", error: false)] public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace = 0, OptionSet? options = null) @@ -25,10 +33,16 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in [Obsolete("Call overload that doesn't take options", error: false)] public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, OptionSet? options, CancellationToken cancellationToken) - => _underlyingObject.TryNavigateToPosition(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken); + { + var obj = _underlyingObject; + return _threadingProvider.Service.Run(() => obj.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken)); + } - /// + /// public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => _underlyingObject.TryNavigateToPosition(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken); + { + var obj = _underlyingObject; + return _threadingProvider.Service.Run(() => obj.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken)); + } } } diff --git a/src/Features/Core/Portable/FindUsages/DefinitionItem.DefaultDefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.DefaultDefinitionItem.cs index eca8e41f33e77..31e902d6f6fbb 100644 --- a/src/Features/Core/Portable/FindUsages/DefinitionItem.DefaultDefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.DefaultDefinitionItem.cs @@ -64,7 +64,8 @@ public sealed override async Task TryNavigateToAsync(Workspace workspace, Contract.ThrowIfNull(project); var navigationService = workspace.Services.GetRequiredService(); - return navigationService.TryNavigateToSymbol(symbol, project, options with { PreferProvisionalTab = true }, cancellationToken); + return await navigationService.TryNavigateToSymbolAsync( + symbol, project, options with { PreferProvisionalTab = true }, cancellationToken).ConfigureAwait(false); } return false; diff --git a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs index f0774a8c8c4d5..52b619fe058fe 100644 --- a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs @@ -11,28 +11,22 @@ namespace Microsoft.CodeAnalysis.Navigation { internal sealed class DefaultDocumentNavigationService : IDocumentNavigationService { - public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) - => false; - public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) => SpecializedTasks.False; - public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) - => false; - - public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => false; + public Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) + => SpecializedTasks.False; - public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) - => false; + public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + => SpecializedTasks.False; public Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) => SpecializedTasks.False; - public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) - => false; + public Task TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) + => SpecializedTasks.False; - public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) - => false; + public Task TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) + => SpecializedTasks.False; } } diff --git a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs index 94f4390a87936..5bf4ca6c06cc4 100644 --- a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs @@ -13,8 +13,8 @@ namespace Microsoft.CodeAnalysis.Navigation { internal sealed class DefaultSymbolNavigationService : ISymbolNavigationService { - public bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken) - => false; + public Task TryNavigateToSymbolAsync(ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken) + => SpecializedTasks.False; public Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) => SpecializedTasks.False; diff --git a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs index 1809039c91abf..7009998b7b306 100644 --- a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs @@ -11,12 +11,6 @@ namespace Microsoft.CodeAnalysis.Navigation { internal interface IDocumentNavigationService : IWorkspaceService { - /// - /// Determines whether it is possible to navigate to the given position in the specified document. - /// - /// Only legal to call on the UI thread. - bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken); - /// /// Determines whether it is possible to navigate to the given position in the specified document. /// @@ -27,18 +21,13 @@ internal interface IDocumentNavigationService : IWorkspaceService /// Determines whether it is possible to navigate to the given line/offset in the specified document. /// /// Only legal to call on the UI thread. - bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); + Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); /// /// Determines whether it is possible to navigate to the given virtual position in the specified document. /// /// Only legal to call on the UI thread. - bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); - - /// - /// Navigates to the given position in the specified document, opening it if necessary. - /// - bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken); + Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); /// /// Navigates to the given position in the specified document, opening it if necessary. @@ -49,38 +38,29 @@ internal interface IDocumentNavigationService : IWorkspaceService /// Navigates to the given line/offset in the specified document, opening it if necessary. /// /// Only legal to call on the UI thread. - bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken); + Task TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken); /// /// Navigates to the given virtual position in the specified document, opening it if necessary. /// - bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken); + Task TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken); } internal static class IDocumentNavigationServiceExtensions { - /// Only legal to call on the UI thread. - public static bool CanNavigateToPosition(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) - => service.CanNavigateToPosition(workspace, documentId, position, virtualSpace: 0, cancellationToken); - - /// Only legal to call on the UI thread. - public static bool TryNavigateToSpan(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) - => service.TryNavigateToSpan(workspace, documentId, textSpan, NavigationOptions.Default, cancellationToken); + public static Task CanNavigateToPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) + => service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace: 0, cancellationToken); - /// Only legal to call on the UI thread. - public static bool TryNavigateToSpan(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, CancellationToken cancellationToken) - => service.TryNavigateToSpan(workspace, documentId, textSpan, options, allowInvalidSpan: false, cancellationToken); + public static Task TryNavigateToSpanAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) + => service.TryNavigateToSpanAsync(workspace, documentId, textSpan, NavigationOptions.Default, cancellationToken); - /// Legal to call from any thread. public static Task TryNavigateToSpanAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, CancellationToken cancellationToken) => service.TryNavigateToSpanAsync(workspace, documentId, textSpan, options, allowInvalidSpan: false, cancellationToken); - /// Only legal to call on the UI thread. - public static bool TryNavigateToLineAndOffset(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) - => service.TryNavigateToLineAndOffset(workspace, documentId, lineNumber, offset, NavigationOptions.Default, cancellationToken); + public static Task TryNavigateToLineAndOffsetAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) + => service.TryNavigateToLineAndOffsetAsync(workspace, documentId, lineNumber, offset, NavigationOptions.Default, cancellationToken); - /// Only legal to call on the UI thread. - public static bool TryNavigateToPosition(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) - => service.TryNavigateToPosition(workspace, documentId, position, virtualSpace: 0, NavigationOptions.Default, cancellationToken); + public static Task TryNavigateToPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) + => service.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace: 0, NavigationOptions.Default, cancellationToken); } } diff --git a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs index 84b01d95580af..caa0d6de36a19 100644 --- a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs @@ -21,7 +21,7 @@ internal interface ISymbolNavigationService : IWorkspaceService /// A set of options. If these options are not supplied the /// current set of options from the project's workspace will be used. /// The token to check for cancellation - bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken); + Task TryNavigateToSymbolAsync(ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken); /// True if the navigation was handled, indicating that the caller should not /// perform the navigation. diff --git a/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs b/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs index 942c3eb3199b1..12ef7bbb7ee64 100644 --- a/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs @@ -7,6 +7,7 @@ using System; using System.Composition; using System.Threading; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Options; @@ -17,10 +18,14 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation [ExportWorkspaceService(typeof(IFSharpDocumentNavigationService)), Shared] internal class FSharpDocumentNavigationService : IFSharpDocumentNavigationService { + private readonly IThreadingContext _threadingContext; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FSharpDocumentNavigationService() + public FSharpDocumentNavigationService( + IThreadingContext threadingContext) { + _threadingContext = threadingContext; } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -30,7 +35,8 @@ public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.CanNavigateToSpan(workspace, documentId, textSpan, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.CanNavigateToSpanAsync(workspace, documentId, textSpan, cancellationToken)); } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -40,7 +46,8 @@ public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentI public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.CanNavigateToLineAndOffset(workspace, documentId, lineNumber, offset, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.CanNavigateToLineAndOffsetAsync(workspace, documentId, lineNumber, offset, cancellationToken)); } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -50,7 +57,8 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.CanNavigateToPosition(workspace, documentId, position, virtualSpace, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken)); } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -60,7 +68,8 @@ public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.TryNavigateToSpan(workspace, documentId, textSpan, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.TryNavigateToSpanAsync(workspace, documentId, textSpan, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -70,7 +79,8 @@ public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentI public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.TryNavigateToLineAndOffset(workspace, documentId, lineNumber, offset, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.TryNavigateToLineAndOffsetAsync(workspace, documentId, lineNumber, offset, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); } [Obsolete("Call overload that takes a CancellationToken", error: false)] @@ -80,7 +90,9 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) { var service = workspace.Services.GetService(); - return service.TryNavigateToPosition(workspace, documentId, position, virtualSpace, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken); + return _threadingContext.JoinableTaskFactory.Run(() => + service.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); + } } } diff --git a/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs b/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs index a14efe8244431..144dd88a9ecda 100644 --- a/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs @@ -28,18 +28,18 @@ internal interface IFSharpDocumentNavigationService : IWorkspaceService [Obsolete("Call overload that takes a CancellationToken", error: false)] bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace = 0, OptionSet options = null); - /// + /// bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken); - /// + /// bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); - /// + /// bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); - /// + /// bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken); - /// + /// bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); - /// + /// bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyDetail.cs b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyDetail.cs index f06e7280e79bc..340494998ff8f 100644 --- a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyDetail.cs +++ b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyDetail.cs @@ -5,6 +5,7 @@ #nullable disable using System.Threading; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Language.CallHierarchy; @@ -13,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy { internal class CallHierarchyDetail : ICallHierarchyItemDetails { + private readonly IThreadingContext _threadingContext; private readonly TextSpan _span; private readonly DocumentId _documentId; private readonly Workspace _workspace; @@ -23,10 +25,11 @@ internal class CallHierarchyDetail : ICallHierarchyItemDetails private readonly int _startLine; private readonly string _text; - public CallHierarchyDetail(Location location, Workspace workspace) + public CallHierarchyDetail(IThreadingContext threadingContext, Location location, Workspace workspace) { _span = location.SourceSpan; _documentId = workspace.CurrentSolution.GetDocumentId(location.SourceTree); + _threadingContext = threadingContext; _workspace = workspace; _endColumn = location.GetLineSpan().Span.End.Character; _endLine = location.GetLineSpan().EndLinePosition.Line; @@ -68,7 +71,8 @@ public void NavigateTo() var navigator = _workspace.Services.GetService(); var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: false); // TODO: Get the platform to use and pass us an operation context, or create one ourselves. - navigator.TryNavigateToSpan(_workspace, document.Id, _span, options, CancellationToken.None); + _threadingContext.JoinableTaskFactory.Run(() => + navigator.TryNavigateToSpanAsync(_workspace, document.Id, _span, options, CancellationToken.None)); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyItem.cs b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyItem.cs index 26918ad127a7f..851a6ac891902 100644 --- a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyItem.cs +++ b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyItem.cs @@ -28,7 +28,14 @@ internal class CallHierarchyItem : ICallHierarchyMemberItem private readonly ProjectId _projectId; private readonly string _sortText; - public CallHierarchyItem(ISymbol symbol, ProjectId projectId, IEnumerable finders, Func glyphCreator, CallHierarchyProvider provider, IEnumerable callsites, Workspace workspace) + public CallHierarchyItem( + ISymbol symbol, + ProjectId projectId, + IEnumerable finders, + Func glyphCreator, + CallHierarchyProvider provider, + IEnumerable callsites, + Workspace workspace) { _symbolId = symbol.GetSymbolKey(); _projectId = projectId; @@ -38,7 +45,7 @@ public CallHierarchyItem(ISymbol symbol, ProjectId projectId, IEnumerable new CallHierarchyDetail(l, workspace)); + _callsites = callsites.Select(l => new CallHierarchyDetail(provider.ThreadingContext, l, workspace)); _sortText = symbol.ToDisplayString(); _workspace = workspace; } @@ -132,7 +139,8 @@ public void ItemSelected() public void NavigateTo() { // Navigating to an item is not cancellable. - _provider.NavigateTo(_symbolId, _workspace.CurrentSolution.GetProject(_projectId), CancellationToken.None); + _provider.ThreadingContext.JoinableTaskFactory.Run(() => + _provider.NavigateToAsync(_symbolId, _workspace.CurrentSolution.GetProject(_projectId), CancellationToken.None)); } public void StartSearch(string categoryName, CallHierarchySearchScope searchScope, ICallHierarchySearchCallback callback) diff --git a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyProvider.cs b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyProvider.cs index 5a87dea807121..a9c368c0158d6 100644 --- a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/CallHierarchyProvider.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy.Finders; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; @@ -28,15 +29,19 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy internal partial class CallHierarchyProvider { private readonly IAsynchronousOperationListener _asyncListener; + + public IThreadingContext ThreadingContext { get; } public IGlyphService GlyphService { get; } [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CallHierarchyProvider( + IThreadingContext threadingContext, IAsynchronousOperationListenerProvider listenerProvider, IGlyphService glyphService) { _asyncListener = listenerProvider.GetListener(FeatureAttribute.CallHierarchy); + ThreadingContext = threadingContext; this.GlyphService = glyphService; } @@ -44,9 +49,9 @@ public async Task CreateItemAsync(ISymbol symbol, Project project, IEnumerable callsites, CancellationToken cancellationToken) { if (symbol.Kind is SymbolKind.Method or - SymbolKind.Property or - SymbolKind.Event or - SymbolKind.Field) + SymbolKind.Property or + SymbolKind.Event or + SymbolKind.Field) { symbol = GetTargetSymbol(symbol); @@ -134,15 +139,16 @@ SymbolKind.Event or return null; } - public void NavigateTo(SymbolKey id, Project project, CancellationToken cancellationToken) + public async Task NavigateToAsync(SymbolKey id, Project project, CancellationToken cancellationToken) { - var compilation = project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var resolution = id.Resolve(compilation, cancellationToken: cancellationToken); var workspace = project.Solution.Workspace; var options = NavigationOptions.Default with { PreferProvisionalTab = true }; var symbolNavigationService = workspace.Services.GetService(); - symbolNavigationService.TryNavigateToSymbol(resolution.Symbol, project, options, cancellationToken); + await symbolNavigationService.TryNavigateToSymbolAsync( + resolution.Symbol, project, options, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs index c2a75143b2077..1603a9ce2b64c 100644 --- a/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs +++ b/src/VisualStudio/Core/Def/Implementation/CallHierarchy/Finders/AbstractCallFinder.cs @@ -31,7 +31,11 @@ internal abstract class AbstractCallFinder // For Testing only internal IImmutableSet Documents; - protected AbstractCallFinder(ISymbol symbol, ProjectId projectId, IAsynchronousOperationListener asyncListener, CallHierarchyProvider provider) + protected AbstractCallFinder( + ISymbol symbol, + ProjectId projectId, + IAsynchronousOperationListener asyncListener, + CallHierarchyProvider provider) { _asyncListener = asyncListener; _symbolKey = symbol.GetSymbolKey(); @@ -154,7 +158,8 @@ protected virtual async Task SearchWorkerAsync(ISymbol symbol, Project project, { if (caller.CallingSymbol.Kind == SymbolKind.Field) { - initializerLocations.AddRange(caller.Locations.Select(l => new CallHierarchyDetail(l, project.Solution.Workspace))); + initializerLocations.AddRange(caller.Locations.Select( + l => new CallHierarchyDetail(this.Provider.ThreadingContext, l, project.Solution.Workspace))); } else { diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs index 4f8626212699e..8ca5a15330dc9 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs @@ -66,47 +66,26 @@ public void NavigateTo(GraphObject graphObject) return; } - if (IsForeground()) - { - // If we are already on the UI thread, invoke NavigateOnForegroundThread - // directly to preserve any existing NewDocumentStateScope. - NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); - } - else - { - // Navigation must be performed on the UI thread. If we are invoked from a - // background thread then the current NewDocumentStateScope is unrelated to - // this navigation and it is safe to continue on the UI thread - // asynchronously. - Task.Factory.SafeStartNewFromAsync( - async () => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); - }, - CancellationToken.None, - TaskScheduler.Default); - } + this.ThreadingContext.JoinableTaskFactory.Run(() => + NavigateToAsync(sourceLocation, symbolId, project, document, CancellationToken.None)); } } } - private void NavigateOnForegroundThread( + private async Task NavigateToAsync( SourceLocation sourceLocation, SymbolKey? symbolId, Project project, Document document, CancellationToken cancellationToken) { - AssertIsForeground(); - // Notify of navigation so third parties can intercept the navigation if (symbolId != null) { var symbolNavigationService = _workspace.Services.GetService(); - var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken), cancellationToken: cancellationToken).Symbol; + var symbol = symbolId.Value.Resolve(await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false), cancellationToken: cancellationToken).Symbol; // Do not allow third party navigation to types or constructors if (symbol != null && symbol is not ITypeSymbol && !symbol.IsConstructor() && - symbolNavigationService.TrySymbolNavigationNotifyAsync(symbol, project, cancellationToken).WaitAndGetResult(cancellationToken)) + await symbolNavigationService.TrySymbolNavigationNotifyAsync(symbol, project, cancellationToken).ConfigureAwait(false)) { return; } @@ -124,12 +103,12 @@ symbol is not ITypeSymbol && var navigationService = editorWorkspace.Services.GetService(); // TODO: Get the platform to use and pass us an operation context, or create one ourselves. - navigationService.TryNavigateToLineAndOffset( + await navigationService.TryNavigateToLineAndOffsetAsync( editorWorkspace, document.Id, sourceLocation.StartPosition.Line, sourceLocation.StartPosition.Character, - cancellationToken); + cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs index d40ad2915c014..6c122a0577d06 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs @@ -158,7 +158,7 @@ private static LinePosition GetLinePosition(ITextSnapshot snapshot, ITrackingPoi return new LinePosition(line.LineNumber, point.Position - line.Start); } - protected static bool TryNavigateTo(Workspace workspace, DocumentId documentId, LinePosition position, NavigationOptions options, CancellationToken cancellationToken) + protected bool TryNavigateTo(Workspace workspace, DocumentId documentId, LinePosition position, NavigationOptions options, CancellationToken cancellationToken) { var navigationService = workspace.Services.GetService(); if (navigationService == null) @@ -166,7 +166,8 @@ protected static bool TryNavigateTo(Workspace workspace, DocumentId documentId, return false; } - return navigationService.TryNavigateToLineAndOffset(workspace, documentId, position.Line, position.Character, options, cancellationToken); + return this.ThreadingContext.JoinableTaskFactory.Run(() => + navigationService.TryNavigateToLineAndOffsetAsync(workspace, documentId, position.Line, position.Character, options, cancellationToken)); } protected bool TryNavigateToItem(int index, NavigationOptions options, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs index 01bd6df99a1e5..911199c7923ae 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs @@ -62,11 +62,6 @@ public VisualStudioDocumentNavigationService( public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return CanNavigateToSpan(workspace, documentId, textSpan, cancellationToken); - } - - public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) - { // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -97,8 +92,9 @@ public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); } - public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) + public async Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -114,8 +110,10 @@ public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentI return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); } - public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + public async Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -146,15 +144,9 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); } - public async Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) + public Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return TryNavigateToSpan(workspace, documentId, textSpan, options, allowInvalidSpan, cancellationToken); - } - - public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) - { - return TryNavigateToLocation(workspace, + return TryNavigateToLocationAsync(workspace, documentId, _ => textSpan, text => GetVsTextSpan(text, textSpan, allowInvalidSpan), @@ -179,10 +171,10 @@ static VsTextSpan GetVsTextSpan(SourceText text, TextSpan textSpan, bool allowIn } } - public bool TryNavigateToLineAndOffset( + public Task TryNavigateToLineAndOffsetAsync( Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) { - return TryNavigateToLocation(workspace, + return TryNavigateToLocationAsync(workspace, documentId, document => GetTextSpanFromLineAndOffset(document, lineNumber, offset, cancellationToken), text => GetVsTextSpan(text, lineNumber, offset), @@ -203,10 +195,10 @@ static VsTextSpan GetVsTextSpan(SourceText text, int lineNumber, int offset) } } - public bool TryNavigateToPosition( + public Task TryNavigateToPositionAsync( Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) { - return TryNavigateToLocation(workspace, + return TryNavigateToLocationAsync(workspace, documentId, document => GetTextSpanFromPosition(document, position, virtualSpace, cancellationToken), text => GetVsTextSpan(text, position, virtualSpace), @@ -242,7 +234,7 @@ static VsTextSpan GetVsTextSpan(SourceText text, int position, int virtualSpace) } } - private bool TryNavigateToLocation( + private async Task TryNavigateToLocationAsync( Workspace workspace, DocumentId documentId, Func getTextSpanForMapping, @@ -250,14 +242,11 @@ private bool TryNavigateToLocation( NavigationOptions options, CancellationToken cancellationToken) { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); - if (!IsForeground()) - { - throw new InvalidOperationException(ServicesVSResources.Navigation_must_be_performed_on_the_foreground_thread); - } - var solution = workspace.CurrentSolution; using (OpenNewDocumentStateScope(options)) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 6503dc395c5eb..729862fd1fddf 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -55,7 +55,8 @@ public VisualStudioSymbolNavigationService( _metadataAsSourceFileService = metadataAsSourceFileService; } - public bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken) + public async Task TryNavigateToSymbolAsync( + ISymbol symbol, Project project, NavigationOptions options, CancellationToken cancellationToken) { if (project == null || symbol == null) { @@ -76,7 +77,8 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptio { var editorWorkspace = targetDocument.Project.Solution.Workspace; var navigationService = editorWorkspace.Services.GetRequiredService(); - return navigationService.TryNavigateToSpan(editorWorkspace, targetDocument.Id, sourceLocation.SourceSpan, options, cancellationToken); + return await navigationService.TryNavigateToSpanAsync( + editorWorkspace, targetDocument.Id, sourceLocation.SourceSpan, options, cancellationToken).ConfigureAwait(false); } } @@ -96,7 +98,7 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptio return false; } - var compilation = project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var navInfo = libraryService.NavInfoFactory.CreateForSymbol(symbol, project, compilation); if (navInfo == null) { @@ -113,7 +115,7 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, NavigationOptio } // Generate new source or retrieve existing source for the symbol in question - return ThreadingContext.JoinableTaskFactory.Run(() => TryNavigateToMetadataAsync(project, symbol, options, cancellationToken)); + return await TryNavigateToMetadataAsync(project, symbol, options, cancellationToken).ConfigureAwait(false); } private async Task TryNavigateToMetadataAsync(Project project, ISymbol symbol, NavigationOptions options, CancellationToken cancellationToken) @@ -156,12 +158,12 @@ private async Task TryNavigateToMetadataAsync(Project project, ISymbol sym var editorWorkspace = openedDocument.Project.Solution.Workspace; var navigationService = editorWorkspace.Services.GetRequiredService(); - return navigationService.TryNavigateToSpan( + return await navigationService.TryNavigateToSpanAsync( editorWorkspace, openedDocument.Id, result.IdentifierLocation.SourceSpan, options with { PreferProvisionalTab = true }, - cancellationToken); + cancellationToken).ConfigureAwait(false); } return true; diff --git a/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs b/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs index 872e570fd1649..eb85750265333 100644 --- a/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs +++ b/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs @@ -143,7 +143,8 @@ public async Task NavigateToFileAsync(CancellationToken cancellationToken) } await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - navigationService.TryNavigateToLineAndOffset(_workspace, document.Id, lineNumber - 1, offset: 0, options, cancellationToken); + await navigationService.TryNavigateToLineAndOffsetAsync( + _workspace, document.Id, lineNumber - 1, offset: 0, options, cancellationToken).ConfigureAwait(false); } } catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken)) diff --git a/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs index e0735b6bc961b..7b871ada7e8aa 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Threading.Tasks; using System.Windows.Documents; using System.Windows.Media; using Microsoft.CodeAnalysis; @@ -112,7 +113,8 @@ public virtual void NavigateTo() // While navigating do not activate the tab, which will change focus from the tool window var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: false); - navigationService.TryNavigateToLineAndOffset(Workspace, DocumentId, LineSpan.Start, 0, options, ThreadingContext.DisposalToken); + this.ThreadingContext.JoinableTaskFactory.Run(() => navigationService.TryNavigateToLineAndOffsetAsync( + Workspace, DocumentId, LineSpan.Start, 0, options, ThreadingContext.DisposalToken)); } private ImmutableArray CalculateInlines() diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs index 995dbe4e19bb5..f07c07ac23fa9 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs @@ -163,7 +163,8 @@ public override void NavigateTo() // While navigating do not activate the tab, which will change focus from the tool window var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: false); - navigationService.TryNavigateToSpan(Workspace, DocumentId, _trackedItem.Span, options, ThreadingContext.DisposalToken); + this.ThreadingContext.JoinableTaskFactory.Run(() => navigationService.TryNavigateToSpanAsync( + Workspace, DocumentId, _trackedItem.Span, options, ThreadingContext.DisposalToken)); } private async Task> CalculateChildrenAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs index d91870a3798cd..24c6754565f69 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Navigation; using Microsoft.Internal.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Imaging; @@ -15,11 +16,18 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore { internal sealed partial class SourceGeneratedFileItem : BaseItem { + private readonly IThreadingContext _threadingContext; private readonly string _languageName; - public SourceGeneratedFileItem(DocumentId documentId, string hintName, string languageName, Workspace workspace) + public SourceGeneratedFileItem( + IThreadingContext threadingContext, + DocumentId documentId, + string hintName, + string languageName, + Workspace workspace) : base(name: hintName) { + _threadingContext = threadingContext; DocumentId = documentId; HintName = hintName; _languageName = languageName; @@ -60,7 +68,8 @@ public bool Invoke(IEnumerable items, InputSource inputSource, bool prev { // TODO: we're navigating back to the top of the file, do we have a way to just bring it to the focus and that's it? // TODO: Use a threaded-wait-dialog here so we can cancel navigation. - didNavigate |= documentNavigationService.TryNavigateToPosition(item.Workspace, item.DocumentId, position: 0, CancellationToken.None); + didNavigate |= item._threadingContext.JoinableTaskFactory.Run(() => + documentNavigationService.TryNavigateToPositionAsync(item.Workspace, item.DocumentId, position: 0, CancellationToken.None)); } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index 3d1770df96462..b33fb4f9e1aeb 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -145,7 +145,8 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel } } - _items.Insert(low, new SourceGeneratedFileItem(document.Id, document.HintName, document.Project.Language, _workspace)); + _items.Insert(low, new SourceGeneratedFileItem( + _threadingContext, document.Id, document.HintName, document.Project.Language, _workspace)); } } finally diff --git a/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs b/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs index 07f873059b7be..de1873087a660 100644 --- a/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs +++ b/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs @@ -34,19 +34,15 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private class MockDocumentNavigationService : IDocumentNavigationService { - public bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) => true; + public Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) => SpecializedTasks.True; - public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) => true; - - public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) => true; + public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) => SpecializedTasks.True; public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) => SpecializedTasks.True; - public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) => true; - - public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) => true; + public Task TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken) => SpecializedTasks.True; - public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpans, CancellationToken cancellationToken) => true; + public Task TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) => SpecializedTasks.True; public Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) => SpecializedTasks.True; } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/IWorkspaceThreadingService.cs b/src/Workspaces/Core/Portable/Shared/Utilities/IWorkspaceThreadingService.cs index bd68191deea62..4df5b7713a370 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/IWorkspaceThreadingService.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/IWorkspaceThreadingService.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.Shared.Utilities { @@ -26,4 +27,9 @@ internal interface IWorkspaceThreadingService { TResult Run(Func> asyncMethod); } + + internal interface IWorkspaceThreadingServiceProvider : IWorkspaceService + { + IWorkspaceThreadingService Service { get; } + } } From a2cdcb178e2d5350d5acc8faa9b400b6a290f881 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 14:40:38 -0800 Subject: [PATCH 02/11] Update tests --- src/EditorFeatures/Test2/Peek/PeekTests.vb | 2 ++ .../MockDocumentNavigationService.vb | 29 +++++------------ .../MockSymbolNavigationService.vb | 5 ++- .../MockDocumentNavigationServiceProvider.vb | 31 +++++-------------- .../MockSymbolNavigationServiceProvider.vb | 5 +-- 5 files changed, 23 insertions(+), 49 deletions(-) diff --git a/src/EditorFeatures/Test2/Peek/PeekTests.vb b/src/EditorFeatures/Test2/Peek/PeekTests.vb index 9e60817ccd6fd..86f289ea5f680 100644 --- a/src/EditorFeatures/Test2/Peek/PeekTests.vb +++ b/src/EditorFeatures/Test2/Peek/PeekTests.vb @@ -7,6 +7,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.CodeAnalysis.Editor.Implementation.Peek Imports Microsoft.CodeAnalysis.Editor.Peek +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.VisualStudio.Imaging.Interop Imports Microsoft.VisualStudio.Language.Intellisense @@ -246,6 +247,7 @@ public class Component Dim peekableItemSource As New PeekableItemSource(textBuffer, workspace.GetService(Of IPeekableItemFactory), New MockPeekResultFactory(workspace.GetService(Of IPersistentSpanFactory)), + workspace.GetService(Of IThreadingContext), workspace.GetService(Of IUIThreadOperationExecutor)) Dim peekableSession As New Mock(Of IPeekSession)(MockBehavior.Strict) diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb index 378db70091a26..8e417ebf7183d 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb @@ -28,49 +28,36 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Public _position As Integer = -1 Public _positionVirtualSpace As Integer = -1 - Public Function CanNavigateToLineAndOffset(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToLineAndOffset - Return _canNavigateToLineAndOffset + Public Function CanNavigateToLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToLineAndOffsetAsync + Return If(_canNavigateToLineAndOffset, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function CanNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToPosition - Return _canNavigateToPosition - End Function - - Public Function CanNavigateToSpan(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToSpan - Return _canNavigateToSpan + Public Function CanNavigateToPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToPositionAsync + Return If(_canNavigateToPosition, SpecializedTasks.True, SpecializedTasks.False) End Function Public Function CanNavigateToSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToSpanAsync Return If(_canNavigateToSpan, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function TryNavigateToLineAndOffset(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToLineAndOffset + Public Function TryNavigateToLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToLineAndOffsetAsync _triedNavigationToLineAndOffset = True _documentId = documentId _options = options _line = lineNumber _offset = offset - Return _canNavigateToLineAndOffset + Return If(_canNavigateToLineAndOffset, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function TryNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToPosition + Public Function TryNavigateToPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToPositionAsync _triedNavigationToPosition = True _documentId = documentId _options = options _position = position _positionVirtualSpace = virtualSpace - Return _canNavigateToPosition - End Function - - Public Function TryNavigateToSpan(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpan As Boolean, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToSpan - _triedNavigationToSpan = True - _documentId = documentId - _options = options - _span = textSpan - - Return _canNavigateToSpan + Return If(_canNavigateToPosition, SpecializedTasks.True, SpecializedTasks.False) End Function Public Function TryNavigateToSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpan As Boolean, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToSpanAsync diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb index c8cae184b1aff..221de0d1004ac 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb @@ -5,7 +5,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.Navigation -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Roslyn.Utilities @@ -17,9 +16,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Public _triedSymbolNavigationNotify As Boolean Public _wouldNavigateToSymbol As Boolean - Public Function TryNavigateToSymbol(symbol As ISymbol, project As Project, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TryNavigateToSymbol + Public Function TryNavigateToSymbolAsync(symbol As ISymbol, project As Project, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TryNavigateToSymbolAsync _triedNavigationToSymbol = True - Return True + Return SpecializedTasks.True End Function Public Function TrySymbolNavigationNotifyAsync(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TrySymbolNavigationNotifyAsync diff --git a/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb b/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb index 4d0669dc837ae..188a9732f3f60 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb @@ -48,26 +48,19 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Public TryNavigateToPositionReturnValue As Boolean = True Public TryNavigateToSpanReturnValue As Boolean = True - Public Function CanNavigateToLineAndOffset(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToLineAndOffset + Public Function CanNavigateToLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToLineAndOffsetAsync Me.ProvidedDocumentId = documentId Me.ProvidedLineNumber = lineNumber - Return CanNavigateToLineAndOffsetReturnValue + Return If(CanNavigateToLineAndOffsetReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function CanNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToPosition + Public Function CanNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToPositionAsync Me.ProvidedDocumentId = documentId Me.ProvidedPosition = position Me.ProvidedVirtualSpace = virtualSpace - Return CanNavigateToPositionReturnValue - End Function - - Public Function CanNavigateToSpan(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.CanNavigateToSpan - Me.ProvidedDocumentId = documentId - Me.ProvidedTextSpan = textSpan - - Return CanNavigateToSpanReturnValue + Return If(CanNavigateToPositionReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function Public Function CanNavigateToSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToSpanAsync @@ -77,30 +70,22 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Return If(CanNavigateToSpanReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function TryNavigateToLineAndOffset(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToLineAndOffset + Public Function TryNavigateToLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToLineAndOffsetAsync Me.ProvidedDocumentId = documentId Me.ProvidedLineNumber = lineNumber Me.ProvidedOffset = offset Me.ProvidedOptions = options - Return TryNavigateToLineAndOffsetReturnValue + Return If(TryNavigateToLineAndOffsetReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function TryNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToPosition + Public Function TryNavigateToPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToPositionAsync Me.ProvidedDocumentId = documentId Me.ProvidedPosition = position Me.ProvidedVirtualSpace = virtualSpace Me.ProvidedOptions = options - Return TryNavigateToPositionReturnValue - End Function - - Public Function TryNavigateToSpan(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpans As Boolean, cancellationToken As CancellationToken) As Boolean Implements IDocumentNavigationService.TryNavigateToSpan - Me.ProvidedDocumentId = documentId - Me.ProvidedTextSpan = textSpan - Me.ProvidedOptions = options - - Return TryNavigateToSpanReturnValue + Return If(TryNavigateToPositionReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function Public Function TryNavigateToSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpans As Boolean, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.TryNavigateToSpanAsync diff --git a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb index 23b7ff8cf3a24..15adcf871c904 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Navigation Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text +Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities ' Note: by default, TestWorkspace produces a composition from all assemblies except EditorServicesTest2. @@ -45,11 +46,11 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Public NavigationLineNumberReturnValue As Integer Public NavigationCharOffsetReturnValue As Integer - Public Function TryNavigateToSymbol(symbol As ISymbol, project As Project, options As NavigationOptions, cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TryNavigateToSymbol + Public Function TryNavigateToSymbolAsync(symbol As ISymbol, project As Project, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TryNavigateToSymbolAsync Me.TryNavigateToSymbolProvidedSymbol = symbol Me.TryNavigateToSymbolProvidedProject = project Me.TryNavigateToSymbolProvidedOptions = options - Return True + Return SpecializedTasks.True End Function Public Function TrySymbolNavigationNotifyAsync( From 00e538b34477e11007e4a2a6d32a70bf8e45125d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 15:43:34 -0800 Subject: [PATCH 03/11] Simplify threading --- .../Extensions/VsTextSpanExtensions.cs | 2 +- .../Workspace/SourceGeneratedFileManager.cs | 4 +- .../VisualStudioDocumentNavigationService.cs | 158 +++++++++++------- 3 files changed, 97 insertions(+), 67 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs b/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs index c450164bc918e..ce7bc62ada346 100644 --- a/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs @@ -13,7 +13,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Extensions { internal static class VsTextSpanExtensions { - public static bool TryMapSpanFromSecondaryBufferToPrimaryBuffer(this VsTextSpan spanInSecondaryBuffer, Microsoft.CodeAnalysis.Workspace workspace, DocumentId documentId, out VsTextSpan spanInPrimaryBuffer) + public static bool TryMapSpanFromSecondaryBufferToPrimaryBuffer(this VsTextSpan spanInSecondaryBuffer, Workspace workspace, DocumentId documentId, out VsTextSpan spanInPrimaryBuffer) { spanInPrimaryBuffer = default; diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index e0f98d99e9435..9b42945de8529 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -101,9 +101,9 @@ public SourceGeneratedFileManager( this); } - public void NavigateToSourceGeneratedFile(SourceGeneratedDocument document, TextSpan sourceSpan, CancellationToken cancellationToken) + public async Task NavigateToSourceGeneratedFileAsync(SourceGeneratedDocument document, TextSpan sourceSpan, CancellationToken cancellationToken) { - _foregroundThreadAffintizedObject.AssertIsForeground(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // We will create an file name to represent this generated file; the Visual Studio shell APIs imply you can use a URI, // but most URIs are blocked other than file:// and http://; they also get extra handling to attempt to download the file so diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs index 911199c7923ae..2c1653cd8f647 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; @@ -61,7 +60,6 @@ public VisualStudioDocumentNavigationService( public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -71,7 +69,7 @@ public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId d } var document = workspace.CurrentSolution.GetRequiredDocument(documentId); - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var boundedTextSpan = GetSpanWithinDocumentBounds(textSpan, text.Length); if (boundedTextSpan != textSpan) @@ -89,12 +87,12 @@ public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId d var vsTextSpan = text.GetVsTextSpanForSpan(textSpan); - return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); + return await CanMapFromSecondaryBufferToPrimaryBufferAsync( + workspace, documentId, vsTextSpan, cancellationToken).ConfigureAwait(false); } public async Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken) { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -104,16 +102,15 @@ public async Task CanNavigateToLineAndOffsetAsync(Workspace workspace, Doc } var document = workspace.CurrentSolution.GetRequiredDocument(documentId); - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var vsTextSpan = text.GetVsTextSpanForLineOffset(lineNumber, offset); - return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); + return await CanMapFromSecondaryBufferToPrimaryBufferAsync( + workspace, documentId, vsTextSpan, cancellationToken).ConfigureAwait(false); } public async Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -123,7 +120,7 @@ public async Task CanNavigateToPositionAsync(Workspace workspace, Document } var document = workspace.CurrentSolution.GetRequiredDocument(documentId); - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length); if (boundedPosition != position) @@ -141,14 +138,15 @@ public async Task CanNavigateToPositionAsync(Workspace workspace, Document var vsTextSpan = text.GetVsTextSpanForPosition(position, virtualSpace); - return CanMapFromSecondaryBufferToPrimaryBuffer(workspace, documentId, vsTextSpan); + return await CanMapFromSecondaryBufferToPrimaryBufferAsync( + workspace, documentId, vsTextSpan, cancellationToken).ConfigureAwait(false); } public Task TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken) { return TryNavigateToLocationAsync(workspace, documentId, - _ => textSpan, + _ => Task.FromResult(textSpan), text => GetVsTextSpan(text, textSpan, allowInvalidSpan), options, cancellationToken); @@ -176,14 +174,14 @@ public Task TryNavigateToLineAndOffsetAsync( { return TryNavigateToLocationAsync(workspace, documentId, - document => GetTextSpanFromLineAndOffset(document, lineNumber, offset, cancellationToken), + document => GetTextSpanFromLineAndOffsetAsync(document, lineNumber, offset, cancellationToken), text => GetVsTextSpan(text, lineNumber, offset), options, cancellationToken); - static TextSpan GetTextSpanFromLineAndOffset(Document document, int lineNumber, int offset, CancellationToken cancellationToken) + static async Task GetTextSpanFromLineAndOffsetAsync(Document document, int lineNumber, int offset, CancellationToken cancellationToken) { - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var linePosition = new LinePosition(lineNumber, offset); return text.Lines.GetTextSpan(new LinePositionSpan(linePosition, linePosition)); @@ -200,14 +198,14 @@ public Task TryNavigateToPositionAsync( { return TryNavigateToLocationAsync(workspace, documentId, - document => GetTextSpanFromPosition(document, position, virtualSpace, cancellationToken), + document => GetTextSpanFromPositionAsync(document, position, virtualSpace, cancellationToken), text => GetVsTextSpan(text, position, virtualSpace), options, cancellationToken); - static TextSpan GetTextSpanFromPosition(Document document, int position, int virtualSpace, CancellationToken cancellationToken) + static async Task GetTextSpanFromPositionAsync(Document document, int position, int virtualSpace, CancellationToken cancellationToken) { - var text = document.GetTextSynchronously(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); text.GetLineAndOffset(position, out var lineNumber, out var offset); offset += virtualSpace; @@ -237,7 +235,7 @@ static VsTextSpan GetVsTextSpan(SourceText text, int position, int virtualSpace) private async Task TryNavigateToLocationAsync( Workspace workspace, DocumentId documentId, - Func getTextSpanForMapping, + Func> getTextSpanForMappingAsync, Func getVsTextSpan, NavigationOptions options, CancellationToken cancellationToken) @@ -247,70 +245,94 @@ private async Task TryNavigateToLocationAsync( // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); - var solution = workspace.CurrentSolution; - using (OpenNewDocumentStateScope(options)) { - var document = solution.GetDocument(documentId); - if (document == null) - { - var project = solution.GetProject(documentId.ProjectId); - if (project is null) - { - // This is a source generated document shown in Solution Explorer, but is no longer valid since - // the configuration and/or platform changed since the last generation completed. - return false; - } - - var generatedDocument = project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).AsTask().GetAwaiter().GetResult(); - if (generatedDocument != null) - { - _sourceGeneratedFileManager.Value.NavigateToSourceGeneratedFile(generatedDocument, getTextSpanForMapping(generatedDocument), cancellationToken); - return true; - } + // Keep this on the UI thread so that the scope disposal stays there. + return await TryNavigateToLocationUnderScopeAsync( + workspace, documentId, getTextSpanForMappingAsync, getVsTextSpan, cancellationToken).ConfigureAwait(true); + } + } + private async Task TryNavigateToLocationUnderScopeAsync( + Workspace workspace, + DocumentId documentId, + Func> getTextSpanForMappingAsync, + Func getVsTextSpan, + CancellationToken cancellationToken) + { + var solution = workspace.CurrentSolution; + var document = solution.GetDocument(documentId); + if (document == null) + { + var project = solution.GetProject(documentId.ProjectId); + if (project is null) + { + // This is a source generated document shown in Solution Explorer, but is no longer valid since + // the configuration and/or platform changed since the last generation completed. return false; } - // Before attempting to open the document, check if the location maps to a different file that should be opened instead. - var spanMappingService = document.Services.GetService(); - if (spanMappingService != null) + var generatedDocument = await project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + if (generatedDocument != null) { - var mappedSpan = GetMappedSpan(spanMappingService, document, getTextSpanForMapping(document), cancellationToken); - if (mappedSpan.HasValue) + await _sourceGeneratedFileManager.Value.NavigateToSourceGeneratedFileAsync( + generatedDocument, + await getTextSpanForMappingAsync(generatedDocument).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + return true; + } + + return false; + } + + // Before attempting to open the document, check if the location maps to a different file that should be opened instead. + var spanMappingService = document.Services.GetService(); + if (spanMappingService != null) + { + var mappedSpan = await GetMappedSpanAsync( + spanMappingService, + document, + await getTextSpanForMappingAsync(document).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + if (mappedSpan.HasValue) + { + // Check if the mapped file matches one already in the workspace. + // If so use the workspace APIs to navigate to it. Otherwise use VS APIs to navigate to the file path. + var documentIdsForFilePath = solution.GetDocumentIdsWithFilePath(mappedSpan.Value.FilePath); + if (!documentIdsForFilePath.IsEmpty) { - // Check if the mapped file matches one already in the workspace. - // If so use the workspace APIs to navigate to it. Otherwise use VS APIs to navigate to the file path. - var documentIdsForFilePath = solution.GetDocumentIdsWithFilePath(mappedSpan.Value.FilePath); - if (!documentIdsForFilePath.IsEmpty) - { - // If the mapped file maps to the same document that was passed in, then re-use the documentId to preserve context. - // Otherwise, just pick one of the ids to use for navigation. - var documentIdToNavigate = documentIdsForFilePath.Contains(documentId) ? documentId : documentIdsForFilePath.First(); - return NavigateToFileInWorkspace(documentIdToNavigate, workspace, getVsTextSpan, cancellationToken); - } - - return TryNavigateToMappedFile(workspace, document, mappedSpan.Value, cancellationToken); + // If the mapped file maps to the same document that was passed in, then re-use the documentId to preserve context. + // Otherwise, just pick one of the ids to use for navigation. + var documentIdToNavigate = documentIdsForFilePath.Contains(documentId) ? documentId : documentIdsForFilePath.First(); + return await NavigateToFileInWorkspaceAsync( + documentIdToNavigate, workspace, getVsTextSpan, cancellationToken).ConfigureAwait(false); } - } - return NavigateToFileInWorkspace(documentId, workspace, getVsTextSpan, cancellationToken); + return await TryNavigateToMappedFileAsync( + workspace, document, mappedSpan.Value, cancellationToken).ConfigureAwait(false); + } } + + return await NavigateToFileInWorkspaceAsync( + documentId, workspace, getVsTextSpan, cancellationToken).ConfigureAwait(false); } - private bool NavigateToFileInWorkspace( + private async Task NavigateToFileInWorkspaceAsync( DocumentId documentId, Workspace workspace, Func getVsTextSpan, CancellationToken cancellationToken) { + // Have to be on the UI thread to open a document. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var document = OpenDocument(workspace, documentId); if (document == null) { return false; } - var text = document.GetTextSynchronously(cancellationToken); + // Keep this on the UI thread since we're about to do span mapping. + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(true); var textBuffer = text.Container.GetTextBuffer(); var vsTextSpan = getVsTextSpan(text); @@ -323,8 +345,10 @@ private bool NavigateToFileInWorkspace( return NavigateTo(textBuffer, vsTextSpan, cancellationToken); } - private bool TryNavigateToMappedFile(Workspace workspace, Document generatedDocument, MappedSpanResult mappedSpanResult, CancellationToken cancellationToken) + private async Task TryNavigateToMappedFileAsync( + Workspace workspace, Document generatedDocument, MappedSpanResult mappedSpanResult, CancellationToken cancellationToken) { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var vsWorkspace = (VisualStudioWorkspaceImpl)workspace; // TODO - Move to IOpenDocumentService - https://github.com/dotnet/roslyn/issues/45954 // Pass the original result's project context so that if the mapped file has the same context available, we navigate @@ -346,7 +370,8 @@ private bool TryNavigateToMappedFile(Workspace workspace, Document generatedDocu return false; } - private MappedSpanResult? GetMappedSpan(ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan, CancellationToken cancellationToken) + private static async Task GetMappedSpanAsync( + ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan, CancellationToken cancellationToken) { // Mappings for opened razor files are retrieved via the LSP client making a request to the razor server. // If we wait for the result on the UI thread, we will hit a bug in the LSP client that brings us to a code path @@ -354,7 +379,8 @@ private bool TryNavigateToMappedFile(Workspace workspace, Document generatedDocu // Instead, we invoke this in JTF run which will mitigate deadlocks when the ConfigureAwait(true) // tries to switch back to the main thread in the LSP client. // Link to LSP client bug for ConfigureAwait(true) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1216657 - var results = _threadingContext.JoinableTaskFactory.Run(() => spanMappingService.MapSpansAsync(generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), cancellationToken)); + var results = await spanMappingService.MapSpansAsync( + generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), cancellationToken).ConfigureAwait(false); if (!results.IsDefaultOrEmpty) { @@ -454,8 +480,12 @@ private static bool IsSecondaryBuffer(Workspace workspace, DocumentId documentId return true; } - private static bool CanMapFromSecondaryBufferToPrimaryBuffer(Workspace workspace, DocumentId documentId, VsTextSpan spanInSecondaryBuffer) - => spanInSecondaryBuffer.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out _); + private async Task CanMapFromSecondaryBufferToPrimaryBufferAsync( + Workspace workspace, DocumentId documentId, VsTextSpan spanInSecondaryBuffer, CancellationToken cancellationToken) + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + return spanInSecondaryBuffer.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out _); + } private static IDisposable OpenNewDocumentStateScope(NavigationOptions options) { From 1782bae617bfe32a016c076b7229681898c9640c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 15:51:11 -0800 Subject: [PATCH 04/11] Remove unnecessary code --- .../VSTypeScript/VSTypeScriptNavigationBarItemService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs index 40a9bb90615b1..e8b1b5355bda0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -50,7 +50,6 @@ public async Task TryNavigateToItemAsync( { // Spans.First() is safe here as we filtered out any items with no spans above in ConvertItems. var navigationSpan = item.GetCurrentItemSpan(textVersion, item.Spans.First()); - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var workspace = document.Project.Solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); From 581976783619ad6de83e68034f1669f00c33b7c6 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 25 Feb 2022 15:56:34 -0800 Subject: [PATCH 05/11] Update src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs --- .../FSharp/Navigation/FSharpDocumentNavigationService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs b/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs index 12ef7bbb7ee64..be7d543cc9af1 100644 --- a/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs @@ -92,7 +92,6 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in var service = workspace.Services.GetService(); return _threadingContext.JoinableTaskFactory.Run(() => service.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); - } } } From 5f6f8e84c96c24e5eff6622b6d405a2cfed678d4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 16:06:31 -0800 Subject: [PATCH 06/11] Remove stale remarks --- .../Core/Portable/Navigation/IDocumentNavigationService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs index 7009998b7b306..5f94a6ee784e4 100644 --- a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs @@ -20,13 +20,11 @@ internal interface IDocumentNavigationService : IWorkspaceService /// /// Determines whether it is possible to navigate to the given line/offset in the specified document. /// - /// Only legal to call on the UI thread. Task CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); /// /// Determines whether it is possible to navigate to the given virtual position in the specified document. /// - /// Only legal to call on the UI thread. Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); /// @@ -37,7 +35,6 @@ internal interface IDocumentNavigationService : IWorkspaceService /// /// Navigates to the given line/offset in the specified document, opening it if necessary. /// - /// Only legal to call on the UI thread. Task TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken); /// From cc7a4b6a185dc5692f2e63ba31a4fffa553385cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 16:08:06 -0800 Subject: [PATCH 07/11] Simplify --- .../Navigation/IFSharpDocumentNavigationService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs b/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs index 144dd88a9ecda..3542d4370a7f5 100644 --- a/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Navigation/IFSharpDocumentNavigationService.cs @@ -28,18 +28,18 @@ internal interface IFSharpDocumentNavigationService : IWorkspaceService [Obsolete("Call overload that takes a CancellationToken", error: false)] bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace = 0, OptionSet options = null); - /// + /// bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken); - /// + /// bool CanNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); - /// + /// bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); - /// + /// bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken); - /// + /// bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, CancellationToken cancellationToken); - /// + /// bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); } } From f3fcff4ff1e3205e3415fe73e5e8d9fd503fa817 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 16:16:21 -0800 Subject: [PATCH 08/11] Push async up --- .../InlineRename/InlineRenameService.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs index d704aec4c43f9..ed072efa35258 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InlineRename; @@ -63,6 +64,14 @@ public InlineRenameSessionInfo StartInlineSession( Document document, TextSpan textSpan, CancellationToken cancellationToken) + { + return _threadingContext.JoinableTaskFactory.Run(() => StartInlineSessionAsync(document, textSpan, cancellationToken)); + } + + private async Task StartInlineSessionAsync( + Document document, + TextSpan textSpan, + CancellationToken cancellationToken) { if (_activeRenameSession != null) { @@ -70,9 +79,10 @@ public InlineRenameSessionInfo StartInlineSession( } var editorRenameService = document.GetRequiredLanguageService(); - var renameInfo = editorRenameService.GetRenameInfoAsync(document, textSpan.Start, cancellationToken).WaitAndGetResult(cancellationToken); + var renameInfo = await editorRenameService.GetRenameInfoAsync(document, textSpan.Start, cancellationToken).ConfigureAwait(false); - var readOnlyOrCannotNavigateToSpanSessionInfo = IsReadOnlyOrCannotNavigateToSpan(_threadingContext, renameInfo, document, cancellationToken); + var readOnlyOrCannotNavigateToSpanSessionInfo = await IsReadOnlyOrCannotNavigateToSpanAsync( + renameInfo, document, cancellationToken).ConfigureAwait(false); if (readOnlyOrCannotNavigateToSpanSessionInfo != null) { return readOnlyOrCannotNavigateToSpanSessionInfo; @@ -83,7 +93,8 @@ public InlineRenameSessionInfo StartInlineSession( return new InlineRenameSessionInfo(renameInfo.LocalizedErrorMessage); } - var snapshot = document.GetTextSynchronously(cancellationToken).FindCorrespondingEditorTextSnapshot(); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var snapshot = text.FindCorrespondingEditorTextSnapshot(); Contract.ThrowIfNull(snapshot, "The document used for starting the inline rename session should still be open and associated with a snapshot."); var options = new SymbolRenameOptions( @@ -111,8 +122,8 @@ public InlineRenameSessionInfo StartInlineSession( return new InlineRenameSessionInfo(ActiveSession); - static InlineRenameSessionInfo? IsReadOnlyOrCannotNavigateToSpan( - IThreadingContext threadingContext, IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) + static async Task IsReadOnlyOrCannotNavigateToSpanAsync( + IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) { if (renameInfo is IInlineRenameInfo inlineRenameInfo && inlineRenameInfo.DefinitionLocations != default) { @@ -121,7 +132,7 @@ public InlineRenameSessionInfo StartInlineSession( foreach (var documentSpan in inlineRenameInfo.DefinitionLocations) { - var sourceText = documentSpan.Document.GetTextSynchronously(cancellationToken); + var sourceText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); var textSnapshot = sourceText.FindCorrespondingEditorTextSnapshot(); if (textSnapshot != null) @@ -135,8 +146,8 @@ public InlineRenameSessionInfo StartInlineSession( } } - var canNavigate = threadingContext.JoinableTaskFactory.Run(() => - navigationService.CanNavigateToSpanAsync(workspace, document.Id, documentSpan.SourceSpan, cancellationToken)); + var canNavigate = await navigationService.CanNavigateToSpanAsync( + workspace, document.Id, documentSpan.SourceSpan, cancellationToken).ConfigureAwait(false); if (!canNavigate) { return new InlineRenameSessionInfo(EditorFeaturesResources.You_cannot_rename_this_element_because_it_is_in_a_location_that_cannot_be_navigated_to); From 487d63ae85c6fa593d887374d582a8dbc413079f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 25 Feb 2022 16:19:02 -0800 Subject: [PATCH 09/11] REmove comment --- .../Workspace/VisualStudioDocumentNavigationService.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs index 2c1653cd8f647..8fc18231df722 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs @@ -373,12 +373,6 @@ private async Task TryNavigateToMappedFileAsync( private static async Task GetMappedSpanAsync( ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan, CancellationToken cancellationToken) { - // Mappings for opened razor files are retrieved via the LSP client making a request to the razor server. - // If we wait for the result on the UI thread, we will hit a bug in the LSP client that brings us to a code path - // using ConfigureAwait(true). This deadlocks as it then attempts to return to the UI thread which is already blocked by us. - // Instead, we invoke this in JTF run which will mitigate deadlocks when the ConfigureAwait(true) - // tries to switch back to the main thread in the LSP client. - // Link to LSP client bug for ConfigureAwait(true) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1216657 var results = await spanMappingService.MapSpansAsync( generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), cancellationToken).ConfigureAwait(false); From 355fa65493beca5f843daf76b636c2875c0edbc0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 26 Feb 2022 12:44:59 -0800 Subject: [PATCH 10/11] lint --- .../VSTypeScript/VSTypeScriptNavigationBarItemService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs index e8b1b5355bda0..2eded9bd2d2ec 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -22,16 +22,13 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript [ExportLanguageService(typeof(INavigationBarItemService), InternalLanguageNames.TypeScript), Shared] internal class VSTypeScriptNavigationBarItemService : INavigationBarItemService { - private readonly IThreadingContext _threadingContext; private readonly IVSTypeScriptNavigationBarItemService _service; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VSTypeScriptNavigationBarItemService( - IThreadingContext threadingContext, IVSTypeScriptNavigationBarItemService service) { - _threadingContext = threadingContext; _service = service; } From bedc0491005f903330e0913a67904e1d685518b0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 26 Feb 2022 13:09:01 -0800 Subject: [PATCH 11/11] Threading --- .../InlineRename/InlineRenameService.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs index ed072efa35258..ada565c309834 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameService.cs @@ -82,7 +82,7 @@ private async Task StartInlineSessionAsync( var renameInfo = await editorRenameService.GetRenameInfoAsync(document, textSpan.Start, cancellationToken).ConfigureAwait(false); var readOnlyOrCannotNavigateToSpanSessionInfo = await IsReadOnlyOrCannotNavigateToSpanAsync( - renameInfo, document, cancellationToken).ConfigureAwait(false); + _threadingContext, renameInfo, document, cancellationToken).ConfigureAwait(false); if (readOnlyOrCannotNavigateToSpanSessionInfo != null) { return readOnlyOrCannotNavigateToSpanSessionInfo; @@ -105,6 +105,8 @@ private async Task StartInlineSessionAsync( var previewChanges = GlobalOptions.GetOption(InlineRenameSessionOptionsStorage.PreviewChanges); + // The session currently has UI thread affinity. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); ActiveSession = new InlineRenameSession( _threadingContext, this, @@ -123,13 +125,13 @@ private async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(ActiveSession); static async Task IsReadOnlyOrCannotNavigateToSpanAsync( - IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) + IThreadingContext threadingContext, IInlineRenameInfo renameInfo, Document document, CancellationToken cancellationToken) { if (renameInfo is IInlineRenameInfo inlineRenameInfo && inlineRenameInfo.DefinitionLocations != default) { var workspace = document.Project.Solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); - + using var _ = PooledObjects.ArrayBuilder<(ITextBuffer, SnapshotSpan)>.GetInstance(out var buffersAndSpans); foreach (var documentSpan in inlineRenameInfo.DefinitionLocations) { var sourceText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -139,11 +141,7 @@ private async Task StartInlineSessionAsync( { var buffer = textSnapshot.TextBuffer; var originalSpan = documentSpan.SourceSpan.ToSnapshotSpan(textSnapshot).TranslateTo(buffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - - if (buffer.IsReadOnly(originalSpan)) - { - return new InlineRenameSessionInfo(EditorFeaturesResources.You_cannot_rename_this_element_because_it_is_contained_in_a_read_only_file); - } + buffersAndSpans.Add((buffer, originalSpan)); } var canNavigate = await navigationService.CanNavigateToSpanAsync( @@ -153,6 +151,15 @@ private async Task StartInlineSessionAsync( return new InlineRenameSessionInfo(EditorFeaturesResources.You_cannot_rename_this_element_because_it_is_in_a_location_that_cannot_be_navigated_to); } } + + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + foreach (var (buffer, originalSpan) in buffersAndSpans) + { + if (buffer.IsReadOnly(originalSpan)) + { + return new InlineRenameSessionInfo(EditorFeaturesResources.You_cannot_rename_this_element_because_it_is_contained_in_a_read_only_file); + } + } } return null;