From ac8015e708969e4232ae01f18f646271ea65c60d 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 --- .../CatalogFilePathExpressionProvider.java | 35 +++ .../XMLCatalogDiagnosticsParticipant.java | 2 +- .../extensions/catalog/XMLCatalogPlugin.java | 3 + .../extensions/filepath/FilePathPlugin.java | 138 +++++++++ .../filepath/IFilePathExpression.java | 15 + .../filepath/IFilePathExpressionProvider.java | 10 + .../filepath/SimpleFilePathExpression.java | 32 ++ .../FilePathCompletionParticipant.java | 258 ++++++++++++++++ .../FilePathCompletionResult.java | 112 +++++++ .../filepath/settings/FilePathExpression.java | 96 ++++++ .../filepath/settings/FilePathMapping.java | 46 +++ .../filepath/settings/FilePathSupport.java | 16 + .../settings/FilePathSupportSettings.java | 66 ++++ .../extensions/general/FilePathPlugin.java | 40 --- .../FilePathCompletionParticipant.java | 286 ------------------ .../RNGFilePathExpressionProvider.java | 36 +++ .../extensions/relaxng/RelaxNGPlugin.java | 3 + .../xsd/XSDFilePathExpressionProvider.java | 36 +++ .../lemminx/extensions/xsd/XSDPlugin.java | 3 + .../XSDDocumentLinkParticipant.java | 4 +- .../xsl/XSLFilePathExpressionProvider.java | 36 +++ .../lemminx/extensions/xsl/XSLPlugin.java | 4 + .../lemminx/settings/PathPatternMatcher.java | 3 +- .../utils/CompletionItemDefaultsUtils.java | 15 +- .../org/eclipse/lemminx/utils/DOMUtils.java | 29 ++ .../eclipse/lemminx/utils/StringUtils.java | 2 +- .../META-INF/native-image/reflect-config.json | 32 ++ ....lemminx.services.extensions.IXMLExtension | 2 +- .../filepath/FilePathSettingsForTest.java | 69 +++++ .../AbstractFilePathCompletionTest.java | 72 +++++ .../FilePathCompletionWithDOCTYPETest.java | 103 +++++++ .../FilePathCompletionWithSettingsTest.java} | 209 +++++-------- .../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 37 files changed, 1346 insertions(+), 467 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogFilePathExpressionProvider.java 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/IFilePathExpressionProvider.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/RNGFilePathExpressionProvider.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDFilePathExpressionProvider.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLFilePathExpressionProvider.java 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 rename org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/{general/FilePathCompletionTest.java => filepath/participants/FilePathCompletionWithSettingsTest.java} (53%) 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 diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogFilePathExpressionProvider.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogFilePathExpressionProvider.java new file mode 100644 index 000000000..82c311ec5 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogFilePathExpressionProvider.java @@ -0,0 +1,35 @@ +package org.eclipse.lemminx.extensions.catalog; + +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.IFilePathExpressionProvider; +import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; +import org.eclipse.lemminx.utils.DOMUtils; + +public class CatalogFilePathExpressionProvider implements IFilePathExpressionProvider { + + private static final List CATALOG_FILE_PATH_EXPRESSIONS; + + static { + CATALOG_FILE_PATH_EXPRESSIONS = Arrays.asList(createUriExpression()); + } + + private static FilePathExpression createUriExpression() { + FilePathExpression uri = new FilePathExpression(); + uri.setXPath("@uri"); + return uri; + } + + @Override + public List getExpressions(DOMDocument document) { + if (!DOMUtils.isCatalog(document)) { + return Collections.emptyList(); + } + return CATALOG_FILE_PATH_EXPRESSIONS; + } + +} 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/XMLCatalogDiagnosticsParticipant.java index f2bea7df6..e7c4a77a6 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/XMLCatalogDiagnosticsParticipant.java @@ -45,7 +45,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/XMLCatalogPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java index 00f751d78..fb027cf41 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 @@ -21,6 +21,7 @@ import org.eclipse.lemminx.client.PathFeature; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings; +import org.eclipse.lemminx.extensions.filepath.FilePathPlugin; import org.eclipse.lemminx.services.IXMLNotificationService; import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; @@ -71,6 +72,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerDocumentLinkParticipant(documentLinkParticipant); registry.registerDiagnosticsParticipant(diagnosticsParticipant); registry.registerCodeLensParticipant(codeLensParticipant); + // register file path support for XML catalog + FilePathPlugin.registerFilePathExpressionProvider(new CatalogFilePathExpressionProvider(), registry); } @Override 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..7b3ebdae2 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java @@ -0,0 +1,138 @@ +/******************************************************************************* +* 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.List; + +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; + +/** + * FilePathPlugin + */ +public class FilePathPlugin implements IXMLExtension { + + private final FilePathCompletionParticipant completionParticipant; + private FilePathSupportSettings filePathsSettings; + private XMLExtensionsRegistry registry; + + private static class ContributedProviderCache extends ArrayList { + + } + + 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) { + this.registry = 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<>(); + fillFromContribution(xmlDocument, expressions); + fillFromUserSettings(xmlDocument, expressions); + return expressions; + } + + private void fillFromContribution(DOMDocument xmlDocument, List expressions) { + ContributedProviderCache cache = registry.getComponent(ContributedProviderCache.class); + if (cache != null) { + for (IFilePathExpressionProvider provider : cache) { + List providerExpressions = provider.getExpressions(xmlDocument); + if (providerExpressions != null && !providerExpressions.isEmpty()) { + expressions.addAll(providerExpressions); + } + } + } + } + + 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 static void registerFilePathExpressionProvider(IFilePathExpressionProvider provider, + XMLExtensionsRegistry registry) { + ContributedProviderCache cache = registry.getComponent(ContributedProviderCache.class); + if (cache == null) { + cache = new ContributedProviderCache(); + registry.registerComponent(cache); + } + cache.add(provider); + } +} \ 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..a478f1a78 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpression.java @@ -0,0 +1,15 @@ +package org.eclipse.lemminx.extensions.filepath; + +import java.nio.file.Path; + +import org.w3c.dom.Node; + +public interface IFilePathExpression { + + boolean match(Node node); + + Character getSeparator(); + + boolean acceptPath(Path path); + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpressionProvider.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpressionProvider.java new file mode 100644 index 000000000..8f48e44fc --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/IFilePathExpressionProvider.java @@ -0,0 +1,10 @@ +package org.eclipse.lemminx.extensions.filepath; + +import java.util.List; + +import org.eclipse.lemminx.dom.DOMDocument; + +public interface IFilePathExpressionProvider { + + List getExpressions(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..ba27b9e39 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/SimpleFilePathExpression.java @@ -0,0 +1,32 @@ +package org.eclipse.lemminx.extensions.filepath; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.w3c.dom.Node; + +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..0548d6a0c --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java @@ -0,0 +1,258 @@ +/******************************************************************************* +* 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 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.dom.SchemaLocationHint; +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: + * + *
    + *
  • attribute value: + * + *
    + * <item path="file:///C:/folder" />
    + * <item path="file:///C:/folder file:///C:/file.txt" />
    + * <item path="/folder" />
    + * 
    + * + *
  • + *
  • DTD DOCTYPE SYSTEM + * + *
    + * <!DOCTYPE parent SYSTEM "file.dtd">
    + * 
    + * + *
  • + * + *
+ * + *

+ * + *

+ */ +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(path.getName(path.getNameCount() - 1).toString()); + }; + }; + + private static final IFilePathExpression NO_NAMESPACE_SCHEMALOCATION_FILE_PATH_EXPRESSION = new SimpleFilePathExpression() { + + @Override + protected boolean acceptFile(Path path) { + return DOMUtils.isXSD(path.getName(path.getNameCount() - 1).toString()); + }; + }; + + private static final IFilePathExpression SCHEMALOCATION_FILE_PATH_EXPRESSION = new SimpleFilePathExpression() { + + @Override + protected boolean acceptFile(Path path) { + return DOMUtils.isXSD(path.getName(path.getNameCount() - 1).toString()); + }; + + 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()) { + for (SchemaLocationHint schemaLocationHint : schemaLocation.getSchemaLocationHints()) { + if (DOMNode.isIncluded(schemaLocationHint, completionOffset)) { + addFileCompletionItems(document, + schemaLocationHint.getStart() + 1 /* increment to be after the quote */, + schemaLocationHint.getEnd() - 1, completionOffset, SCHEMALOCATION_FILE_PATH_EXPRESSION, + response); + break; + } + } + 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 { + 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) { + return; + } + Range replaceRange = XMLPositionUtility.createRange(result.getStart(), result.getEnd(), 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 f, Range replaceRange, ICompletionResponse response) { + 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 = 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); + } +} \ 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..fe370fa9f --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java @@ -0,0 +1,112 @@ +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; + +public class FilePathCompletionResult { + + private static final Predicate isStartValidChar = (c) -> c != '/' && c != '\\'; + + private final int start; + + private final int end; + + private final Path baseDir; + + public FilePathCompletionResult(int start, int end, Path baseDir) { + super(); + this.start = start; + this.end = end; + this.baseDir = baseDir; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public Path getBaseDir() { + return baseDir; + } + + 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 + 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 && !isStartValidChar.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..ff386f643 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathExpression.java @@ -0,0 +1,96 @@ +/******************************************************************************* +* 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.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 acceptPatterns; + + public String getXPath() { + return xpath; + } + + @Override + public Character getSeparator() { + return separator; + } + + public List getAcceptPatterns() { + return acceptPatterns; + } + + public FilePathExpression setXPath(String xpath) { + this.xpath = xpath; + return this; + } + + public FilePathExpression setSeparator(Character separator) { + this.separator = separator; + return this; + } + + public FilePathExpression setAcceptPatterns(List acceptPatterns) { + this.acceptPatterns = acceptPatterns; + 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 (acceptPatterns == null || acceptPatterns.isEmpty()) { + return true; + } + String fileName = path.getName(path.getNameCount() - 1).toString(); + for (String fileExtension : acceptPatterns) { + 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..aff39c225 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/settings/FilePathSupport.java @@ -0,0 +1,16 @@ +package org.eclipse.lemminx.extensions.filepath.settings; + +import java.util.List; + +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..9f5001725 --- /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": [ + // 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: - * - *
    - *
  • attribute value: - * - *
    - * <item path="file:///C:/folder" />
    - * <item path="file:///C:/folder file:///C:/file.txt" />
    - * <item path="/folder" />
    - * 
    - * - *
  • - *
  • DTD DOCTYPE SYSTEM - * - *
    - * <!DOCTYPE parent SYSTEM "file.dtd">
    - * 
    - * - *
  • - * - *
- * - *

- * - *

- */ -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/RNGFilePathExpressionProvider.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/RNGFilePathExpressionProvider.java new file mode 100644 index 000000000..750892f46 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/RNGFilePathExpressionProvider.java @@ -0,0 +1,36 @@ +package org.eclipse.lemminx.extensions.relaxng; + +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.IFilePathExpressionProvider; +import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; +import org.eclipse.lemminx.utils.DOMUtils; + +public class RNGFilePathExpressionProvider implements IFilePathExpressionProvider { + + private static final List RNG_FILE_PATH_EXPRESSIONS; + + static { + RNG_FILE_PATH_EXPRESSIONS = Arrays.asList(createExpression("include/@href"), + createExpression("externalRef/@href")); + } + + private static FilePathExpression createExpression(String xpath) { + FilePathExpression expression = new FilePathExpression(); + expression.setXPath(xpath); + return expression; + } + + @Override + public List getExpressions(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/relaxng/RelaxNGPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/RelaxNGPlugin.java index 5e1330f7e..a61505a56 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/RelaxNGPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/RelaxNGPlugin.java @@ -14,6 +14,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelProvider; +import org.eclipse.lemminx.extensions.filepath.FilePathPlugin; import org.eclipse.lemminx.extensions.relaxng.grammar.rng.RNGCodeLensParticipant; import org.eclipse.lemminx.extensions.relaxng.grammar.rng.RNGCompletionParticipant; import org.eclipse.lemminx.extensions.relaxng.grammar.rng.RNGDefinitionParticipant; @@ -99,6 +100,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerRenameParticipant(renameParticipant); registry.registerDocumentLinkParticipant(documentLinkParticipant); registry.registerDiagnosticsParticipant(diagnosticsParticipant); + // register file path support for RNG + FilePathPlugin.registerFilePathExpressionProvider(new RNGFilePathExpressionProvider(), registry); } @Override diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDFilePathExpressionProvider.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDFilePathExpressionProvider.java new file mode 100644 index 000000000..bb70caf72 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDFilePathExpressionProvider.java @@ -0,0 +1,36 @@ +package org.eclipse.lemminx.extensions.xsd; + +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.IFilePathExpressionProvider; +import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; +import org.eclipse.lemminx.utils.DOMUtils; + +public class XSDFilePathExpressionProvider implements IFilePathExpressionProvider { + + private static final List XSD_FILE_PATH_EXPRESSIONS; + + static { + XSD_FILE_PATH_EXPRESSIONS = Arrays.asList(createExpression("include/@schemaLocation"), + createExpression("import/@schemaLocation")); + } + + private static FilePathExpression createExpression(String xpath) { + FilePathExpression expression = new FilePathExpression(); + expression.setXPath(xpath); + return expression; + } + + @Override + public List getExpressions(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/xsd/XSDPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java index 88dcb2645..ce50c660c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java @@ -15,6 +15,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelProvider; +import org.eclipse.lemminx.extensions.filepath.FilePathPlugin; import org.eclipse.lemminx.extensions.xsd.contentmodel.CMXSDContentModelProvider; import org.eclipse.lemminx.extensions.xsd.participants.XSDCodeLensParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDCompletionParticipant; @@ -99,6 +100,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerHighlightingParticipant(highlightingParticipant); registry.registerRenameParticipant(renameParticipant); registry.registerDocumentLinkParticipant(documentLinkParticipant); + // register file path support for XSD + FilePathPlugin.registerFilePathExpressionProvider(new XSDFilePathExpressionProvider(), registry); } @Override 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/xsl/XSLFilePathExpressionProvider.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLFilePathExpressionProvider.java new file mode 100644 index 000000000..9d3f31337 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLFilePathExpressionProvider.java @@ -0,0 +1,36 @@ +package org.eclipse.lemminx.extensions.xsl; + +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.IFilePathExpressionProvider; +import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; +import org.eclipse.lemminx.utils.DOMUtils; + +public class XSLFilePathExpressionProvider implements IFilePathExpressionProvider { + + private static final List XSL_FILE_PATH_EXPRESSIONS; + + static { + XSL_FILE_PATH_EXPRESSIONS = Arrays.asList(createExpression("include/@href"), + createExpression("import/@href")); + } + + private static FilePathExpression createExpression(String xpath) { + FilePathExpression expression = new FilePathExpression(); + expression.setXPath(xpath); + return expression; + } + + @Override + public List getExpressions(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/extensions/xsl/XSLPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLPlugin.java index 9f03b3b5c..68770a40d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsl/XSLPlugin.java @@ -12,6 +12,7 @@ */ package org.eclipse.lemminx.extensions.xsl; +import org.eclipse.lemminx.extensions.filepath.FilePathPlugin; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; import org.eclipse.lsp4j.InitializeParams; @@ -27,6 +28,9 @@ public class XSLPlugin implements IXMLExtension { public void start(InitializeParams params, XMLExtensionsRegistry registry) { uiResolver = new XSLURIResolverExtension(registry.getDocumentProvider()); registry.getResolverExtensionManager().registerResolver(uiResolver); + // register file path support for XML catalog + FilePathPlugin.registerFilePathExpressionProvider(new XSLFilePathExpressionProvider(), registry); + } @Override 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/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.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/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..54cf119e3 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/FilePathSettingsForTest.java @@ -0,0 +1,69 @@ +/******************************************************************************* +* 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" + * } + */ + FilePathExpression attrPath = new FilePathExpression(); + attrPath.setXPath("@path"); + + FilePathExpression textPath = new FilePathExpression(); + textPath.setXPath("path/text()"); + + FilePathExpression multiAttrPath = new FilePathExpression(); + multiAttrPath.setXPath("@paths"); + multiAttrPath.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..19f4c0b6f --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/AbstractFilePathCompletionTest.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* 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 = "file://" + userDirForwardSlash + "/src/test/resources/filePathCompletion/main.xml"; + testCompletionFor(xml, fileURI, expectedItemCount, expectedItems); + } + + 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..10072513f --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithDOCTYPETest.java @@ -0,0 +1,103 @@ +/******************************************************************************* +* 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.utils.platform.Platform.isWindows; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lsp4j.CompletionItem; +import org.junit.jupiter.api.Test; + +/** + * FilePathCompletionTest + * + * Test folders are in + * org.eclipse.lemminx/src/test/resources/filePathCompletion/ + */ +public class FilePathCompletionWithDOCTYPETest extends AbstractFilePathCompletionTest { + + @Test + public void testFilePathCompletionDTD() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 24, 24, "folderA", "folderB", "folderC", "NestedA", + "main.dtd"); + testCompletionFor(xml, 5, items); + + } + + @Test + public void testFilePathCompletionDTDFollowingBySlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 24, 25, "folderA", "folderB", "NestedA"); + testCompletionFor(xml, items); + } + + @Test + public void testFilePathCompletionDTDBackSlash() 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 testFilePathCompletionDTDFolderA() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdA1.dtd", "dtdA2.dtd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionDTDFolderABackSlash() throws BadLocationException { + if (!isWindows) { + return; + } + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdA1.dtd", "dtdA2.dtd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionDTDFolderB() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdB1.dtd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionDTDFolderBBackSlash() throws BadLocationException { + if (!isWindows) { + return; + } + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 32, 32, "dtdB1.dtd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionForEmptyDoctype() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 22, 22, "folderA", "folderB", "folderC", "NestedA", + "main.dtd"); + testCompletionFor(xml, 5, 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/general/FilePathCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionWithSettingsTest.java similarity index 53% 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..f358e9991 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,18 +9,14 @@ * 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; /** @@ -29,51 +25,57 @@ * Test folders are in * org.eclipse.lemminx/src/test/resources/filePathCompletion/ */ -public class FilePathCompletionTest extends AbstractCacheBasedTest { - - 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("\\", "/"); +public class FilePathCompletionWithSettingsTest extends AbstractFilePathCompletionTest { @Test public void testFilePathCompletion() throws BadLocationException { String xml = ""; - CompletionItem[] items = getCompletionItemList("/", 0, 10, 11, "folderA", "folderB", "NestedA"); + CompletionItem[] items = getCompletionItemList(0, 11, 11, "folderA", "folderB", "NestedA"); testCompletionFor(xml, items); } @Test public void testFilePathCompletionBackSlash() throws BadLocationException { + if (!isWindows) { + return; + } String xml = ""; - CompletionItem[] items = getCompletionItemList("\\", 0, 10, 11, "folderA", "folderB", "NestedA"); + CompletionItem[] items = getCompletionItemList(0, 11, 11, "folderA", "folderB", "NestedA"); testCompletionFor(xml, 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 +83,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 +96,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 +107,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 +158,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 +173,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 @@ -222,79 +219,26 @@ public void testFilePathCompletionEndsWithFileAndBackSlash() throws BadLocationE @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"); + CompletionItem[] items = getCompletionItemList(0, 9, 9, "folderA", "folderB", "folderC", "NestedA", "main.xml"); 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 +251,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/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