未验证 提交 59051436 编写于 作者: D David 提交者: GitHub

Merge pull request #47104 from dibarbet/razor_local_rename

Razor local rename
......@@ -169,6 +169,11 @@ public RemoteCodeLensReferencesService()
}
var excerpter = document.Services.GetService<IDocumentExcerptService>();
if (excerpter == null)
{
continue;
}
var referenceExcerpt = await excerpter.TryExcerptAsync(document, span, ExcerptMode.SingleLine, cancellationToken).ConfigureAwait(false);
var tooltipExcerpt = await excerpter.TryExcerptAsync(document, span, ExcerptMode.Tooltip, cancellationToken).ConfigureAwait(false);
......
......@@ -2,6 +2,9 @@
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Undo;
using Microsoft.CodeAnalysis.Text;
......@@ -13,17 +16,30 @@ internal static class TextEditApplication
{
internal static void UpdateText(SourceText newText, ITextBuffer buffer, EditOptions options)
{
using var edit = buffer.CreateEdit(options, reiteratedVersionNumber: null, editTag: null);
var oldSnapshot = buffer.CurrentSnapshot;
var oldText = oldSnapshot.AsText();
var changes = newText.GetTextChanges(oldText);
UpdateText(changes.ToImmutableArray(), buffer, oldSnapshot, oldText, options);
}
public static void UpdateText(ImmutableArray<TextChange> textChanges, ITextBuffer buffer, EditOptions options)
{
var oldSnapshot = buffer.CurrentSnapshot;
var oldText = oldSnapshot.AsText();
UpdateText(textChanges, buffer, oldSnapshot, oldText, options);
}
private static void UpdateText(ImmutableArray<TextChange> textChanges, ITextBuffer buffer, ITextSnapshot oldSnapshot, SourceText oldText, EditOptions options)
{
using var edit = buffer.CreateEdit(options, reiteratedVersionNumber: null, editTag: null);
if (CodeAnalysis.Workspace.TryGetWorkspace(oldText.Container, out var workspace))
{
var undoService = workspace.Services.GetService<ISourceTextUndoService>();
var undoService = workspace.Services.GetRequiredService<ISourceTextUndoService>();
undoService.BeginUndoTransaction(oldSnapshot);
}
foreach (var change in changes)
foreach (var change in textChanges)
{
edit.Replace(change.Span.Start, change.Span.Length, change.NewText);
}
......
......@@ -653,6 +653,76 @@ protected override void ApplyAnalyzerReferenceRemoved(ProjectId projectId, Analy
}
}
internal override void ApplyMappedFileChanges(SolutionChanges solutionChanges)
{
// Get the original text changes from all documents and call the span mapping service to get span mappings for the text changes.
// Create mapped text changes using the mapped spans and original text changes' text.
// Mappings for opened razor files are retrieved via the LSP client making a request to the razor server.
// If we wait for the result on the UI thread, we will hit a bug in the LSP client that brings us to a code path
// using ConfigureAwait(true). This deadlocks as it then attempts to return to the UI thread which is already blocked by us.
// Instead, we invoke this in JTF run which will mitigate deadlocks when the ConfigureAwait(true)
// tries to switch back to the main thread in the LSP client.
// Link to LSP client bug for ConfigureAwait(true) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1216657
var mappedChanges = _threadingContext.JoinableTaskFactory.Run(() => GetMappedTextChanges(solutionChanges));
// Group the mapped text changes by file, then apply all mapped text changes for the file.
foreach (var changesForFile in mappedChanges)
{
// It doesn't matter which of the file's projectIds we pass to the invisible editor, so just pick the first.
var projectId = changesForFile.Value.First().ProjectId;
// Make sure we only take distinct changes - we'll have duplicates from different projects for linked files or multi-targeted files.
var distinctTextChanges = changesForFile.Value.Select(change => change.TextChange).Distinct().ToImmutableArray();
using var invisibleEditor = new InvisibleEditor(ServiceProvider.GlobalProvider, changesForFile.Key, GetHierarchy(projectId), needsSave: true, needsUndoDisabled: false);
TextEditApplication.UpdateText(distinctTextChanges, invisibleEditor.TextBuffer, EditOptions.None);
}
return;
async Task<MultiDictionary<string, (TextChange TextChange, ProjectId ProjectId)>> GetMappedTextChanges(SolutionChanges solutionChanges)
{
var filePathToMappedTextChanges = new MultiDictionary<string, (TextChange TextChange, ProjectId ProjectId)>();
foreach (var projectChanges in solutionChanges.GetProjectChanges())
{
foreach (var changedDocumentId in projectChanges.GetChangedDocuments())
{
var oldDocument = projectChanges.OldProject.GetRequiredDocument(changedDocumentId);
if (!ShouldApplyChangesToMappedDocuments(oldDocument, out var mappingService))
{
continue;
}
var newDocument = projectChanges.NewProject.GetRequiredDocument(changedDocumentId);
var textChanges = (await newDocument.GetTextChangesAsync(oldDocument, CancellationToken.None).ConfigureAwait(false)).ToImmutableArray();
var mappedSpanResults = await mappingService.MapSpansAsync(oldDocument, textChanges.Select(tc => tc.Span), CancellationToken.None).ConfigureAwait(false);
Contract.ThrowIfFalse(mappedSpanResults.Length == textChanges.Length);
for (var i = 0; i < mappedSpanResults.Length; i++)
{
// Only include changes that could be mapped.
var newText = textChanges[i].NewText;
if (!mappedSpanResults[i].IsDefault && newText != null)
{
var newTextChange = new TextChange(mappedSpanResults[i].Span, newText);
filePathToMappedTextChanges.Add(mappedSpanResults[i].FilePath, (newTextChange, projectChanges.ProjectId));
}
}
}
}
return filePathToMappedTextChanges;
}
bool ShouldApplyChangesToMappedDocuments(CodeAnalysis.Document document, [NotNullWhen(true)] out ISpanMappingService? spanMappingService)
{
spanMappingService = document.Services.GetService<ISpanMappingService>();
// Only consider files that are mapped and that we are unable to apply changes to.
// TODO - refactor how this is determined - https://github.com/dotnet/roslyn/issues/47908
return spanMappingService != null && document?.CanApplyChange() == false;
}
}
protected override void ApplyProjectReferenceAdded(
ProjectId projectId, ProjectReference projectReference)
{
......
......@@ -40,6 +40,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
private readonly IServiceProvider _serviceProvider;
private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;
private readonly IVsRunningDocumentTable4 _runningDocumentTable;
private readonly IThreadingContext _threadingContext;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
......@@ -52,6 +53,7 @@ internal sealed class VisualStudioDocumentNavigationService : ForegroundThreadAf
_serviceProvider = serviceProvider;
_editorAdaptersFactoryService = editorAdaptersFactoryService;
_runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
_threadingContext = threadingContext;
}
public bool CanNavigateToSpan(Workspace workspace, DocumentId documentId, TextSpan textSpan)
......@@ -303,12 +305,15 @@ private bool TryNavigateToMappedFile(Workspace workspace, Document generatedDocu
return false;
}
private static MappedSpanResult? GetMappedSpan(ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan)
private 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);
// Mappings for opened razor files are retrieved via the LSP client making a request to the razor server.
// If we wait for the result on the UI thread, we will hit a bug in the LSP client that brings us to a code path
// using ConfigureAwait(true). This deadlocks as it then attempts to return to the UI thread which is already blocked by us.
// Instead, we invoke this in JTF run which will mitigate deadlocks when the ConfigureAwait(true)
// tries to switch back to the main thread in the LSP client.
// Link to LSP client bug for ConfigureAwait(true) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1216657
var results = _threadingContext.JoinableTaskFactory.Run(() => spanMappingService.MapSpansAsync(generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), CancellationToken.None));
if (!results.IsDefaultOrEmpty)
{
......
......@@ -14,12 +14,12 @@ public static bool CanApplyChange([NotNullWhen(returnValue: true)] this TextDocu
=> document?.State.CanApplyChange() ?? false;
public static bool CanApplyChange([NotNullWhen(returnValue: true)] this TextDocumentState? document)
=> document?.Services.GetService<IDocumentOperationService>().CanApplyChange ?? false;
=> document?.Services.GetService<IDocumentOperationService>()?.CanApplyChange ?? false;
public static bool SupportsDiagnostics([NotNullWhen(returnValue: true)] this TextDocument? document)
=> document?.State.SupportsDiagnostics() ?? false;
public static bool SupportsDiagnostics([NotNullWhen(returnValue: true)] this TextDocumentState? document)
=> document?.Services.GetService<IDocumentOperationService>().SupportDiagnostics ?? false;
=> document?.Services.GetService<IDocumentOperationService>()?.SupportDiagnostics ?? false;
}
}
......@@ -2,6 +2,8 @@
// 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
namespace Microsoft.CodeAnalysis.Host
{
internal interface IDocumentServiceProvider
......@@ -10,6 +12,6 @@ internal interface IDocumentServiceProvider
/// Gets a document specific service provided by the host identified by the service type.
/// If the host does not provide the service, this method returns null.
/// </summary>
TService GetService<TService>() where TService : class, IDocumentService;
TService? GetService<TService>() where TService : class, IDocumentService;
}
}
......@@ -1220,6 +1220,9 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgressTracker pro
progressTracker.ItemCompleted();
}
// changes in mapped files outside the workspace (may span multiple projects)
this.ApplyMappedFileChanges(solutionChanges);
// removed projects
foreach (var proj in solutionChanges.GetRemovedProjects())
{
......@@ -1248,6 +1251,11 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgressTracker pro
}
}
internal virtual void ApplyMappedFileChanges(SolutionChanges solutionChanges)
{
return;
}
private void CheckAllowedSolutionChanges(SolutionChanges solutionChanges)
{
if (solutionChanges.GetRemovedProjects().Any() && !this.CanApplyChange(ApplyChangesKind.RemoveProject))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册