| File: Wrapping\WrapItemsAction.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using static Microsoft.CodeAnalysis.CodeActions.CodeAction; namespace Microsoft.CodeAnalysis.Wrapping; /// <summary> /// Code action for actually wrapping items. Provided as a special subclass because it will /// also update the wrapping most-recently-used list when the code action is actually /// invoked. /// </summary> internal sealed class WrapItemsAction(string title, string parentTitle, Func<IProgress<CodeAnalysisProgress>, CancellationToken, Task<Document>> createChangedDocument) : DocumentChangeAction(title, createChangedDocument, GetSortTitle(parentTitle, title), CodeActionPriority.Low) { private static string GetSortTitle(string title, string parentTitle) => $"{parentTitle}_{title}"; // Keeps track of the invoked code actions. That way we can prioritize those code actions // in the future since they're more likely the ones the user wants. This is important as // we have 9 different code actions offered (3 major groups, with 3 actions per group). // It's likely the user will just pick from a few of these. So we'd like the ones they // choose to be prioritized accordingly. private static ImmutableArray<string> s_mruTitles = []; public string ParentTitle { get; } = parentTitle; public string SortTitle { get; } = GetSortTitle(parentTitle, title); protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken) { // For preview, we don't want to compute the normal operations. Specifically, we don't // want to compute the stateful operation that tracks which code action was triggered. return await base.ComputeOperationsAsync(CodeAnalysisProgress.None, cancellationToken).ConfigureAwait(false); } protected override async Task<ImmutableArray<CodeActionOperation>> ComputeOperationsAsync(IProgress<CodeAnalysisProgress> progress, CancellationToken cancellationToken) { var operations = await base.ComputeOperationsAsync(progress, cancellationToken).ConfigureAwait(false); return operations.Add(new RecordCodeActionOperation(SortTitle, ParentTitle)); } public static ImmutableArray<CodeAction> SortActionsByMostRecentlyUsed(ImmutableArray<CodeAction> codeActions) => SortByMostRecentlyUsed(codeActions, s_mruTitles, GetSortTitle); public static ImmutableArray<T> SortByMostRecentlyUsed<T>( ImmutableArray<T> items, ImmutableArray<string> mostRecentlyUsedKeys, Func<T, string> getKey) { return items.Sort((d1, d2) => { var mruIndex1 = mostRecentlyUsedKeys.IndexOf(getKey(d1)); var mruIndex2 = mostRecentlyUsedKeys.IndexOf(getKey(d2)); // If both are in the mru, prefer the one earlier on. if (mruIndex1 >= 0 && mruIndex2 >= 0) return mruIndex1 - mruIndex2; // if either is in the mru, and the other is not, then the mru item is preferred. if (mruIndex1 >= 0) return -1; if (mruIndex2 >= 0) return 1; // Neither are in the mru. Sort them based on their original locations. var index1 = items.IndexOf(d1); var index2 = items.IndexOf(d2); // Note: we don't return 0 here as ImmutableArray.Sort is not stable. return index1 - index2; }); } private static string GetSortTitle(CodeAction codeAction) => (codeAction as WrapItemsAction)?.SortTitle ?? codeAction.Title; private sealed class RecordCodeActionOperation(string sortTitle, string parentTitle) : CodeActionOperation { private readonly string _sortTitle = sortTitle; private readonly string _parentTitle = parentTitle; internal override bool ApplyDuringTests => false; public override void Apply(Workspace workspace, CancellationToken cancellationToken) { // Record both the sortTitle of the nested action and the tile of the parent // action. This way we any invocation of a code action helps prioritize both // the parent lists and the nested lists. s_mruTitles = s_mruTitles.Remove(_sortTitle).Remove(_parentTitle) .Insert(0, _sortTitle).Insert(0, _parentTitle); } } }