Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the IDocumentNavigationSerivice entirely async. #59784

Merged
merged 13 commits into from
Feb 27, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -26,32 +26,20 @@ public InteractiveDocumentNavigationService(IThreadingContext threadingContext)
_threadingContext = threadingContext;
}

public async Task<bool> CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken)
public Task<bool> 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<bool> 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<bool> CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken)
=> SpecializedTasks.False;

public async Task<bool> 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!");
Expand Down Expand Up @@ -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<bool> 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<bool> TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken)
=> throw new NotSupportedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<DescriptionItem> _descriptionItems;

public NavigateToItemDisplay(INavigateToSearchResult searchResult)
=> _searchResult = searchResult;
public NavigateToItemDisplay(IThreadingContext threadingContext, INavigateToSearchResult searchResult)
{
_threadingContext = threadingContext;
_searchResult = searchResult;
}

public string AdditionalInformation => _searchResult.AdditionalInformation;

Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public NavigateToItemProvider(

_workspace = workspace;
_asyncListener = asyncListener;
_displayFactory = new NavigateToItemDisplayFactory();
_displayFactory = new NavigateToItemDisplayFactory(threadingContext);
_threadingContext = threadingContext;
}

Expand Down
97 changes: 53 additions & 44 deletions src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -58,63 +64,65 @@ public void AugmentPeekSession(IPeekSession session, IList<IPeekableItem> peekab

_uiThreadOperationExecutor.Execute(EditorFeaturesResources.Peek, EditorFeaturesResources.Loading_Peek_information, allowCancellation: true, showProgress: false, action: context =>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review with whitespace off.

{
var cancellationToken = context.UserCancellationToken;
var services = document.Project.Solution.Workspace.Services;
_threadingContext.JoinableTaskFactory.Run(() => AugumentPeekSessionAsync(peekableItems, context, triggerPoint.Value, document));
});
}

IEnumerable<IPeekableItem> results;
private async Task AugumentPeekSessionAsync(
IList<IPeekableItem> 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<IGoToDefinitionService>();
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<IGoToDefinitionService>();
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<ISymbolMappingService>();
symbol = symbol.GetOriginalUnreducedDefinition();

var mappingResult = symbolMappingService.MapSymbolAsync(document, symbol, cancellationToken)
.WaitAndGetResult(cancellationToken);
// Get the symbol back from the originating workspace
var symbolMappingService = services.GetRequiredService<ISymbolMappingService>();

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<IPeekableItem> GetPeekableItemsForNavigableItems(
private static async IAsyncEnumerable<IPeekableItem> GetPeekableItemsForNavigableItemsAsync(
IEnumerable<INavigableItem>? navigableItems, Project project,
IPeekResultFactory peekResultFactory,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
if (navigableItems != null)
{
Expand All @@ -124,9 +132,10 @@ private static IEnumerable<IPeekableItem> 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)
{
Expand Down
10 changes: 6 additions & 4 deletions src/EditorFeatures/Core.Wpf/Peek/PeekableItemSourceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,21 +22,25 @@ internal sealed class PeekableItemSourceProvider : IPeekableItemSourceProvider
{
private readonly IPeekableItemFactory _peekableItemFactory;
private readonly IPeekResultFactory _peekResultFactory;
private readonly IThreadingContext _threadingContext;
private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDocumentNavigationService>();
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<INotificationService>();
notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error);
}
Expand Down
Loading