Skip to content

Commit

Permalink
Merge pull request #59798 from CyrusNajmabadi/asyncNavigation2
Browse files Browse the repository at this point in the history
Change navigation helpers to operate in two steps.
  • Loading branch information
CyrusNajmabadi authored Mar 1, 2022
2 parents ebac581 + 23491d4 commit cd34435
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ public Task<bool> CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentI
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)
public async Task<INavigableLocation?> GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
if (workspace is not InteractiveWindowWorkspace interactiveWorkspace)
{
Debug.Fail("InteractiveDocumentNavigationService called with incorrect workspace!");
return false;
return null;
}

if (interactiveWorkspace.Window is null)
{
Debug.Fail("We are trying to navigate with a workspace that doesn't have a window!");
return false;
return null;
}

var textView = interactiveWorkspace.Window.TextView;
Expand All @@ -58,35 +58,40 @@ public async Task<bool> TryNavigateToSpanAsync(Workspace workspace, DocumentId d
var textSnapshot = document?.GetTextSynchronously(cancellationToken).FindCorrespondingEditorTextSnapshot();
if (textSnapshot == null)
{
return false;
return null;
}

var snapshotSpan = new SnapshotSpan(textSnapshot, textSpan.Start, textSpan.Length);
var virtualSnapshotSpan = new VirtualSnapshotSpan(snapshotSpan);

if (!textView.TryGetSurfaceBufferSpan(virtualSnapshotSpan, out var surfaceBufferSpan))
{
return false;
return null;
}

textView.Selection.Select(surfaceBufferSpan.Start, surfaceBufferSpan.End);
textView.ViewScroller.EnsureSpanVisible(surfaceBufferSpan.SnapshotSpan, EnsureSpanVisibleOptions.AlwaysCenter);
return new NavigableLocation(async cancellationToken =>
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
textView.Selection.Select(surfaceBufferSpan.Start, surfaceBufferSpan.End);
textView.ViewScroller.EnsureSpanVisible(surfaceBufferSpan.SnapshotSpan, EnsureSpanVisibleOptions.AlwaysCenter);
// Moving the caret must be the last operation involving surfaceBufferSpan because
// it might update the version number of textView.TextSnapshot (VB does line commit
// when the caret leaves a line which might cause pretty listing), which must be
// equal to surfaceBufferSpan.SnapshotSpan.Snapshot's version number.
textView.Caret.MoveTo(surfaceBufferSpan.Start);
// Moving the caret must be the last operation involving surfaceBufferSpan because
// it might update the version number of textView.TextSnapshot (VB does line commit
// when the caret leaves a line which might cause pretty listing), which must be
// equal to surfaceBufferSpan.SnapshotSpan.Snapshot's version number.
textView.Caret.MoveTo(surfaceBufferSpan.Start);
textView.VisualElement.Focus();
textView.VisualElement.Focus();
return true;
return true;
});
}

public Task<bool> TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken)
public Task<INavigableLocation?> GetLocationForLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken)
=> throw new NotSupportedException();

public Task<bool> TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken)
public Task<INavigableLocation?> GetLocationForPositionAsync(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 @@ -40,33 +40,33 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers
Return If(_canNavigateToSpan, SpecializedTasks.True, SpecializedTasks.False)
End Function

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
Public Function GetLocationForLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForLineAndOffsetAsync
_triedNavigationToLineAndOffset = True
_documentId = documentId
_options = options
_line = lineNumber
_offset = offset

Return If(_canNavigateToLineAndOffset, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(_canNavigateToLineAndOffset)
End Function

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
Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync
_triedNavigationToPosition = True
_documentId = documentId
_options = options
_position = position
_positionVirtualSpace = virtualSpace

Return If(_canNavigateToPosition, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(_canNavigateToPosition)
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
Public Function GetLocationForSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpan As Boolean, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForSpanAsync
_triedNavigationToSpan = True
_documentId = documentId
_options = options
_span = textSpan

Return If(_canNavigateToSpan, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(_canNavigateToSpan)
End Function
End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -70,30 +70,30 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
Return If(CanNavigateToSpanReturnValue, SpecializedTasks.True, SpecializedTasks.False)
End Function

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
Public Function GetLocationForLineAndOffsetAsync(workspace As Workspace, documentId As DocumentId, lineNumber As Integer, offset As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForLineAndOffsetAsync
Me.ProvidedDocumentId = documentId
Me.ProvidedLineNumber = lineNumber
Me.ProvidedOffset = offset
Me.ProvidedOptions = options

Return If(TryNavigateToLineAndOffsetReturnValue, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(TryNavigateToLineAndOffsetReturnValue)
End Function

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
Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, options As NavigationOptions, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync
Me.ProvidedDocumentId = documentId
Me.ProvidedPosition = position
Me.ProvidedVirtualSpace = virtualSpace
Me.ProvidedOptions = options

Return If(TryNavigateToPositionReturnValue, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(TryNavigateToPositionReturnValue)
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
Public Function GetLocationForSpanAsync(workspace As Workspace, documentId As DocumentId, textSpan As TextSpan, options As NavigationOptions, allowInvalidSpans As Boolean, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForSpanAsync
Me.ProvidedDocumentId = documentId
Me.ProvidedTextSpan = textSpan
Me.ProvidedOptions = options

Return If(TryNavigateToSpanReturnValue, SpecializedTasks.True, SpecializedTasks.False)
Return NavigableLocation.TestAccessor.Create(TryNavigateToSpanReturnValue)
End Function
End Class
End Class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
return _threadingProvider.Service.Run(() => obj.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken));
}

/// <inheritdoc cref="IDocumentNavigationService.TryNavigateToPositionAsync"/>
public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken)
{
var obj = _underlyingObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public Task<bool> CanNavigateToLineAndOffsetAsync(Workspace workspace, DocumentI
public Task<bool> CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken)
=> SpecializedTasks.False;

public Task<bool> TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)
=> SpecializedTasks.False;
public Task<INavigableLocation?> GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)
=> SpecializedTasks.Null<INavigableLocation>();

public Task<bool> TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken)
=> SpecializedTasks.False;
public Task<INavigableLocation?> GetLocationForLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken)
=> SpecializedTasks.Null<INavigableLocation>();

public Task<bool> TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken)
=> SpecializedTasks.False;
public Task<INavigableLocation?> GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken)
=> SpecializedTasks.Null<INavigableLocation>();
}
}
50 changes: 36 additions & 14 deletions src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,9 @@ internal interface IDocumentNavigationService : IWorkspaceService
/// </summary>
Task<bool> CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken);

/// <summary>
/// Navigates to the given position in the specified document, opening it if necessary.
/// </summary>
Task<bool> TryNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken);

/// <summary>
/// Navigates to the given line/offset in the specified document, opening it if necessary.
/// </summary>
Task<bool> TryNavigateToLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken);

/// <summary>
/// Navigates to the given virtual position in the specified document, opening it if necessary.
/// </summary>
Task<bool> TryNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken);
Task<INavigableLocation?> GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken);
Task<INavigableLocation?> GetLocationForLineAndOffsetAsync(Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken);
Task<INavigableLocation?> GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken);
}

internal static class IDocumentNavigationServiceExtensions
Expand All @@ -59,5 +48,38 @@ public static Task<bool> TryNavigateToLineAndOffsetAsync(this IDocumentNavigatio

public static Task<bool> TryNavigateToPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken)
=> service.TryNavigateToPositionAsync(workspace, documentId, position, virtualSpace: 0, NavigationOptions.Default, cancellationToken);

/// <summary>
/// Navigates to the given position in the specified document, opening it if necessary.
/// </summary>
public static async Task<bool> TryNavigateToSpanAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)
{
var location = await service.GetLocationForSpanAsync(
workspace, documentId, textSpan, options, allowInvalidSpan, cancellationToken).ConfigureAwait(false);
return location != null &&
await location.NavigateToAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Navigates to the given line/offset in the specified document, opening it if necessary.
/// </summary>
public static async Task<bool> TryNavigateToLineAndOffsetAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int lineNumber, int offset, NavigationOptions options, CancellationToken cancellationToken)
{
var location = await service.GetLocationForLineAndOffsetAsync(
workspace, documentId, lineNumber, offset, options, cancellationToken).ConfigureAwait(false);
return location != null &&
await location.NavigateToAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Navigates to the given virtual position in the specified document, opening it if necessary.
/// </summary>
public static async Task<bool> TryNavigateToPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken)
{
var location = await service.GetLocationForPositionAsync(
workspace, documentId, position, virtualSpace, options, cancellationToken).ConfigureAwait(false);
return location != null &&
await location.NavigateToAsync(cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Navigation
{
internal interface INavigableLocation
{
/// <summary>
/// Navigates to a location opening or presenting it in a UI if necessary. This work must happen quickly. Any
/// expensive async work must be done by whatever component creates this value. This method is async only to
/// allow final clients to call this from a non-UI thread while allowing the navigation to jump to the UI
/// thread.
/// </summary>
Task<bool> NavigateToAsync(CancellationToken cancelletionToken);
}

internal class NavigableLocation : INavigableLocation
{
private readonly Func<CancellationToken, Task<bool>> _callback;

public NavigableLocation(Func<CancellationToken, Task<bool>> callback)
=> _callback = callback;

public Task<bool> NavigateToAsync(CancellationToken cancellationToken)
=> _callback(cancellationToken);

public static class TestAccessor
{
#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
public static Task<INavigableLocation?> Create(bool value)
#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
{
return Task.FromResult<INavigableLocation?>(
new NavigableLocation(c => value ? SpecializedTasks.True : SpecializedTasks.False));
}
}
}
}
Loading

0 comments on commit cd34435

Please sign in to comment.