提交 dd0e7741 编写于 作者: C CyrusNajmabadi

Merge pull request #8993 from CyrusNajmabadi/mergePreviewItems

Add support for merging preview items across multiple operations.
上级 9e2d304d
......@@ -6,6 +6,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Undo;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Notification;
......@@ -16,7 +17,7 @@
namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeActions
{
[Export(typeof(ICodeActionEditHandlerService))]
internal class CodeActionEditHandlerService : ICodeActionEditHandlerService
internal class CodeActionEditHandlerService : ForegroundThreadAffinitizedObject, ICodeActionEditHandlerService
{
private readonly IPreviewFactoryService _previewService;
private readonly IInlineRenameService _renameService;
......@@ -38,13 +39,16 @@ public ITextBufferAssociatedViewService AssociatedViewService
get { return _associatedViewService; }
}
public SolutionPreviewResult GetPreviews(Workspace workspace, IEnumerable<CodeActionOperation> operations, CancellationToken cancellationToken)
public SolutionPreviewResult GetPreviews(
Workspace workspace, IEnumerable<CodeActionOperation> operations, CancellationToken cancellationToken)
{
if (operations == null)
{
return null;
}
SolutionPreviewResult currentResult = null;
foreach (var op in operations)
{
cancellationToken.ThrowIfCancellationRequested();
......@@ -59,30 +63,39 @@ public SolutionPreviewResult GetPreviews(Workspace workspace, IEnumerable<CodeAc
if (preview != null && !preview.IsEmpty)
{
return preview;
currentResult = SolutionPreviewResult.Merge(currentResult, preview);
continue;
}
}
var previewOp = op as PreviewOperation;
if (previewOp != null)
{
return new SolutionPreviewResult(new List<SolutionPreviewItem>() { new SolutionPreviewItem(projectId: null, documentId: null,
lazyPreview: c => previewOp.GetPreviewAsync(c)) });
currentResult = SolutionPreviewResult.Merge(currentResult,
new SolutionPreviewResult(new SolutionPreviewItem(
projectId: null, documentId: null,
lazyPreview: c => previewOp.GetPreviewAsync(c))));
continue;
}
var title = op.Title;
if (title != null)
{
return new SolutionPreviewResult(new List<SolutionPreviewItem>() { new SolutionPreviewItem(projectId: null, documentId: null,
lazyPreview: c => Task.FromResult<object>(title)) });
currentResult = SolutionPreviewResult.Merge(currentResult,
new SolutionPreviewResult(new SolutionPreviewItem(
projectId: null, documentId: null, text: title)));
continue;
}
}
return null;
return currentResult;
}
public void Apply(Workspace workspace, Document fromDocument, IEnumerable<CodeActionOperation> operations, string title, CancellationToken cancellationToken)
{
this.AssertIsForeground();
if (_renameService.ActiveSession != null)
{
workspace.Services.GetService<INotificationService>()?.SendNotification(
......@@ -108,7 +121,66 @@ public void Apply(Workspace workspace, Document fromDocument, IEnumerable<CodeAc
var oldSolution = workspace.CurrentSolution;
Solution updatedSolution = oldSolution;
foreach (var operation in operations)
var operationsList = operations.ToList();
if (operationsList.Count > 1)
{
// Make a linked undo to wrap all these operations. This way we should
// be able to undo them all with one user action.
//
// Note: we only wrap things with an undo action if:
//
// 1. We have multiple operations (this code here).
// 2. We have a SolutionChangedAction and we're making changes to multiple
// documents. (Below in ProcessOperations).
//
// Or, in other words, if we know we're only editing a single file, then we
// don't wrap things with a global undo action.
//
// The reason for this is a global undo forces all files to save. And that's
// rather a heavyweight and unexpected experience for users (for the common
// case where a single file got edited).
//
// When we have multiple operations we assume that this is going to be
// more heavyweight. (After all, a full Roslyn solution change can be represented
// with a single operation). As such, we wrap with an undo so all the operations
// can be undone at once.
using (var transaction = workspace.OpenGlobalUndoTransaction(title))
{
updatedSolution = ProcessOperations(workspace, fromDocument, title, oldSolution, updatedSolution, operationsList, cancellationToken);
// link current file in the global undo transaction
if (fromDocument != null)
{
transaction.AddDocument(fromDocument.Id);
}
transaction.Commit();
}
}
else
{
updatedSolution = ProcessOperations(workspace, fromDocument, title, oldSolution, updatedSolution, operationsList, cancellationToken);
}
#if DEBUG
foreach (var project in workspace.CurrentSolution.Projects)
{
foreach (var document in project.Documents)
{
if (documentErrorLookup.Contains(document.Id))
{
document.VerifyNoErrorsAsync("CodeAction introduced error in error-free code", cancellationToken).Wait(cancellationToken);
}
}
}
#endif
TryStartRenameSession(workspace, oldSolution, updatedSolution, cancellationToken);
}
private static Solution ProcessOperations(Workspace workspace, Document fromDocument, string title, Solution oldSolution, Solution updatedSolution, List<CodeActionOperation> operationsList, CancellationToken cancellationToken)
{
foreach (var operation in operationsList)
{
var applyChanges = operation as ApplyChangesOperation;
if (applyChanges == null)
......@@ -174,20 +246,7 @@ public void Apply(Workspace workspace, Document fromDocument, IEnumerable<CodeAc
}
}
#if DEBUG
foreach (var project in workspace.CurrentSolution.Projects)
{
foreach (var document in project.Documents)
{
if (documentErrorLookup.Contains(document.Id))
{
document.VerifyNoErrorsAsync("CodeAction introduced error in error-free code", cancellationToken).Wait(cancellationToken);
}
}
}
#endif
TryStartRenameSession(workspace, oldSolution, updatedSolution, cancellationToken);
return updatedSolution;
}
private void TryStartRenameSession(Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
......
......@@ -31,11 +31,9 @@ public SolutionPreviewItem(ProjectId projectId, DocumentId documentId, Func<Canc
}
public SolutionPreviewItem(ProjectId projectId, DocumentId documentId, string text)
: this(projectId, documentId, c => Task.FromResult<object>(text))
{
ProjectId = projectId;
DocumentId = documentId;
Text = text;
LazyPreview = c => Task.FromResult<object>(text);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Text.Differencing;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor
......@@ -18,6 +14,11 @@ internal class SolutionPreviewResult : ForegroundThreadAffinitizedObject
private readonly IList<SolutionPreviewItem> _previews;
public readonly SolutionChangeSummary ChangeSummary;
public SolutionPreviewResult(SolutionPreviewItem preview, SolutionChangeSummary changeSummary = null)
: this(new List<SolutionPreviewItem> { preview }, changeSummary)
{
}
public SolutionPreviewResult(IList<SolutionPreviewItem> previews, SolutionChangeSummary changeSummary = null)
{
_previews = previews ?? SpecializedCollections.EmptyList<SolutionPreviewItem>();
......@@ -71,5 +72,24 @@ public async Task<IReadOnlyList<object>> GetPreviewsAsync(DocumentId preferredDo
return result.Count == 0 ? null : result;
}
/// <summary>Merge two different previews into one final preview result. The final preview will
/// have a concatenation of all the inidivual previews contained within each result.</summary>
internal static SolutionPreviewResult Merge(SolutionPreviewResult result1, SolutionPreviewResult result2)
{
if (result1 == null)
{
return result2;
}
if (result2 == null)
{
return result1;
}
return new SolutionPreviewResult(
result1._previews.Concat(result2._previews).ToList(),
result1.ChangeSummary ?? result2.ChangeSummary);
}
}
}
\ No newline at end of file
......@@ -121,7 +121,7 @@ private FrameworkElement CreatePreviewElement(IReadOnlyList<object> previewItems
}
else if (previewItem is string)
{
previewElement = GetPreviewForString((string)previewItem, createBorder: previewItems.Count == 0);
previewElement = GetPreviewForString((string)previewItem);
}
else if (previewItem is FrameworkElement)
{
......@@ -159,38 +159,15 @@ private void InitializeHyperlinks(Uri helpLink, string helpLinkToolTipText)
LearnMoreHyperlink.ToolTip = helpLinkToolTipText;
}
public static FrameworkElement GetPreviewForString(string previewContent, bool useItalicFontStyle = false, bool centerAlignTextHorizontally = false, bool createBorder = false)
private static FrameworkElement GetPreviewForString(string previewContent)
{
var textBlock = new TextBlock()
return new TextBlock()
{
Margin = new Thickness(5),
VerticalAlignment = VerticalAlignment.Center,
Text = previewContent,
TextWrapping = TextWrapping.Wrap,
};
if (useItalicFontStyle)
{
textBlock.FontStyle = FontStyles.Italic;
}
if (centerAlignTextHorizontally)
{
textBlock.TextAlignment = TextAlignment.Center;
}
FrameworkElement preview = textBlock;
if (createBorder)
{
preview = new Border()
{
Width = DefaultWidth,
MinHeight = 75,
Child = textBlock
};
}
return preview;
}
// This method adjusts the width of the header section to match that of the preview content
......
......@@ -81,7 +81,8 @@ private static Uri GetHelpLink(DiagnosticData diagnostic, string language, strin
return helpLink;
}
object IPreviewPaneService.GetPreviewPane(DiagnosticData diagnostic, string language, string projectType, IReadOnlyList<object> previewContent)
object IPreviewPaneService.GetPreviewPane(
DiagnosticData diagnostic, string language, string projectType, IReadOnlyList<object> previewContent)
{
var title = diagnostic?.Message;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册