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

Change navigation helpers to operate in two steps. #59798

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member Author

Choose a reason for hiding this comment

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

View with whitespace off.

=> 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)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public async Task<INavigableLocation?> GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)
public async Task<INavigableLocation?> GetNavigableLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, NavigationOptions options, bool allowInvalidSpan, CancellationToken cancellationToken)

Copy link
Member Author

Choose a reason for hiding this comment

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

I actually liked the new name, since it's the DocNavigationService and it returns a NavigableLocation, i found it just fine to say GetLocation. but if you feewl strongly, i can change :)

{
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();
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
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))
Copy link
Member

Choose a reason for hiding this comment

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

Does this bit need to move into the deferred work? Since I think if there was async stuff streaming to the interactive window, the buffers could be updated and this would be stale?

Copy link
Member Author

Choose a reason for hiding this comment

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

in this case, we're technically on the UI thread the entire time. It's teh first line of GetLocationForSpanAsync. So it's less clear how much value any split has here. The split i was at least going for was having the first half be the part that detemines "is this safe to nav to?" and the second half is the actual "ok, now actually go there".

{
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>();
Copy link
Member

Choose a reason for hiding this comment

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

This actually seems a bit conceptually nicer in that "we can't navigate to something" is something you can find out without actually trying the operation.


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>();
}
}
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);
Copy link
Member Author

Choose a reason for hiding this comment

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

@jasonmalinowski i started with this being sync... but it still felt oogy. i'm not sure how i feel about any particular feature needing to have to have way to move itself to the UI thread before calling this.

that said, i don't feel super strongly about this. i think there's merit in your point that thsi must be fast/sync/UI bound.

Copy link
Member Author

Choose a reason for hiding this comment

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

note: for this PR/set-of-refactorings, i'd like to have this async for the moment. then, once all the work is done, we should have a good idea if it can be safely made sync.

Copy link
Member

Choose a reason for hiding this comment

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

I'm OK with this, but let's keep an eye out for places the "this should be async only..." rule gets violated. And maybe we will still have cases where this needs to be async; I could imagine in VS even opening a file this may still have some async bits since we at least have to check if the file exists, or something.

CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
}

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