diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCodeActions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCodeActions.java index 7a6e199c3..5de8e9782 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCodeActions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCodeActions.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 Angelo ZERR + * Copyright (c) 2018, 2023 Angelo ZERR * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -13,10 +13,12 @@ package org.eclipse.lemminx.services; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CancellationException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; @@ -27,6 +29,7 @@ import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -39,6 +42,8 @@ public class XMLCodeActions { private static final Logger LOGGER = Logger.getLogger(XMLCompletions.class.getName()); private final XMLExtensionsRegistry extensionsRegistry; + + public static String NO_ERROR = "NoError"; public XMLCodeActions(XMLExtensionsRegistry extensionsRegistry) { this.extensionsRegistry = extensionsRegistry; @@ -46,30 +51,47 @@ public XMLCodeActions(XMLExtensionsRegistry extensionsRegistry) { public List doCodeActions(CodeActionContext context, Range range, DOMDocument document, SharedSettings sharedSettings, CancelChecker cancelChecker) { - cancelChecker.checkCanceled(); + try { + cancelChecker.checkCanceled(); + + List codeActions = new ArrayList<>(); + List diagnostics = context.getDiagnostics(); + + // Add a "No Error" diagnostic for two reasons: + // 1) In order to allow providing Code Actions for document offsets where no Error/sWarnings/Info + // diagnostic code exist + // 2) In order to prevent duplication of Code Actions that do not require a certain diagnostic code. + // All code action participants that do not require a certain diagnostics node should react only to + // "NoError" diagnostic code - thus preventing the creation of duplicated code actions.. + diagnostics = diagnostics != null ? diagnostics.stream().collect(Collectors.toList()) : new ArrayList<>(); + Diagnostic noErrorDiagnostic = new Diagnostic(range, "No Error", DiagnosticSeverity.Hint, null, NO_ERROR); + if(!diagnostics.contains(noErrorDiagnostic)) { + diagnostics.add(noErrorDiagnostic); + } - List codeActions = new ArrayList<>(); - List diagnostics = context.getDiagnostics(); - if (diagnostics != null) { for (Diagnostic diagnostic : context.getDiagnostics()) { for (ICodeActionParticipant codeActionParticipant : extensionsRegistry.getCodeActionsParticipants()) { + cancelChecker.checkCanceled(); try { - cancelChecker.checkCanceled(); CodeActionRequest request = new CodeActionRequest(diagnostic, range, document, extensionsRegistry, sharedSettings); codeActionParticipant.doCodeAction(request, codeActions, cancelChecker); } catch (CancellationException e) { - throw e; + LOGGER.log(Level.FINER, e.getMessage(), e); + return Collections.emptyList(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error while processing code actions for the participant '" + codeActionParticipant.getClass().getName() + "'.", e); } } } + + cancelChecker.checkCanceled(); + return codeActions; + } catch (CancellationException e) { + LOGGER.log(Level.FINER, e.getMessage(), e); + return Collections.emptyList(); } - - cancelChecker.checkCanceled(); - return codeActions; } public CodeAction resolveCodeAction(CodeAction unresolved, DOMDocument document, SharedSettings sharedSettings, @@ -80,20 +102,25 @@ public CodeAction resolveCodeAction(CodeAction unresolved, DOMDocument document, if (StringUtils.isEmpty(participantId)) { return null; } - for (ICodeActionParticipant codeActionParticipant : extensionsRegistry.getCodeActionsParticipants()) { - try { - cancelChecker.checkCanceled(); - ICodeActionResolvesParticipant resolveCodeActionParticipant = codeActionParticipant - .getResolveCodeActionParticipant(participantId); - if (resolveCodeActionParticipant != null) { - return resolveCodeActionParticipant.resolveCodeAction(request, cancelChecker); + try { + for (ICodeActionParticipant codeActionParticipant : extensionsRegistry.getCodeActionsParticipants()) { + try { + cancelChecker.checkCanceled(); + ICodeActionResolvesParticipant resolveCodeActionParticipant = codeActionParticipant + .getResolveCodeActionParticipant(participantId); + if (resolveCodeActionParticipant != null) { + return resolveCodeActionParticipant.resolveCodeAction(request, cancelChecker); + } + } catch (CancellationException e) { + LOGGER.log(Level.FINER, e.getMessage(), e); + return null; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while processing resolve code action for the participant '" + + codeActionParticipant.getClass().getName() + "'.", e); } - } catch (CancellationException e) { - throw e; - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Error while processing resolve code action for the participant '" - + codeActionParticipant.getClass().getName() + "'.", e); } + } catch (CancellationException e) { + LOGGER.log(Level.FINER, e.getMessage(), e); } return null; }