未验证 提交 05f60b77 编写于 作者: J Joey Robichaud 提交者: GitHub

Merge pull request #46177 from dotnet/merges/release/dev16.8-preview1-to-master

Merge release/dev16.8-preview1 to master
......@@ -2,8 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Editor;
......@@ -125,7 +128,11 @@ public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
public int OnBeforeSave(uint docCookie)
=> VSConstants.E_NOTIMPL;
public bool IsFileOpen(string fileName) => _runningDocumentTable.IsMonikerValid(fileName);
public bool IsFileOpen(string fileName)
{
_foregroundAffinitization.AssertIsForeground();
return _runningDocumentTable.IsFileOpen(fileName);
}
/// <summary>
/// Attempts to get a text buffer from the specified moniker.
......@@ -133,26 +140,14 @@ public int OnBeforeSave(uint docCookie)
/// <param name="moniker">the moniker to retrieve the text buffer for.</param>
/// <param name="textBuffer">the output text buffer or null if the moniker is invalid / document is not initialized.</param>
/// <returns>true if the buffer was found with a non null value.</returns>
public bool TryGetBufferFromMoniker(string moniker, out ITextBuffer textBuffer)
public bool TryGetBufferFromMoniker(string moniker, [NotNullWhen(true)] out ITextBuffer? textBuffer)
{
_foregroundAffinitization.AssertIsForeground();
textBuffer = null;
if (!IsFileOpen(moniker))
{
return false;
}
var cookie = _runningDocumentTable.GetDocumentCookie(moniker);
if (!_runningDocumentTable.IsDocumentInitialized(cookie))
{
return false;
}
return TryGetBuffer(cookie, out textBuffer);
return _runningDocumentTable.TryGetBufferFromMoniker(_editorAdaptersFactoryService, moniker, out textBuffer);
}
public IVsHierarchy GetDocumentHierarchy(string moniker)
public IVsHierarchy? GetDocumentHierarchy(string moniker)
{
if (!IsFileOpen(moniker))
{
......@@ -213,20 +208,8 @@ private bool TryGetMoniker(uint docCookie, out string moniker)
return !string.IsNullOrEmpty(moniker);
}
private bool TryGetBuffer(uint docCookie, out ITextBuffer textBuffer)
{
textBuffer = null;
// The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder
// which saves us JIT time in this method and an assembly load.
if ((object)_runningDocumentTable.GetDocumentData(docCookie) is IVsTextBuffer bufferAdapter)
{
textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter);
return textBuffer != null;
}
return false;
}
private bool TryGetBuffer(uint docCookie, [NotNullWhen(true)] out ITextBuffer? textBuffer)
=> _runningDocumentTable.TryGetBuffer(_editorAdaptersFactoryService, docCookie, out textBuffer);
public void Dispose()
{
......
// 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.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
/// <summary>
/// Helper extensions for calling into the RDT.
/// These must all be called from the UI thread.
/// </summary>
internal static class RunningDocumentTableExtensions
{
public static bool TryGetBufferFromMoniker(this IVsRunningDocumentTable4 runningDocumentTable,
IVsEditorAdaptersFactoryService editorAdaptersFactoryService,
string moniker, [NotNullWhen(true)] out ITextBuffer? textBuffer)
{
textBuffer = null;
if (!runningDocumentTable.IsFileOpen(moniker))
{
return false;
}
var cookie = runningDocumentTable.GetDocumentCookie(moniker);
if (!runningDocumentTable.IsDocumentInitialized(cookie))
{
return false;
}
return TryGetBuffer(runningDocumentTable, editorAdaptersFactoryService, cookie, out textBuffer);
}
public static bool IsFileOpen(this IVsRunningDocumentTable4 runningDocumentTable, string fileName)
=> runningDocumentTable.IsMonikerValid(fileName);
public static bool TryGetBuffer(this IVsRunningDocumentTable4 runningDocumentTable, IVsEditorAdaptersFactoryService editorAdaptersFactoryService,
uint docCookie, [NotNullWhen(true)] out ITextBuffer? textBuffer)
{
textBuffer = null;
// The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder
// which saves us JIT time in this method and an assembly load.
if ((object)runningDocumentTable.GetDocumentData(docCookie) is IVsTextBuffer bufferAdapter)
{
textBuffer = editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter);
return textBuffer != null;
}
return false;
}
}
}
......@@ -118,7 +118,7 @@ public static async Task<OpenFileTracker> CreateAsync(VisualStudioWorkspaceImpl
return new OpenFileTracker(workspace, runningDocumentTable, componentModel);
}
private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy)
private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy)
{
_foregroundAffinitization.AssertIsForeground();
......@@ -171,7 +171,7 @@ private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffe
});
}
private ProjectId GetActiveContextProjectIdAndWatchHierarchies(string moniker, IEnumerable<ProjectId> projectIds, IVsHierarchy hierarchy)
private ProjectId GetActiveContextProjectIdAndWatchHierarchies(string moniker, IEnumerable<ProjectId> projectIds, IVsHierarchy? hierarchy)
{
_foregroundAffinitization.AssertIsForeground();
......
......@@ -998,31 +998,43 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true)
var document = this.CurrentSolution.GetTextDocument(documentId);
if (document != null)
{
if (TryGetFrame(document, out var frame))
OpenDocumentFromPath(document.FilePath, document.Project.Id, activate);
}
}
internal void OpenDocumentFromPath(string? filePath, ProjectId projectId, bool activate = true)
{
if (TryGetFrame(filePath, projectId, out var frame))
{
if (activate)
{
if (activate)
{
frame.Show();
}
else
{
frame.ShowNoActivate();
}
frame.Show();
}
else
{
frame.ShowNoActivate();
}
}
}
private bool TryGetFrame(CodeAnalysis.TextDocument document, [NotNullWhen(returnValue: true)] out IVsWindowFrame? frame)
/// <summary>
/// Opens a file and retrieves the window frame.
/// </summary>
/// <param name="filePath">the file path of the file to open.</param>
/// <param name="projectId">used to retrieve the IVsHierarchy to ensure the file is opened in a matching context.</param>
/// <param name="frame">the window frame.</param>
/// <returns></returns>
private bool TryGetFrame(string? filePath, ProjectId projectId, [NotNullWhen(returnValue: true)] out IVsWindowFrame? frame)
{
frame = null;
if (document.FilePath == null)
if (filePath == null)
{
return false;
}
var hierarchy = GetHierarchy(document.Project.Id);
var itemId = hierarchy?.TryGetItemId(document.FilePath) ?? (uint)VSConstants.VSITEMID.Nil;
var hierarchy = GetHierarchy(projectId);
var itemId = hierarchy?.TryGetItemId(filePath) ?? (uint)VSConstants.VSITEMID.Nil;
if (itemId == (uint)VSConstants.VSITEMID.Nil)
{
// If the ItemId is Nil, then IVsProject would not be able to open the
......@@ -1031,7 +1043,7 @@ private bool TryGetFrame(CodeAnalysis.TextDocument document, [NotNullWhen(return
var openDocumentService = ServiceProvider.GlobalProvider.GetService<SVsUIShellOpenDocument, IVsUIShellOpenDocument>();
return ErrorHandler.Succeeded(openDocumentService.OpenDocumentViaProject(
document.FilePath,
filePath,
VSConstants.LOGVIEWID.TextView_guid,
out _,
out _,
......
......@@ -3,14 +3,18 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
......@@ -20,6 +24,7 @@
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
using TextSpan = Microsoft.CodeAnalysis.Text.TextSpan;
using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
......@@ -31,6 +36,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
{
private readonly IServiceProvider _serviceProvider;
private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;
private readonly IVsRunningDocumentTable4 _runningDocumentTable;
public VisualStudioDocumentNavigationService(
IThreadingContext threadingContext,
......@@ -40,6 +46,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
{
_serviceProvider = serviceProvider;
_editorAdaptersFactoryService = editorAdaptersFactoryService;
_runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
}
public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan)
......@@ -125,25 +132,14 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in
public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, OptionSet options)
{
// Navigation should not change the context of linked files and Shared Projects.
documentId = workspace.GetDocumentIdInCurrentContext(documentId);
if (!IsForeground())
{
throw new InvalidOperationException(ServicesVSResources.Navigation_must_be_performed_on_the_foreground_thread);
}
return TryNavigateToLocation(workspace,
documentId,
_ => textSpan,
text => GetVsTextSpan(text, textSpan),
options);
using (OpenNewDocumentStateScope(options ?? workspace.Options))
static VsTextSpan GetVsTextSpan(SourceText text, TextSpan textSpan)
{
var document = OpenDocument(workspace, documentId);
if (document == null)
{
return false;
}
var text = document.GetTextSynchronously(CancellationToken.None);
var textBuffer = text.Container.GetTextBuffer();
var boundedTextSpan = GetSpanWithinDocumentBounds(textSpan, text.Length);
if (boundedTextSpan != textSpan)
{
......@@ -156,52 +152,70 @@ public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp
}
}
var vsTextSpan = text.GetVsTextSpanForSpan(boundedTextSpan);
if (IsSecondaryBuffer(workspace, documentId) &&
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan))
{
return false;
}
return NavigateTo(textBuffer, vsTextSpan);
return text.GetVsTextSpanForSpan(boundedTextSpan);
}
}
public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, OptionSet options)
{
// Navigation should not change the context of linked files and Shared Projects.
documentId = workspace.GetDocumentIdInCurrentContext(documentId);
return TryNavigateToLocation(workspace,
documentId,
(document) => GetTextSpanFromLineAndOffset(document, lineNumber, offset),
(text) => GetVsTextSpan(text, lineNumber, offset),
options);
if (!IsForeground())
static TextSpan GetTextSpanFromLineAndOffset(Document document, int lineNumber, int offset)
{
throw new InvalidOperationException(ServicesVSResources.Navigation_must_be_performed_on_the_foreground_thread);
var text = document.GetTextSynchronously(CancellationToken.None);
var linePosition = new LinePosition(lineNumber, offset);
return text.Lines.GetTextSpan(new LinePositionSpan(linePosition, linePosition));
}
using (OpenNewDocumentStateScope(options ?? workspace.Options))
static VsTextSpan GetVsTextSpan(SourceText text, int lineNumber, int offset)
{
var document = OpenDocument(workspace, documentId);
if (document == null)
{
return false;
}
return text.GetVsTextSpanForLineOffset(lineNumber, offset);
}
}
public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, OptionSet options)
{
return TryNavigateToLocation(workspace,
documentId,
(document) => GetTextSpanFromPosition(document, position, virtualSpace),
(text) => GetVsTextSpan(text, position, virtualSpace),
options);
static TextSpan GetTextSpanFromPosition(Document document, int position, int virtualSpace)
{
var text = document.GetTextSynchronously(CancellationToken.None);
var textBuffer = text.Container.GetTextBuffer();
text.GetLineAndOffset(position, out var lineNumber, out var offset);
var vsTextSpan = text.GetVsTextSpanForLineOffset(lineNumber, offset);
offset += virtualSpace;
if (IsSecondaryBuffer(workspace, documentId) &&
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan))
var linePosition = new LinePosition(lineNumber, offset);
return text.Lines.GetTextSpan(new LinePositionSpan(linePosition, linePosition));
}
static VsTextSpan GetVsTextSpan(SourceText text, int position, int virtualSpace)
{
var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length);
if (boundedPosition != position)
{
return false;
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) when (FatalError.ReportWithoutCrash(e))
{
}
}
return NavigateTo(textBuffer, vsTextSpan);
return text.GetVsTextSpanForPosition(boundedPosition, virtualSpace);
}
}
public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, int position, int virtualSpace, OptionSet options)
private bool TryNavigateToLocation(Workspace workspace, DocumentId documentId, Func<Document, TextSpan> getTextSpanForMapping, Func<SourceText, VsTextSpan> getVsTextSpan, OptionSet options)
{
// Navigation should not change the context of linked files and Shared Projects.
documentId = workspace.GetDocumentIdInCurrentContext(documentId);
......@@ -213,7 +227,19 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
using (OpenNewDocumentStateScope(options ?? workspace.Options))
{
var document = OpenDocument(workspace, documentId);
// Before attempting to open the document, check if the location maps to a different file that should be opened instead.
var document = workspace.CurrentSolution.GetDocument(documentId);
var spanMappingService = document?.Services.GetService<ISpanMappingService>();
if (spanMappingService != null)
{
var mappedSpan = GetMappedSpan(spanMappingService, document, getTextSpanForMapping(document));
if (mappedSpan.HasValue)
{
return TryNavigateToMappedFile(workspace, document, mappedSpan.Value);
}
}
document = OpenDocument(workspace, documentId);
if (document == null)
{
return false;
......@@ -222,20 +248,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
var text = document.GetTextSynchronously(CancellationToken.None);
var textBuffer = text.Container.GetTextBuffer();
var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length);
if (boundedPosition != position)
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) when (FatalError.ReportWithoutCrash(e))
{
}
}
var vsTextSpan = text.GetVsTextSpanForPosition(boundedPosition, virtualSpace);
var vsTextSpan = getVsTextSpan(text);
if (IsSecondaryBuffer(workspace, documentId) &&
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan))
{
......@@ -246,6 +259,44 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
}
}
private bool TryNavigateToMappedFile(Workspace workspace, Document generatedDocument, MappedSpanResult mappedSpanResult)
{
var vsWorkspace = (VisualStudioWorkspaceImpl)workspace;
// TODO - Move to IOpenDocumentService - https://github.com/dotnet/roslyn/issues/45954
// Pass the original result's project context so that if the mapped file has the same context available, we navigate
// to the mapped file with a consistent project context.
vsWorkspace.OpenDocumentFromPath(mappedSpanResult.FilePath, generatedDocument.Project.Id);
if (_runningDocumentTable.TryGetBufferFromMoniker(_editorAdaptersFactoryService, mappedSpanResult.FilePath, out var textBuffer))
{
var vsTextSpan = new VsTextSpan
{
iStartIndex = mappedSpanResult.LinePositionSpan.Start.Character,
iStartLine = mappedSpanResult.LinePositionSpan.Start.Line,
iEndIndex = mappedSpanResult.LinePositionSpan.End.Character,
iEndLine = mappedSpanResult.LinePositionSpan.End.Line
};
return NavigateTo(textBuffer, vsTextSpan);
}
return false;
}
private static MappedSpanResult? GetMappedSpan(ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan)
{
var results = System.Threading.Tasks.Task.Run(async () =>
{
return await spanMappingService.MapSpansAsync(generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), CancellationToken.None).ConfigureAwait(true);
}).WaitAndGetResult(CancellationToken.None);
if (!results.IsDefaultOrEmpty)
{
return results.First();
}
return null;
}
/// <summary>
/// It is unclear why, but we are sometimes asked to navigate to a position that is not
/// inside the bounds of the associated <see cref="Document"/>. This method returns a
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册