File: Interactive\InteractiveDocumentNavigationService.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_dp51ti4n_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures.Wpf)
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Interactive
{
    internal sealed class InteractiveDocumentNavigationService : AbstractDocumentNavigationService
    {
        private readonly IThreadingContext _threadingContext;
 
        public InteractiveDocumentNavigationService(IThreadingContext threadingContext)
        {
            _threadingContext = threadingContext;
        }
 
        public override Task<bool> CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken)
            => SpecializedTasks.True;
 
        public override async Task<INavigableLocation?> GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken)
        {
            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            if (workspace is not InteractiveWindowWorkspace interactiveWorkspace)
            {
                Debug.Fail("InteractiveDocumentNavigationService called with incorrect workspace!");
                return null;
            }
 
            if (interactiveWorkspace.Window is null)
            {
                Debug.Fail("We are trying to navigate with a workspace that doesn't have a window!");
                return null;
            }
 
            var textView = interactiveWorkspace.Window.TextView;
            var document = interactiveWorkspace.CurrentSolution.GetDocument(documentId);
            if (document is null)
                return null;
 
            var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
            var textSnapshot = text.FindCorrespondingEditorTextSnapshot();
            if (textSnapshot == null)
                return null;
 
            var snapshotSpan = new SnapshotSpan(textSnapshot, textSpan.Start, textSpan.Length);
            var virtualSnapshotSpan = new VirtualSnapshotSpan(snapshotSpan);
 
            if (!textView.TryGetSurfaceBufferSpan(virtualSnapshotSpan, out var surfaceBufferSpan))
            {
                return null;
            }
 
            return new NavigableLocation(async (options, 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);
 
                textView.VisualElement.Focus();
 
                return true;
            });
        }
    }
}