From 5d97581878b3fecf6d11fcf6bf332f3b66c64a34 Mon Sep 17 00:00:00 2001
From: azerr
Date: Thu, 23 Nov 2023 08:40:07 +0100
Subject: [PATCH] XML Files support with settings
Fixes #1464
Signed-off-by: azerr
---
.../extensions/catalog/XMLCatalogPlugin.java | 6 +-
.../CatalogFilePathSupportParticipant.java | 45 +++
.../XMLCatalogCodeLensParticipant.java | 2 +-
.../XMLCatalogDiagnosticsParticipant.java | 7 +-
.../XMLCatalogDocumentLinkParticipant.java | 4 +-
.../extensions/filepath/FilePathPlugin.java | 152 ++++++++++
.../filepath/IFilePathExpression.java | 56 ++++
.../filepath/IFilePathSupportParticipant.java | 39 +++
.../filepath/SimpleFilePathExpression.java | 46 +++
.../FilePathCompletionParticipant.java | 255 ++++++++++++++++
.../FilePathCompletionResult.java | 163 ++++++++++
.../filepath/settings/FilePathExpression.java | 106 +++++++
.../filepath/settings/FilePathMapping.java | 46 +++
.../filepath/settings/FilePathSupport.java | 30 ++
.../settings/FilePathSupportSettings.java | 66 ++++
.../extensions/general/FilePathPlugin.java | 40 ---
.../FilePathCompletionParticipant.java | 286 ------------------
.../rng/RNGFilePathSupportParticipant.java | 49 +++
.../XSDDocumentLinkParticipant.java | 4 +-
.../XSDFilePathSupportParticipant.java | 64 ++++
.../XSLFilePathSupportParticipant.java | 65 ++++
.../snippets/NewFileSnippetContext.java | 10 +-
.../snippets/XMLSnippetRegistryLoader.java | 5 +-
.../lemminx/settings/PathPatternMatcher.java | 3 +-
.../utils/CompletionItemDefaultsUtils.java | 15 +-
.../org/eclipse/lemminx/utils/DOMUtils.java | 29 ++
.../org/eclipse/lemminx/utils/FilesUtils.java | 24 +-
.../eclipse/lemminx/utils/StringUtils.java | 2 +-
.../META-INF/native-image/reflect-config.json | 32 ++
...sions.filepath.IFilePathSupportParticipant | 4 +
....lemminx.services.extensions.IXMLExtension | 2 +-
.../services/snippets/new-xsl-snippets.json | 16 +
.../filepath/FilePathSettingsForTest.java | 64 ++++
.../AbstractFilePathCompletionTest.java | 76 +++++
.../FilePathCompletionWithDOCTYPETest.java | 103 +++++++
...ePathCompletionWithSchemaLocationTest.java | 38 +++
.../FilePathCompletionWithSettingsTest.java} | 230 ++++++--------
...ionWithtNoNamespaceSchemaLocationTest.java | 122 ++++++++
.../rng/FilePathCompletionWithRNGTest.java | 53 ++++
.../xsd/FilePathCompletionWithXSDTest.java | 53 ++++
.../xsl/FilePathCompletionWithXSLTest.java | 51 ++++
.../filePathCompletion/folderA/dtdA1.dtd | 0
.../filePathCompletion/folderA/dtdA2.dtd | 0
.../filePathCompletion/folderB/dtdB1.dtd | 0
.../resources/filePathCompletion/main.dtd | 0
.../resources/filePathCompletion/main.xsd | 0
.../resources/filePathCompletion/main.xsl | 0
47 files changed, 1976 insertions(+), 487 deletions(-)
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/CatalogFilePathSupportParticipant.java
rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/{ => participants}/XMLCatalogCodeLensParticipant.java (98%)
rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/{ => participants}/XMLCatalogDiagnosticsParticipant.java (86%)
rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/{ => participants}/XMLCatalogDocumentLinkParticipant.java (92%)
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpression.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathSupportParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/SimpleFilePathExpression.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathExpression.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathMapping.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupport.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupportSettings.java
delete mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/FilePathPlugin.java
delete mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGFilePathSupportParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDFilePathSupportParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/participants/XSLFilePathSupportParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant
create mode 100644 org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/services/snippets/new-xsl-snippets.json
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/FilePathSettingsForTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/AbstractFilePathCompletionTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithDOCTYPETest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSchemaLocationTest.java
rename org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/{general/FilePathCompletionTest.java => filepath/participants/FilePathCompletionWithSettingsTest.java} (52%)
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithtNoNamespaceSchemaLocationTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/FilePathCompletionWithRNGTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/FilePathCompletionWithXSDTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsl/FilePathCompletionWithXSLTest.java
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA1.dtd
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA2.dtd
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/folderB/dtdB1.dtd
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/main.dtd
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsd
create mode 100644 org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsl
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java
index 00f751d78..6439ee642 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java
@@ -19,6 +19,9 @@
import org.eclipse.lemminx.client.InvalidPathWarner;
import org.eclipse.lemminx.client.PathFeature;
+import org.eclipse.lemminx.extensions.catalog.participants.XMLCatalogCodeLensParticipant;
+import org.eclipse.lemminx.extensions.catalog.participants.XMLCatalogDiagnosticsParticipant;
+import org.eclipse.lemminx.extensions.catalog.participants.XMLCatalogDocumentLinkParticipant;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.services.IXMLNotificationService;
@@ -51,7 +54,8 @@ public XMLCatalogPlugin() {
@Override
public void doSave(ISaveContext context) {
Object initializationOptionsSettings = context.getSettings();
- ContentModelSettings cmSettings = ContentModelSettings.getContentModelXMLSettings(initializationOptionsSettings);
+ ContentModelSettings cmSettings = ContentModelSettings
+ .getContentModelXMLSettings(initializationOptionsSettings);
if (cmSettings == null) {
return;
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/CatalogFilePathSupportParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/CatalogFilePathSupportParticipant.java
new file mode 100644
index 000000000..e30f94270
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/CatalogFilePathSupportParticipant.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+* 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.catalog.participants;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.filepath.IFilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.utils.DOMUtils;
+
+/**
+ * Catalog file path support for catalog.xml to provide completion for @uri
+ * attribute.
+ */
+public class CatalogFilePathSupportParticipant implements IFilePathSupportParticipant {
+
+ private static final List CATALOG_FILE_PATH_EXPRESSIONS;
+
+ static {
+ CATALOG_FILE_PATH_EXPRESSIONS = Arrays.asList(new FilePathExpression("@uri"));
+ }
+
+ @Override
+ public List collectFilePathExpressions(DOMDocument document) {
+ if (!DOMUtils.isCatalog(document)) {
+ return Collections.emptyList();
+ }
+ // The DOM document is an XML catalog, returns their expressions.
+ return CATALOG_FILE_PATH_EXPRESSIONS;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogCodeLensParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogCodeLensParticipant.java
similarity index 98%
rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogCodeLensParticipant.java
rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogCodeLensParticipant.java
index 8952cd96e..b9eb0ca1e 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogCodeLensParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogCodeLensParticipant.java
@@ -7,7 +7,7 @@
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
-package org.eclipse.lemminx.extensions.catalog;
+package org.eclipse.lemminx.extensions.catalog.participants;
import java.util.Arrays;
import java.util.List;
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDiagnosticsParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDiagnosticsParticipant.java
similarity index 86%
rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDiagnosticsParticipant.java
rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDiagnosticsParticipant.java
index f2bea7df6..9ab3d47f9 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDiagnosticsParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDiagnosticsParticipant.java
@@ -8,12 +8,15 @@
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
-package org.eclipse.lemminx.extensions.catalog;
+package org.eclipse.lemminx.extensions.catalog.participants;
import java.text.MessageFormat;
import java.util.List;
import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.catalog.CatalogEntry;
+import org.eclipse.lemminx.extensions.catalog.CatalogUtils;
+import org.eclipse.lemminx.extensions.catalog.XMLCatalogErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant;
import org.eclipse.lemminx.utils.DOMUtils;
@@ -45,7 +48,7 @@ public void doDiagnostics(DOMDocument xmlDocument, List diagnostics,
// appending it in the case when original URI does not start with 'file://'.
// Ex: originalURI ="foo/bar.xsd" -> path ="file://foo/bar.xsd"
String path = CatalogUtils.getResolvedLocation(xmlDocument, catalogEntry);
- if (!FilesUtils.isValidPath(FilesUtils.getPath(path)) && URIUtils.isFileResource(path)) {
+ if (path != null && !FilesUtils.isValidPath(FilesUtils.getPath(path)) && URIUtils.isFileResource(path)) {
Range range = XMLPositionUtility.selectValueWithoutQuote(catalogEntry.getLinkRange());
String msg = MessageFormat.format(ERROR_STRING, catalogEntry.getResolvedURI());
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDocumentLinkParticipant.java
similarity index 92%
rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java
rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDocumentLinkParticipant.java
index 9e8faae7e..119b71174 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/participants/XMLCatalogDocumentLinkParticipant.java
@@ -8,7 +8,7 @@
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
-package org.eclipse.lemminx.extensions.catalog;
+package org.eclipse.lemminx.extensions.catalog.participants;
import java.util.List;
import java.util.logging.Level;
@@ -16,6 +16,8 @@
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.catalog.CatalogEntry;
+import org.eclipse.lemminx.extensions.catalog.CatalogUtils;
import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.DocumentLink;
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java
new file mode 100644
index 000000000..6a56898ec
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+* Copyright (c) 2019 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.filepath;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.filepath.participants.FilePathCompletionParticipant;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathMapping;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathSupportSettings;
+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;
+
+/**
+ * File Path support plugin.
+ */
+public class FilePathPlugin implements IXMLExtension {
+
+ private static final Logger LOGGER = Logger.getLogger(FilePathPlugin.class.getName());
+
+ private final FilePathCompletionParticipant completionParticipant;
+ private FilePathSupportSettings filePathsSettings;
+
+ private List filePathSupportParticipants;
+
+ public FilePathPlugin() {
+ completionParticipant = new FilePathCompletionParticipant(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();
+ FilePathSupportSettings settings = FilePathSupportSettings
+ .getFilePathsSettings(initializationOptionsSettings);
+ updateSettings(settings, saveContext);
+ }
+
+ private void updateSettings(FilePathSupportSettings settings, ISaveContext context) {
+ this.filePathsSettings = settings;
+ }
+
+ @Override
+ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
+ registry.registerCompletionParticipant(completionParticipant);
+ }
+
+ @Override
+ public void stop(XMLExtensionsRegistry registry) {
+ registry.unregisterCompletionParticipant(completionParticipant);
+ }
+
+ public FilePathSupportSettings getFilePathsSettings() {
+ return filePathsSettings;
+ }
+
+ /**
+ * Return the list of {@link FilePathExpression} for the given document and an
+ * empty list otherwise.
+ *
+ * @param xmlDocument the DOM document
+ *
+ * @return the list of {@link FilePathExpression} for the given document and an
+ * empty list otherwise.
+ */
+ public List findFilePathExpressions(DOMDocument xmlDocument) {
+ List expressions = new ArrayList<>();
+ fillFromParticipants(xmlDocument, expressions);
+ fillFromUserSettings(xmlDocument, expressions);
+ return expressions;
+ }
+
+ private void fillFromParticipants(DOMDocument xmlDocument, List allExpressions) {
+ for (IFilePathSupportParticipant participant : getFilePathSupportParticipants()) {
+ List expressions = participant.collectFilePathExpressions(xmlDocument);
+ if (expressions != null && !expressions.isEmpty()) {
+ allExpressions.addAll(expressions);
+ }
+ }
+ }
+
+ private void fillFromUserSettings(DOMDocument xmlDocument, List expressions) {
+ FilePathSupportSettings settings = getFilePathsSettings();
+ if (settings == null) {
+ return;
+ }
+
+ List mappings = settings.getFilePathMappings();
+ fillFromMappings(xmlDocument, mappings, expressions);
+ }
+
+ private static void fillFromMappings(DOMDocument xmlDocument, List mappings,
+ List expressions) {
+ if (mappings == null) {
+ return;
+ }
+
+ for (FilePathMapping filePaths : mappings) {
+ if (filePaths.matches(xmlDocument.getDocumentURI())) {
+ expressions.addAll(filePaths.getExpressions());
+ }
+ }
+ }
+
+ public List getFilePathSupportParticipants() {
+ if (filePathSupportParticipants == null) {
+ loadFilePathSupportParticipants();
+ }
+ return filePathSupportParticipants;
+ }
+
+ private synchronized void loadFilePathSupportParticipants() {
+ if (filePathSupportParticipants != null) {
+ return;
+ }
+ List participants = new ArrayList<>();
+ Iterator extensions = ServiceLoader.load(IFilePathSupportParticipant.class)
+ .iterator();
+ while (extensions.hasNext()) {
+ try {
+ participants.add(extensions.next());
+ } catch (ServiceConfigurationError e) {
+ LOGGER.log(Level.SEVERE, "Error while instantiating file path support participant", e);
+ }
+ }
+ filePathSupportParticipants = participants;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpression.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpression.java
new file mode 100644
index 000000000..1ac3b0113
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpression.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+* 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.filepath;
+
+import java.nio.file.Path;
+
+import org.w3c.dom.Node;
+
+/**
+ * File path support expression API.
+ */
+public interface IFilePathExpression {
+
+ /**
+ * Returns true if the given DOM node matches the file path expression and false
+ * otherwise.
+ *
+ * @param node the DOM node.
+ *
+ * @return true if the given DOM node matches the file path expression and false
+ * otherwise.
+ */
+ boolean match(Node node);
+
+ /**
+ * Returns the separator character (ex: ';') used to separate multiple files
+ * declaration (ex:
+ * file1.xml;file2.xml) and null otherwise.
+ *
+ * @return the separator character (ex: ';') used to separate multiple files
+ * declaration (ex:
+ * file1.xml;file2.xml) and null otherwise.
+ */
+ Character getSeparator();
+
+ /**
+ * Returns true if given file path is allowed for the file path completion and
+ * false otherwise.
+ *
+ * @param path the file path.
+ *
+ * @return true if given file path is allowed for the file path completion and
+ * false otherwise.
+ */
+ boolean acceptPath(Path path);
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathSupportParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathSupportParticipant.java
new file mode 100644
index 000000000..e68b91996
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathSupportParticipant.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+* 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.filepath;
+
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+
+/**
+ * File path support participant API.
+ *
+ *
+ * This API provides the capability to contribute to
+ * mark some DOM nodes as file type to have file path completion inside the DOM
+ * node.
+ *
+ */
+public interface IFilePathSupportParticipant {
+
+ /**
+ * Returns the file path expressions used to mark DOM nodes as file type for the
+ * given DOM document and null or empty otherwise.
+ *
+ * @param document the DOM document.
+ *
+ * @return the file path expressions used to mark DOM nodes as file type for the
+ * given DOM document and null or empty otherwise.
+ */
+ List collectFilePathExpressions(DOMDocument document);
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/SimpleFilePathExpression.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/SimpleFilePathExpression.java
new file mode 100644
index 000000000..9449ca504
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/SimpleFilePathExpression.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.filepath;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.w3c.dom.Node;
+
+/**
+ * Basic implementation of the {@link IFilePathExpression}.
+ */
+public class SimpleFilePathExpression implements IFilePathExpression {
+
+ @Override
+ public boolean match(Node node) {
+ return true;
+ }
+
+ @Override
+ public Character getSeparator() {
+ return null;
+ }
+
+ @Override
+ public boolean acceptPath(Path path) {
+ if (!Files.isDirectory(path)) {
+ return acceptFile(path);
+ }
+ return true;
+ }
+
+ protected boolean acceptFile(Path path) {
+ return false;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java
new file mode 100644
index 000000000..796602328
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java
@@ -0,0 +1,255 @@
+/*******************************************************************************
+* 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.filepath.participants;
+
+import static org.eclipse.lemminx.utils.FilesUtils.getFileName;
+import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+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.DOMRange;
+import org.eclipse.lemminx.dom.DOMText;
+import org.eclipse.lemminx.dom.DTDDeclParameter;
+import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation;
+import org.eclipse.lemminx.dom.SchemaLocation;
+import org.eclipse.lemminx.extensions.filepath.FilePathPlugin;
+import org.eclipse.lemminx.extensions.filepath.IFilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.SimpleFilePathExpression;
+import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter;
+import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest;
+import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse;
+import org.eclipse.lemminx.utils.CompletionSortTextHelper;
+import org.eclipse.lemminx.utils.DOMUtils;
+import org.eclipse.lemminx.utils.FilesUtils;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionItemKind;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+
+/**
+ * Extension to support completion for file, folder path in:
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+public class FilePathCompletionParticipant extends CompletionParticipantAdapter {
+
+ private static final Logger LOGGER = Logger.getLogger(FilePathCompletionParticipant.class.getName());
+
+ private static final IFilePathExpression DOCTYPE_FILE_PATH_EXPRESSION = new SimpleFilePathExpression() {
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ return DOMUtils.isDTD(getFileName(path));
+ };
+ };
+
+ private static final IFilePathExpression NO_NAMESPACE_SCHEMALOCATION_FILE_PATH_EXPRESSION = new SimpleFilePathExpression() {
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ return DOMUtils.isXSD(getFileName(path));
+ };
+ };
+
+ private static final IFilePathExpression SCHEMALOCATION_FILE_PATH_EXPRESSION = new SimpleFilePathExpression() {
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ return DOMUtils.isXSD(getFileName(path));
+ };
+
+ public Character getSeparator() {
+ return ' ';
+ };
+ };
+
+ private final FilePathPlugin filePathPlugin;
+
+ public FilePathCompletionParticipant(FilePathPlugin filePathPlugin) {
+ this.filePathPlugin = filePathPlugin;
+ }
+
+ @Override
+ public void onAttributeValue(String value, ICompletionRequest request, ICompletionResponse response,
+ CancelChecker cancelChecker) throws Exception {
+ DOMDocument document = request.getXMLDocument();
+ int completionOffset = request.getOffset();
+ DOMNode node = request.getNode();
+ DOMAttr attr = node.findAttrAt(request.getOffset());
+
+ // Check if completion is triggered in the value of
+ // xsi:noNamespaceSchemaLocation
+ NoNamespaceSchemaLocation noNamespaceSchemaLocation = document.getNoNamespaceSchemaLocation();
+ if (noNamespaceSchemaLocation != null && attr == noNamespaceSchemaLocation.getAttr()) {
+ addFileCompletionItems(attr, document, completionOffset, NO_NAMESPACE_SCHEMALOCATION_FILE_PATH_EXPRESSION,
+ response);
+ return;
+ }
+
+ // Check if completion is triggered in the value of xsi:schemaLocation
+ SchemaLocation schemaLocation = document.getSchemaLocation();
+ if (schemaLocation != null && attr == schemaLocation.getAttr()) {
+ addFileCompletionItems(attr, document, completionOffset, SCHEMALOCATION_FILE_PATH_EXPRESSION,
+ response);
+ return;
+ }
+
+ // File path completion on attribute value
+ List expressions = filePathPlugin.findFilePathExpressions(document);
+ if (expressions.isEmpty()) {
+ return;
+ }
+
+ for (IFilePathExpression expression : expressions) {
+ if (expression.match(attr)) {
+ addFileCompletionItems(attr, document, completionOffset, expression, response);
+ }
+ }
+ }
+
+ private void addFileCompletionItems(DOMAttr attr, DOMDocument xmlDocument, int completionOffset,
+ IFilePathExpression expression, ICompletionResponse response) throws Exception {
+ DOMRange attrValueRange = attr.getNodeAttrValue();
+ addFileCompletionItems(xmlDocument, attrValueRange.getStart() + 1 /* increment to be after the quote */,
+ attrValueRange.getEnd() - 1, completionOffset, expression,
+ response);
+ }
+
+ @Override
+ public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker)
+ throws Exception {
+ // File path completion on text node
+ List expressions = filePathPlugin.findFilePathExpressions(request.getXMLDocument());
+ if (expressions.isEmpty()) {
+ return;
+ }
+ DOMText textNode = findTextNode(request.getNode(), request.getOffset());
+ if (textNode == null) {
+ return;
+ }
+ DOMDocument xmlDocument = request.getXMLDocument();
+ for (IFilePathExpression expression : expressions) {
+ if (expression.match(textNode)) {
+ DOMRange textRange = textNode;
+ addFileCompletionItems(xmlDocument, textRange.getStart(), textRange.getEnd(), request.getOffset(),
+ expression, response);
+ }
+ }
+ }
+
+ private static DOMText findTextNode(DOMNode node, int offset) {
+ if (node == null || node.isText()) {
+ return (DOMText) node;
+ }
+ if (node.isElement()) {
+ DOMText text = ((DOMElement) node).findTextAt(offset);
+ if (text != null) {
+ return text;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onDTDSystemId(String value, ICompletionRequest request, ICompletionResponse response,
+ CancelChecker cancelChecker) throws Exception {
+ // File path completion on DTD DOCTYPE SYSTEM
+ DOMDocument xmlDocument = request.getXMLDocument();
+ DTDDeclParameter systemId = xmlDocument.getDoctype().getSystemIdNode();
+ addFileCompletionItems(xmlDocument, systemId.getStart() + 1 /* increment to be after the quote */,
+ systemId.getEnd() - 1, request.getOffset(), DOCTYPE_FILE_PATH_EXPRESSION, response);
+ }
+
+ private static void addFileCompletionItems(DOMDocument xmlDocument, int startOffset, int endOffset,
+ int completionOffset, IFilePathExpression expression, ICompletionResponse response) throws Exception {
+ // Get the resolved base dir of the file path declared insidet startOffset and
+ // endOffset
+ // ex:
+ // base dir is equals for instance to C://path/to
+ Character separator = expression != null ? expression.getSeparator() : null;
+ FilePathCompletionResult result = FilePathCompletionResult.create(xmlDocument.getText(),
+ xmlDocument.getDocumentURI(), startOffset, endOffset, completionOffset, separator);
+ Path baseDir = result.getBaseDir();
+ if (baseDir == null) {
+ // The base dir cannot be resolved, stop file completion
+ return;
+ }
+ // Fill completions with files / directories of the resolved base dir.
+ Range replaceRange = XMLPositionUtility.createRange(result.getStartOffset(), result.getEndOffset(),
+ xmlDocument);
+ try (DirectoryStream stream = Files.newDirectoryStream(baseDir)) {
+ for (Path entry : stream) {
+ if (expression == null || expression.acceptPath(entry)) {
+ createFilePathCompletionItem(entry.toFile(), replaceRange, response);
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error while getting files/directories", e);
+ }
+ }
+
+ private static void createFilePathCompletionItem(File file, Range replaceRange, ICompletionResponse response) {
+ CompletionItem item = new CompletionItem();
+ String fileName = FilesUtils.encodePath(file.getName());
+ if (isWindows && fileName.isEmpty()) { // Edge case for Windows drive letter
+ fileName = file.getPath();
+ fileName = fileName.substring(0, fileName.length() - 1);
+ }
+ String insertText = fileName;
+ item.setLabel(insertText);
+
+ CompletionItemKind kind = file.isDirectory() ? CompletionItemKind.Folder : CompletionItemKind.File;
+ item.setKind(kind);
+
+ item.setSortText(CompletionSortTextHelper.getSortText(kind));
+ item.setFilterText(insertText);
+ item.setTextEdit(Either.forLeft(new TextEdit(replaceRange, insertText)));
+ response.addCompletionItem(item);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java
new file mode 100644
index 000000000..02b99cee8
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+* 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.filepath.participants;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import org.eclipse.lemminx.utils.FilesUtils;
+import org.eclipse.lemminx.utils.StringUtils;
+
+/**
+ * File path completion result information.
+ */
+public class FilePathCompletionResult {
+
+ private static final Predicate isStartValidChar = (c) -> c != '/' && c != '\\';
+
+ private final int startOffset;
+
+ private final int endOffset;
+
+ private final Path baseDir;
+
+ public FilePathCompletionResult(int startOffset, int endOffset, Path baseDir) {
+ super();
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.baseDir = baseDir;
+ }
+
+ /**
+ * Returns the start offset of the last path declared (ex :
+ * Ex :
+ *
+ *
+ *
+ * the method will return 'file://C:/path/to'
+ *
+ *
+ *
+ * @return the resolved base directory of the declared path.
+ */
+ public Path getBaseDir() {
+ return baseDir;
+ }
+
+ /**
+ * Create the file path completion result.
+ *
+ * @param content the xml content.
+ * @param fileUri the file Uri.
+ * @param startNodeOffset the start node offset where file path is declared.
+ * @param endNodeOffset the end node offset where file path is declared.
+ * @param completionOffset the completion offset.
+ * @param separator the separator used to declare multiple files and null
+ * otherwise.
+ * @return the file path completion result.
+ */
+ public static FilePathCompletionResult create(String content, String fileUri, int startNodeOffset,
+ int endNodeOffset, int completionOffset, Character separator) {
+ boolean isMultiFilePath = separator != null;
+ Predicate isStartValidChar2 = isStartValidChar;
+ int endPathOffset = endNodeOffset;
+ if (isMultiFilePath) {
+ // multiple file path (ex :
+ isStartValidChar2 = c -> c != separator && isStartValidChar.test(c);
+ endPathOffset = StringUtils.findEndWord(content, completionOffset, endNodeOffset, c -> c != separator);
+ if (endPathOffset == -1) {
+ endPathOffset = endNodeOffset;
+ }
+ }
+ int startPathOffset = StringUtils.findStartWord(content, completionOffset, startNodeOffset, isStartValidChar2);
+ int startBaseDirOffset = startNodeOffset;
+ if (isMultiFilePath && !isStartValidChar2.test(content.charAt(startPathOffset - 1))) {
+ int tmp = StringUtils.findStartWord(content, completionOffset, startNodeOffset, c -> c != separator);
+ if (tmp != -1) {
+ startBaseDirOffset = tmp;
+ }
+ }
+
+ Path baseDir = getBaseDir(content, fileUri, startBaseDirOffset, startPathOffset);
+ if (baseDir == null || !Files.exists(baseDir)) {
+ baseDir = null;
+ }
+ return new FilePathCompletionResult(startPathOffset, endPathOffset, baseDir);
+ }
+
+ private static Path getBaseDir(String content, String fileUri, int start, int end) {
+ if (end > start) {
+ String basePath = content.substring(start, end);
+ if (!hasPathBeginning(basePath)) {
+ try {
+ Path baseDir = FilesUtils.getPath(basePath);
+ if (Files.exists(baseDir)) {
+ return baseDir;
+ }
+ } catch (Exception e) {
+
+ }
+ }
+ try {
+ return FilesUtils.getPath(fileUri).getParent().resolve(basePath);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ return FilesUtils.getPath(fileUri).getParent();
+ }
+
+ private static boolean hasPathBeginning(String currentText) {
+ if (currentText.startsWith("/")
+ || currentText.startsWith("./")
+ || currentText.startsWith("../")
+ || currentText.startsWith("..\\")
+ || currentText.startsWith(".\\")) {
+ return true;
+ }
+ return isAbsoluteWindowsPath(currentText);
+ }
+
+ private static boolean isAbsoluteWindowsPath(String currentText) {
+ if (currentText.length() < 3) {
+ return false;
+ }
+ if (!Character.isLetter(currentText.charAt(0))) {
+ return false;
+ }
+ return currentText.charAt(1) == ':' && (currentText.charAt(2) == '\\' || currentText.charAt(2) == '/');
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathExpression.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathExpression.java
new file mode 100644
index 000000000..15ba179b4
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathExpression.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+* 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.filepath.settings;
+
+import static org.eclipse.lemminx.utils.FilesUtils.getFileName;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.eclipse.lemminx.extensions.filepath.SimpleFilePathExpression;
+import org.eclipse.lemminx.xpath.matcher.XPathMatcher;
+import org.w3c.dom.Node;
+
+/**
+ * File path expression
+ *
+ *
+ * {
+ "xpath": "@paths",
+ "separator": " "
+ }
+ *
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class FilePathExpression extends SimpleFilePathExpression {
+
+ private transient XPathMatcher pathMatcher;
+
+ private String xpath;
+
+ private Character separator;
+
+ private List filter;
+
+ public FilePathExpression() {
+ this(null);
+ }
+
+ public FilePathExpression(String xpath) {
+ setXPath(xpath);
+ }
+
+ public String getXPath() {
+ return xpath;
+ }
+
+ @Override
+ public Character getSeparator() {
+ return separator;
+ }
+
+ public List getFilter() {
+ return filter;
+ }
+
+ public FilePathExpression setXPath(String xpath) {
+ this.xpath = xpath;
+ return this;
+ }
+
+ public FilePathExpression setSeparator(Character separator) {
+ this.separator = separator;
+ return this;
+ }
+
+ public FilePathExpression setFilter(List filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ @Override
+ public boolean match(final Node node) {
+ if (xpath == null) {
+ return false;
+ }
+ if (pathMatcher == null) {
+ pathMatcher = new XPathMatcher(xpath);
+ }
+ return pathMatcher.match(node);
+ }
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ if (filter == null || filter.isEmpty()) {
+ return true;
+ }
+ String fileName = getFileName(path);
+ for (String fileExtension : filter) {
+ if (fileName.endsWith(fileExtension)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathMapping.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathMapping.java
new file mode 100644
index 000000000..7722c7d69
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathMapping.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.filepath.settings;
+
+import java.util.List;
+
+import org.eclipse.lemminx.settings.PathPatternMatcher;
+
+/**
+ * File path mapping which stores list of {@link FilePathExpression} applied
+ * for a given input file pattern.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class FilePathMapping extends PathPatternMatcher {
+
+ private List expressions;
+
+ /**
+ * Returns list of file path expressions.
+ *
+ * @return list of file path expressions.
+ */
+ public List getExpressions() {
+ return expressions;
+ }
+
+ /**
+ * Set list of file path expressions.
+ *
+ * @param expressions list of file path expressions.
+ */
+ public void setExpressions(List expressions) {
+ this.expressions = expressions;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupport.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupport.java
new file mode 100644
index 000000000..b47dc19fb
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupport.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+* 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.filepath.settings;
+
+import java.util.List;
+
+/**
+ * File path support settings.
+ */
+public class FilePathSupport {
+
+ private List mappings;
+
+ public List getMappings() {
+ return mappings;
+ }
+
+ public void setMappings(List mappings) {
+ this.mappings = mappings;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupportSettings.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupportSettings.java
new file mode 100644
index 000000000..f19628ba0
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupportSettings.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+* 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.filepath.settings;
+
+import java.util.List;
+
+import org.eclipse.lemminx.utils.JSONUtility;
+
+/**
+ * File paths settings:
+ *
+ *
+ "xml.filePathSupport.mappings": [
+ // File paths applied for text node items.xml files
+ {
+ "pattern": "*.xml",
+ "expressions": [
+ {
+ "xpath": "path/text()"
+ },
+ {
+ "xpath": "item/@path"
+ },
+ {
+ "xpath": "item/@paths",
+ "separator": " "
+ }
+ ]
+ }
+]
+ *
+ *
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class FilePathSupportSettings {
+
+ private FilePathSupport filePathSupport;
+
+ public FilePathSupport getFilePathSupport() {
+ return filePathSupport;
+ }
+
+ public void setFilePathSupport(FilePathSupport filePathSupport) {
+ this.filePathSupport = filePathSupport;
+ }
+
+ public static FilePathSupportSettings getFilePathsSettings(Object initializationOptionsSettings) {
+ return JSONUtility.toModel(initializationOptionsSettings, FilePathSupportSettings.class);
+ }
+
+ public List getFilePathMappings() {
+ return filePathSupport != null ? filePathSupport.getMappings() : null;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/FilePathPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/FilePathPlugin.java
deleted file mode 100644
index 5897c5cc7..000000000
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/FilePathPlugin.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*******************************************************************************
-* Copyright (c) 2019 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.general;
-
-import org.eclipse.lemminx.extensions.general.completion.FilePathCompletionParticipant;
-import org.eclipse.lemminx.services.extensions.IXMLExtension;
-import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
-import org.eclipse.lsp4j.InitializeParams;
-
-/**
- * FilePathPlugin
- */
-public class FilePathPlugin implements IXMLExtension {
-
- private final FilePathCompletionParticipant completionParticipant;
-
- public FilePathPlugin() {
- completionParticipant = new FilePathCompletionParticipant();
- }
-
- @Override
- public void start(InitializeParams params, XMLExtensionsRegistry registry) {
- registry.registerCompletionParticipant(completionParticipant);
- }
-
- @Override
- public void stop(XMLExtensionsRegistry registry) {
- registry.unregisterCompletionParticipant(completionParticipant);
- }
-
-}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java
deleted file mode 100644
index d996c56de..000000000
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*******************************************************************************
-* Copyright (c) 2019 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.general.completion;
-
-import static org.eclipse.lemminx.utils.FilesUtils.getFilePathSlash;
-import static org.eclipse.lemminx.utils.StringUtils.isEmpty;
-import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.eclipse.lemminx.commons.BadLocationException;
-import org.eclipse.lemminx.dom.DOMDocument;
-import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter;
-import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest;
-import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse;
-import org.eclipse.lemminx.utils.CompletionSortTextHelper;
-import org.eclipse.lemminx.utils.FilesUtils;
-import org.eclipse.lemminx.utils.StringUtils;
-import org.eclipse.lemminx.utils.XMLPositionUtility;
-import org.eclipse.lsp4j.CompletionItem;
-import org.eclipse.lsp4j.CompletionItemKind;
-import org.eclipse.lsp4j.Position;
-import org.eclipse.lsp4j.Range;
-import org.eclipse.lsp4j.TextEdit;
-import org.eclipse.lsp4j.jsonrpc.CancelChecker;
-import org.eclipse.lsp4j.jsonrpc.messages.Either;
-
-/**
- * Extension to support completion for file, folder path in:
- *
- *
- *
- *
- *
- *
- */
-public class FilePathCompletionParticipant extends CompletionParticipantAdapter {
-
- private static final Logger LOGGER = Logger.getLogger(FilePathCompletionParticipant.class.getName());
-
- @Override
- public void onAttributeValue(String value, ICompletionRequest request, ICompletionResponse response,
- CancelChecker cancelChecker) throws Exception {
- // File path completion on attribute value
- addCompletionItems(value, request, response, false);
- }
-
- @Override
- public void onDTDSystemId(String value, ICompletionRequest request, ICompletionResponse response,
- CancelChecker cancelChecker) throws Exception {
- // File path completion on DTD DOCTYPE SYSTEM
- addCompletionItems(value, request, response, true);
- }
-
- private static void addCompletionItems(String value, ICompletionRequest request, ICompletionResponse response, boolean isInDoctype)
- throws Exception {
- String fullValue = value;
- if (isEmpty(fullValue) && !isInDoctype) {
- return;
- }
-
- DOMDocument xmlDocument = request.getXMLDocument();
- String text = xmlDocument.getText();
-
- // Get value and range for file path declared inside the attribute value
- // ex value="file:///C:/fold|er"
- int valuePathStartOffset = xmlDocument.offsetAt(request.getReplaceRange().getStart());
- int endPathOffset = request.getOffset(); // offset after the typed character
- int startPathOffset = (fullValue.length() == 0 ? 0 : StringUtils.getOffsetAfterWhitespace(fullValue, endPathOffset - valuePathStartOffset))
- + valuePathStartOffset; // first character of URI
- Range filePathRange = XMLPositionUtility.createRange(startPathOffset, endPathOffset, xmlDocument);
- String originalValuePath = text.substring(startPathOffset, endPathOffset);
- // ex: valuePath="file:///C:/fold"
- String valuePath = originalValuePath;
- String slashInAttribute = getFilePathSlash(valuePath);
-
- boolean hasFileScheme = valuePath.startsWith(FilesUtils.FILE_SCHEME);
- if (hasFileScheme) {
- // remove file:// scheme
- // ex: valuePath="/C:/fold"
- valuePath = FilesUtils.removeFileScheme(valuePath);
- if (valuePath.length() == 0 || valuePath.charAt(0) != '/') {
- // use of 'file://' and the path was not absolute
- return;
- }
- if (isWindows) {
- // For Windows OS, remove the last '/' from file:///
- // ex: valuePath="C:/fold"
- valuePath = valuePath.substring(1, valuePath.length());
- if (valuePath.length() == 1) {
- // only '/', so list Windows Drives
- Range replaceRange = adjustReplaceRange(xmlDocument, filePathRange, originalValuePath, "/");
- File[] drives = File.listRoots();
- for (File drive : drives) {
- createFilePathCompletionItem(drive, replaceRange, response, "/");
- }
- return;
- }
- }
- } else if (!hasPathBeginning(valuePath) && !isInDoctype) {
- // the user probably didn't intend to complete a path
- return;
- }
- // On Linux, Mac OS replace '\\' with '/'
- if (!isWindows) {
- if ("\\".equals(slashInAttribute)) { // Backslash used in Unix
- valuePath = valuePath.replace("\\", "/");
- }
- }
-
- // Get IO path from the given value path
- Path validAttributePath = getPath(valuePath, xmlDocument.getTextDocument().getUri());
- if (validAttributePath == null) {
- return;
- }
-
- // Get adjusted range for the completion item (insert at end, or overwrite some
- // existing text in the path)
- Range replaceRange = adjustReplaceRange(xmlDocument, filePathRange, originalValuePath, slashInAttribute);
- createNextValidCompletionPaths(validAttributePath, slashInAttribute, replaceRange, response, null);
- }
-
- /**
- * Returns the IO Path from the given value path.
- *
- * @param valuePath the value path
- * @param xmlFileUri the XML file URI where completion has been triggered.
- * @return the IO Path from the given value path.
- */
- private static Path getPath(String valuePath, String xmlFileUri) {
- // the value path is the filepath URI without file://
- try {
- Path validAttributePath = FilesUtils.getPath(valuePath);
- if (!validAttributePath.isAbsolute()) {
- // Absolute path, use the XML file URI folder as base dirctory.
- Path workingDirectoryPath = FilesUtils.getPath(xmlFileUri).getParent();
- validAttributePath = workingDirectoryPath.resolve(validAttributePath).normalize();
- }
- if (!".".equals(valuePath) && !valuePath.endsWith("/") && !valuePath.endsWith("\\")) {
- // ex : C:/folder|/ -> in this case the path is the folder parent (C:)
- validAttributePath = validAttributePath.getParent();
- }
- return Files.exists(validAttributePath) ? validAttributePath : null;
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Returns a Range that covers trailing content after a slash, or if it already
- * ends with a slash then a Range right after it.
- *
- * @param xmlDocument
- * @param fullRange
- * @param attributeValue
- * @param slash
- * @return
- */
- private static Range adjustReplaceRange(DOMDocument xmlDocument, Range fullRange, String attributeValue,
- String slash) {
- // In the case the currently typed file/directory needs to be overwritten
- Position replaceStart = null;
- Position currentEnd = fullRange.getEnd();
-
- int startOffset;
- try {
- startOffset = xmlDocument.offsetAt(fullRange.getStart());
- } catch (BadLocationException e) {
- return null;
- }
- int lastSlashIndex = attributeValue.lastIndexOf(slash);
- if (lastSlashIndex > -1) {
- try {
- replaceStart = xmlDocument.positionAt(startOffset + lastSlashIndex);
- } catch (BadLocationException e) {
- return null;
- }
- }
- Range replaceRange = new Range();
- if (replaceStart != null) {
- replaceRange.setStart(replaceStart);
- } else {
- replaceRange.setStart(currentEnd);
- }
- replaceRange.setEnd(currentEnd);
- return replaceRange;
- }
-
- /**
- * Creates the completion items based off the given absolute path
- *
- * @param pathToAttributeDirectory
- * @param attributePath
- * @param replaceRange
- * @param response
- * @param filter
- */
- private static void createNextValidCompletionPaths(Path pathToAttributeDirectory, String slash, Range replaceRange,
- ICompletionResponse response, FilenameFilter filter) {
- try (DirectoryStream stream = Files.newDirectoryStream(pathToAttributeDirectory)) {
- for (Path entry : stream) {
- createFilePathCompletionItem(entry.toFile(), replaceRange, response, slash);
- }
- } catch (IOException e) {
- LOGGER.log(Level.SEVERE, "Error while getting files/directories", e);
- }
- }
-
- private static void createFilePathCompletionItem(File f, Range replaceRange, ICompletionResponse response,
- String slash) {
- CompletionItem item = new CompletionItem();
- String fName = FilesUtils.encodePath(f.getName());
- if (isWindows && fName.isEmpty()) { // Edge case for Windows drive letter
- fName = f.getPath();
- fName = fName.substring(0, fName.length() - 1);
- }
- String insertText;
- insertText = slash + fName;
- item.setLabel(insertText);
-
- CompletionItemKind kind = f.isDirectory() ? CompletionItemKind.Folder : CompletionItemKind.File;
- item.setKind(kind);
-
- item.setSortText(CompletionSortTextHelper.getSortText(kind));
- item.setFilterText(insertText);
- item.setTextEdit(Either.forLeft(new TextEdit(replaceRange, insertText)));
- response.addCompletionItem(item);
- }
-
- private static boolean hasPathBeginning(String currentText) {
- if (currentText.startsWith("/")
- || currentText.startsWith("./")
- || currentText.startsWith("../")
- || currentText.startsWith("..\\")
- || currentText.startsWith(".\\")) {
- return true;
- }
- return isAbsoluteWindowsPath(currentText);
- }
-
- private static boolean isAbsoluteWindowsPath(String currentText) {
- if (currentText.length() < 3) {
- return false;
- }
- if (!Character.isLetter(currentText.charAt(0))) {
- return false;
- }
- return currentText.charAt(1) == ':' && (currentText.charAt(2) == '\\' || currentText.charAt(2) == '/');
- }
-
-}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGFilePathSupportParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGFilePathSupportParticipant.java
new file mode 100644
index 000000000..153011da7
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGFilePathSupportParticipant.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+* 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.relaxng.grammar.rng;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.filepath.IFilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.utils.DOMUtils;
+
+/**
+ * RelaxNG file path support for *.rng to provide completion for attributes :
+ *
+ *
+ * - include/@href
+ * - externalRef/@href
+ *
+ */
+public class RNGFilePathSupportParticipant implements IFilePathSupportParticipant {
+
+ private static final List RNG_FILE_PATH_EXPRESSIONS;
+
+ static {
+ RNG_FILE_PATH_EXPRESSIONS = Arrays.asList(new FilePathExpression("include/@href"),
+ new FilePathExpression("externalRef/@href"));
+ }
+
+ @Override
+ public List collectFilePathExpressions(DOMDocument document) {
+ if (!DOMUtils.isRelaxNG(document)) {
+ return Collections.emptyList();
+ }
+ return RNG_FILE_PATH_EXPRESSIONS;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java
index e91c73665..58707f6da 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java
@@ -33,8 +33,8 @@
*
* Implements document links in .xsd files for
*
- * - xs:include/schemaLocation
- * - xs:import/schemaLocation
+ * - xs:include/@schemaLocation
+ * - xs:import/@schemaLocation
*
*
*/
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDFilePathSupportParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDFilePathSupportParticipant.java
new file mode 100644
index 000000000..f19b07555
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDFilePathSupportParticipant.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.xsd.participants;
+
+import static org.eclipse.lemminx.utils.FilesUtils.getFileName;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.filepath.IFilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.utils.DOMUtils;
+
+/**
+ * XML Schema file path support for *.xsd to provide completion for attributes :
+ *
+ *
+ * - include/@schemaLocation
+ * - import/@schemaLocation
+ *
+ */
+public class XSDFilePathSupportParticipant implements IFilePathSupportParticipant {
+
+ private static class XSDFilePathExpression extends FilePathExpression {
+
+ public XSDFilePathExpression(String xpath) {
+ super(xpath);
+ }
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ return DOMUtils.isXSD(getFileName(path));
+ }
+ }
+
+ private static final List XSD_FILE_PATH_EXPRESSIONS;
+
+ static {
+ XSD_FILE_PATH_EXPRESSIONS = Arrays.asList(new XSDFilePathExpression("include/@schemaLocation"),
+ new XSDFilePathExpression("import/@schemaLocation"));
+ }
+
+ @Override
+ public List collectFilePathExpressions(DOMDocument document) {
+ if (!DOMUtils.isXSD(document)) {
+ return Collections.emptyList();
+ }
+ return XSD_FILE_PATH_EXPRESSIONS;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/participants/XSLFilePathSupportParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/participants/XSLFilePathSupportParticipant.java
new file mode 100644
index 000000000..10aa825a0
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/participants/XSLFilePathSupportParticipant.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+* 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.xsl.participants;
+
+import static org.eclipse.lemminx.utils.FilesUtils.getFileName;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.filepath.IFilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.utils.DOMUtils;
+
+/**
+ * XML Stylesheet file path support for *.xsl to provide completion for
+ * attributes :
+ *
+ *
+ * - include/@href
+ * - import/@href
+ *
+ */
+public class XSLFilePathSupportParticipant implements IFilePathSupportParticipant {
+
+ private static class XSLFilePathExpression extends FilePathExpression {
+
+ public XSLFilePathExpression(String xpath) {
+ super(xpath);
+ }
+
+ @Override
+ protected boolean acceptFile(Path path) {
+ return DOMUtils.isXSL(getFileName(path));
+ }
+ }
+
+ private static final List XSL_FILE_PATH_EXPRESSIONS;
+
+ static {
+ XSL_FILE_PATH_EXPRESSIONS = Arrays.asList(new XSLFilePathExpression("include/@href"),
+ new XSLFilePathExpression("import/@href"));
+ }
+
+ @Override
+ public List collectFilePathExpressions(DOMDocument document) {
+ if (!DOMUtils.isXSL(document)) {
+ return Collections.emptyList();
+ }
+ return XSL_FILE_PATH_EXPRESSIONS;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/NewFileSnippetContext.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/NewFileSnippetContext.java
index ffd1928bd..970de9458 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/NewFileSnippetContext.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/NewFileSnippetContext.java
@@ -28,7 +28,8 @@ public abstract class NewFileSnippetContext implements IXMLSnippetContext {
public static final IXMLSnippetContext XML_CONTEXT = new NewFileSnippetContext() {
@Override
protected boolean isMatchType(DOMDocument document) {
- return !(document.isDTD() || DOMUtils.isXSD(document) || DOMUtils.isRelaxNG(document));
+ return !(document.isDTD() || DOMUtils.isXSD(document) || DOMUtils.isRelaxNG(document)
+ || DOMUtils.isXSL(document));
}
};
@@ -53,6 +54,13 @@ protected boolean isMatchType(DOMDocument document) {
}
};
+ public static final IXMLSnippetContext XSL_CONTEXT = new NewFileSnippetContext() {
+ @Override
+ protected boolean isMatchType(DOMDocument document) {
+ return DOMUtils.isXSL(document);
+ }
+ };
+
@Override
public boolean isMatch(ICompletionRequest request, Map model) {
DOMDocument document = request.getXMLDocument();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/XMLSnippetRegistryLoader.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/XMLSnippetRegistryLoader.java
index eddea21f8..afff9d966 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/XMLSnippetRegistryLoader.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/XMLSnippetRegistryLoader.java
@@ -30,6 +30,8 @@ public void load(SnippetRegistry registry) throws Exception {
NewFileSnippetContext.XSD_CONTEXT);
registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("new-rng-snippets.json"),
NewFileSnippetContext.RNG_CONTEXT);
+ registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("new-xsl-snippets.json"),
+ NewFileSnippetContext.XSL_CONTEXT);
registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("cdata-snippets.json"),
CDATASnippetContext.DEFAULT_CONTEXT);
registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("comment-snippets.json"),
@@ -40,7 +42,8 @@ public void load(SnippetRegistry registry) throws Exception {
XMLDeclarationSnippetContext.DEFAULT_CONTEXT);
registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("dtdnode-snippets.json"),
DTDNodeSnippetContext.DEFAULT_CONTEXT);
- registry.registerSnippets(XMLSnippetRegistryLoader.class.getResourceAsStream("processing-instruction-snippets.json"),
+ registry.registerSnippets(
+ XMLSnippetRegistryLoader.class.getResourceAsStream("processing-instruction-snippets.json"),
ProcessingInstructionSnippetContext.DEFAULT_CONTEXT);
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/PathPatternMatcher.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/PathPatternMatcher.java
index c2fe92081..30bb7aa77 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/PathPatternMatcher.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/PathPatternMatcher.java
@@ -26,9 +26,10 @@ public String getPattern() {
return pattern;
}
- public void setPattern(String pattern) {
+ public PathPatternMatcher setPattern(String pattern) {
this.pattern = pattern;
this.pathMatcher = null;
+ return this;
}
public PathMatcher getPathMatcher() {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/CompletionItemDefaultsUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/CompletionItemDefaultsUtils.java
index 9e6d0214e..25916ba64 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/CompletionItemDefaultsUtils.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/CompletionItemDefaultsUtils.java
@@ -62,7 +62,10 @@ public static void process(CompletionResponse completionResponse, SharedSettings
*/
private static void setToMostCommonEditRange(List completionList,
CompletionItemDefaults itemDefaults) {
- Map> itemsByRange = completionList.stream()
+ Map> itemsByRange = completionList
+ .stream()
+ .filter(item -> item.getTextEdit() != null && item.getTextEdit().isLeft()
+ && item.getTextEdit().getLeft() != null)
.collect(Collectors.groupingBy(item -> item.getTextEdit().getLeft().getRange()));
int maxCount = 0;
Range mostCommonRange = null;
@@ -73,12 +76,17 @@ private static void setToMostCommonEditRange(List completionList
mostCommonRange = entry.getKey();
}
}
+ if (mostCommonRange == null) {
+ return;
+ }
itemsByRange.get(mostCommonRange).forEach(item -> {
item.setTextEditText(item.getTextEdit().getLeft().getNewText());
item.setTextEdit(null);
});
itemDefaults.setEditRange(Either.forLeft(mostCommonRange));
- completionList = itemsByRange.values().stream().flatMap(Collection::stream)
+ completionList = itemsByRange.values()
+ .stream()
+ .flatMap(Collection::stream)
.collect(Collectors.toList());
}
@@ -91,7 +99,8 @@ private static void setToMostCommonEditRange(List completionList
*/
private static void setToMostCommonInsertTextFormat(List completionList,
CompletionItemDefaults itemDefaults) {
- Map> itemsByInsertTextFormat = completionList.stream()
+ Map> itemsByInsertTextFormat = completionList
+ .stream()
.filter(item -> item.getInsertTextFormat() != null)
.collect(Collectors.groupingBy(item -> item.getInsertTextFormat()));
int maxCount = 0;
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java
index 7b2764e6b..309eaa9c9 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java
@@ -34,6 +34,8 @@ public class DOMUtils {
private static final String XSD_EXTENSION = ".xsd";
+ private static final String XSL_EXTENSION = ".xsl";
+
// DTD file extensions
private static final String DTD_EXTENSION = ".dtd";
@@ -189,6 +191,33 @@ public static boolean isDTD(String uri) {
&& (uri.endsWith(DTD_EXTENSION) || uri.endsWith(ENT_EXTENSION) || uri.endsWith(MOD_EXTENSION));
}
+ /**
+ * Returns true if the XML document is a XSL and false otherwise.
+ *
+ * @return true if the XML document is a XSL and false otherwise.
+ */
+ public static boolean isXSL(DOMDocument document) {
+ if (document == null) {
+ return false;
+ }
+ String uri = document.getDocumentURI();
+ if (isXSL(uri)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given URI is a XSL and false otherwise.
+ *
+ * @param uri the URI to check
+ * @return true if the given URI is a XSL and false otherwise.
+ */
+ public static boolean isXSL(String uri) {
+ return uri != null
+ && (uri.endsWith(XSL_EXTENSION));
+ }
+
/**
* Returns true if element contains only DOMText and false otherwise.
*
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/FilesUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/FilesUtils.java
index 6fc3548be..6e02b50dc 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/FilesUtils.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/FilesUtils.java
@@ -277,12 +277,13 @@ public static Path getPath(String uri) {
public static String encodePath(String path) {
return path.replace(" ", "%20");
}
-
+
/**
- * Very simple implementation of reading all content from a file into a string using the UTF-8 charset. Uses the System's line separator.
- * Not suited for reading large files.
+ * Very simple implementation of reading all content from a file into a string
+ * using the UTF-8 charset. Uses the System's line separator.
+ * Not suited for reading large files.
*
- * @param path the path to a readable file.
+ * @param path the path to a readable file.
* @return the contents of the file.
* @throws IOException if an error occurred while reading the file.
*/
@@ -295,12 +296,23 @@ public static String readString(Path path) throws IOException {
*
* @param path the path.
*
- * @return true if the file at the given path exists.
+ * @return true if the file at the given path exists.
*/
- public static boolean isValidPath(Path path){
+ public static boolean isValidPath(Path path) {
if (Files.exists(path)) {
return true;
}
return false;
}
+
+ /**
+ * Returns the file name of the given path.
+ *
+ * @param path the path.
+ *
+ * @return the file name of the given path.
+ */
+ public static String getFileName(Path path) {
+ return path.getName(path.getNameCount() - 1).toString();
+ }
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
index 8562db439..fcac579e2 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
@@ -438,7 +438,7 @@ public static int findStartWord(String text, int offset, Predicate is
* to the given min
and -1 if no word.
*/
public static int findStartWord(String text, int offset, int min, Predicate isValidChar) {
- if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
+ if (offset < 0 || offset >= text.length() /*|| !isValidChar.test(text.charAt(offset))*/) {
return -1;
}
for (int i = offset - 1; i >= min; i--) {
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 daecd3a5a..ac18cc39d 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
@@ -481,6 +481,38 @@
"parameterTypes": []
}]
},
+ {
+ "name": "org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression",
+ "allDeclaredFields": true,
+ "methods": [{
+ "name": "",
+ "parameterTypes": []
+ }]
+ },
+ {
+ "name": "org.eclipse.lemminx.extensions.filepath.settings.FilePathMapping",
+ "allDeclaredFields": true,
+ "methods": [{
+ "name": "",
+ "parameterTypes": []
+ }]
+ },
+ {
+ "name": "org.eclipse.lemminx.extensions.filepath.settings.FilePathSupport",
+ "allDeclaredFields": true,
+ "methods": [{
+ "name": "",
+ "parameterTypes": []
+ }]
+ },
+ {
+ "name": "org.eclipse.lemminx.extensions.filepath.settings.FilePathSupportSettings",
+ "allDeclaredFields": true,
+ "methods": [{
+ "name": "",
+ "parameterTypes": []
+ }]
+ },
{
"name": "org.eclipse.lemminx.settings.QuoteStyle",
"allDeclaredFields": true
diff --git a/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant
new file mode 100644
index 000000000..cc7109378
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.extensions.filepath.IFilePathSupportParticipant
@@ -0,0 +1,4 @@
+org.eclipse.lemminx.extensions.catalog.participants.CatalogFilePathSupportParticipant
+org.eclipse.lemminx.extensions.relaxng.grammar.rng.RNGFilePathSupportParticipant
+org.eclipse.lemminx.extensions.xsd.participants.XSDFilePathSupportParticipant
+org.eclipse.lemminx.extensions.xsl.participants.XSLFilePathSupportParticipant
\ No newline at end of file
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 e23229121..abdc794ec 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
@@ -6,7 +6,7 @@ org.eclipse.lemminx.extensions.xsl.XSLPlugin
org.eclipse.lemminx.extensions.catalog.XMLCatalogPlugin
org.eclipse.lemminx.extensions.xsi.XSISchemaPlugin
org.eclipse.lemminx.extensions.prolog.PrologPlugin
-org.eclipse.lemminx.extensions.general.FilePathPlugin
+org.eclipse.lemminx.extensions.filepath.FilePathPlugin
org.eclipse.lemminx.extensions.entities.EntitiesPlugin
org.eclipse.lemminx.extensions.xmlmodel.XMLModelPlugin
org.eclipse.lemminx.extensions.generators.FileContentGeneratorPlugin
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/services/snippets/new-xsl-snippets.json b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/services/snippets/new-xsl-snippets.json
new file mode 100644
index 000000000..015c916fd
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/services/snippets/new-xsl-snippets.json
@@ -0,0 +1,16 @@
+{
+ "New XSL Stylesheet": {
+ "prefix": [
+ "",
+ "body": [
+ "",
+ "\t",
+ "\t",
+ ""
+ ],
+ "label": "$description",
+ "description": "New XSL Stylesheet"
+ }
+ }
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/FilePathSettingsForTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/FilePathSettingsForTest.java
new file mode 100644
index 000000000..38bb50328
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/FilePathSettingsForTest.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.filepath;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathMapping;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathSupport;
+import org.eclipse.lemminx.extensions.filepath.settings.FilePathSupportSettings;
+
+/**
+ * {@link FilePathSupportSettings} for tests.
+ *
+ */
+public class FilePathSettingsForTest {
+
+ public static FilePathSupportSettings createFilePathsSettings() {
+ FilePathSupportSettings filePathsSettings = new FilePathSupportSettings();
+ FilePathSupport support = new FilePathSupport();
+ filePathsSettings.setFilePathSupport(support);
+ support.setMappings(createFilePathMappings());
+ return filePathsSettings;
+ }
+
+ private static List createFilePathMappings() {
+ List filePaths = new ArrayList<>();
+
+ FilePathMapping path = new FilePathMapping();
+ path.setPattern("**/*.xml");
+ filePaths.add(path);
+ /*
+ * {
+ * "xpath": "@path"
+ * },
+ * {
+ * "xpath": "path/text()"
+ * },
+ * {
+ * "xpath": "@paths",
+ * "separator": " "
+ * }
+ */
+ FilePathExpression attrPath = new FilePathExpression("@path");
+ FilePathExpression textPath = new FilePathExpression("path/text()");
+ FilePathExpression multiAttrPath = new FilePathExpression("@paths").setSeparator(' ');
+
+ path.setExpressions(Arrays.asList(attrPath, textPath, multiAttrPath));
+
+ return filePaths;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/AbstractFilePathCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/AbstractFilePathCompletionTest.java
new file mode 100644
index 000000000..e13d980b7
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/AbstractFilePathCompletionTest.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+* Copyright (c) 2019 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.filepath.participants;
+
+import static org.eclipse.lemminx.XMLAssert.c;
+import static org.eclipse.lemminx.XMLAssert.te;
+
+import org.eclipse.lemminx.AbstractCacheBasedTest;
+import org.eclipse.lemminx.XMLAssert;
+import org.eclipse.lemminx.XMLAssert.SettingsSaveContext;
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.extensions.filepath.FilePathSettingsForTest;
+import org.eclipse.lemminx.services.XMLLanguageService;
+import org.eclipse.lemminx.utils.FilesUtils;
+import org.eclipse.lsp4j.CompletionItem;
+
+/**
+ * FilePathCompletionTest
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class AbstractFilePathCompletionTest extends AbstractCacheBasedTest {
+
+ private static final String userDir = FilesUtils.encodePath(System.getProperty("user.dir")); // C:..\..\folderName
+ // || /bin/.../java
+ protected static final String userDirBackSlash = userDir.replace("/", "\\");
+ protected static final String userDirForwardSlash = userDir.replace("\\", "/");
+
+ protected static void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException {
+ testCompletionFor(xml, null, expectedItems);
+ }
+
+ protected static void testCompletionFor(String xml, Integer expectedItemCount, CompletionItem... expectedItems)
+ throws BadLocationException {
+ String fileURI = getFileUri("main.xml");
+ testCompletionFor(xml, fileURI, expectedItemCount, expectedItems);
+ }
+
+ protected static String getFileUri(String fileName) {
+ return "file://" + userDirForwardSlash + "/src/test/resources/filePathCompletion/" + fileName;
+ }
+
+ protected static void testCompletionFor(String xml, String fileURI, Integer expectedItemCount,
+ CompletionItem... expectedItems)
+ throws BadLocationException {
+ XMLAssert.testCompletionFor(new XMLLanguageService(), xml, null, ls -> {
+ ls.doSave(new SettingsSaveContext(FilePathSettingsForTest.createFilePathsSettings()));
+
+ }, fileURI, expectedItemCount, true, expectedItems);
+ }
+
+ protected static CompletionItem[] getCompletionItemList(int line, int startChar, int endChar,
+ String... fileOrFolderNames) {
+ int fOfSize = fileOrFolderNames.length;
+ CompletionItem[] items = new CompletionItem[fOfSize];
+
+ for (int i = 0; i < fOfSize; i++) {
+ String fOf = fileOrFolderNames[i];
+ items[i] = c(fOf, te(line, startChar, line, endChar, fOf), fOf);
+ }
+
+ return items;
+
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithDOCTYPETest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithDOCTYPETest.java
new file mode 100644
index 000000000..bd8fb3186
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithDOCTYPETest.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+* 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.filepath.participants;
+
+import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test with DOCTYPE.
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithDOCTYPETest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void empty() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 22, 22, "folderA", "folderB", "folderC", "NestedA",
+ "main.dtd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void dotSlash() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 24, 24, "folderA", "folderB", "folderC", "NestedA",
+ "main.dtd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void dotSlashFollowingBySlash() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 24, 25, "folderA", "folderB", "folderC", "NestedA",
+ "main.dtd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void backSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 24, 24, "folderA", "folderB", "folderC", "NestedA",
+ "main.dtd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void afterFolderA() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 2, items);
+ }
+
+ @Test
+ public void afterFolderABackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 2, items);
+ }
+
+ @Test
+ public void afterFolderB() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdB1.dtd");
+ testCompletionFor(xml, 1, items);
+ }
+
+ @Test
+ public void afterFolderBBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdB1.dtd");
+ testCompletionFor(xml, 1, items);
+ }
+
+ @Test
+ public void testFilePathNoCompletionMissingSystemId() throws BadLocationException {
+ String xml = "";
+ testCompletionFor(xml, 0);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSchemaLocationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSchemaLocationTest.java
new file mode 100644
index 000000000..a337d9297
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSchemaLocationTest.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+* 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.filepath.participants;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test with xsi:schemaLocation.
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithSchemaLocationTest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void empty() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(3, 37, 37, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd");
+ testCompletionFor(xml, 5, items);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/general/FilePathCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSettingsTest.java
similarity index 52%
rename from org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/general/FilePathCompletionTest.java
rename to org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSettingsTest.java
index 749586bc7..f5e94f0b7 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/general/FilePathCompletionTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSettingsTest.java
@@ -9,71 +9,85 @@
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
-package org.eclipse.lemminx.extensions.general;
+package org.eclipse.lemminx.extensions.filepath.participants;
-import static org.eclipse.lemminx.XMLAssert.c;
-import static org.eclipse.lemminx.XMLAssert.te;
import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
-import org.eclipse.lemminx.AbstractCacheBasedTest;
-import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.utils.FilesUtils;
-import org.eclipse.lemminx.utils.platform.Platform;
import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
- * FilePathCompletionTest
+ * File path support completion test with user settings.
*
* Test folders are in
* org.eclipse.lemminx/src/test/resources/filePathCompletion/
*/
-public class FilePathCompletionTest extends AbstractCacheBasedTest {
+public class FilePathCompletionWithSettingsTest extends AbstractFilePathCompletionTest {
- private static final String userDir = FilesUtils.encodePath(System.getProperty("user.dir")); // C:..\..\folderName
- // || /bin/.../java
- private static final String userDirBackSlash = userDir.replace("/", "\\");
- private static final String userDirForwardSlash = userDir.replace("\\", "/");
+ @Test
+ public void empty() throws BadLocationException {
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 9, 9, "folderA", "folderB", "folderC", "NestedA", "main.xml",
+ "main.xsd", "main.xsl", "main.dtd");
+ testCompletionFor(xml, 8, items);
+ }
@Test
public void testFilePathCompletion() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 10, 11, "folderA", "folderB", "NestedA");
- testCompletionFor(xml, items);
+ CompletionItem[] items = getCompletionItemList(0, 11, 11, "folderA", "folderB", "folderC", "NestedA",
+ "main.xml",
+ "main.xsd", "main.xsl", "main.dtd");
+ testCompletionFor(xml, 8, items);
}
@Test
public void testFilePathCompletionBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 10, 11, "folderA", "folderB", "NestedA");
- testCompletionFor(xml, items);
+ CompletionItem[] items = getCompletionItemList(0, 11, 11, "folderA", "folderB", "folderC", "NestedA",
+ "main.xml",
+ "main.xsd", "main.xsl", "main.dtd");
+ testCompletionFor(xml, 8, items);
}
@Test
public void testFilePathCompletionFolderA() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd");
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "xsdA1.xsd", "xsdA2.xsd");
testCompletionFor(xml, items);
}
@Test
public void testFilePathCompletionFolderABackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd");
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "xsdA1.xsd", "xsdA2.xsd");
testCompletionFor(xml, items);
}
@Test
public void testFilePathCompletionFolderB() throws BadLocationException {
String xml = "";
- testCompletionFor(xml, 0);
+ CompletionItem[] items = getCompletionItemList(0, 17, 17, "xsdB1.xsd", "xmlB1.xml", "dtdB1.dtd");
+ testCompletionFor(xml, 3, items);
}
@Test
public void testFilePathCompletionFolderBBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- testCompletionFor(xml, 0);
+ CompletionItem[] items = getCompletionItemList(0, 17, 17, "xsdB1.xsd", "xmlB1.xml", "dtdB1.dtd");
+ testCompletionFor(xml, 3, items);
}
@Test
@@ -81,9 +95,9 @@ public void testFilePathCompletionFolderBAbsolutePath() throws BadLocationExcept
String filePath = userDirForwardSlash + "/src/test/resources/filePathCompletion/folderB/"; // C:/.../src/test...
int filePathLength = filePath.length();
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 9 + filePathLength - 1, 9 + filePathLength, "xsdB1.xsd",
- "xmlB1.xml");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 9 + filePathLength, 9 + filePathLength, "xsdB1.xsd",
+ "xmlB1.xml", "dtdB1.dtd");
+ testCompletionFor(xml, 3, items);
}
@Test
@@ -94,9 +108,9 @@ public void testFilePathCompletionFolderBAbsolutePathBackSlash() throws BadLocat
String filePath = userDirBackSlash + "\\src\\test\\resources\\filePathCompletion\\folderB\\"; // C:\...\src\test...
int filePathLength = filePath.length();
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 9 + filePathLength - 1, 9 + filePathLength, "xsdB1.xsd",
- "xmlB1.xml");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 9 + filePathLength, 9 + filePathLength, "xsdB1.xsd",
+ "xmlB1.xml", "dtdB1.dtd");
+ testCompletionFor(xml, 3, items);
}
@Test
@@ -105,36 +119,42 @@ public void testFilePathCompletionFolderBAbsolutePathWithFileScheme() throws Bad
+ "/src/test/resources/filePathCompletion/folderB/";
int filePathLength = filePath.length();
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 9 + filePathLength - 1, 9 + filePathLength, "xsdB1.xsd",
- "xmlB1.xml");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 9 + filePathLength, 9 + filePathLength, "xsdB1.xsd",
+ "xmlB1.xml", "dtdB1.dtd");
+ testCompletionFor(xml, 3, items);
}
@Test
public void testFilePathCompletionNestedA() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 18, 19, "NestedB");
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "NestedB");
testCompletionFor(xml, 1, items);
}
@Test
public void testFilePathCompletionNestedABackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 18, 19, "NestedB");
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "NestedB");
testCompletionFor(xml, 1, items);
}
@Test
public void testFilePathCompletionNestedBIncomplete() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 26, 30, "nestedXSD.xsd");
+ CompletionItem[] items = getCompletionItemList(0, 27, 30, "nestedXSD.xsd");
testCompletionFor(xml, 1, items);
}
@Test
public void testFilePathCompletionNestedBIncompleteBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 26, 30, "nestedXSD.xsd");
+ CompletionItem[] items = getCompletionItemList(0, 27, 30, "nestedXSD.xsd");
testCompletionFor(xml, 1, items);
}
@@ -150,23 +170,6 @@ public void testFilePathCompletionExtraTextInValueBackSlash() throws BadLocation
testCompletionFor(xml, 0);
}
- @Test
- public void testFilePathCompletionExtraTextInValueAbsolute() throws BadLocationException {
- String filePath = userDirForwardSlash + "/src/test/resources/filePathCompletion/NestedA/NestedB/";
- int filePathLength = filePath.length();
- String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 29 + filePathLength - 1, 29 + filePathLength,
- "nestedXSD.xsd");
- testCompletionFor(xml, 1, items);
- }
-
- @Test
- public void testFilePathCompletionExtraTextInValueAbsoluteBackSlash() throws BadLocationException {
- String filePath = userDirBackSlash + "\\src\\test\\resources\\filePathCompletion\\NestedA\\NestedB\\";
- String xml = "";
- testCompletionFor(xml, Platform.isWindows ? 1 : 0);
- }
-
@Test
public void testFilePathCompletionBadFolder() throws BadLocationException {
String xml = "";
@@ -182,29 +185,35 @@ public void testFilePathCompletionBadFolderBackSlash() throws BadLocationExcepti
@Test
public void testFilePathCompletionStartWithDotDot() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 38, 39, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 39, 39, "xsdA1.xsd", "xsdA2.xsd", "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 4, items);
}
@Test
public void testFilePathCompletionStartWithDotDotBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 38, 39, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 39, 39, "xsdA1.xsd", "xsdA2.xsd", "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 4, items);
}
@Test
public void testFilePathCompletionStartWithDot() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "xsdA1.xsd", "xsdA2.xsd", "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 4, items);
}
@Test
public void testFilePathCompletionStartWithDotBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "xsdA1.xsd", "xsdA2.xsd", "dtdA1.dtd", "dtdA2.dtd");
+ testCompletionFor(xml, 4, items);
}
@Test
@@ -219,82 +228,22 @@ public void testFilePathCompletionEndsWithFileAndBackSlash() throws BadLocationE
testCompletionFor(xml, 0);
}
- @Test
- public void testFilePathCompletionNotValue() throws BadLocationException {
- String xml = "";
- testCompletionFor(xml, 0);
- }
-
- @Test
- public void testFilePathCompletionDTD() throws BadLocationException {
- String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 23, 24, "folderA", "folderB", "NestedA");
- testCompletionFor(xml, items);
- }
-
- @Test
- public void testFilePathCompletionDTDBackSlash() throws BadLocationException {
-
- String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 23, 24, "folderA", "folderB", "NestedA");
- testCompletionFor(xml, items);
- }
-
- @Test
- public void testFilePathCompletionDTDFolderA() throws BadLocationException {
- String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 31, 32, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, items);
- }
-
- @Test
- public void testFilePathCompletionDTDFolderABackSlash() throws BadLocationException {
- String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 31, 32, "xsdA1.xsd", "xsdA2.xsd");
- testCompletionFor(xml, items);
- }
-
- @Test
- public void testFilePathCompletionDTDFolderB() throws BadLocationException {
- String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 31, 32, "xsdB1.xsd", "xmlB1.xml");
- testCompletionFor(xml, 2, items);
- }
-
- @Test
- public void testFilePathCompletionDTDFolderBBackSlash() throws BadLocationException {
- String xml = "";
- CompletionItem[] items = getCompletionItemList("\\", 0, 31, 32, "xsdB1.xsd", "xmlB1.xml");
- testCompletionFor(xml, 2, items);
- }
-
- @Test
- public void testFilePathCompletionForEmptyDoctype() throws BadLocationException {
- String xml = "";
- testCompletionFor(xml, 11);
- }
-
- @Test
- public void testFilePathNoCompletionMissingSystemId() throws BadLocationException {
- String xml = "";
- testCompletionFor(xml, 0);
- }
-
@Test
public void testFilePathCompletionWithSpacesFolder() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 18, 19, "a@b", "with%20spaces");
+ CompletionItem[] items = getCompletionItemList(0, 19, 19, "a@b", "with%20spaces");
testCompletionFor(xml, 2, items);
}
@Test
public void testFilePathCompletionInsideSpecialChars() throws BadLocationException {
String xml = "";
- CompletionItem[] items = getCompletionItemList("/", 0, 11, 12, "a@b", "with%20spaces");
String fileURI = "file://" + userDirForwardSlash + "/src/test/resources/filePathCompletion/folderC/a@b/foo.xml";
- XMLAssert.testCompletionFor(xml, null, fileURI, 2, items);
+ CompletionItem[] items = getCompletionItemList(0, 12, 12, "a@b", "with%20spaces");
+ testCompletionFor(xml, fileURI, 2, items);
}
+ @Disabled
@Test
public void testFilePathCompletionWithBrokenAbsoluteWindowsPath() throws BadLocationException {
String xml = "";
@@ -307,29 +256,30 @@ public void testFilePathCompletionWithBrokenAbsoluteWindowsPath() throws BadLoca
testCompletionFor(xml, 0);
}
- private static void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException {
- testCompletionFor(xml, null, expectedItems);
- }
+ // Test with multiple file path
- private static void testCompletionFor(String xml, Integer expectedItemCount, CompletionItem... expectedItems)
- throws BadLocationException {
- String fileURI = "file://" + userDirForwardSlash + "/src/test/resources/filePathCompletion/main.xml";
- XMLAssert.testCompletionFor(xml, null, fileURI, expectedItemCount, expectedItems);
+ @Test
+ public void testFilePathCompletionExtraTextInValueAbsolute() throws BadLocationException {
+ String filePath = userDirForwardSlash + "/src/test/resources/filePathCompletion/NestedA/NestedB/";
+ int filePathLength = filePath.length();
+ String xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 30 + filePathLength, 30 + filePathLength, "nestedXSD.xsd");
+ testCompletionFor(xml, 1, items);
}
- private static CompletionItem[] getCompletionItemList(String slash, int line, int startChar, int endChar,
- String... fileOrFolderNames) {
- String s = slash;
- int fOfSize = fileOrFolderNames.length;
- CompletionItem[] items = new CompletionItem[fOfSize];
-
- for (int i = 0; i < fOfSize; i++) {
- String fOf = s + fileOrFolderNames[i];
- items[i] = c(fOf, te(line, startChar, line, endChar, fOf), fOf);
+ @Test
+ public void testFilePathCompletionExtraTextInValueAbsoluteBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
}
+ String filePath = userDirBackSlash + "\\src\\test\\resources\\filePathCompletion\\NestedA\\NestedB\\";
+ int filePathLength = filePath.length();
- return items;
+ String xml = "";
+ testCompletionFor(xml, 0);
+ xml = "";
+ CompletionItem[] items = getCompletionItemList(0, 30 + filePathLength, 30 + filePathLength, "nestedXSD.xsd");
+ testCompletionFor(xml, 1, items);
}
-
}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithtNoNamespaceSchemaLocationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithtNoNamespaceSchemaLocationTest.java
new file mode 100644
index 000000000..9406e4fa7
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithtNoNamespaceSchemaLocationTest.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+* 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.filepath.participants;
+
+import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test with xsi:noNamespaceSchemaLocation.
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithtNoNamespaceSchemaLocationTest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void empty() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 32, 32, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void dotSlash() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 34, 34, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd");
+ testCompletionFor(xml, 5, items);
+
+ }
+
+ @Test
+ public void dotSlashFollowingBySlash() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 34, 35, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void backSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 34, 34, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd");
+ testCompletionFor(xml, 5, items);
+ }
+
+ @Test
+ public void afterFolderA() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 42, 42, "xsdA1.xsd", "xsdA2.xsd");
+ testCompletionFor(xml, 2, items);
+ }
+
+ @Test
+ public void afterFolderABackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 42, 42, "xsdA1.xsd", "xsdA2.xsd");
+ testCompletionFor(xml, 2, items);
+ }
+
+ @Test
+ public void afterFolderB() throws BadLocationException {
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 42, 42, "xsdB1.xsd");
+ testCompletionFor(xml, 1, items);
+ }
+
+ @Test
+ public void afterFolderBBackSlash() throws BadLocationException {
+ if (!isWindows) {
+ return;
+ }
+ String xml = "\n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 42, 42, "xsdB1.xsd");
+ testCompletionFor(xml, 1, items);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/FilePathCompletionWithRNGTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/FilePathCompletionWithRNGTest.java
new file mode 100644
index 000000000..335fddd98
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/FilePathCompletionWithRNGTest.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.relaxng.grammar.rng;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.extensions.filepath.participants.AbstractFilePathCompletionTest;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test with
+ *
+ *
+ * - include/@href
+ * - externalRef/@href
+ *
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithRNGTest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void includeHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 16, 16, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd", "main.dtd", "main.xml", "main.xsl");
+ testCompletionFor(xml, 8, items);
+ }
+
+ @Test
+ public void externalRefHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 20, 20, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd", "main.dtd", "main.xml", "main.xsl");
+ testCompletionFor(xml, 8, items);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/FilePathCompletionWithXSDTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/FilePathCompletionWithXSDTest.java
new file mode 100644
index 000000000..12c7f1d34
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/FilePathCompletionWithXSDTest.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.xsd;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.extensions.filepath.participants.AbstractFilePathCompletionTest;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test with
+ *
+ *
+ * - include/@href
+ * - externalRef/@href
+ *
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithXSDTest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void includeHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 16, 16, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd", "main.dtd", "main.xml", "main.xsl");
+ testCompletionFor(xml, 8, items);
+ }
+
+ @Test
+ public void externalRefHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(2, 20, 20, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsd", "main.dtd", "main.xml", "main.xsl");
+ testCompletionFor(xml, 8, items);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsl/FilePathCompletionWithXSLTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsl/FilePathCompletionWithXSLTest.java
new file mode 100644
index 000000000..07898cf92
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsl/FilePathCompletionWithXSLTest.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+* 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.xsl;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.extensions.filepath.participants.AbstractFilePathCompletionTest;
+import org.eclipse.lsp4j.CompletionItem;
+import org.junit.jupiter.api.Test;
+
+/**
+ * File path support completion test for XSL with
+ *
+ *
+ * - include/@href
+ * - import/@href
+ *
+ *
+ * Test folders are in
+ * org.eclipse.lemminx/src/test/resources/filePathCompletion/
+ */
+public class FilePathCompletionWithXSLTest extends AbstractFilePathCompletionTest {
+
+ @Test
+ public void includeHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(1, 20, 20, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsl");
+ testCompletionFor(xml, getFileUri("test.xsl"), 5, items);
+ }
+
+ @Test
+ public void importHref() throws BadLocationException {
+ String xml = "\n"
+ + " \n"
+ + "";
+ CompletionItem[] items = getCompletionItemList(1, 19, 19, "folderA", "folderB", "folderC", "NestedA",
+ "main.xsl");
+ testCompletionFor(xml, getFileUri("test.xsl"), 5, items);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA1.dtd b/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA1.dtd
new file mode 100644
index 000000000..e69de29bb
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA2.dtd b/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderA/dtdA2.dtd
new file mode 100644
index 000000000..e69de29bb
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderB/dtdB1.dtd b/org.eclipse.lemminx/src/test/resources/filePathCompletion/folderB/dtdB1.dtd
new file mode 100644
index 000000000..e69de29bb
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.dtd b/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.dtd
new file mode 100644
index 000000000..e69de29bb
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsd b/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsd
new file mode 100644
index 000000000..e69de29bb
diff --git a/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsl b/org.eclipse.lemminx/src/test/resources/filePathCompletion/main.xsl
new file mode 100644
index 000000000..e69de29bb