From ada3c1bdba7c2b15738018e966b33bda9643881d Mon Sep 17 00:00:00 2001 From: Nikolas Komonen Date: Tue, 4 Jun 2019 12:11:18 -0400 Subject: [PATCH] File completion in attribute value Provides file completion when the user starts typing a path in an attribute value Displays all folders and files as of now. Fixes #345 Signed-off-by: Nikolas Komonen --- .../lsp4xml/commons/ParentProcessWatcher.java | 6 +- .../extensions/general/FilePathPlugin.java | 46 ++++ .../FilePathCompletionParticipant.java | 235 ++++++++++++++++++ .../utils/CompletionSortTextHelper.java | 56 +++++ .../eclipse/lsp4xml/utils/StringUtils.java | 122 +++++++++ ....lsp4xml.services.extensions.IXMLExtension | 3 +- .../general/FilePathCompletionTest.java | 155 ++++++++++++ .../NestedA/NestedB/nestedXSD.xsd | 0 .../filePathCompletion/folderA/xsdA1.xsd | 0 .../filePathCompletion/folderA/xsdA2.xsd | 0 .../filePathCompletion/folderB/xmlB1.xml | 0 .../filePathCompletion/folderB/xsdB1.xsd | 0 .../resources/filePathCompletion/main.xml | 0 13 files changed, 619 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/FilePathPlugin.java create mode 100644 org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java create mode 100644 org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelper.java create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/general/FilePathCompletionTest.java create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/NestedA/NestedB/nestedXSD.xsd create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA1.xsd create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA2.xsd create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xmlB1.xml create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xsdB1.xsd create mode 100644 org.eclipse.lsp4xml/src/test/resources/filePathCompletion/main.xml diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/commons/ParentProcessWatcher.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/commons/ParentProcessWatcher.java index 1df863748c..4d7a3f0726 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/commons/ParentProcessWatcher.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/commons/ParentProcessWatcher.java @@ -19,11 +19,11 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.google.common.io.Closeables; + import org.eclipse.lsp4j.jsonrpc.MessageConsumer; import org.eclipse.lsp4j.services.LanguageServer; -import com.google.common.io.Closeables; - /** * Watches the parent process PID and invokes exit if it is no longer available. * This implementation waits for periods of inactivity to start querying the PIDs. @@ -38,7 +38,7 @@ public final class ParentProcessWatcher implements Runnable, Function= 0; + public static final boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("win") >= 0; private static final long INACTIVITY_DELAY_SECS = 30 *1000; private static final int POLL_DELAY_SECS = 10; diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/FilePathPlugin.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/FilePathPlugin.java new file mode 100644 index 0000000000..19c487adc8 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/FilePathPlugin.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* 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 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.extensions.general; + +import org.eclipse.lsp4j.InitializeParams; +import org.eclipse.lsp4xml.extensions.general.completion.FilePathCompletionParticipant; +import org.eclipse.lsp4xml.services.extensions.IXMLExtension; +import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lsp4xml.services.extensions.save.ISaveContext; + +/** + * 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); + } + + @Override + public void doSave(ISaveContext context) { + + } + + +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java new file mode 100644 index 0000000000..4907e34ee2 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java @@ -0,0 +1,235 @@ +/******************************************************************************* +* 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 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.extensions.general.completion; + +import static org.eclipse.lsp4xml.utils.StringUtils.getNormalizedPath; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.xml.crypto.dsig.XMLObject; + +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.lsp4xml.commons.BadLocationException; +import org.eclipse.lsp4xml.dom.DOMDocument; +import org.eclipse.lsp4xml.services.extensions.ICompletionParticipant; +import org.eclipse.lsp4xml.services.extensions.ICompletionRequest; +import org.eclipse.lsp4xml.services.extensions.ICompletionResponse; +import org.eclipse.lsp4xml.settings.SharedSettings; +import org.eclipse.lsp4xml.utils.CompletionSortTextHelper; +import org.eclipse.lsp4xml.utils.StringUtils; + +/** + * FilePathCompletionParticipant + */ +public class FilePathCompletionParticipant implements ICompletionParticipant { + + public static final String FILE_SCHEME = "file"; + + private static final FilenameFilter xsdFilter = new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + if(name.matches(".+\\.[^\\s]+$") == false) { + return true; + } + return name.toLowerCase().endsWith(".xsd"); + } + }; + + @Override + public void onTagOpen(ICompletionRequest completionRequest, ICompletionResponse completionResponse) throws Exception { + + } + + @Override + public void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception { + + } + + @Override + public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, + ICompletionResponse response, SharedSettings settings) throws Exception { + + } + + @Override + public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, + ICompletionResponse response, SharedSettings settings) throws Exception { + + DOMDocument xmlDocument = request.getXMLDocument(); + String text = xmlDocument.getText(); + + //Get full attribute value range + // +- 1 since it includes the quotations + int documentStartOffset = xmlDocument.offsetAt(fullRange.getStart()) + 1; + int documentEndOffset = xmlDocument.offsetAt(fullRange.getEnd()) - 1; + String fullAttributeValue = text.substring(documentStartOffset, documentEndOffset); + + + //Get uri value and range from fullAttributeValue + int completionOffset = request.getOffset(); //offset after the typed character + int parsedAttributeStartOffset = StringUtils.getOffsetAfterWhitespace(fullAttributeValue, completionOffset - documentStartOffset) + documentStartOffset; //first character of URI + String attributeValue = text.substring(parsedAttributeStartOffset, completionOffset); //sub value of fullAttributeValue + Position startValue = xmlDocument.positionAt(parsedAttributeStartOffset); + Position endValue = xmlDocument.positionAt(completionOffset); + fullRange = new Range(startValue, endValue); + + + //Get the URI string from the attribute value in case it has a file scheme header (eg: "file://") + String tempCurrVal; + String fileScheme = null; + try { + URI attributeValueURI = URI.create(attributeValue); + tempCurrVal = attributeValueURI.getPath(); + fileScheme = attributeValueURI.getScheme(); + } catch (IllegalArgumentException e) { + tempCurrVal = attributeValue; + } + if(FILE_SCHEME.equals(fileScheme) && !(tempCurrVal.startsWith("\\") || tempCurrVal.startsWith("/"))) { + return; //use of 'file://' and the path was not absolute + } + + + //Get the normalized URI string from the parent directory file + String uriString = xmlDocument.getTextDocument().getUri(); + String directoryOfFile = Paths.get(uriString).getParent().toString(); + URI uri = URI.create(directoryOfFile); + String scheme = uri.getScheme(); + if(!(scheme == null || FILE_SCHEME.equals(scheme))) { + return; + } + directoryOfFile = uri.getPath(); + + //Try to get a proper path from the given values + Path validAttributeValuePath = getNormalizedPath(directoryOfFile, tempCurrVal); + + if(validAttributeValuePath == null) { + return; + } + + File f = new File(validAttributeValuePath.toString()); + if(f.isFile()) { + return; + } + + String slash = StringUtils.getFilePathSlash(attributeValue); + + //Get adjusted range for the completion item (insert at end, or overwrite some existing text in the path) + Range replaceRange = adjustReplaceRange(xmlDocument, fullRange, attributeValue, slash); + + createNextValidCompletionPaths(validAttributeValuePath, slash, replaceRange, response, 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 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 void createNextValidCompletionPaths(Path pathToAttributeDirectory, String slash, Range replaceRange, ICompletionResponse response, + FilenameFilter filter) { + + File[] proposedFiles = gatherFiles(pathToAttributeDirectory, filter); + if (proposedFiles != null) { + for (File child : proposedFiles) { + if (child != null) { + createFilePathCompletionItem(pathToAttributeDirectory, child, replaceRange, response, slash); + } + } + } + } + + /** + * Returns a list of File objects that are in the given directory + * @param pathOfDirectory + * @param filter + * @return + */ + private File[] gatherFiles(Path pathOfDirectory, FilenameFilter filter) { + + File f = new File(pathOfDirectory.toString()); + if(f.isDirectory()) { + return f.listFiles(filter); + } + return null; + } + + private void createFilePathCompletionItem(Path pathOfDirectory, File f, Range replaceRange, ICompletionResponse response, String slash) { + CompletionItem item = new CompletionItem(); + String fName = f.getName(); + 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(new TextEdit(replaceRange, insertText)); + response.addCompletionItem(item); + } + + + + + +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelper.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelper.java new file mode 100644 index 0000000000..cc34bd6653 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelper.java @@ -0,0 +1,56 @@ +/******************************************************************************* +* 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 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.utils; + +import org.eclipse.lsp4j.CompletionItemKind; + +/** + * CompletionSortText class to get sortText for CompletionItem's + */ +public class CompletionSortTextHelper { + + private int i; + private final String base; + + public CompletionSortTextHelper(CompletionItemKind kind) { + this.base = getSortText(kind); + i = 0; + } + + public String next() { + i++; + return base + Integer.toString(i); + } + + public String next(CompletionItemKind kind) { + String tempBase = getSortText(kind); + i++; + return tempBase + Integer.toString(i); + } + + public static String getSortText(CompletionItemKind kind) { + switch (kind) { + case Variable: + case Property: // DOMElement + case Enum: + case EnumMember: + case Value: + return "aa"; + case File: + return "ab"; + case Folder: + return "ac"; + default: + return "zz"; + } + } + +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/StringUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/StringUtils.java index ee7a31a2a9..4b98fe8662 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/StringUtils.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/StringUtils.java @@ -10,6 +10,11 @@ */ package org.eclipse.lsp4xml.utils; +import static org.eclipse.lsp4xml.commons.ParentProcessWatcher.isWindows; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; @@ -24,6 +29,7 @@ public class StringUtils { public static final String TRUE = "true"; public static final String FALSE = "false"; public static final Collection TRUE_FALSE_ARRAY = Arrays.asList(TRUE, FALSE); + public static String SLASH = isWindows ? "\\" : "/"; private StringUtils() { } @@ -229,4 +235,120 @@ public static String getDefaultString(String text) { return ""; } + /** + * Will return a Path object representing an existing path. + * + * If this method is able to find an existing file/folder then it will return the path to that, + * else it will try to find the parent directory of the givenPath. + * + * @param parentDirectory The directory that the given path is relative to + * @param givenPath Path that could be absolute or relative + * @return + */ + public static Path getNormalizedPath(String parentDirectory, String givenPath) { + + + if(givenPath == null) { + return null; + } + + if(parentDirectory.endsWith(SLASH)) { + parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1); + } + + int lastIndexOfSlash = givenPath.lastIndexOf(SLASH); + if(lastIndexOfSlash == -1) { + return null; + } + + //in case the given path is incomplete + String givenPathCleaned = givenPath.substring(0, lastIndexOfSlash); + + + Path p; + //The following 2 are for when the given path is already valid + + if(givenPath.startsWith(SLASH)) { + p = getPathIfExists(givenPath); + if(p != null) { + //givenPath is absolute + return p; + } + + p = getPathIfExists(givenPathCleaned); + if(p != null) { + //givenPath is absolute + return p; + } + + + } + + String combinedPath = parentDirectory + SLASH + givenPath; + p = getPathIfExists(combinedPath); + if(p != null) { + return p; + } + + combinedPath = parentDirectory + SLASH + givenPathCleaned; + p = getPathIfExists(combinedPath); + if(p != null) { + return p; + } + + return null; + } + + private static Path getPathIfExists(String path) { + Path p = Paths.get(path).normalize(); + String newPath = p.toString(); + File f = new File(newPath); + if(f.exists()) { + return p; + } + return null; + } + + public static String getFilePathSlash(String text) { + if(text.contains("\\")) { + return "\\"; + } + else if(text.contains("/")) { + return "/"; + } + else { + return SLASH; + } + } + + /** + * + * text = "abcd efg|h", endOffset = 8 -> 5 + * + * IMPORTANT: Stops at quotation character + * @param text + * @param endOffset non-inclusive + */ + public static int getOffsetAfterWhitespace(String text, int endOffset) { + if(text == null || endOffset <= 0 || endOffset > text.length()) { + return -1; + } + + char c = text.charAt(endOffset - 1); + int i = endOffset; + + if(!Character.isWhitespace(c)) { + while(!Character.isWhitespace(c)) { + i--; + if(i <= 0) { + break; + } + c = text.charAt(i - 1); + + } + return i; + } + return -1; + } + } diff --git a/org.eclipse.lsp4xml/src/main/resources/META-INF/services/org.eclipse.lsp4xml.services.extensions.IXMLExtension b/org.eclipse.lsp4xml/src/main/resources/META-INF/services/org.eclipse.lsp4xml.services.extensions.IXMLExtension index 99c16bdb19..478b4228e5 100644 --- a/org.eclipse.lsp4xml/src/main/resources/META-INF/services/org.eclipse.lsp4xml.services.extensions.IXMLExtension +++ b/org.eclipse.lsp4xml/src/main/resources/META-INF/services/org.eclipse.lsp4xml.services.extensions.IXMLExtension @@ -5,4 +5,5 @@ org.eclipse.lsp4xml.extensions.dtd.DTDPlugin org.eclipse.lsp4xml.extensions.xsl.XSLPlugin org.eclipse.lsp4xml.extensions.catalog.XMLCatalogPlugin org.eclipse.lsp4xml.extensions.xsi.XSISchemaPlugin -org.eclipse.lsp4xml.extensions.prolog.PrologPlugin \ No newline at end of file +org.eclipse.lsp4xml.extensions.prolog.PrologPlugin +org.eclipse.lsp4xml.extensions.general.FilePathPlugin \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/general/FilePathCompletionTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/general/FilePathCompletionTest.java new file mode 100644 index 0000000000..5e24134625 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/general/FilePathCompletionTest.java @@ -0,0 +1,155 @@ +/******************************************************************************* +* 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 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.extensions.general; + +import static org.eclipse.lsp4xml.XMLAssert.c; +import static org.eclipse.lsp4xml.XMLAssert.te; +import static org.eclipse.lsp4xml.utils.StringUtils.SLASH; + +import java.nio.file.Paths; + +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4xml.XMLAssert; +import org.eclipse.lsp4xml.commons.BadLocationException; +import org.junit.Test; + +/** + * FilePathCompletionTest + * + * Test folders are in + * org.eclipse.lsp4xml/src/test/resources/filePathCompletion/ + */ +public class FilePathCompletionTest { + + @Test + public void testFilePathCompletion() throws BadLocationException { + String filePath = "." + SLASH; // ./ + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 10, 11, "folderA", "folderB", "NestedA"); + testCompletionFor(xml, items); + } + + @Test + public void testFilePathCompletionFolderA() throws BadLocationException { + String filePath = Paths.get(".", "folderA").toString() + SLASH; // ./folderA/ + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 18, 19, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, items); + } + + @Test + public void testFilePathCompletionFolderB() throws BadLocationException { + String filePath = "folderB" + SLASH; + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 16, 17, "xsdB1.xsd", "xmlB1.xml"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionFolderBAbsolutePath() throws BadLocationException { + String userDir = System.getProperty("user.dir"); + int userDirLength = userDir.length(); + String filePath = Paths.get(SLASH, "src", "test", "resources", "filePathCompletion", "folderB").toString() + SLASH; // /src/test/resources/filePathCompletion/folderB/ + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 55 + userDirLength, 56 + userDirLength, "xsdB1.xsd", "xmlB1.xml"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionFolderBAbsolutePathWithFileScheme() throws BadLocationException { + String userDir = System.getProperty("user.dir"); + int userDirLength = userDir.length(); + int fileSchemeLength = "file://".length(); + String filePath = Paths.get(SLASH, "src", "test", "resources", "filePathCompletion", "folderB").toString() + SLASH; // {PATH_TO_org.eclipse.lsp4xml}/src/test/resources/filePathCompletion/folderB/ + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 55 + userDirLength + fileSchemeLength, 56 + userDirLength + fileSchemeLength, "xsdB1.xsd", "xmlB1.xml"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionNestedA() throws BadLocationException { + String filePath = "NestedA" + SLASH; + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 16, 17, "NestedB"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionNestedBIncomplete() throws BadLocationException { + String filePath = Paths.get("NestedA", "NestedB", "ZZZ").toString(); + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 24, 28, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionExtraTextInValue() throws BadLocationException { + String filePath = Paths.get("NestedA", "NestedB").toString() + SLASH; + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 44, 45, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionBadFolder() throws BadLocationException { + String filePath = Paths.get("NestedA", "BAD_FOLDER").toString() + SLASH; + String xml = ""; + testCompletionFor(xml, 0); + } + + @Test + public void testFilePathCompletionStartWithDotDot() throws BadLocationException { + String filePath = Paths.get("..", "filePathCompletion", "folderA").toString() + SLASH; + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 38, 39, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionStartWithDot() throws BadLocationException { + String filePath = Paths.get(".", "folderA").toString() + SLASH; + String xml = ""; + CompletionItem[] items = getCompletionItemList(0, 18, 19, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionNotValue() throws BadLocationException { + String xml = ""; + testCompletionFor(xml, 0); + } + + private void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException { + testCompletionFor(xml, null, expectedItems); + } + + private void testCompletionFor(String xml, Integer expectedItemCount, CompletionItem... expectedItems) + throws BadLocationException { + String fileURI = Paths.get("src", "test", "resources", "filePathCompletion", "main.xml").toString(); + XMLAssert.testCompletionFor(xml, null, fileURI, expectedItemCount, + expectedItems); + } + + private CompletionItem[] getCompletionItemList(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 = SLASH + 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.lsp4xml/src/test/resources/filePathCompletion/NestedA/NestedB/nestedXSD.xsd b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/NestedA/NestedB/nestedXSD.xsd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA1.xsd b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA1.xsd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA2.xsd b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderA/xsdA2.xsd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xmlB1.xml b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xmlB1.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xsdB1.xsd b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/folderB/xsdB1.xsd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/main.xml b/org.eclipse.lsp4xml/src/test/resources/filePathCompletion/main.xml new file mode 100644 index 0000000000..e69de29bb2