From d5608e19ce45d936584cf6a5b6490dfe104ae977 Mon Sep 17 00:00:00 2001 From: azerr Date: Mon, 26 Dec 2022 13:36:50 +0100 Subject: [PATCH] Support for textDocument/documentColor Fixes #639 Signed-off-by: azerr --- .../lemminx/XMLTextDocumentService.java | 18 + .../extensions/colors/XMLColorsPlugin.java | 98 +++++ .../XMLDocumentColorParticipant.java | 177 +++++++++ .../colors/settings/XMLColorExpression.java | 53 +++ .../extensions/colors/settings/XMLColors.java | 46 +++ .../colors/settings/XMLColorsSettings.java | 64 +++ .../extensions/colors/utils/ColorUtils.java | 374 ++++++++++++++++++ .../lemminx/services/XMLDocumentColor.java | 83 ++++ .../lemminx/services/XMLLanguageService.java | 14 + .../extensions/IDocumentColorParticipant.java | 54 +++ .../extensions/XMLExtensionsRegistry.java | 15 + .../ClientCapabilitiesWrapper.java | 4 + .../ServerCapabilitiesConstants.java | 4 + .../ServerCapabilitiesInitializer.java | 1 + .../capabilities/XMLCapabilityManager.java | 6 + .../lemminx/utils/XMLPositionUtility.java | 6 +- .../META-INF/native-image/reflect-config.json | 56 +++ ....lemminx.services.extensions.IXMLExtension | 3 +- .../java/org/eclipse/lemminx/XMLAssert.java | 68 ++++ .../colors/XMLColorsExtensionsTest.java | 134 +++++++ 20 files changed, 1274 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/XMLColorsPlugin.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/participants/XMLDocumentColorParticipant.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorExpression.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColors.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorsSettings.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/utils/ColorUtils.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLDocumentColor.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IDocumentColorParticipant.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/colors/XMLColorsExtensionsTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index 258a8f702..7e4db3f20 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -58,6 +58,9 @@ import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.CodeLensParams; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; @@ -69,6 +72,7 @@ import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams; import org.eclipse.lsp4j.DidSaveTextDocumentParams; +import org.eclipse.lsp4j.DocumentColorParams; import org.eclipse.lsp4j.DocumentFormattingParams; import org.eclipse.lsp4j.DocumentHighlight; import org.eclipse.lsp4j.DocumentHighlightParams; @@ -553,6 +557,20 @@ public CompletableFuture linkedEditingRange(LinkedEditingRa }); } + @Override + public CompletableFuture> documentColor(DocumentColorParams params) { + return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { + return getXMLLanguageService().findDocumentColors(xmlDocument, cancelChecker); + }); + } + + @Override + public CompletableFuture> colorPresentation(ColorPresentationParams params) { + return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { + return getXMLLanguageService().getColorPresentations(xmlDocument, params, cancelChecker); + }); + } + @Override public void didSave(DidSaveTextDocumentParams params) { computeAsync((monitor) -> { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/XMLColorsPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/XMLColorsPlugin.java new file mode 100644 index 000000000..7fdb294a1 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/XMLColorsPlugin.java @@ -0,0 +1,98 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors; + +import org.eclipse.lemminx.extensions.colors.participants.XMLDocumentColorParticipant; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings; +import org.eclipse.lemminx.services.extensions.IDocumentColorParticipant; +import org.eclipse.lemminx.services.extensions.IXMLExtension; +import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.save.ISaveContext; +import org.eclipse.lsp4j.InitializeParams; + +/** + * XML colors plugin. + * + * This plugin provides the capability to support color with 'xml.colors' + * settings. + * + * + "xml.colors": [ + // XML colors applied for text node for android colors.xml files + { + "pattern": "/res/values/colors.xml", + "expressions": [ + { + "path": "resources/color/text()" + } + ] + }, + // XML colors applied for @color attribute for another files + { + "pattern": "/my-colors.xml", + "expressions": [ + { + "path": "@color" + } + ] + } +] + * + * + * + * + * @author Angelo ZERR + * + */ +public class XMLColorsPlugin implements IXMLExtension { + + private final IDocumentColorParticipant documentColorParticipant; + + private XMLColorsSettings colorsSettings; + + public XMLColorsPlugin() { + documentColorParticipant = new XMLDocumentColorParticipant(this); + } + + @Override + public void doSave(ISaveContext context) { + if (context.getType() != ISaveContext.SaveContextType.DOCUMENT) { + // Settings + updateSettings(context); + } + } + + private void updateSettings(ISaveContext saveContext) { + Object initializationOptionsSettings = saveContext.getSettings(); + XMLColorsSettings referencesSettings = XMLColorsSettings + .getXMLColorsSettings(initializationOptionsSettings); + updateSettings(referencesSettings, saveContext); + } + + private void updateSettings(XMLColorsSettings settings, ISaveContext context) { + this.colorsSettings = settings; + } + + @Override + public void start(InitializeParams params, XMLExtensionsRegistry registry) { + registry.registerDocumentColorParticipant(documentColorParticipant); + } + + @Override + public void stop(XMLExtensionsRegistry registry) { + registry.unregisterDocumentColorParticipant(documentColorParticipant); + } + + public XMLColorsSettings getColorsSettings() { + return colorsSettings; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/participants/XMLDocumentColorParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/participants/XMLDocumentColorParticipant.java new file mode 100644 index 000000000..bf34f3348 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/participants/XMLDocumentColorParticipant.java @@ -0,0 +1,177 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors.participants; + +import static org.eclipse.lemminx.extensions.colors.utils.ColorUtils.getColorValue; +import static org.eclipse.lemminx.extensions.colors.utils.ColorUtils.toHexa; +import static org.eclipse.lemminx.extensions.colors.utils.ColorUtils.toRGB; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.DOMText; +import org.eclipse.lemminx.extensions.colors.XMLColorsPlugin; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorExpression; +import org.eclipse.lemminx.extensions.colors.settings.XMLColors; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings; +import org.eclipse.lemminx.services.extensions.IDocumentColorParticipant; +import org.eclipse.lemminx.utils.XMLPositionUtility; +import org.eclipse.lsp4j.Color; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * XML document color particpant based on the {@link XMLColorsSettings}. + * + * @author Angelo ZERR + * + */ +public class XMLDocumentColorParticipant implements IDocumentColorParticipant { + + private final XMLColorsPlugin xmlColorsPlugin; + + public XMLDocumentColorParticipant(XMLColorsPlugin xmlColorsPlugin) { + this.xmlColorsPlugin = xmlColorsPlugin; + } + + @Override + public void doDocumentColor(DOMDocument xmlDocument, List colors, CancelChecker cancelChecker) { + List expressions = findColorExpression(xmlDocument); + if (expressions.isEmpty()) { + return; + } + doDocumentColor(xmlDocument, expressions, colors, cancelChecker); + } + + private void doDocumentColor(DOMNode node, List expressions, List colors, + CancelChecker cancelChecker) { + if (node.isElement()) { + DOMElement element = (DOMElement) node; + if (element.hasAttributes()) { + List attributes = element.getAttributeNodes(); + for (DOMAttr attr : attributes) { + if (isColorNode(attr, expressions)) { + // The current attribute node matches an XML color expression declared in the + // "xml/colors" + // settings + // ex : + // - xpath="foo/@color" + Color color = getColorValue(attr.getValue()); + if (color != null) { + Range range = XMLPositionUtility.selectAttributeValue(attr, true); + ColorInformation colorInformation = new ColorInformation(range, color); + colors.add(colorInformation); + } + } + } + } + } else if (node.isText()) { + if (isColorNode(node, expressions)) { + // The current text node matches an XML color expression declared in the + // "xml/colors" + // settings + // ex : + // - xpath="resources/color/text()" + DOMText text = (DOMText) node; + Color color = getColorValue(text.getData()); + if (color != null) { + Range range = XMLPositionUtility.selectText(text); + ColorInformation colorInformation = new ColorInformation(range, color); + colors.add(colorInformation); + } + } + } + List children = node.getChildren(); + for (DOMNode child : children) { + cancelChecker.checkCanceled(); + doDocumentColor(child, expressions, colors, cancelChecker); + } + } + + @Override + public void doColorPresentations(DOMDocument xmlDocument, ColorPresentationParams params, + List presentations, CancelChecker cancelChecker) { + Color color = params.getColor(); + Range replace = params.getRange(); + // RGB color presentation + presentations.add(toRGB(color, replace)); + // Hexa color presentation + presentations.add(toHexa(color, replace)); + } + + /** + * Returns true if the given node>code> matches an XML color expression + * and false otherwise. + * + * @param node the node to match. + * @param expressions XML color expressions. + * + * @return true if the given node>code> matches an XML color expression + * and false otherwise. + */ + private static boolean isColorNode(DOMNode node, List expressions) { + if (node.isAttribute()) { + DOMAttr attr = (DOMAttr) node; + if (attr.getValue() == null && attr.getValue().isEmpty()) { + return false; + } + } else if (node.isText()) { + DOMText text = (DOMText) node; + if (!text.hasData()) { + return false; + } + } + for (XMLColorExpression expression : expressions) { + if (expression.match(node)) { + return true; + } + } + return false; + } + + /** + * Return the list of {@link XMLColorExpression} for the given document and an + * empty list otherwise. + * + * @param xmlDocument the DOM document + * + * @return the list of {@link XMLColorExpression} for the given document and an + * empty list otherwise. + */ + private List findColorExpression(DOMDocument xmlDocument) { + XMLColorsSettings settings = xmlColorsPlugin.getColorsSettings(); + if (settings == null) { + return Collections.emptyList(); + } + + List colorsDef = settings.getColors(); + if (colorsDef == null) { + return Collections.emptyList(); + } + List expressions = new ArrayList<>(); + for (XMLColors xmlColors : colorsDef) { + if (xmlColors.matches(xmlDocument.getDocumentURI())) { + expressions.addAll(xmlColors.getExpressions()); + } + } + return expressions; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorExpression.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorExpression.java new file mode 100644 index 000000000..db43bf772 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorExpression.java @@ -0,0 +1,53 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors.settings; + +import org.eclipse.lemminx.xpath.matcher.XPathMatcher; +import org.w3c.dom.Node; + +/** + * XML colors expression + * + * + * { + "xpath": "@color" + } + * + * + * @author Angelo ZERR + * + */ +public class XMLColorExpression { + + private transient XPathMatcher pathMatcher; + + private String xpath; + + public String getXPath() { + return xpath; + } + + public void setXPath(String xpath) { + this.xpath = xpath; + } + + public boolean match(final Node node) { + if (xpath == null) { + return false; + } + if (pathMatcher == null) { + pathMatcher = new XPathMatcher(xpath); + } + return pathMatcher.match(node); + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColors.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColors.java new file mode 100644 index 000000000..1bf2b01ab --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColors.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors.settings; + +import java.util.List; + +import org.eclipse.lemminx.settings.PathPatternMatcher; + +/** + * XML colors which stores list of {@link XMLColorExpression} applied + * for a give pattern. + * + * @author Angelo ZERR + * + */ +public class XMLColors extends PathPatternMatcher { + + private List expressions; + + /** + * Returns list of XML color expressions. + * + * @return list of XML color expressions. + */ + public List getExpressions() { + return expressions; + } + + /** + * Set list of XML color expressions. + * + * @param expressions list of XML color expressions. + */ + public void setExpressions(List expressions) { + this.expressions = expressions; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorsSettings.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorsSettings.java new file mode 100644 index 000000000..18ff43637 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/settings/XMLColorsSettings.java @@ -0,0 +1,64 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors.settings; + +import java.util.List; + +import org.eclipse.lemminx.utils.JSONUtility; + +/** + * XML colors settings: + * + * + "xml.colors": [ + // XML colors applied for text node for android colors.xml files + { + "pattern": "/res/values/colors.xml", + "expressions": [ + { + "xpath": "resources/color/text()" + } + ] + }, + // XML colors applied for @color attribute for another files + { + "pattern": "/my-colors.xml", + "expressions": [ + { + "xpath": "@color" + } + ] + } +] + * + * + * + * @author Angelo ZERR + * + */ +public class XMLColorsSettings { + + private List colors; + + public List getColors() { + return colors; + } + + public void setColors(List colors) { + this.colors = colors; + } + + public static XMLColorsSettings getXMLColorsSettings(Object initializationOptionsSettings) { + return JSONUtility.toModel(initializationOptionsSettings, XMLColorsSettings.class); + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/utils/ColorUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/utils/ColorUtils.java new file mode 100644 index 000000000..7fec85f9a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/colors/utils/ColorUtils.java @@ -0,0 +1,374 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors.utils; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.lsp4j.Color; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; + +/** + * Color utilities. + * + * Some piece of code comes the vscode CSS language server written in TypeScript + * which has been translated to Java. + * + * @see https://github.com/microsoft/vscode-css-languageservice/blob/main/src/languageFacts/colors.ts + */ +public class ColorUtils { + + private static final int Digit0 = 48; + private static final int Digit9 = 57; + private static final int A = 65; + private static final int a = 97; + private static final int f = 102; + + private static final Map colors = new HashMap<>(); + + static { + colors.put("aliceblue", "#f0f8ff"); + colors.put("antiquewhite", "#faebd7"); + colors.put("aqua", "#00ffff"); + colors.put("aquamarine", "#7fffd4"); + colors.put("azure", "#f0ffff"); + colors.put("beige", "#f5f5dc"); + colors.put("bisque", "#ffe4c4"); + colors.put("black", "#000000"); + colors.put("blanchedalmond", "#ffebcd"); + colors.put("blue", "#0000ff"); + colors.put("blueviolet", "#8a2be2"); + colors.put("brown", "#a52a2a"); + colors.put("burlywood", "#deb887"); + colors.put("cadetblue", "#5f9ea0"); + colors.put("chartreuse", "#7fff00"); + colors.put("chocolate", "#d2691e"); + colors.put("coral", "#ff7f50"); + colors.put("cornflowerblue", "#6495ed"); + colors.put("cornsilk", "#fff8dc"); + colors.put("crimson", "#dc143c"); + colors.put("cyan", "#00ffff"); + colors.put("darkblue", "#00008b"); + colors.put("darkcyan", "#008b8b"); + colors.put("darkgoldenrod", "#b8860b"); + colors.put("darkgray", "#a9a9a9"); + colors.put("darkgrey", "#a9a9a9"); + colors.put("darkgreen", "#006400"); + colors.put("darkkhaki", "#bdb76b"); + colors.put("darkmagenta", "#8b008b"); + colors.put("darkolivegreen", "#556b2f"); + colors.put("darkorange", "#ff8c00"); + colors.put("darkorchid", "#9932cc"); + colors.put("darkred", "#8b0000"); + colors.put("darksalmon", "#e9967a"); + colors.put("darkseagreen", "#8fbc8f"); + colors.put("darkslateblue", "#483d8b"); + colors.put("darkslategray", "#2f4f4f"); + colors.put("darkslategrey", "#2f4f4f"); + colors.put("darkturquoise", "#00ced1"); + colors.put("darkviolet", "#9400d3"); + colors.put("deeppink", "#ff1493"); + colors.put("deepskyblue", "#00bfff"); + colors.put("dimgray", "#696969"); + colors.put("dimgrey", "#696969"); + colors.put("dodgerblue", "#1e90ff"); + colors.put("firebrick", "#b22222"); + colors.put("floralwhite", "#fffaf0"); + colors.put("forestgreen", "#228b22"); + colors.put("fuchsia", "#ff00ff"); + colors.put("gainsboro", "#dcdcdc"); + colors.put("ghostwhite", "#f8f8ff"); + colors.put("gold", "#ffd700"); + colors.put("goldenrod", "#daa520"); + colors.put("gray", "#808080"); + colors.put("grey", "#808080"); + colors.put("green", "#008000"); + colors.put("greenyellow", "#adff2f"); + colors.put("honeydew", "#f0fff0"); + colors.put("hotpink", "#ff69b4"); + colors.put("indianred", "#cd5c5c"); + colors.put("indigo", "#4b0082"); + colors.put("ivory", "#fffff0"); + colors.put("khaki", "#f0e68c"); + colors.put("lavender", "#e6e6fa"); + colors.put("lavenderblush", "#fff0f5"); + colors.put("lawngreen", "#7cfc00"); + colors.put("lemonchiffon", "#fffacd"); + colors.put("lightblue", "#add8e6"); + colors.put("lightcoral", "#f08080"); + colors.put("lightcyan", "#e0ffff"); + colors.put("lightgoldenrodyellow", "#fafad2"); + colors.put("lightgray", "#d3d3d3"); + colors.put("lightgrey", "#d3d3d3"); + colors.put("lightgreen", "#90ee90"); + colors.put("lightpink", "#ffb6c1"); + colors.put("lightsalmon", "#ffa07a"); + colors.put("lightseagreen", "#20b2aa"); + colors.put("lightskyblue", "#87cefa"); + colors.put("lightslategray", "#778899"); + colors.put("lightslategrey", "#778899"); + colors.put("lightsteelblue", "#b0c4de"); + colors.put("lightyellow", "#ffffe0"); + colors.put("lime", "#00ff00"); + colors.put("limegreen", "#32cd32"); + colors.put("linen", "#faf0e6"); + colors.put("magenta", "#ff00ff"); + colors.put("maroon", "#800000"); + colors.put("mediumaquamarine", "#66cdaa"); + colors.put("mediumblue", "#0000cd"); + colors.put("mediumorchid", "#ba55d3"); + colors.put("mediumpurple", "#9370d8"); + colors.put("mediumseagreen", "#3cb371"); + colors.put("mediumslateblue", "#7b68ee"); + colors.put("mediumspringgreen", "#00fa9a"); + colors.put("mediumturquoise", "#48d1cc"); + colors.put("mediumvioletred", "#c71585"); + colors.put("midnightblue", "#191970"); + colors.put("mintcream", "#f5fffa"); + colors.put("mistyrose", "#ffe4e1"); + colors.put("moccasin", "#ffe4b5"); + colors.put("navajowhite", "#ffdead"); + colors.put("navy", "#000080"); + colors.put("oldlace", "#fdf5e6"); + colors.put("olive", "#808000"); + colors.put("olivedrab", "#6b8e23"); + colors.put("orange", "#ffa500"); + colors.put("orangered", "#ff4500"); + colors.put("orchid", "#da70d6"); + colors.put("palegoldenrod", "#eee8aa"); + colors.put("palegreen", "#98fb98"); + colors.put("paleturquoise", "#afeeee"); + colors.put("palevioletred", "#d87093"); + colors.put("papayawhip", "#ffefd5"); + colors.put("peachpuff", "#ffdab9"); + colors.put("peru", "#cd853f"); + colors.put("pink", "#ffc0cb"); + colors.put("plum", "#dda0dd"); + colors.put("powderblue", "#b0e0e6"); + colors.put("purple", "#800080"); + colors.put("red", "#ff0000"); + colors.put("rebeccapurple", "#663399"); + colors.put("rosybrown", "#bc8f8f"); + colors.put("royalblue", "#4169e1"); + colors.put("saddlebrown", "#8b4513"); + colors.put("salmon", "#fa8072"); + colors.put("sandybrown", "#f4a460"); + colors.put("seagreen", "#2e8b57"); + colors.put("seashell", "#fff5ee"); + colors.put("sienna", "#a0522d"); + colors.put("silver", "#c0c0c0"); + colors.put("skyblue", "#87ceeb"); + colors.put("slateblue", "#6a5acd"); + colors.put("slategray", "#708090"); + colors.put("slategrey", "#708090"); + colors.put("snow", "#fffafa"); + colors.put("springgreen", "#00ff7f"); + colors.put("steelblue", "#4682b4"); + colors.put("tan", "#d2b48c"); + colors.put("teal", "#008080"); + colors.put("thistle", "#d8bfd8"); + colors.put("tomato", "#ff6347"); + colors.put("turquoise", "#40e0d0"); + colors.put("violet", "#ee82ee"); + colors.put("wheat", "#f5deb3"); + colors.put("white", "#ffffff"); + colors.put("whitesmoke", "#f5f5f5"); + colors.put("yellow", "#ffff00"); + colors.put("yellowgreen", "#9acd32"); + } + + /** + * Returns the {@link Color} instance value from the given text and + * null otherwise. + * + * @param text the color text. + * + * @return the {@link Color} instance value from the given text and + * null otherwise. + */ + public static Color getColorValue(String text) { + String candidateColor = colors.get(text); + if (candidateColor != null) { + return colorFromHex(candidateColor.toLowerCase()); + } + Color color = colorFromHex(text); + if (color != null) { + return color; + } + int startComma = text.indexOf('('); + if (startComma == -1) { + return null; + } + int endComma = text.indexOf(')'); + if (endComma == -1 || endComma < startComma) { + return null; + } + String name = text.substring(0, startComma); + if (name.isEmpty()) { + return null; + } + String[] colorValues = text.substring(startComma + 1, endComma).split(","); + if (colorValues.length < 3 || colorValues.length > 4) { + return null; + } + + try { + double alpha = colorValues.length == 4 ? getNumericValue(colorValues[3], 1) : 1; + if (name.equals("rgb") || name.equals("rgba")) { + double red = getNumericValue(colorValues[0], 255.0); + double green = getNumericValue(colorValues[1], 255.0); + double blue = getNumericValue(colorValues[2], 255.0); + return new Color(red, green, blue, alpha); + } + if (text.startsWith("rgb(") || text.startsWith("rgba(")) { + + } + } catch (Exception e) { + return null; + } + return null; + } + + private static double getNumericValue(String value, double factor) { + double result = Double.parseDouble(value); + return result / factor; + } + + /** + * Returns the RGB color presentation of the given color and + * replace range. + * + * @param color the color. + * @param replace the replace range. + * + * @return the RGB color presentation of the given color and + * range. + */ + public static ColorPresentation toRGB(Color color, Range replace) { + int red256 = (int) Math.round(color.getRed() * 255); + int green256 = (int) Math.round(color.getGreen() * 255); + int blue256 = (int) Math.round(color.getBlue() * 255); + int alpha = (int) color.getAlpha(); + + String label = getRGB(red256, green256, blue256, alpha == 1 ? null : alpha); + TextEdit textEdit = new TextEdit(replace, label); + return new ColorPresentation(label, textEdit); + } + + private static String getRGB(int red256, int green256, int blue256, Integer alpha) { + StringBuilder label = new StringBuilder("rgb("); + label.append(red256); + label.append(","); + label.append(green256); + label.append(","); + label.append(blue256); + if (alpha != null) { + label.append(","); + label.append(alpha); + } + label.append(")"); + return label.toString(); + } + + /** + * Returns the Hexa color presentation of the given color and + * replace range. + * + * @param color the color. + * @param replace the replace range. + * + * @return the Hexa color presentation of the given color and + * range. + */ + public static ColorPresentation toHexa(Color color, Range replace) { + double red256 = Math.round(color.getRed() * 255); + double green256 = Math.round(color.getGreen() * 255); + double blue256 = Math.round(color.getBlue() * 255); + double alpha = color.getAlpha(); + + String label = getHexa(red256, green256, blue256, alpha == 1 ? null : alpha); + TextEdit textEdit = new TextEdit(replace, label); + return new ColorPresentation(label, textEdit); + } + + private static String getHexa(double red256, double green256, double blue256, Double alpha) { + StringBuilder label = new StringBuilder("#"); + label.append(toTwoDigitHex(red256)); + label.append(toTwoDigitHex(green256)); + label.append(toTwoDigitHex(blue256)); + if (alpha != null) { + label.append(toTwoDigitHex(Math.round(alpha * 255))); + } + return label.toString(); + } + + private static String toTwoDigitHex(double n) { + String r = Integer.toHexString((int) n); + return r.length() != 2 ? '0' + r : r; + } + + public static int hexDigit(int charCode) { + if (charCode < Digit0) { + return 0; + } + if (charCode <= Digit9) { + return charCode - Digit0; + } + if (charCode < a) { + charCode += (a - A); + } + if (charCode >= a && charCode <= f) { + return charCode - a + 10; + } + return 0; + } + + private static Color colorFromHex(String text) { + if (text.isEmpty() || text.charAt(0) != '#') { + return null; + } + switch (text.length()) { + case 4: { + double red = (hexDigit(text.codePointAt(1)) * 0x11) / 255.0; + double green = (hexDigit(text.codePointAt(2)) * 0x11) / 255.0; + double blue = (hexDigit(text.codePointAt(3)) * 0x11) / 255.0; + double alpha = 1; + return new Color(red, green, blue, alpha); + } + case 5: { + double red = (hexDigit(text.codePointAt(1)) * 0x11) / 255.0; + double green = (hexDigit(text.codePointAt(2)) * 0x11) / 255.0; + double blue = (hexDigit(text.codePointAt(3)) * 0x11) / 255.0; + double alpha = (hexDigit(text.codePointAt(4)) * 0x11) / 255.0; + return new Color(red, green, blue, alpha); + } + case 7: { + double red = (hexDigit(text.codePointAt(1)) * 0x10 + hexDigit(text.codePointAt(2))) / 255.0; + double green = (hexDigit(text.codePointAt(3)) * 0x10 + hexDigit(text.codePointAt(4))) / 255.0; + double blue = (hexDigit(text.codePointAt(5)) * 0x10 + hexDigit(text.codePointAt(6))) / 255.0; + double alpha = 1; + return new Color(red, green, blue, alpha); + } + case 9: { + double red = (hexDigit(text.codePointAt(1)) * 0x10 + hexDigit(text.codePointAt(2))) / 255.0; + double green = (hexDigit(text.codePointAt(3)) * 0x10 + hexDigit(text.codePointAt(4))) / 255.0; + double blue = (hexDigit(text.codePointAt(5)) * 0x10 + hexDigit(text.codePointAt(6))) / 255.0; + double alpha = (hexDigit(text.codePointAt(7)) * 0x10 + hexDigit(text.codePointAt(8))) / 255.0; + return new Color(red, green, blue, alpha); + } + } + return null; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLDocumentColor.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLDocumentColor.java new file mode 100644 index 000000000..15cb7641b --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLDocumentColor.java @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.extensions.IDocumentColorParticipant; +import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * XML document color support. + * + */ +class XMLDocumentColor { + + private static final Logger LOGGER = Logger.getLogger(XMLDocumentColor.class.getName()); + + private final XMLExtensionsRegistry extensionsRegistry; + + public XMLDocumentColor(XMLExtensionsRegistry extensionsRegistry) { + this.extensionsRegistry = extensionsRegistry; + } + + public List findDocumentColors(DOMDocument xmlDocument, CancelChecker cancelChecker) { + cancelChecker.checkCanceled(); + + List colors = new ArrayList<>(); + for (IDocumentColorParticipant participant : extensionsRegistry.getDocumentColorParticipants()) { + try { + participant.doDocumentColor(xmlDocument, colors, cancelChecker); + } catch (CancellationException e) { + throw e; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while processing document color for the participant '" + + participant.getClass().getName() + "'.", e); + } + } + + cancelChecker.checkCanceled(); + + return colors; + } + + public List getColorPresentations(DOMDocument xmlDocument, ColorPresentationParams params, + CancelChecker cancelChecker) { + cancelChecker.checkCanceled(); + + List presentations = new ArrayList<>(); + for (IDocumentColorParticipant participant : extensionsRegistry.getDocumentColorParticipants()) { + try { + participant.doColorPresentations(xmlDocument, params, presentations, cancelChecker); + } catch (CancellationException e) { + throw e; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while processing color presentation for the participant '" + + participant.getClass().getName() + "'.", e); + } + } + + cancelChecker.checkCanceled(); + + return presentations; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java index d5ea7376c..93042080c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java @@ -37,6 +37,9 @@ import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.Diagnostic; @@ -88,6 +91,7 @@ public void checkCanceled() { private final XMLRename rename; private final XMLSelectionRanges selectionRanges; private final XMLLinkedEditing linkedEditing; + private final XMLDocumentColor documentColor; public XMLLanguageService() { this.formatter = new XMLFormatter(this); @@ -98,6 +102,7 @@ public XMLLanguageService() { this.diagnostics = new XMLDiagnostics(this); this.foldings = new XMLFoldings(this); this.documentLink = new XMLDocumentLink(this); + this.documentColor = new XMLDocumentColor(this); this.definition = new XMLDefinition(this); this.typeDefinition = new XMLTypeDefinition(this); this.reference = new XMLReference(this); @@ -235,6 +240,15 @@ public List findDocumentLinks(DOMDocument document) { return documentLink.findDocumentLinks(document); } + public List findDocumentColors(DOMDocument xmlDocument, CancelChecker cancelChecker) { + return documentColor.findDocumentColors(xmlDocument, cancelChecker); + } + + public List getColorPresentations(DOMDocument xmlDocument, ColorPresentationParams params, + CancelChecker cancelChecker) { + return documentColor.getColorPresentations(xmlDocument, params, cancelChecker); + } + public List findDefinition(DOMDocument xmlDocument, Position position, CancelChecker cancelChecker) { return definition.findDefinition(xmlDocument, position, cancelChecker); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IDocumentColorParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IDocumentColorParticipant.java new file mode 100644 index 000000000..755011a36 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IDocumentColorParticipant.java @@ -0,0 +1,54 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.extensions; + +import java.util.List; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * Document color participant API. + * + * @author Angelo ZERR + * + */ +public interface IDocumentColorParticipant { + + /** + * Fill the given the colors list of {@link ColorInformation} for + * the given DOM document xmlDocument. + * + * @param xmlDocument the DOM document. + * @param colors the colors list to update. + * @param cancelChecker the cancel checker. + */ + void doDocumentColor(DOMDocument xmlDocument, List colors, CancelChecker cancelChecker); + + /** + * Fill the given the presentations list of + * {@link ColorPresentation} for the given DOM document xmlDocument + * and the given params colors presentation parameter. + * + * @param xmlDocument the DOM document. + * @param params the color presentation parameter. + * @param presentations the presentations list to update. + * @param cancelChecker the cancel checker. + */ + void doColorPresentations(DOMDocument xmlDocument, ColorPresentationParams params, + List presentations, + CancelChecker cancelChecker); + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java index c61aa6c23..ea4c93b57 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java @@ -53,6 +53,7 @@ public class XMLExtensionsRegistry implements IComponentProvider { private final List diagnosticsParticipants; private final List codeActionsParticipants; private final List documentLinkParticipants; + private final List documentColorParticipants; private final List definitionParticipants; private final List typeDefinitionParticipants; private final List referenceParticipants; @@ -86,6 +87,7 @@ public XMLExtensionsRegistry() { diagnosticsParticipants = new ArrayList<>(); codeActionsParticipants = new ArrayList<>(); documentLinkParticipants = new ArrayList<>(); + documentColorParticipants = new ArrayList<>(); definitionParticipants = new ArrayList<>(); typeDefinitionParticipants = new ArrayList<>(); referenceParticipants = new ArrayList<>(); @@ -168,6 +170,11 @@ public Collection getDocumentLinkParticipants() { return documentLinkParticipants; } + public Collection getDocumentColorParticipants() { + initializeIfNeeded(); + return documentColorParticipants; + } + public Collection getDefinitionParticipants() { initializeIfNeeded(); return definitionParticipants; @@ -331,6 +338,14 @@ public void unregisterDocumentLinkParticipant(IDocumentLinkParticipant documentL documentLinkParticipants.remove(documentLinkParticipant); } + public void registerDocumentColorParticipant(IDocumentColorParticipant documentColorParticipant) { + documentColorParticipants.add(documentColorParticipant); + } + + public void unregisterDocumentColorParticipant(IDocumentColorParticipant documentColorParticipant) { + documentColorParticipants.remove(documentColorParticipant); + } + public void registerDefinitionParticipant(IDefinitionParticipant definitionParticipant) { definitionParticipants.add(definitionParticipant); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java index 06eb94b72..9b5f404ec 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java @@ -55,6 +55,10 @@ public boolean isLinkDynamicRegistrationSupported() { return v3Supported && isDynamicRegistrationSupported(getTextDocument().getDocumentLink()); } + public boolean isColorDynamicRegistrationSupported() { + return v3Supported && isDynamicRegistrationSupported(getTextDocument().getColorProvider()); + } + public boolean isRangeFoldingDynamicRegistrationSupported() { return v3Supported && isDynamicRegistrationSupported(getTextDocument().getFoldingRange()); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java index 21a8c78d0..332720ab2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java @@ -17,6 +17,7 @@ import org.eclipse.lsp4j.CodeActionOptions; import org.eclipse.lsp4j.CodeLensOptions; +import org.eclipse.lsp4j.ColorProviderOptions; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.DocumentLinkOptions; import org.eclipse.lsp4j.TextDocumentSyncKind; @@ -38,6 +39,7 @@ private ServerCapabilitiesConstants() { public static final String TEXT_DOCUMENT_COMPLETION = "textDocument/completion"; public static final String TEXT_DOCUMENT_SYNC = "textDocument/synchronization"; public static final String TEXT_DOCUMENT_LINK = "textDocument/documentLink"; + public static final String TEXT_DOCUMENT_COLOR = "textDocument/documentColor"; public static final String TEXT_DOCUMENT_FOLDING_RANGE = "textDocument/foldingRange"; public static final String TEXT_DOCUMENT_DOCUMENT_SYMBOL = "textDocument/documentSymbol"; public static final String TEXT_DOCUMENT_CODE_ACTION = "textDocument/codeAction"; @@ -59,6 +61,7 @@ private ServerCapabilitiesConstants() { public static final String SYNC_ID = UUID.randomUUID().toString(); public static final String FOLDING_RANGE_ID = UUID.randomUUID().toString(); public static final String LINK_ID = UUID.randomUUID().toString(); + public static final String COLOR_ID = UUID.randomUUID().toString(); public static final String FORMATTING_ON_TYPE_ID = UUID.randomUUID().toString(); public static final String FORMATTING_RANGE_ID = UUID.randomUUID().toString(); public static final String CODE_LENS_ID = UUID.randomUUID().toString(); @@ -82,6 +85,7 @@ private ServerCapabilitiesConstants() { Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'", "&")); public static final TextDocumentSyncKind DEFAULT_SYNC_OPTION = TextDocumentSyncKind.Full; public static final DocumentLinkOptions DEFAULT_LINK_OPTIONS = new DocumentLinkOptions(true); + public static final ColorProviderOptions DEFAULT_COLOR_OPTIONS = new ColorProviderOptions(); public static final CodeLensOptions DEFAULT_CODELENS_OPTIONS = new CodeLensOptions(); public static final CodeActionOptions DEFAULT_CODEACTION_OPTIONS = createDefaultCodeActionOptions(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java index 38cbf9e4c..8874f3416 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java @@ -59,6 +59,7 @@ public static ServerCapabilities getNonDynamicServerCapabilities(ClientCapabilit serverCapabilities.setTypeDefinitionProvider(!clientCapabilities.isTypeDefinitionDynamicRegistered()); serverCapabilities.setReferencesProvider(!clientCapabilities.isReferencesDynamicRegistrationSupported()); serverCapabilities.setLinkedEditingRangeProvider(!clientCapabilities.isLinkedEditingRangeDynamicRegistered()); + serverCapabilities.setColorProvider(!clientCapabilities.isColorDynamicRegistrationSupported()); if (clientCapabilities.isWorkspaceFoldersSupported()) { WorkspaceFoldersOptions workspaceFolders = new WorkspaceFoldersOptions(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java index e97ffb701..99acc25da 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java @@ -14,8 +14,10 @@ import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.CODE_ACTION_ID; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.CODE_LENS_ID; +import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.COLOR_ID; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.COMPLETION_ID; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_CODEACTION_OPTIONS; +import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_COLOR_OPTIONS; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_COMPLETION_OPTIONS; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_LINK_OPTIONS; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFINITION_ID; @@ -32,6 +34,7 @@ import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.SELECTION_RANGE_ID; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_CODE_ACTION; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_CODE_LENS; +import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_COLOR; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_COMPLETION; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_DEFINITION; import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_DOCUMENT_SYMBOL; @@ -163,6 +166,9 @@ public void initializeCapabilities() { if (this.getClientCapabilities().isLinkDynamicRegistrationSupported()) { registerCapability(LINK_ID, TEXT_DOCUMENT_LINK, DEFAULT_LINK_OPTIONS); } + if (this.getClientCapabilities().isColorDynamicRegistrationSupported()) { + registerCapability(COLOR_ID, TEXT_DOCUMENT_COLOR, DEFAULT_COLOR_OPTIONS); + } if (this.getClientCapabilities().isRenameDynamicRegistrationSupported()) { registerCapability(RENAME_ID, TEXT_DOCUMENT_RENAME); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java index 1a0b67243..d45f292cc 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java @@ -112,12 +112,12 @@ public static Range selectAttributeValue(DOMAttr attr) { * Returns the attribute value range and null otherwise. * * @param attr the attribute. - * @param withouQuote true if range must remove the quote and false otherwise. + * @param withoutQuote true if range must remove the quote and false otherwise. * @return the attribute value range and null otherwise. */ - public static Range selectAttributeValue(DOMAttr attr, boolean withouQuote) { + public static Range selectAttributeValue(DOMAttr attr, boolean withoutQuote) { if (attr != null) { - return createAttrValueRange(attr, attr.getOwnerDocument()); + return createAttrValueRange(attr, attr.getOwnerDocument(), withoutQuote); } return null; } diff --git a/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json b/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json index c59ba35be..8d00ce943 100644 --- a/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json +++ b/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json @@ -441,6 +441,30 @@ "parameterTypes": [] }] }, + { + "name": "org.eclipse.lemminx.extensions.colors.settings.XMLColorExpression", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lemminx.extensions.colors.settings.XMLColors", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, { "name": "org.eclipse.lemminx.settings.QuoteStyle", "allDeclaredFields": true @@ -1097,6 +1121,38 @@ "parameterTypes": [] }] }, + { + "name": "org.eclipse.lsp4j.ColorInformation", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lsp4j.ColorProviderCapabilities", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lsp4j.ColorProviderOptions", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lsp4j.DocumentColorParams", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, { "name": "org.eclipse.lsp4j.DocumentOnTypeFormattingOptions", "allDeclaredFields": true, diff --git a/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension index 74906bdae..e23229121 100644 --- a/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension +++ b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension @@ -11,4 +11,5 @@ org.eclipse.lemminx.extensions.entities.EntitiesPlugin org.eclipse.lemminx.extensions.xmlmodel.XMLModelPlugin org.eclipse.lemminx.extensions.generators.FileContentGeneratorPlugin org.eclipse.lemminx.extensions.relaxng.RelaxNGPlugin -org.eclipse.lemminx.extensions.xinclude.XIncludePlugin \ No newline at end of file +org.eclipse.lemminx.extensions.xinclude.XIncludePlugin +org.eclipse.lemminx.extensions.colors.XMLColorsPlugin \ No newline at end of file diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java index c97f1e2bb..37de6e37d 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java @@ -43,6 +43,7 @@ import org.eclipse.lemminx.customservice.AutoCloseTagResponse; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMParser; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings; import org.eclipse.lemminx.extensions.contentmodel.commands.SurroundWithCommand; import org.eclipse.lemminx.extensions.contentmodel.commands.SurroundWithCommand.SurroundWithKind; import org.eclipse.lemminx.extensions.contentmodel.commands.SurroundWithCommand.SurroundWithResponse; @@ -65,6 +66,10 @@ import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Color; +import org.eclipse.lsp4j.ColorInformation; +import org.eclipse.lsp4j.ColorPresentation; +import org.eclipse.lsp4j.ColorPresentationParams; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.CompletionCapabilities; import org.eclipse.lsp4j.CompletionItem; @@ -1852,4 +1857,67 @@ public static void assertSurroundWith(String xml, SurroundWithKind kind, boolean assertEquals(expected, actual); } + // ------------------- ColorInformation assert + + public static void testColorInformationFor(String value, String fileURI, XMLColorsSettings colorSettings, + ColorInformation... expected) { + TextDocument document = new TextDocument(value, fileURI != null ? fileURI : "test://test/test.xml"); + + XMLLanguageService xmlLanguageService = new XMLLanguageService(); + xmlLanguageService.doSave(new SettingsSaveContext(colorSettings)); + + DOMDocument xmlDocument = DOMParser.getInstance().parse(document, + xmlLanguageService.getResolverExtensionManager()); + xmlLanguageService.setDocumentProvider((uri) -> xmlDocument); + + List actual = xmlLanguageService.findDocumentColors(xmlDocument, () -> { + }); + assertColorInformation(actual, expected); + } + + public static ColorInformation colorInfo(double red, double green, double blue, double alpha, Range range) { + return new ColorInformation(range, new Color(red, green, blue, alpha)); + } + + public static void assertColorInformation(List actual, ColorInformation... expected) { + assertEquals(expected.length, actual.size()); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i].getRange(), actual.get(i).getRange()); + assertEquals(expected[i].getColor(), actual.get(i).getColor()); + } + } + + // ------------------- ColorInformation assert + + public static void testColorPresentationFor(String value, String fileURI, Color color, Range range, + XMLColorsSettings colorSettings, + ColorPresentation... expected) { + TextDocument document = new TextDocument(value, fileURI != null ? fileURI : "test://test/test.xml"); + + XMLLanguageService xmlLanguageService = new XMLLanguageService(); + xmlLanguageService.doSave(new SettingsSaveContext(colorSettings)); + + DOMDocument xmlDocument = DOMParser.getInstance().parse(document, + xmlLanguageService.getResolverExtensionManager()); + xmlLanguageService.setDocumentProvider((uri) -> xmlDocument); + + ColorPresentationParams params = new ColorPresentationParams(new TextDocumentIdentifier(document.getUri()), + color, range); + List actual = xmlLanguageService.getColorPresentations(xmlDocument, params, () -> { + }); + assertColorPresentation(actual, expected); + } + + public static void assertColorPresentation(List actual, + ColorPresentation... expected) { + assertEquals(expected.length, actual.size()); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i].getLabel(), actual.get(i).getLabel()); + assertEquals(expected[i].getTextEdit(), actual.get(i).getTextEdit()); + } + } + + public static ColorPresentation colorPres(String label, TextEdit textEdit) { + return new ColorPresentation(label, textEdit); + } } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/colors/XMLColorsExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/colors/XMLColorsExtensionsTest.java new file mode 100644 index 000000000..9e8598b9c --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/colors/XMLColorsExtensionsTest.java @@ -0,0 +1,134 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.colors; + +import static org.eclipse.lemminx.XMLAssert.colorInfo; +import static org.eclipse.lemminx.XMLAssert.colorPres; +import static org.eclipse.lemminx.XMLAssert.r; +import static org.eclipse.lemminx.XMLAssert.te; +import static org.eclipse.lemminx.XMLAssert.testColorInformationFor; +import static org.eclipse.lemminx.XMLAssert.testColorPresentationFor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorExpression; +import org.eclipse.lemminx.extensions.colors.settings.XMLColors; +import org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings; +import org.eclipse.lsp4j.ColorInformation; +import org.junit.jupiter.api.Test; + +/** + * XML colors tests. + * + */ +public class XMLColorsExtensionsTest { + + @Test + public void colorOnText() throws BadLocationException { + // Here color is done for color/text() only + XMLColorsSettings settings = createXMLColorsSettings(); + String xml = "\r\n" + + "\r\n" + + " #f00\r\n" // <-- here color/text() is colorized + + " #80ff0000\r\n" // <-- here color/text() is colorized + + " BAD_COLOR\r\n" // <-- here color/text() is not done because + // color syntax is not good + + " #f00\r\n" + + " \r\n" + + " \r\n" + + ""; + testColorInformationFor(xml, "file:///test/res/values/colors.xml", settings, // + colorInfo(1, 0, 0, 1, r(2, 26, 2, 30)), // + colorInfo(0.5019607843137255, 1, 0, 0, r(3, 31, 3, 40))); + } + + @Test + public void colorOnAttr() throws BadLocationException { + // Here color is done for item/@color only + XMLColorsSettings settings = createXMLColorsSettings(); + String xml = "\r\n" + + "\r\n" + + " #f00\r\n" + + " #80ff0000\r\n" + + " BAD_COLOR\r\n" + + " \r\n" // <-- here item/@color is colorized + + " \r\n" // <-- here item/@color is not done because color syntax is not + // good + + ""; + testColorInformationFor(xml, "file:///test/colors-attr.xml", settings, // + colorInfo(1, 0, 0, 1, r(5, 14, 5, 17))); + } + + @Test + public void colorAllPresentation() throws BadLocationException { + // Here color is done for color/text() only + XMLColorsSettings settings = createXMLColorsSettings(); + String xml = "\r\n" + + "\r\n" + + " \r\n" // color name + + " \r\n" // hexa color + + " \r\n" // rgb color + + " \r\n" // rgba color + + ""; + testColorInformationFor(xml, "file:///test/colors-attr.xml", settings, // + colorInfo(0, 0.5019607843137255, 0, 1, r(2, 14, 2, 19)), // + colorInfo(0.5254901960784314, 0.7568627450980392, 0.33725490196078434, 1, r(3, 14, 3, 21)), // + colorInfo(0.33725490196078434, 0.5607843137254902, 0.7568627450980392, 1, r(4, 14, 4, 31)), // + colorInfo(0.0784313725490196, 0.0784313725490196, 0.0784313725490196, 0.5, r(5, 14, 5, 31))); + } + + @Test + public void colorPresentation() throws BadLocationException { + + ColorInformation green = colorInfo(0, 0.5019607843137255, 0, 1, r(2, 14, 2, 19)); + + XMLColorsSettings settings = createXMLColorsSettings(); + String xml = "\r\n" + + "\r\n" + + " \r\n" // color name + + " \r\n" // hexa color + + " \r\n" // rgb color + + " \r\n" // rgba color + + ""; + testColorPresentationFor(xml, "file:///test/colors-attr.xml", green.getColor(), green.getRange(), settings, // + colorPres("rgb(0,128,0)", te(2, 14, 2, 19, "rgb(0,128,0)")), // + colorPres("#008000", te(2, 14, 2, 19, "#008000"))); + } + + private XMLColorsSettings createXMLColorsSettings() { + XMLColorsSettings settings = new XMLColorsSettings(); + List colors = new ArrayList<>(); + settings.setColors(colors); + + // color/text() + XMLColors colorsOnText = new XMLColors(); + colors.add(colorsOnText); + colorsOnText.setPattern("**/res/values/colors.xml"); + XMLColorExpression expressionOnText = new XMLColorExpression(); + expressionOnText.setXPath("resources/color/text()"); + colorsOnText.setExpressions(Arrays.asList(expressionOnText)); + + // color/text() + XMLColors colorsOnAttr = new XMLColors(); + colors.add(colorsOnAttr); + colorsOnAttr.setPattern("**/colors-attr.xml"); + XMLColorExpression expressionOnAttr = new XMLColorExpression(); + expressionOnAttr.setXPath("item/@color"); + colorsOnAttr.setExpressions(Arrays.asList(expressionOnAttr)); + + return settings; + } + +}