提交 fe24b8a7 编写于 作者: A Allison Chou

Code review feedback and move resource file

上级 a9e73e51
......@@ -153,9 +153,6 @@
<data name="Regex_OtherEscape" xml:space="preserve">
<value>Regex - Other Escape</value>
</data>
<data name="Suppress_or_Configure_issues" xml:space="preserve">
<value>Suppress or Configure issues</value>
</data>
<data name="Gathering_Suggestions_0" xml:space="preserve">
<value>Gathering Suggestions - '{0}'</value>
</data>
......
......@@ -684,7 +684,7 @@ static CodeFixGroupKey GetGroupKey(CodeFix fix)
// to avoid clutter in the light bulb menu.
var wrappingSuggestedAction = new SuggestedActionWithNestedActions(
ThreadingContext, _owner, workspace, _subjectBuffer, this,
codeAction: new NoChangeAction(EditorFeaturesWpfResources.Suppress_or_Configure_issues),
codeAction: new NoChangeAction(CodeFixesResources.Suppress_or_Configure_issues),
nestedActionSets: suppressionSets.ToImmutable());
// Combine the spans and the category of each of the nested suggested actions
......@@ -693,7 +693,7 @@ static CodeFixGroupKey GetGroupKey(CodeFix fix)
var wrappingSet = new SuggestedActionSet(
category,
actions: SpecializedCollections.SingletonEnumerable(wrappingSuggestedAction),
title: EditorFeaturesWpfResources.Suppress_or_Configure_issues,
title: CodeFixesResources.Suppress_or_Configure_issues,
priority: SuggestedActionSetPriority.None,
applicableToSpan: span);
sets = sets.Add(wrappingSet);
......
......@@ -72,11 +72,6 @@
<target state="translated">Regulární výrazy – ostatní řídicí znaky</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Potlačit nebo konfigurovat problémy</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">RegEx - Anderes Escapezeichen</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Issues unterdrücken oder konfigurieren</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - otro Escape</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Suprimir o configurar incidencias</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - autre échappement</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Supprimer ou configurer les problèmes</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - Altro carattere di escape</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Elimina o configura problemi</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">正規表現 - 他のエスケープ</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">問題の抑制または構成</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - 기타 이스케이프</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">문제 표시 안 함 또는 구성</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Wyrażenie regularne — inna sekwencja ucieczki</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Problemy z pomijaniem lub konfigurowaniem</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex – Outro Escape</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Suprimir ou Configurar os problemas</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Регулярные выражения — другая escape-последовательность</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Подавление проблем или настройка уровня их серьезности</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - diğer kaçış</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">Sorunları Gizle veya Yapılandır</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">正则表达式 - 其他转义</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">禁止或配置方面的问题</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -72,11 +72,6 @@
<target state="translated">Regex - 其他逸出</target>
<note />
</trans-unit>
<trans-unit id="Suppress_or_Configure_issues">
<source>Suppress or Configure issues</source>
<target state="translated">隱藏或設定問題</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -154,7 +154,7 @@ protected static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind ki
protected static LSP.TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri, ProjectId? projectContext = null)
{
var documentIdentifier = new LSP.VSTextDocumentIdentifier() { Uri = uri };
var documentIdentifier = new LSP.VSTextDocumentIdentifier { Uri = uri };
if (projectContext != null)
{
......@@ -209,12 +209,7 @@ protected static LSP.VSCompletionItem CreateCompletionItem(string text, LSP.Comp
};
private protected static CodeActionResolveData CreateCodeActionResolveData(string codeActionTitle, LSP.Location location)
=> new CodeActionResolveData()
{
DistinctTitle = codeActionTitle,
Range = location.Range,
TextDocument = CreateTextDocumentIdentifier(location.Uri)
};
=> new CodeActionResolveData(codeActionTitle, location.Range, CreateTextDocumentIdentifier(location.Uri));
/// <summary>
/// Creates a solution with a document.
......
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions
{
internal static class CodeActionHelpers
{
public static async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(
Document? document,
ICodeFixService codeFixService,
ICodeRefactoringService codeRefactoringService,
LSP.Range selection,
CancellationToken cancellationToken)
{
if (document == null)
{
return ImmutableArray<CodeAction>.Empty;
}
var (codeFixCollections, codeRefactorings) = await GetCodeFixesAndRefactoringsAsync(
document, codeFixService,
codeRefactoringService, selection,
cancellationToken).ConfigureAwait(false);
var codeActions = codeFixCollections.SelectMany(c => c.Fixes.Select(f => f.Action)).Concat(
codeRefactorings.SelectMany(r => r.CodeActions.Select(ca => ca.action)));
return codeActions;
}
public static async Task<(ImmutableArray<CodeFixCollection>, ImmutableArray<CodeRefactoring>)> GetCodeFixesAndRefactoringsAsync(
Document document,
ICodeFixService codeFixService,
ICodeRefactoringService codeRefactoringService,
LSP.Range selection,
CancellationToken cancellationToken)
{
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var textSpan = ProtocolConversions.RangeToTextSpan(selection, text);
var codeFixCollections = Array.Empty<CodeFixCollection>().ToImmutableArray();
var codeRefactorings = Array.Empty<CodeRefactoring>().ToImmutableArray();
var codeFixCollectionsTask = Task.Run(
async () => codeFixCollections = await codeFixService.GetFixesAsync(
document, textSpan, includeSuppressionFixes: true, cancellationToken).ConfigureAwait(false));
var codeRefactoringsTask = Task.Run(
async () => codeRefactorings = await codeRefactoringService.GetRefactoringsAsync(
document, textSpan, cancellationToken).ConfigureAwait(false));
await Task.WhenAll(codeFixCollectionsTask, codeRefactoringsTask).ConfigureAwait(false);
return (codeFixCollections, codeRefactorings);
}
public static CodeAction? GetCodeActionToResolve(string distinctTitle, ImmutableArray<CodeAction> codeActions)
{
// Searching for the matching code action. We compare against the unique identifier
// (e.g. "Suppress or Configure issues.Configure IDExxxx.Warning") instead of the
// code action's title (e.g. "Warning") since there's a chance that multiple code
// actions may have the same title (e.g. there could be multiple code actions with
// the title "Warning" that appear in the code action menu if there are multiple
// diagnostics on the same line).
foreach (var c in codeActions)
{
var action = CheckForMatchingAction(c, distinctTitle, currentTitle: "");
if (action != null)
{
return action;
}
}
return null;
}
private static CodeAction? CheckForMatchingAction(CodeAction codeAction, string goalTitle, string currentTitle)
{
// Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues.Suppress IDEXXXX.in Source'
if (currentTitle != "")
{
currentTitle += '.';
}
// If the unique identifier of the current code action matches the unique identifier of the code action
// we're looking for, return the code action. If not, check to see if one of the current code action's
// nested actions may be a match.
var updatedTitle = currentTitle + codeAction.Title;
if (updatedTitle == goalTitle)
{
return codeAction;
}
foreach (var nestedAction in codeAction.NestedCodeActions)
{
var match = CheckForMatchingAction(nestedAction, goalTitle, updatedTitle);
if (match != null)
{
return match;
}
}
return null;
}
}
}
......@@ -7,22 +7,31 @@
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions
{
/// <summary>
/// Provides data needed to resolve code actions.
/// This class provides the intermediate data passed between CodeActionsHandler, CodeActionResolveHandler,
/// and RunCodeActionsHandler. The class provides enough information for each handler to identify the code
/// action that it is dealing with. The information is passed along via the Data property in LSP.VSCodeAction.
/// </summary>
internal class CodeActionResolveData
{
/// <summary>
/// The unique title of a code action. No two code actions should
/// have the same unique title.
/// The unique identifier of a code action. No two code actions should have the same unique identifier.
/// </summary>
/// <remarks>
/// The distinct tiel is currently set as:
/// name of top level code action + name of nested code action + nested nested code action etc.
/// The unique identifier is currently set as:
/// name of top level code action + '.' + name of nested code action + '.' + name of nested nested code action + etc.
/// e.g. 'Suppress or Configure issues.Suppress IDEXXXX.in Source'
/// </remarks>
public string DistinctTitle { get; set; }
public string UniqueIdentifier { get; }
public LSP.Range Range { get; set; }
public LSP.Range Range { get; }
public LSP.TextDocumentIdentifier TextDocument { get; set; }
public LSP.TextDocumentIdentifier TextDocument { get; }
public CodeActionResolveData(string uniqueIdentifier, LSP.Range range, LSP.TextDocumentIdentifier textDocument)
{
UniqueIdentifier = uniqueIdentifier;
Range = range;
TextDocument = textDocument;
}
}
}
......@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
......@@ -14,19 +15,18 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using CodeAction = Microsoft.CodeAnalysis.CodeActions.CodeAction;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
/// <summary>
/// Resolves a code action by filling out its Edit or Command property.
/// Resolves a code action by filling out its Edit and/or Command property.
/// The handler is triggered only when a user hovers over a code action, which
/// saves us from having to compute unnecessary edits.
/// </summary>
[ExportLspMethod(MSLSPMethods.TextDocumentCodeActionResolveName), Shared]
internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeAction, LSP.VSCodeAction>
......@@ -54,19 +54,15 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
{
var data = ((JToken)codeAction.Data).ToObject<CodeActionResolveData>();
var document = SolutionProvider.GetDocument(data.TextDocument, clientName);
var codeActions = await CodeActionsHandler.GetCodeActionsAsync(
var codeActions = await CodeActionHelpers.GetCodeActionsAsync(
document,
_codeFixService,
_codeRefactoringService,
data.Range,
cancellationToken).ConfigureAwait(false);
if (codeActions == null || !codeActions.Any())
{
return codeAction;
}
var codeActionToResolve = GetCodeActionToResolve(data.DistinctTitle, codeActions);
var codeActionToResolve = CodeActionHelpers.GetCodeActionToResolve(
data.UniqueIdentifier, codeActions.ToImmutableArray());
// We didn't find a matching action, so just return the action without an edit or command.
if (codeActionToResolve == null)
......@@ -90,11 +86,12 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/
var runAsCommand = false;
var applyChangesOperations = operations.Where(operation => operation is ApplyChangesOperation);
// Add workspace edits
var applyChangesOperations = operations.OfType<ApplyChangesOperation>();
if (applyChangesOperations.Any())
{
var workspaceEdits = await ComputeWorkspaceEdits(applyChangesOperations, document!, cancellationToken).ConfigureAwait(false);
if (workspaceEdits.Any())
if (workspaceEdits != null)
{
codeAction.Edit = new LSP.WorkspaceEdit { DocumentChanges = workspaceEdits };
}
......@@ -109,19 +106,24 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
var commandOperations = operations.All(operation => !(operation is ApplyChangesOperation));
if (commandOperations || runAsCommand)
{
codeAction.Command = SetCommand(codeAction, data);
codeAction.Command = new LSP.Command
{
CommandIdentifier = CodeActionsHandler.RunCodeActionCommandName,
Title = codeAction.Title,
Arguments = new object[] { data }
};
}
return codeAction;
// Local functions
static async Task<TextDocumentEdit[]> ComputeWorkspaceEdits(
IEnumerable<CodeActionOperation> applyChangesOperations,
static async Task<TextDocumentEdit[]?> ComputeWorkspaceEdits(
IEnumerable<ApplyChangesOperation> applyChangesOperations,
Document document,
CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<TextDocumentEdit>.GetInstance(out var textDocumentEdits);
foreach (ApplyChangesOperation applyChangesOperation in applyChangesOperations)
foreach (var applyChangesOperation in applyChangesOperations)
{
var solution = document.Project.Solution;
var changes = applyChangesOperation.ChangedSolution.GetChanges(solution);
......@@ -134,7 +136,7 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
pc => pc.GetAddedDocuments().Concat(pc.GetAddedAdditionalDocuments().Concat(pc.GetAddedAnalyzerConfigDocuments())));
if (addedDocuments.Any())
{
return Array.Empty<TextDocumentEdit>();
return null;
}
var changedDocuments = projectChanges.SelectMany(pc => pc.GetChangedDocuments());
......@@ -149,11 +151,11 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
changedAnalyzerConfigDocuments.Any() ||
changedAdditionalDocuments.Any())
{
return Array.Empty<TextDocumentEdit>();
return null;
}
// Changed documents
await GetTextDocumentEdits(
await AddTextDocumentEdits(
textDocumentEdits, applyChangesOperation, solution, changedDocuments,
applyChangesOperation.ChangedSolution.GetDocument, solution.GetDocument,
cancellationToken).ConfigureAwait(false);
......@@ -161,23 +163,23 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
// Changed analyzer config documents
// We won't get any results until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/
// is fixed.
await GetTextDocumentEdits(
textDocumentEdits, applyChangesOperation, solution, changedDocuments,
await AddTextDocumentEdits(
textDocumentEdits, applyChangesOperation, solution, changedAnalyzerConfigDocuments,
applyChangesOperation.ChangedSolution.GetAnalyzerConfigDocument, solution.GetAnalyzerConfigDocument,
cancellationToken).ConfigureAwait(false);
// Changed additional documents
// We won't get any results until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/
// is fixed.
await GetTextDocumentEdits(
textDocumentEdits, applyChangesOperation, solution, changedDocuments,
await AddTextDocumentEdits(
textDocumentEdits, applyChangesOperation, solution, changedAdditionalDocuments,
applyChangesOperation.ChangedSolution.GetAdditionalDocument, solution.GetAdditionalDocument,
cancellationToken).ConfigureAwait(false);
}
return textDocumentEdits.ToArray();
static async Task GetTextDocumentEdits<T>(
static async Task AddTextDocumentEdits<T>(
ArrayBuilder<TextDocumentEdit> textDocumentEdits,
ApplyChangesOperation applyChangesOperation,
Solution solution,
......@@ -191,67 +193,18 @@ internal class CodeActionResolveHandler : AbstractRequestHandler<LSP.VSCodeActio
{
var newDoc = getNewDocumentFunc(docId);
var oldDoc = getOldDocumentFunc(docId);
if (oldDoc == null || newDoc == null)
{
continue;
}
var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newText = await newDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);
var oldText = await oldDoc!.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newText = await newDoc!.GetTextAsync(cancellationToken).ConfigureAwait(false);
var textChanges = newText.GetTextChanges(oldText).ToList();
var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray();
var documentIdentifier = new VersionedTextDocumentIdentifier() { Uri = newDoc.GetURI() };
textDocumentEdits.Add(new TextDocumentEdit() { TextDocument = documentIdentifier, Edits = edits.ToArray() });
var documentIdentifier = new VersionedTextDocumentIdentifier { Uri = newDoc.GetURI() };
textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits.ToArray() });
}
}
}
static LSP.Command SetCommand(VSCodeAction codeAction, CodeActionResolveData data) => new LSP.Command
{
CommandIdentifier = CodeActionsHandler.RunCodeActionCommandName,
Title = codeAction.Title,
Arguments = new object[] { data }
};
}
internal static CodeAction? GetCodeActionToResolve(string distinctTitle, IEnumerable<CodeAction> codeActions)
{
// Searching for the matching code action. We compare against the distinct title (e.g. "Suppress IDExxxxNone")
// instead of the regular title (e.g. "None") since there's a chance that multiple code actions may have
// the same regular title.
CodeAction? codeActionToResolve = null;
foreach (var c in codeActions)
{
var action = CheckForMatchingAction(c, distinctTitle, currentTitle: "");
if (action != null)
{
codeActionToResolve = action;
break;
}
}
return codeActionToResolve;
}
private static CodeAction? CheckForMatchingAction(CodeAction codeAction, string goalTitle, string currentTitle)
{
if (currentTitle + codeAction.Title == goalTitle)
{
return codeAction;
}
foreach (var nestedAction in codeAction.NestedCodeActions)
{
var match = CheckForMatchingAction(nestedAction, goalTitle, currentTitle + codeAction.Title);
if (match != null)
{
return match;
}
}
return null;
}
}
}
......@@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -26,7 +25,9 @@
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
/// <summary>
/// Handles the get code actions command.
/// Handles the initial request for code actions. Leaves the Edit and Command properties
/// of the returned VSCodeActions blank, as these properties should be populated by the
/// CodeActionsResolveHandler only when the user requests them.
/// </summary>
[ExportLspMethod(LSP.Methods.TextDocumentCodeActionName), Shared]
internal class CodeActionsHandler : AbstractRequestHandler<LSP.CodeActionParams, LSP.VSCodeAction[]>
......@@ -60,19 +61,14 @@ internal class CodeActionsHandler : AbstractRequestHandler<LSP.CodeActionParams,
return Array.Empty<VSCodeAction>();
}
var (codeFixCollections, codeRefactorings) = await GetCodeFixesAndRefactoringsAsync(
document, _codeFixService, _codeRefactoringService,
request.Range, cancellationToken).ConfigureAwait(false);
var (codeFixCollections, codeRefactorings) = await CodeActionHelpers.GetCodeFixesAndRefactoringsAsync(
document, _codeFixService, _codeRefactoringService,
request.Range, cancellationToken).ConfigureAwait(false);
var codeFixes = codeFixCollections.SelectMany(c => c.Fixes);
var suppressionActions = codeFixes.Where(
a => a.Action is AbstractConfigurationActionWithNestedActions &&
(a.Action as AbstractConfigurationActionWithNestedActions)?.IsBulkConfigurationAction == false);
using var _ = ArrayBuilder<VSCodeAction>.GetInstance(out var results);
// We go through code fixes and code refactorings separately so that we can properly set the CodeActionKind.
// Go through code fixes and code refactorings separately so that we can properly set the CodeActionKind.
foreach (var codeFix in codeFixes)
{
// Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options.
......@@ -122,6 +118,14 @@ internal class CodeActionsHandler : AbstractRequestHandler<LSP.CodeActionParams,
string parentTitle = "")
{
using var _ = ArrayBuilder<VSCodeAction>.GetInstance(out var nestedActions);
// Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues.Suppress IDEXXXX.in Source'
if (parentTitle != "")
{
parentTitle += '.';
}
// Nested code actions' unique identifiers consist of: parent code action unique identifier + '.' + title of code action
foreach (var action in codeAction.NestedCodeActions)
{
nestedActions.Add(GenerateVSCodeAction(request, action, codeActionKind, codeAction.Title));
......@@ -133,51 +137,9 @@ internal class CodeActionsHandler : AbstractRequestHandler<LSP.CodeActionParams,
Kind = codeActionKind,
Diagnostics = request.Context.Diagnostics,
Children = nestedActions.ToArray(),
Data = new CodeActionResolveData
{
DistinctTitle = parentTitle + codeAction.Title,
Range = request.Range,
TextDocument = request.TextDocument
}
Data = new CodeActionResolveData(parentTitle + codeAction.Title, request.Range, request.TextDocument)
};
}
}
internal static async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(
Document? document,
ICodeFixService codeFixService,
ICodeRefactoringService codeRefactoringService,
LSP.Range selection,
CancellationToken cancellationToken)
{
if (document == null)
{
return ImmutableArray<CodeAction>.Empty;
}
var (codeFixCollections, codeRefactorings) = await GetCodeFixesAndRefactoringsAsync(
document, codeFixService,
codeRefactoringService, selection,
cancellationToken).ConfigureAwait(false);
var codeActions = codeFixCollections.SelectMany(c => c.Fixes.Select(f => f.Action)).Concat(
codeRefactorings.SelectMany(r => r.CodeActions.Select(ca => ca.action)));
return codeActions;
}
internal static async Task<(ImmutableArray<CodeFixCollection>, ImmutableArray<CodeRefactoring>)> GetCodeFixesAndRefactoringsAsync(
Document document,
ICodeFixService codeFixService,
ICodeRefactoringService codeRefactoringService,
LSP.Range selection,
CancellationToken cancellationToken)
{
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var textSpan = ProtocolConversions.RangeToTextSpan(selection, text);
var codeFixCollections = await codeFixService.GetFixesAsync(document, textSpan, true, cancellationToken).ConfigureAwait(false);
var codeRefactorings = await codeRefactoringService.GetRefactoringsAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
return (codeFixCollections, codeRefactorings);
}
}
}
......@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
......@@ -12,7 +13,6 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
......@@ -22,7 +22,11 @@
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
/// <summary>
/// Runs code actions as a command on the server.
/// Runs a code action as a command on the server.
/// This is done when a code action cannot be applied as a WorkspaceEdit on the LSP client.
/// For example, all non-ApplyChangesOperations must be applied as a command.
/// TO-DO: Currently, any ApplyChangesOperation that adds or modifies an outside document must also be
/// applied as a command due to an LSP bug (see https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1147293/).
/// Commands must be applied from the UI thread in VS.
/// </summary>
[ExportExecuteWorkspaceCommand(CodeActionsHandler.RunCodeActionCommandName)]
......@@ -54,28 +58,28 @@ internal class RunCodeActionsHandler : IExecuteWorkspaceCommandHandler
{
var runRequest = ((JToken)request.Arguments.Single()).ToObject<CodeActionResolveData>();
var document = _solutionProvider.GetDocument(runRequest.TextDocument);
var codeActions = await CodeActionsHandler.GetCodeActionsAsync(document, _codeFixService, _codeRefactoringService,
var codeActions = await CodeActionHelpers.GetCodeActionsAsync(document, _codeFixService, _codeRefactoringService,
runRequest.Range, cancellationToken).ConfigureAwait(false);
if (codeActions == null)
{
return false;
}
var actionToRun = CodeActionResolveHandler.GetCodeActionToResolve(runRequest.DistinctTitle, codeActions);
if (actionToRun != null)
var actionToRun = CodeActionHelpers.GetCodeActionToResolve(runRequest.UniqueIdentifier, codeActions.ToImmutableArray());
if (actionToRun == null)
{
foreach (var operation in await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(false))
{
// TODO - This UI thread dependency should be removed.
// https://github.com/dotnet/roslyn/projects/45#card-20619668
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
operation.Apply(document.Project.Solution.Workspace, cancellationToken);
}
return false;
}
return true;
foreach (var operation in await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(false))
{
// TODO - This UI thread dependency should be removed.
// https://github.com/dotnet/roslyn/projects/45#card-20619668
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
operation.Apply(document.Project.Solution.Workspace, cancellationToken);
}
return false;
return true;
}
}
}
......@@ -57,7 +57,7 @@ public Task<LSP.InitializeResult> HandleRequestAsync(LSP.InitializeParams reques
DocumentHighlightProvider = true,
ReferencesProvider = true,
ProjectContextProvider = true,
ExecuteCommandProvider = new ExecuteCommandOptions(),
ExecuteCommandProvider = new LSP.ExecuteCommandOptions(),
TextDocumentSync = new LSP.TextDocumentSyncOptions
{
Change = LSP.TextDocumentSyncKind.None
......
......@@ -50,21 +50,21 @@ void M()
children: Array.Empty<LSP.VSCodeAction>(),
data: CreateCodeActionResolveData(CSharpAnalyzersResources.Use_implicit_type, locations["caret"].Single()),
diagnostics: null,
edit: new LSP.WorkspaceEdit()
edit: new LSP.WorkspaceEdit
{
DocumentChanges = new TextDocumentEdit[]
{
new TextDocumentEdit()
new TextDocumentEdit
{
TextDocument = new VersionedTextDocumentIdentifier()
TextDocument = new VersionedTextDocumentIdentifier
{
Uri = locations["caret"].Single().Uri
},
Edits = new TextEdit[] {
new TextEdit()
new TextEdit
{
NewText = expectedMarkup,
Range = new LSP.Range() { Start = new Position(0, 0), End = new Position(6, 1) }
Range = new LSP.Range { Start = new Position(0, 0), End = new Position(6, 1) }
}
}
}
......@@ -116,21 +116,21 @@ void M()
FeaturesResources.Introduce_constant + string.Format(FeaturesResources.Introduce_constant_for_0, "1"),
locations["caret"].Single()),
diagnostics: null,
edit: new LSP.WorkspaceEdit()
edit: new LSP.WorkspaceEdit
{
DocumentChanges = new TextDocumentEdit[]
{
new TextDocumentEdit()
new TextDocumentEdit
{
TextDocument = new VersionedTextDocumentIdentifier()
TextDocument = new VersionedTextDocumentIdentifier
{
Uri = locations["caret"].Single().Uri
},
Edits = new TextEdit[] {
new TextEdit()
new TextEdit
{
NewText = expectedMarkup,
Range = new LSP.Range() { Start = new Position(0, 0), End = new Position(6, 1) }
Range = new LSP.Range { Start = new Position(0, 0), End = new Position(6, 1) }
}
}
}
......
......@@ -39,9 +39,9 @@ void M()
children: Array.Empty<LSP.VSCodeAction>(),
data: new CodeActionResolveData
{
DistinctTitle = CSharpAnalyzersResources.Use_implicit_type,
UniqueIdentifier = CSharpAnalyzersResources.Use_implicit_type,
Range = caretLocation.Range,
TextDocument = new TextDocumentIdentifier() { Uri = caretLocation.Uri }
TextDocument = new TextDocumentIdentifier { Uri = caretLocation.Uri }
},
diagnostics: null);
......@@ -71,15 +71,15 @@ void M()
children: Array.Empty<LSP.VSCodeAction>(),
data: new CodeActionResolveData
{
DistinctTitle = FeaturesResources.Introduce_constant + string.Format(FeaturesResources.Introduce_constant_for_0, "1"),
UniqueIdentifier = FeaturesResources.Introduce_constant + string.Format(FeaturesResources.Introduce_constant_for_0, "1"),
Range = caretLocation.Range,
TextDocument = new TextDocumentIdentifier() { Uri = caretLocation.Uri }
TextDocument = new TextDocumentIdentifier { Uri = caretLocation.Uri }
},
diagnostics: null);
var results = await RunGetCodeActionsAsync(workspace.CurrentSolution, caretLocation);
var introduceConstant = results[0].Children.FirstOrDefault(
r => ((CodeActionResolveData)r.Data).DistinctTitle == FeaturesResources.Introduce_constant
r => ((CodeActionResolveData)r.Data).UniqueIdentifier == FeaturesResources.Introduce_constant
+ string.Format(FeaturesResources.Introduce_constant_for_0, "1"));
AssertJsonEquals(expected, introduceConstant);
......@@ -97,11 +97,11 @@ void M()
}
internal static LSP.CodeActionParams CreateCodeActionParams(LSP.Location caret)
=> new LSP.CodeActionParams()
=> new LSP.CodeActionParams
{
TextDocument = CreateTextDocumentIdentifier(caret.Uri),
Range = caret.Range,
Context = new LSP.CodeActionContext()
Context = new LSP.CodeActionContext
{
// TODO - Code actions should respect context.
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册