|
// 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.FindSymbols.FindReferences;
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;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.FindReferences
{
using Workspace = Microsoft.CodeAnalysis.Workspace;
[ExportWorkspaceService(typeof(IDefinitionsAndReferencesFactory), ServiceLayer.Desktop), Shared]
internal class VisualStudioDefinitionsAndReferencesFactory
: DefaultDefinitionsAndReferencesFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly IThreadingContext _threadingContext;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VisualStudioDefinitionsAndReferencesFactory(
SVsServiceProvider serviceProvider,
IThreadingContext threadingContext)
{
_serviceProvider = serviceProvider;
_threadingContext = threadingContext;
}
public override 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 ImmutableArray.Create(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 class ExternalDefinitionItem : DefinitionItem
{
private readonly IServiceProvider _serviceProvider;
private readonly IThreadingContext _threadingContext;
private readonly string _filePath;
private readonly LinePosition _linePosition;
internal override bool IsExternal => true;
public ExternalDefinitionItem(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
IServiceProvider serviceProvider,
IThreadingContext threadingContext,
string filePath,
LinePosition linePosition)
: base(tags,
displayParts,
nameDisplayParts: ImmutableArray<TaggedText>.Empty,
originationParts: default,
sourceSpans: default,
properties: null,
displayableProperties: null,
displayIfNoReferences: true)
{
_serviceProvider = serviceProvider;
_threadingContext = threadingContext;
_filePath = filePath;
_linePosition = linePosition;
}
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);
}
}
}
}
}
}
|