未验证 提交 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 @@ ...@@ -2,8 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Editor;
...@@ -125,7 +128,11 @@ public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) ...@@ -125,7 +128,11 @@ public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
public int OnBeforeSave(uint docCookie) public int OnBeforeSave(uint docCookie)
=> VSConstants.E_NOTIMPL; => VSConstants.E_NOTIMPL;
public bool IsFileOpen(string fileName) => _runningDocumentTable.IsMonikerValid(fileName); public bool IsFileOpen(string fileName)
{
_foregroundAffinitization.AssertIsForeground();
return _runningDocumentTable.IsFileOpen(fileName);
}
/// <summary> /// <summary>
/// Attempts to get a text buffer from the specified moniker. /// Attempts to get a text buffer from the specified moniker.
...@@ -133,26 +140,14 @@ public int OnBeforeSave(uint docCookie) ...@@ -133,26 +140,14 @@ public int OnBeforeSave(uint docCookie)
/// <param name="moniker">the moniker to retrieve the text buffer for.</param> /// <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> /// <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> /// <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(); _foregroundAffinitization.AssertIsForeground();
textBuffer = null; return _runningDocumentTable.TryGetBufferFromMoniker(_editorAdaptersFactoryService, moniker, out textBuffer);
if (!IsFileOpen(moniker))
{
return false;
}
var cookie = _runningDocumentTable.GetDocumentCookie(moniker);
if (!_runningDocumentTable.IsDocumentInitialized(cookie))
{
return false;
}
return TryGetBuffer(cookie, out textBuffer);
} }
public IVsHierarchy GetDocumentHierarchy(string moniker) public IVsHierarchy? GetDocumentHierarchy(string moniker)
{ {
if (!IsFileOpen(moniker)) if (!IsFileOpen(moniker))
{ {
...@@ -213,20 +208,8 @@ private bool TryGetMoniker(uint docCookie, out string moniker) ...@@ -213,20 +208,8 @@ private bool TryGetMoniker(uint docCookie, out string moniker)
return !string.IsNullOrEmpty(moniker); return !string.IsNullOrEmpty(moniker);
} }
private bool TryGetBuffer(uint docCookie, out ITextBuffer textBuffer) private bool TryGetBuffer(uint docCookie, [NotNullWhen(true)] out ITextBuffer? textBuffer)
{ => _runningDocumentTable.TryGetBuffer(_editorAdaptersFactoryService, docCookie, out 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;
}
public void Dispose() 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 ...@@ -118,7 +118,7 @@ public static async Task<OpenFileTracker> CreateAsync(VisualStudioWorkspaceImpl
return new OpenFileTracker(workspace, runningDocumentTable, componentModel); 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(); _foregroundAffinitization.AssertIsForeground();
...@@ -171,7 +171,7 @@ private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffe ...@@ -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(); _foregroundAffinitization.AssertIsForeground();
......
...@@ -998,31 +998,43 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true) ...@@ -998,31 +998,43 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true)
var document = this.CurrentSolution.GetTextDocument(documentId); var document = this.CurrentSolution.GetTextDocument(documentId);
if (document != null) 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();
{ }
frame.Show(); else
} {
else frame.ShowNoActivate();
{
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; frame = null;
if (document.FilePath == null) if (filePath == null)
{ {
return false; return false;
} }
var hierarchy = GetHierarchy(document.Project.Id); var hierarchy = GetHierarchy(projectId);
var itemId = hierarchy?.TryGetItemId(document.FilePath) ?? (uint)VSConstants.VSITEMID.Nil; var itemId = hierarchy?.TryGetItemId(filePath) ?? (uint)VSConstants.VSITEMID.Nil;
if (itemId == (uint)VSConstants.VSITEMID.Nil) if (itemId == (uint)VSConstants.VSITEMID.Nil)
{ {
// If the ItemId is Nil, then IVsProject would not be able to open the // 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 ...@@ -1031,7 +1043,7 @@ private bool TryGetFrame(CodeAnalysis.TextDocument document, [NotNullWhen(return
var openDocumentService = ServiceProvider.GlobalProvider.GetService<SVsUIShellOpenDocument, IVsUIShellOpenDocument>(); var openDocumentService = ServiceProvider.GlobalProvider.GetService<SVsUIShellOpenDocument, IVsUIShellOpenDocument>();
return ErrorHandler.Succeeded(openDocumentService.OpenDocumentViaProject( return ErrorHandler.Succeeded(openDocumentService.OpenDocumentViaProject(
document.FilePath, filePath,
VSConstants.LOGVIEWID.TextView_guid, VSConstants.LOGVIEWID.TextView_guid,
out _, out _,
out _, out _,
......
...@@ -3,14 +3,18 @@ ...@@ -3,14 +3,18 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
...@@ -20,6 +24,7 @@ ...@@ -20,6 +24,7 @@
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
using TextSpan = Microsoft.CodeAnalysis.Text.TextSpan; using TextSpan = Microsoft.CodeAnalysis.Text.TextSpan;
using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
...@@ -31,6 +36,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf ...@@ -31,6 +36,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;
private readonly IVsRunningDocumentTable4 _runningDocumentTable;
public VisualStudioDocumentNavigationService( public VisualStudioDocumentNavigationService(
IThreadingContext threadingContext, IThreadingContext threadingContext,
...@@ -40,6 +46,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf ...@@ -40,6 +46,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_editorAdaptersFactoryService = editorAdaptersFactoryService; _editorAdaptersFactoryService = editorAdaptersFactoryService;
_runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
} }
public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan) public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan)
...@@ -125,25 +132,14 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in ...@@ -125,25 +132,14 @@ public bool CanNavigateToPosition(Workspace workspace, DocumentId documentId, in
public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, OptionSet options) public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan, OptionSet options)
{ {
// Navigation should not change the context of linked files and Shared Projects. return TryNavigateToLocation(workspace,
documentId = workspace.GetDocumentIdInCurrentContext(documentId); documentId,
_ => textSpan,
if (!IsForeground()) text => GetVsTextSpan(text, textSpan),
{ options);
throw new InvalidOperationException(ServicesVSResources.Navigation_must_be_performed_on_the_foreground_thread);
}
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); var boundedTextSpan = GetSpanWithinDocumentBounds(textSpan, text.Length);
if (boundedTextSpan != textSpan) if (boundedTextSpan != textSpan)
{ {
...@@ -156,52 +152,70 @@ public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp ...@@ -156,52 +152,70 @@ public bool TryNavigateToSpan(Workspace workspace, DocumentId documentId, TextSp
} }
} }
var vsTextSpan = text.GetVsTextSpanForSpan(boundedTextSpan); return text.GetVsTextSpanForSpan(boundedTextSpan);
if (IsSecondaryBuffer(workspace, documentId) &&
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan))
{
return false;
}
return NavigateTo(textBuffer, vsTextSpan);
} }
} }
public bool TryNavigateToLineAndOffset(Workspace workspace, DocumentId documentId, int lineNumber, int offset, OptionSet options) 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. return TryNavigateToLocation(workspace,
documentId = workspace.GetDocumentIdInCurrentContext(documentId); 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); return text.GetVsTextSpanForLineOffset(lineNumber, offset);
if (document == null) }
{ }
return false;
} 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 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) && var linePosition = new LinePosition(lineNumber, offset);
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan)) 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. // Navigation should not change the context of linked files and Shared Projects.
documentId = workspace.GetDocumentIdInCurrentContext(documentId); documentId = workspace.GetDocumentIdInCurrentContext(documentId);
...@@ -213,7 +227,19 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in ...@@ -213,7 +227,19 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
using (OpenNewDocumentStateScope(options ?? workspace.Options)) 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) if (document == null)
{ {
return false; return false;
...@@ -222,20 +248,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in ...@@ -222,20 +248,7 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in
var text = document.GetTextSynchronously(CancellationToken.None); var text = document.GetTextSynchronously(CancellationToken.None);
var textBuffer = text.Container.GetTextBuffer(); var textBuffer = text.Container.GetTextBuffer();
var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length); var vsTextSpan = getVsTextSpan(text);
if (boundedPosition != position)
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) when (FatalError.ReportWithoutCrash(e))
{
}
}
var vsTextSpan = text.GetVsTextSpanForPosition(boundedPosition, virtualSpace);
if (IsSecondaryBuffer(workspace, documentId) && if (IsSecondaryBuffer(workspace, documentId) &&
!vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan)) !vsTextSpan.TryMapSpanFromSecondaryBufferToPrimaryBuffer(workspace, documentId, out vsTextSpan))
{ {
...@@ -246,6 +259,44 @@ public bool TryNavigateToPosition(Workspace workspace, DocumentId documentId, in ...@@ -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> /// <summary>
/// It is unclear why, but we are sometimes asked to navigate to a position that is not /// 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 /// 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.
先完成此消息的编辑!
想要评论请 注册