File: FindReferences\VisualStudioDefinitionsAndReferencesFactory.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_ozsccwvc_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.Collections.Immutable;
using System.Composition;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.FindReferences;
 
[ExportWorkspaceService(typeof(IExternalDefinitionItemProvider), ServiceLayer.Desktop), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal class VisualStudioDefinitionsAndReferencesFactory(
    SVsServiceProvider serviceProvider,
    IThreadingContext threadingContext) : IExternalDefinitionItemProvider
{
    public async Task<DefinitionItem?> GetThirdPartyDefinitionItemAsync(
        Solution solution, DefinitionItem definitionItem, CancellationToken cancellationToken)
    {
        var symbolNavigationService = solution.Services.GetRequiredService<ISymbolNavigationService>();
        var result = await symbolNavigationService.GetExternalNavigationSymbolLocationAsync(definitionItem, cancellationToken).ConfigureAwait(false);
        if (result is not var (filePath, linePosition))
            return null;
 
        await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        var displayParts = GetDisplayParts_MustCallOnUIThread(filePath, linePosition);
        return new ExternalDefinitionItem(
            definitionItem.Tags, displayParts,
            serviceProvider, threadingContext,
            filePath, linePosition);
    }
 
    private ImmutableArray<TaggedText> GetDisplayParts_MustCallOnUIThread(
        string filePath, LinePosition linePosition)
    {
        var sourceLine = GetSourceLine_MustCallOnUIThread(filePath, linePosition.Line).Trim(' ', '\t');
 
        // Put the line in 1-based for the presentation of this item.
        var formatted = $"{filePath} - ({linePosition.Line + 1}, {linePosition.Character + 1}) : {sourceLine}";
 
        return [new TaggedText(TextTags.Text, formatted)];
    }
 
    private string GetSourceLine_MustCallOnUIThread(string filePath, int lineNumber)
    {
        using var invisibleEditor = new InvisibleEditor(
            serviceProvider, filePath, hierarchy: null, needsSave: false, needsUndoDisabled: false);
        var vsTextLines = invisibleEditor.VsTextLines;
        if (vsTextLines.GetLengthOfLine(lineNumber, out var lineLength) == VSConstants.S_OK &&
            vsTextLines.GetLineText(lineNumber, 0, lineNumber, lineLength, out var lineText) == VSConstants.S_OK)
        {
            return lineText;
        }
 
        return ServicesVSResources.Preview_unavailable;
    }
 
    private sealed class ExternalDefinitionItem(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        IServiceProvider serviceProvider,
        IThreadingContext threadingContext,
        string filePath,
        LinePosition linePosition)
        : DefinitionItem(
            tags,
            displayParts,
            nameDisplayParts: ImmutableArray<TaggedText>.Empty,
            sourceSpans: default,
            metadataLocations: default,
            classifiedSpans: default,
            properties: null,
            displayableProperties: [],
            displayIfNoReferences: true)
    {
        internal override bool IsExternal => true;
 
        public override Task<INavigableLocation?> GetNavigableLocationAsync(Workspace workspace, CancellationToken cancellationToken)
        {
            return Task.FromResult<INavigableLocation?>(new NavigableLocation(async (options, cancellationToken) =>
            {
                await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
                return TryOpenFile() && TryNavigateToPosition();
            }));
        }
 
        private bool TryOpenFile()
        {
            var shellOpenDocument = (IVsUIShellOpenDocument)serviceProvider.GetService(typeof(SVsUIShellOpenDocument));
            var textViewGuid = VSConstants.LOGVIEWID.TextView_guid;
            if (shellOpenDocument.OpenDocumentViaProject(
                    filePath, ref textViewGuid, out _,
                    out _, out _, out var frame) == VSConstants.S_OK)
            {
                frame.Show();
                return true;
            }
 
            return false;
        }
 
        private bool TryNavigateToPosition()
        {
            var docTable = (IVsRunningDocumentTable)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
            if (docTable.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, filePath,
                    out _, out _, out var bufferPtr, out _) != VSConstants.S_OK)
            {
                return false;
            }
 
            try
            {
                if (Marshal.GetObjectForIUnknown(bufferPtr) is not IVsTextLines lines)
                {
                    return false;
                }
 
                var textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager));
                if (textManager == null)
                {
                    return false;
                }
 
                return textManager.NavigateToLineAndColumn(
                    lines, VSConstants.LOGVIEWID.TextView_guid,
                    linePosition.Line, linePosition.Character,
                    linePosition.Line, linePosition.Character) == VSConstants.S_OK;
            }
            finally
            {
                if (bufferPtr != IntPtr.Zero)
                {
                    Marshal.Release(bufferPtr);
                }
            }
        }
    }
}