diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs index a288e389ecbd0..a6e02dc7821bd 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveDocumentNavigationService.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Interactive { - internal sealed class InteractiveDocumentNavigationService : IDocumentNavigationService + internal sealed class InteractiveDocumentNavigationService : AbstractDocumentNavigationService { private readonly IThreadingContext _threadingContext; @@ -24,13 +24,10 @@ public InteractiveDocumentNavigationService(IThreadingContext threadingContext) _threadingContext = threadingContext; } - public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + public override Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) => SpecializedTasks.True; - public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.False; - - public async Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + public override async Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); if (workspace is not InteractiveWindowWorkspace interactiveWorkspace) @@ -81,8 +78,5 @@ public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId doc return true; }); } - - public Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.Null(); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index 857c35f47cbcb..67a2922a8d923 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -58,7 +58,8 @@ protected async Task NavigateToPositionAsync(Workspace workspace, DocumentId doc var navigationService = workspace.Services.GetRequiredService(); if (!await navigationService.TryNavigateToPositionAsync( - ThreadingContext, workspace, documentId, position, virtualSpace, NavigationOptions.Default, cancellationToken).ConfigureAwait(false)) + ThreadingContext, workspace, documentId, position, virtualSpace, + allowInvalidPosition: false, NavigationOptions.Default, cancellationToken).ConfigureAwait(false)) { // Ensure we're back on the UI thread before showing a failure message. await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs index 8437277de74c9..5ec8bc9f7de2d 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -59,7 +59,7 @@ public async Task TryNavigateToItemAsync( var workspace = document.Project.Solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); return await navigationService.TryNavigateToPositionAsync( - _threadingContext, workspace, document.Id, navigationSpan.Start, virtualSpace: 0, NavigationOptions.Default, cancellationToken).ConfigureAwait(false); + _threadingContext, workspace, document.Id, navigationSpan.Start, cancellationToken).ConfigureAwait(false); } public bool ShowItemGrayedIfNear(NavigationBarItem item) diff --git a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs index 569073467d235..ed6ff3af80e9b 100644 --- a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs +++ b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs @@ -34,7 +34,7 @@ internal abstract partial class AbstractDefinitionLocationService( var service = workspace.Services.GetRequiredService(); return service.GetLocationForPositionAsync( - workspace, document.Id, position, virtualSpace: 0, cancellationToken); + workspace, document.Id, position, cancellationToken); } public async Task GetDefinitionLocationAsync(Document document, int position, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs b/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs index 4921763e60213..fbaa8a20e8f98 100644 --- a/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs +++ b/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs @@ -49,18 +49,24 @@ public static async Task TryNavigateToSpanAsync( } public static async Task TryNavigateToPositionAsync( - this IDocumentNavigationService service, IThreadingContext threadingContext, Workspace workspace, DocumentId documentId, int position, int virtualSpace, NavigationOptions options, CancellationToken cancellationToken) + this IDocumentNavigationService service, IThreadingContext threadingContext, Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, NavigationOptions options, CancellationToken cancellationToken) { - var location = await service.GetLocationForPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false); + var location = await service.GetLocationForPositionAsync(workspace, documentId, position, virtualSpace, allowInvalidPosition, cancellationToken).ConfigureAwait(false); return await location.TryNavigateToAsync(threadingContext, options, cancellationToken).ConfigureAwait(false); } - public static async Task TryNavigateToPositionAsync( + public static Task TryNavigateToPositionAsync( this IDocumentNavigationService service, IThreadingContext threadingContext, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) + { + return service.TryNavigateToPositionAsync(threadingContext, workspace, documentId, position, NavigationOptions.Default, cancellationToken); + } + + public static async Task TryNavigateToPositionAsync( + this IDocumentNavigationService service, IThreadingContext threadingContext, Workspace workspace, DocumentId documentId, int position, NavigationOptions options, CancellationToken cancellationToken) { var location = await service.GetLocationForPositionAsync( workspace, documentId, position, cancellationToken).ConfigureAwait(false); - return await location.TryNavigateToAsync(threadingContext, NavigationOptions.Default, cancellationToken).ConfigureAwait(false); + return await location.TryNavigateToAsync(threadingContext, options, cancellationToken).ConfigureAwait(false); } public static async Task TryNavigateToLineAndOffsetAsync( diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb index 32e9103e25b07..57c189557e160 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb @@ -65,16 +65,16 @@ class C handler.ExecuteCommand(New GoToDefinitionCommandArgs(view, baseDocument.GetTextBuffer()), TestCommandExecutionContext.Create()) Await waiter.ExpeditedWaitAsync() - Assert.True(mockDocumentNavigationService._triedNavigationToSpan) - Assert.Equal(New TextSpan(78, 2), mockDocumentNavigationService._span) + Assert.True(mockDocumentNavigationService._triedNavigationToPosition) + Assert.Equal(78, mockDocumentNavigationService._position) workspace.SetDocumentContext(linkDocument.Id) handler.ExecuteCommand(New GoToDefinitionCommandArgs(view, baseDocument.GetTextBuffer()), TestCommandExecutionContext.Create()) Await waiter.ExpeditedWaitAsync() - Assert.True(mockDocumentNavigationService._triedNavigationToSpan) - Assert.Equal(New TextSpan(121, 2), mockDocumentNavigationService._span) + Assert.True(mockDocumentNavigationService._triedNavigationToPosition) + Assert.Equal(121, mockDocumentNavigationService._position) End Using End Function @@ -107,8 +107,8 @@ int y = x$$ handler.ExecuteCommand(New GoToDefinitionCommandArgs(view, document.GetTextBuffer()), TestCommandExecutionContext.Create()) Await waiter.ExpeditedWaitAsync() - Assert.True(mockDocumentNavigationService._triedNavigationToSpan) - Assert.Equal(New TextSpan(4, 1), mockDocumentNavigationService._span) + Assert.True(mockDocumentNavigationService._triedNavigationToPosition) + Assert.Equal(4, mockDocumentNavigationService._position) Assert.Equal(document.Id, mockDocumentNavigationService._documentId) End Using End Function @@ -157,8 +157,8 @@ class C handler.ExecuteCommand(New GoToDefinitionCommandArgs(view, document.GetTextBuffer()), TestCommandExecutionContext.Create()) Await waiter.ExpeditedWaitAsync() - Assert.True(mockDocumentNavigationService._triedNavigationToSpan) - Assert.Equal(New TextSpan(22, 1), mockDocumentNavigationService._span) + Assert.True(mockDocumentNavigationService._triedNavigationToPosition) + Assert.Equal(22, mockDocumentNavigationService._position) Assert.Equal(document.Id, mockDocumentNavigationService._documentId) End Using End Function diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb index eb5a4c2d975d9..f4daa0e32fa88 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTestsBase.vb @@ -101,7 +101,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition Dim definitionDocument = workspace.GetTestDocument(mockDocumentNavigationService._documentId) Assert.Single(definitionDocument.SelectedSpans) Dim expected = definitionDocument.SelectedSpans.Single() - Assert.True(expected.Length = 0) Assert.Equal(expected.Start, mockDocumentNavigationService._position) ' The INavigableItemsPresenter should not have been called diff --git a/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb b/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb index ce28694549f81..8ab47c5c34f02 100644 --- a/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb +++ b/src/EditorFeatures/Test2/NavigableSymbols/NavigableSymbolsTest.vb @@ -7,7 +7,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.NavigableSymbols Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Navigation Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Text @@ -17,11 +16,9 @@ Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigableSymbols - <[UseExportProvider]> Public Class NavigableSymbolsTest - Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeatures.AddParts( GetType(MockDocumentNavigationServiceProvider), GetType(MockSymbolNavigationServiceProvider)) @@ -29,7 +26,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigableSymbols Public Async Function TestCharp() As Task Dim markup = " -class {|target:C|} +class {|target:|}C { {|highlighted:C|}$$ c }" @@ -72,7 +69,7 @@ class {|target:C|} Public Async Function TestVB() As Task Dim markup = " -Class {|target:C|} +Class {|target:|}C Dim c as {|highlighted:C|}$$ End Class" Dim text As String = Nothing @@ -140,7 +137,7 @@ End Class" Dim value As ImmutableArray(Of TextSpan) = Nothing If spans.TryGetValue("target", value) Then - Assert.Equal(value.First(), navigationService.ProvidedTextSpan) + Assert.Equal(value.First().Start, navigationService.ProvidedPosition) End If End Function End Class diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb index 382c7703e7250..1dd2706e31da3 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockDocumentNavigationService.vb @@ -25,7 +25,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Public _position As Integer = -1 Public _positionVirtualSpace As Integer = -1 - 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 + Public Function CanNavigateToPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, allowInvalidPosition As Boolean, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToPositionAsync Return If(_canNavigateToPosition, SpecializedTasks.True, SpecializedTasks.False) End Function @@ -33,7 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Return If(_canNavigateToSpan, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync + Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, allowInvalidPosition As Boolean, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync Return Task.FromResult(Of INavigableLocation)(New NavigableLocation( Function(o, c) _triedNavigationToPosition = True diff --git a/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb b/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb index 03e2a65135287..bf10493b61f37 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/MockDocumentNavigationServiceProvider.vb @@ -42,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Public TryNavigateToPositionReturnValue As Boolean = True Public TryNavigateToSpanReturnValue As Boolean = True - 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 + Public Function CanNavigateToPosition(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, allowInvalidPosition As Boolean, cancellationToken As CancellationToken) As Task(Of Boolean) Implements IDocumentNavigationService.CanNavigateToPositionAsync Me.ProvidedDocumentId = documentId Me.ProvidedPosition = position Me.ProvidedVirtualSpace = virtualSpace @@ -57,7 +57,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Return If(CanNavigateToSpanReturnValue, SpecializedTasks.True, SpecializedTasks.False) End Function - Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync + Public Function GetLocationForPositionAsync(workspace As Workspace, documentId As DocumentId, position As Integer, virtualSpace As Integer, allowInvalidPosition As Boolean, cancellationToken As CancellationToken) As Task(Of INavigableLocation) Implements IDocumentNavigationService.GetLocationForPositionAsync Me.ProvidedDocumentId = documentId Me.ProvidedPosition = position Me.ProvidedVirtualSpace = virtualSpace diff --git a/src/Features/Core/Portable/DocumentSpanExtensions.cs b/src/Features/Core/Portable/DocumentSpanExtensions.cs index be2edfaaae424..295d2545fbe9c 100644 --- a/src/Features/Core/Portable/DocumentSpanExtensions.cs +++ b/src/Features/Core/Portable/DocumentSpanExtensions.cs @@ -22,7 +22,8 @@ private static (Workspace workspace, IDocumentNavigationService service) GetNavi public static Task GetNavigableLocationAsync(this DocumentSpan documentSpan, CancellationToken cancellationToken) { var (workspace, service) = GetNavigationParts(documentSpan); - return service.GetLocationForSpanAsync(workspace, documentSpan.Document.Id, documentSpan.SourceSpan, allowInvalidSpan: false, cancellationToken); + return service.GetLocationForPositionAsync( + workspace, documentSpan.Document.Id, documentSpan.SourceSpan.Start, cancellationToken); } public static async Task IsHiddenAsync( diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs index 45317eb5b0b32..e3447ca9024df 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentNavigationServiceWrapper.cs @@ -32,7 +32,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in return _threadingProvider.Service.Run(async () => { var location = await obj.GetLocationForPositionAsync( - workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false); + workspace, documentId, position, virtualSpace, allowInvalidPosition: false, cancellationToken).ConfigureAwait(false); return location != null && await location.NavigateToAsync(NavigationOptions.Default, cancellationToken).ConfigureAwait(false); }); @@ -44,7 +44,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in return _threadingProvider.Service.Run(async () => { var location = await obj.GetLocationForPositionAsync( - workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false); + workspace, documentId, position, virtualSpace, allowInvalidPosition: false, cancellationToken).ConfigureAwait(false); return location != null && await location.NavigateToAsync(NavigationOptions.Default, cancellationToken).ConfigureAwait(false); }); diff --git a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs index 164341deca9a8..b4e44bb8faccd 100644 --- a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs @@ -4,28 +4,11 @@ using System; using System.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Navigation; [ExportWorkspaceService(typeof(IDocumentNavigationService), ServiceLayer.Default), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DefaultDocumentNavigationService() : IDocumentNavigationService -{ - public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) - => SpecializedTasks.False; - - public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.False; - - public Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) - => SpecializedTasks.Null(); - - public Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.Null(); -} +internal sealed class DefaultDocumentNavigationService() : AbstractDocumentNavigationService; diff --git a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs index 461eb0af0dbca..006506a55e49c 100644 --- a/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/IDocumentNavigationService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Navigation; @@ -21,10 +22,25 @@ internal interface IDocumentNavigationService : IWorkspaceService /// Determines whether it is possible to navigate to the given virtual position in the specified document. /// /// Legal to call from any thread. - Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); + Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken); Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken); - Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken); + Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken); +} + +internal abstract class AbstractDocumentNavigationService : IDocumentNavigationService +{ + public virtual Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + => SpecializedTasks.False; + + public virtual Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) + => CanNavigateToSpanAsync(workspace, documentId, new TextSpan(position, 0), allowInvalidSpan: allowInvalidPosition, cancellationToken); + + public virtual Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + => SpecializedTasks.Null(); + + public virtual Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) + => GetLocationForSpanAsync(workspace, documentId, new TextSpan(position, 0), allowInvalidSpan: allowInvalidPosition, cancellationToken); } internal static class IDocumentNavigationServiceExtensions @@ -33,11 +49,11 @@ public static Task CanNavigateToSpanAsync(this IDocumentNavigationService => service.CanNavigateToSpanAsync(workspace, documentId, textSpan, allowInvalidSpan: false, cancellationToken); public static Task CanNavigateToPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) - => service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace: 0, cancellationToken); + => service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace: 0, allowInvalidPosition: false, cancellationToken); public static Task GetLocationForSpanAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) => service.GetLocationForSpanAsync(workspace, documentId, textSpan, allowInvalidSpan: false, cancellationToken); public static Task GetLocationForPositionAsync(this IDocumentNavigationService service, Workspace workspace, DocumentId documentId, int position, CancellationToken cancellationToken) - => service.GetLocationForPositionAsync(workspace, documentId, position, virtualSpace: 0, cancellationToken); + => service.GetLocationForPositionAsync(workspace, documentId, position, virtualSpace: 0, allowInvalidPosition: false, cancellationToken); } diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs index 1d8310925273f..fb0cfa542c6fb 100644 --- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs @@ -19,22 +19,17 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp; [ExportWorkspaceService(typeof(IDocumentNavigationService), WorkspaceKind.SemanticSearch), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class SemanticSearchDocumentNavigationService(SemanticSearchToolWindowImpl window) : IDocumentNavigationService +internal sealed class SemanticSearchDocumentNavigationService(SemanticSearchToolWindowImpl window) + : AbstractDocumentNavigationService { - public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + public override Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) => SpecializedTasks.True; - public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.False; - - public Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + public override Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) { Debug.Assert(workspace is SemanticSearchWorkspace); Debug.Assert(documentId == SemanticSearchUtilities.GetQueryDocumentId(workspace.CurrentSolution)); return Task.FromResult(window.GetNavigableLocation(textSpan)); } - - public Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => SpecializedTasks.Null(); } diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs index b5cd1fd9c0cab..69965dc0e8136 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs @@ -52,11 +52,16 @@ public async Task NavigateToAsync(NavigationOptions options, CancellationToken c var document = Document; var documentNavigationService = document.Project.Solution.Services.GetRequiredService(); - await documentNavigationService.TryNavigateToSpanAsync( + await documentNavigationService.TryNavigateToPositionAsync( threadingContext, document.Project.Solution.Workspace, document.Id, - NavigateToTargetSpan, + NavigateToTargetSpan.Start, + virtualSpace: 0, + // The location we're trying to navigate to may be gone at this point. For example if the location was + // at the end of a file, and the user edited the document to be shorter. We want to not throw in this + // case as stale results are a normal part of how find-references works. + allowInvalidPosition: true, options, cancellationToken).ConfigureAwait(false); } diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs index 64feec718b4bf..08f2f620527be 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs @@ -65,7 +65,8 @@ public async Task CanNavigateToSpanAsync(Workspace workspace, DocumentId d documentId, vsTextSpan, cancellationToken).ConfigureAwait(false); } - public async Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + public async Task CanNavigateToPositionAsync( + Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) { // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); @@ -77,7 +78,7 @@ public async Task CanNavigateToPositionAsync(Workspace workspace, Document var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length); - if (boundedPosition != position) + if (boundedPosition != position && !allowInvalidPosition) { try { @@ -110,9 +111,9 @@ public async Task CanNavigateToPositionAsync(Workspace workspace, Document } public async Task GetLocationForPositionAsync( - Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) { - if (!await this.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken).ConfigureAwait(false)) + if (!await this.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, allowInvalidPosition, cancellationToken).ConfigureAwait(false)) return null; return await GetNavigableLocationAsync(workspace, diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index 656990405dbac..6de40fd0dc600 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs @@ -67,9 +67,8 @@ internal sealed partial class VisualStudioSymbolNavigationService( if (targetDocument != null) { var navigationService = solution.Services.GetRequiredService(); - return await navigationService.GetLocationForSpanAsync( - solution.Workspace, targetDocument.Id, sourceLocation.SourceSpan, - allowInvalidSpan: false, cancellationToken).ConfigureAwait(false); + return await navigationService.GetLocationForPositionAsync( + solution.Workspace, targetDocument.Id, sourceLocation.SourceSpan.Start, cancellationToken).ConfigureAwait(false); } } @@ -163,11 +162,11 @@ internal sealed partial class VisualStudioSymbolNavigationService( var editorWorkspace = openedDocument.Project.Solution.Workspace; var navigationService = editorWorkspace.Services.GetRequiredService(); - await navigationService.TryNavigateToSpanAsync( + await navigationService.TryNavigateToPositionAsync( _threadingContext, editorWorkspace, openedDocument.Id, - result.IdentifierLocation.SourceSpan, + result.IdentifierLocation.SourceSpan.Start, options with { PreferProvisionalTab = true }, cancellationToken).ConfigureAwait(false); } diff --git a/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb b/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb index e7f6cfe009ee8..1ec5fe361a224 100644 --- a/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb +++ b/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb @@ -319,7 +319,7 @@ class D : C Dim mockNavigationService = DirectCast(testState.Workspace.Services.GetService(Of IDocumentNavigationService)(), MockDocumentNavigationServiceProvider.MockDocumentNavigationService) Dim document = testState.Workspace.CurrentSolution.GetRequiredDocument(mockNavigationService.ProvidedDocumentId) Assert.Equal("OtherDoc.cs", document.Name) - Assert.Equal(TextSpan.FromBounds(43, 46), mockNavigationService.ProvidedTextSpan) + Assert.Equal(43, mockNavigationService.ProvidedPosition) End Using End Function diff --git a/src/VisualStudio/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs b/src/VisualStudio/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs index 121b0ecf4f6af..ce77f26b62dc4 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/Navigation/FSharpDocumentNavigationService.cs @@ -31,7 +31,7 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in { var service = workspace.Services.GetService(); return threadingContext.JoinableTaskFactory.Run(() => - service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, cancellationToken)); + service.CanNavigateToPositionAsync(workspace, documentId, position, virtualSpace, allowInvalidPosition: false, cancellationToken)); } public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, CancellationToken cancellationToken) @@ -47,6 +47,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in var service = workspace.Services.GetService(); return threadingContext.JoinableTaskFactory.Run(() => service.TryNavigateToPositionAsync( - threadingContext, workspace, documentId, position, virtualSpace, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); + threadingContext, workspace, documentId, position, virtualSpace, + allowInvalidPosition: false, NavigationOptions.Default with { PreferProvisionalTab = true }, cancellationToken)); } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs index a5039b8c1105c..f37d5a8f0ec34 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs @@ -2,18 +2,15 @@ // 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.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.TestSourceGenerator; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.Shell.TableControl; using Roslyn.VisualStudio.IntegrationTests; using Roslyn.VisualStudio.IntegrationTests.InProcess; using Roslyn.VisualStudio.NewIntegrationTests.InProcess; @@ -24,15 +21,10 @@ namespace Roslyn.VisualStudio.NewIntegrationTests.CSharp; [Trait(Traits.Feature, Traits.Features.SourceGenerators)] -public class CSharpSourceGenerators : AbstractEditorTest +public sealed class CSharpSourceGenerators(ITestOutputHelper testOutputHelper) + : AbstractEditorTest(nameof(CSharpSourceGenerators), WellKnownProjectTemplates.ConsoleApplication) { - private readonly ITestOutputHelper _testOutputHelper; - - public CSharpSourceGenerators(ITestOutputHelper testOutputHelper) - : base(nameof(CSharpSourceGenerators), WellKnownProjectTemplates.ConsoleApplication) - { - _testOutputHelper = testOutputHelper; - } + private readonly ITestOutputHelper _testOutputHelper = testOutputHelper; protected override string LanguageName => LanguageNames.CSharp; @@ -47,19 +39,23 @@ public override async Task InitializeAsync() [IdeFact] public async Task GoToDefinitionOpensGeneratedFile() { - await TestServices.Editor.SetTextAsync(@"using System; -internal static class Program -{ - public static void Main() - { - Console.WriteLine(" + HelloWorldGenerator.GeneratedEnglishClassName + @".GetMessage()); - } -}", HangMitigatingCancellationToken); + await TestServices.Editor.SetTextAsync($$""" + using System; + internal static class Program + { + public static void Main() + { + Console.WriteLine({{HelloWorldGenerator.GeneratedEnglishClassName}}.GetMessage()); + } + } + """, HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync(HelloWorldGenerator.GeneratedEnglishClassName, charsOffset: 0, HangMitigatingCancellationToken); await TestServices.Editor.GoToDefinitionAsync(HangMitigatingCancellationToken); Assert.Equal($"{HelloWorldGenerator.GeneratedEnglishClassName}.cs {ServicesVSResources.generated_suffix}", await TestServices.Shell.GetActiveWindowCaptionAsync(HangMitigatingCancellationToken)); - Assert.Equal(HelloWorldGenerator.GeneratedEnglishClassName, await TestServices.Editor.GetSelectedTextAsync(HangMitigatingCancellationToken)); + + var line = await TestServices.Editor.GetLineTextAfterCaretAsync(HangMitigatingCancellationToken); + Assert.True(line.StartsWith(HelloWorldGenerator.GeneratedEnglishClassName)); } [IdeFact] @@ -75,21 +71,25 @@ void M({{HelloWorldGenerator.GeneratedFolderClassName}} x) { } await TestServices.Editor.PlaceCaretAsync(HelloWorldGenerator.GeneratedFolderClassName, charsOffset: 0, HangMitigatingCancellationToken); await TestServices.Editor.GoToDefinitionAsync(HangMitigatingCancellationToken); Assert.Equal($"{HelloWorldGenerator.GeneratedFolderName}/{HelloWorldGenerator.GeneratedFolderClassName}.cs {ServicesVSResources.generated_suffix}", await TestServices.Shell.GetActiveWindowCaptionAsync(HangMitigatingCancellationToken)); - Assert.Equal(HelloWorldGenerator.GeneratedFolderClassName, await TestServices.Editor.GetSelectedTextAsync(HangMitigatingCancellationToken)); + + var line = await TestServices.Editor.GetLineTextAfterCaretAsync(HangMitigatingCancellationToken); + Assert.True(line.StartsWith(HelloWorldGenerator.GeneratedFolderClassName)); } [IdeTheory(Skip = "https://github.com/dotnet/roslyn/issues/64721")] [CombinatorialData] public async Task FindReferencesForFileWithDefinitionInSourceGeneratedFile(bool invokeFromSourceGeneratedFile) { - await TestServices.Editor.SetTextAsync(@"using System; -internal static class Program -{ - public static void Main() - { - Console.WriteLine(" + HelloWorldGenerator.GeneratedEnglishClassName + @".GetMessage()); - } -}", HangMitigatingCancellationToken); + await TestServices.Editor.SetTextAsync($$""" + using System; + internal static class Program + { + public static void Main() + { + Console.WriteLine({{HelloWorldGenerator.GeneratedEnglishClassName}}.GetMessage()); + } + } + """, HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync(HelloWorldGenerator.GeneratedEnglishClassName, charsOffset: 0, HangMitigatingCancellationToken); @@ -136,14 +136,16 @@ public static void Main() [IdeTheory, CombinatorialData] public async Task FindReferencesAndNavigateToReferenceInGeneratedFile(bool isPreview) { - await TestServices.Editor.SetTextAsync(@"using System; -internal static class Program -{ - public static void Main() - { - Console.WriteLine(" + HelloWorldGenerator.GeneratedEnglishClassName + @".GetMessage()); - } -}", HangMitigatingCancellationToken); + await TestServices.Editor.SetTextAsync($$""" + using System; + internal static class Program + { + public static void Main() + { + Console.WriteLine({{HelloWorldGenerator.GeneratedEnglishClassName}}.GetMessage()); + } + } + """, HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync(HelloWorldGenerator.GeneratedEnglishClassName, charsOffset: 0, HangMitigatingCancellationToken); await TestServices.Input.SendAsync((VirtualKeyCode.F12, VirtualKeyCode.SHIFT), HangMitigatingCancellationToken); diff --git a/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs b/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs index bc55ce15d78de..a78e33358c98b 100644 --- a/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs +++ b/src/VisualStudio/LiveShare/Test/MockDocumentNavigationServiceFactory.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests; @@ -19,17 +18,11 @@ namespace Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests; [ExportWorkspaceService(typeof(IDocumentNavigationService), ServiceLayer.Test), Shared, PartNotDiscoverable] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class MockDocumentNavigationService() : IDocumentNavigationService +internal sealed class MockDocumentNavigationService() : AbstractDocumentNavigationService { - public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + public override Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) => SpecializedTasks.True; - public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) - => SpecializedTasks.True; - - public Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) - => NavigableLocation.TestAccessor.Create(true); - - public Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + public override Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, bool allowInvalidPosition, CancellationToken cancellationToken) => NavigableLocation.TestAccessor.Create(true); }