From b8e5a7a51bf6147e02da91eb489faa51ce49c14d Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <32624665+NikolasKomonen@users.noreply.github.com> Date: Mon, 10 Jun 2019 18:57:15 -0400 Subject: [PATCH] File completion in attribute value (#410) 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 | 27 +- .../eclipse/lsp4xml/dom/parser/Constants.java | 3 +- .../extensions/general/FilePathPlugin.java | 46 +++ .../FilePathCompletionParticipant.java | 241 ++++++++++++++++ .../ServerCapabilitiesConstants.java | 2 +- .../utils/CompletionSortTextHelper.java | 56 ++++ .../org/eclipse/lsp4xml/utils/FilesUtils.java | 154 +++++++++- .../org/eclipse/lsp4xml/utils/OSUtils.java | 22 ++ .../eclipse/lsp4xml/utils/StringUtils.java | 94 +++++-- ....lsp4xml.services.extensions.IXMLExtension | 3 +- .../java/org/eclipse/lsp4xml/XMLAssert.java | 36 +-- .../general/FilePathCompletionTest.java | 263 ++++++++++++++++++ .../utils/CompletionSortTextHelperTest.java | 37 +++ .../eclipse/lsp4xml/utils/FilesUtilsTest.java | 18 +- .../lsp4xml/utils/StringUtilsTest.java | 10 + .../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 21 files changed, 946 insertions(+), 66 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/main/java/org/eclipse/lsp4xml/utils/OSUtils.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/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelperTest.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 1df863748..e6c178970 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 @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.lsp4xml.commons; +import static org.eclipse.lsp4xml.utils.OSUtils.isWindows; + import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -19,16 +21,17 @@ 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. + * This implementation waits for periods of inactivity to start querying the + * PIDs. */ -public final class ParentProcessWatcher implements Runnable, Function{ +public final class ParentProcessWatcher implements Runnable, Function { private static final Logger LOGGER = Logger.getLogger(ParentProcessWatcher.class.getName()); private static final boolean isJava1x = System.getProperty("java.version").startsWith("1."); @@ -38,9 +41,7 @@ public final class ParentProcessWatcher implements Runnable, Function= 0; - - private static final long INACTIVITY_DELAY_SECS = 30 *1000; + private static final long INACTIVITY_DELAY_SECS = 30 * 1000; private static final int POLL_DELAY_SECS = 10; private volatile long lastActivityTime; private final ProcessLanguageServer server; @@ -50,14 +51,14 @@ public final class ParentProcessWatcher implements Runnable, Function?]?"); public static final Pattern DOCTYPE_KIND_OPTIONS = Pattern.compile("^(PUBLIC|SYSTEM)([\\s<>\"'])"); @@ -91,4 +91,5 @@ public class Constants { public static final Pattern DOCTYPE_NAME = Pattern.compile("^[_:\\w][_:\\w-.\\d]*"); + } 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 000000000..19c487adc --- /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 000000000..a11e6a048 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java @@ -0,0 +1,241 @@ +/******************************************************************************* +* 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.FilesUtils.convertToWindowsPath; +import static org.eclipse.lsp4xml.utils.FilesUtils.getFilePathSlash; +import static org.eclipse.lsp4xml.utils.FilesUtils.getNormalizedPath; +import static org.eclipse.lsp4xml.utils.OSUtils.isWindows; +import static org.eclipse.lsp4xml.utils.StringUtils.isEmpty; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.URI; +import java.nio.file.Path; + +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.CompletionParticipantAdapter; +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.FilesUtils; +import org.eclipse.lsp4xml.utils.StringUtils; + +/** + * FilePathCompletionParticipant + */ +public class FilePathCompletionParticipant extends CompletionParticipantAdapter { + + public static final String FILE_SCHEME = "file"; + + @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; + + String fullAttributeValue = valuePrefix; + if (isEmpty(fullAttributeValue)) { + return; + } + + // Get 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 attributePath = text.substring(parsedAttributeStartOffset, completionOffset); + + Position startValue = xmlDocument.positionAt(parsedAttributeStartOffset); + Position endValue = xmlDocument.positionAt(completionOffset); + fullRange = new Range(startValue, endValue); + + // Try to get the URI string from the attribute value in case it has a file scheme + // header (eg: "file://") + String osSpecificAttributePath = attributePath; + boolean hasFileScheme = false; + + hasFileScheme = attributePath.startsWith(FilesUtils.FILE_SCHEME); + if (hasFileScheme) { + osSpecificAttributePath = attributePath.substring(FilesUtils.FILE_SCHEME.length()); + } + + String slashInAttribute = getFilePathSlash(attributePath); + + if (hasFileScheme) { + if (!osSpecificAttributePath.startsWith("/")) { + return; // use of 'file://' and the path was not absolute + } + if (isWindows && osSpecificAttributePath.length() == 1) { // only '/', so list Windows Drives + + Range replaceRange = adjustReplaceRange(xmlDocument, fullRange, attributePath, "/"); + + File[] drives = File.listRoots(); + for (File drive : drives) { + createFilePathCompletionItem(drive, replaceRange, response, "/"); + } + return; + } + } + + if(isWindows) { + osSpecificAttributePath = convertToWindowsPath(osSpecificAttributePath); + } + else if("\\".equals(slashInAttribute)) { // Backslash used in Unix + osSpecificAttributePath = osSpecificAttributePath.replace("\\", "/"); + } + + // Get the normalized URI string from the parent directory file if necessary + String workingDirectory = null; // The OS specific path for a working directory + + if (!hasFileScheme) { //The path from the attribute value is not a uri, so we might need to reference the working directory path + String uriString = xmlDocument.getTextDocument().getUri(); + URI uri = new URI(uriString); + + if(!FILE_SCHEME.equals(uri.getScheme())) { + return; + } + + String uriPathString = uri.getPath(); + if(!uriPathString.startsWith("/")) { + return; //file uri is incorrect + } + int lastSlash = uriPathString.lastIndexOf("/"); + if(lastSlash > -1) { + workingDirectory = uriPathString.substring(0, lastSlash); + + if(isWindows) { + // Necessary, so that this path is readable in Windows + workingDirectory = convertToWindowsPath(workingDirectory); + } + } + } + + //Try to get a correctly formatted path from the given values + Path validAttributeValuePath = getNormalizedPath(workingDirectory, osSpecificAttributePath); + + if(validAttributeValuePath == null) { + return; + } + + //Get adjusted range for the completion item (insert at end, or overwrite some existing text in the path) + Range replaceRange = adjustReplaceRange(xmlDocument, fullRange, attributePath, slashInAttribute); + + createNextValidCompletionPaths(validAttributeValuePath, slashInAttribute, 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(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()); + return f.isDirectory() ? f.listFiles(filter) : null; + } + + private void createFilePathCompletionItem(File f, Range replaceRange, ICompletionResponse response, String slash) { + CompletionItem item = new CompletionItem(); + String fName = 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(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/settings/capabilities/ServerCapabilitiesConstants.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/capabilities/ServerCapabilitiesConstants.java index 82a4fdc30..6d1707b10 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/capabilities/ServerCapabilitiesConstants.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/capabilities/ServerCapabilitiesConstants.java @@ -71,7 +71,7 @@ private ServerCapabilitiesConstants() { public static final String WORKSPACE_CHANGE_FOLDERS_ID = UUID.randomUUID().toString(); public static final String WORKSPACE_WATCHED_FILES_ID = UUID.randomUUID().toString(); - public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(false, Arrays.asList(".", ":", "<", "\"", "=", "/", "?", "\'")); + public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(false, Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'")); public static final TextDocumentSyncKind DEFAULT_SYNC_OPTION = TextDocumentSyncKind.Full; public static final DocumentLinkOptions DEFAULT_LINK_OPTIONS = new DocumentLinkOptions(true); } \ 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 000000000..cc34bd665 --- /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/FilesUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java index 258b47b87..897e4a472 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java @@ -10,6 +10,8 @@ */ package org.eclipse.lsp4xml.utils; +import static org.eclipse.lsp4xml.utils.OSUtils.SLASH; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -19,6 +21,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -29,18 +33,21 @@ */ public class FilesUtils { + public static final String FILE_SCHEME = "file://"; public static final String LSP4XML_WORKDIR_KEY = "lsp4xml.workdir"; private static String cachePathSetting = null; + private static Pattern uriSchemePattern = Pattern.compile("^([a-zA-Z\\-]+:\\/\\/).*"); + private static Pattern endFilePattern = Pattern.compile(".*[\\\\\\/]\\.[\\S]+"); + public static String getCachePathSetting() { return cachePathSetting; } public static void setCachePathSetting(String cachePathSetting) { - if(StringUtils.isEmpty(cachePathSetting)) { + if (StringUtils.isEmpty(cachePathSetting)) { FilesUtils.cachePathSetting = null; - } - else { + } else { FilesUtils.cachePathSetting = cachePathSetting; } resetDeployPath(); @@ -61,16 +68,16 @@ public static void resetDeployPath() { } /** - * Given a file path as a string, will normalize it - * and return the normalized string if valid, or null if not. + * Given a file path as a string, will normalize it and return the normalized + * string if valid, or null if not. * - * The '~' home symbol will be converted into the actual home path. - * Slashes will be corrected depending on the OS. + * The '~' home symbol will be converted into the actual home path. Slashes will + * be corrected depending on the OS. */ public static String normalizePath(String pathString) { if (pathString != null && !pathString.isEmpty()) { if (pathString.indexOf("~") == 0) { - pathString = System.getProperty("user.home") + (pathString.length() > 1? pathString.substring(1):""); + pathString = System.getProperty("user.home") + (pathString.length() > 1 ? pathString.substring(1) : ""); } pathString = pathString.replace("/", File.separator); pathString = pathString.replace("\\", File.separator); @@ -86,7 +93,7 @@ private static Path getDeployedBasePath() { if (dir != null) { return Paths.get(dir); } - if(cachePathSetting != null && !cachePathSetting.isEmpty()) { + if (cachePathSetting != null && !cachePathSetting.isEmpty()) { return Paths.get(cachePathSetting); } dir = System.getProperty("user.home"); @@ -145,4 +152,133 @@ static String toString(InputStream is) { return s.hasNext() ? s.next() : ""; } } + + public static int getOffsetAfterScheme(String uri) { + Matcher m = uriSchemePattern.matcher(uri); + + if (m.matches()) { + return m.group(1).length(); + } + + return -1; + } + + /** + * 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. + * + * **IMPORTANT** The slashes of the given paths have to match the supported OS file path slash + * + * @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; + } + + int lastIndexOfSlash = givenPath.lastIndexOf(SLASH); + + // in case the given path is incomplete, trim the end + String givenPathCleaned; + if(lastIndexOfSlash == 0) { // Looks like `/someFileOrFolder` + return Paths.get(SLASH); + } + else { + givenPathCleaned = lastIndexOfSlash > -1 ? givenPath.substring(0, lastIndexOfSlash) : null; + } + + + Path p; + + // The following 2 are for when the given path is already valid + p = getPathIfExists(givenPath); + if (p != null) { + // givenPath is absolute + return p; + } + + p = getPathIfExists(givenPathCleaned); + if (p != null) { + // givenPath is absolute + return p; + } + + + + if (parentDirectory == null) { + return null; + } + + if (parentDirectory.endsWith(SLASH)) { + parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1); + } + + 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) { + try { + Path p = Paths.get(path).normalize(); + return p.toFile().exists() ? p : null; + } catch (Exception e) { + return null; + } + + } + + /** + * Returns the slash ("/" or "\") that is used by the given string. + * If no slash is given "/" is returned by default. + * @param text + * @return + */ + public static String getFilePathSlash(String text) { + if (text.contains("\\")) { + return "\\"; + } + return "/"; + } + + /** + * Ensures there is no slash before a drive letter, and + * forces use of '\' + * @param pathString + * @return + */ + public static String convertToWindowsPath(String pathString) { + String pathSlash = getFilePathSlash(pathString); + if(pathString.startsWith(pathSlash) ) { + if(pathString.length() > 3) { + char letter = pathString.charAt(1); + char colon = pathString.charAt(2); + if(Character.isLetter(letter) && ':' == colon) { + pathString = pathString.substring(1); + } + } + } + return pathString.replace("/", "\\"); + } + + public static boolean pathEndsWithFile(String pathString) { + Matcher m = endFilePattern.matcher(pathString); + return m.matches(); + } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/OSUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/OSUtils.java new file mode 100644 index 000000000..548fcf71c --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/OSUtils.java @@ -0,0 +1,22 @@ +/******************************************************************************* +* 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; + +/** + * OSUtils + */ +public class OSUtils { + + public static final boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("win") >= 0; + public static String SLASH = isWindows ? "\\" : "/"; + + +} \ 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 ee7a31a2a..919978c0d 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 @@ -24,6 +24,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); + private StringUtils() { } @@ -38,15 +39,15 @@ public static boolean isQuote(char c) { } public static boolean isWhitespace(String value) { - if(value == null) { + if (value == null) { return false; } char c; int end = value.length(); int index = 0; - while(index < end) { + while (index < end) { c = value.charAt(index); - if(Character.isWhitespace(c) == false) { + if (Character.isWhitespace(c) == false) { return false; } index++; @@ -55,8 +56,8 @@ public static boolean isWhitespace(String value) { } /** - * Normalizes the whitespace characters of a given string and applies it - * to the given string builder. + * Normalizes the whitespace characters of a given string and applies it to the + * given string builder. * * @param str * @return the result of normalize space of the given string. @@ -168,7 +169,7 @@ public static String lTrim(String value) { char c = val[i]; // left trim - while(i < value.length() && Character.isWhitespace(c)) { + while (i < value.length() && Character.isWhitespace(c)) { i++; c = val[i]; } @@ -177,37 +178,37 @@ public static String lTrim(String value) { } /** - * Given a string that is only whitespace, - * this will return the amount of newline characters. + * Given a string that is only whitespace, this will return the amount of + * newline characters. + * + * If the newLineCounter becomes > newLineLimit, then the value of newLineLimit + * is always returned. * - * If the newLineCounter becomes > newLineLimit, then the value of - * newLineLimit is always returned. * @param text * @param isWhitespace * @param delimiter * @return */ public static int getNumberOfNewLines(String text, boolean isWhitespace, String delimiter, int newLineLimit) { - if(!isWhitespace){ + if (!isWhitespace) { return 0; } int newLineCounter = 0; boolean delimiterHasTwoCharacters = delimiter.length() == 2; - for(int i = 0; newLineCounter <= newLineLimit && i < text.length(); i++) { + for (int i = 0; newLineCounter <= newLineLimit && i < text.length(); i++) { String c; - if(delimiterHasTwoCharacters) { - if(i + 1 < text.length()) { + if (delimiterHasTwoCharacters) { + if (i + 1 < text.length()) { c = text.substring(i, i + 2); - if(delimiter.equals(c)) { + if (delimiter.equals(c)) { newLineCounter++; - i++; //skip the second char of the delimiter + i++; // skip the second char of the delimiter } } - } - else { + } else { c = String.valueOf(text.charAt(i)); - if(delimiter.equals(c)) { + if (delimiter.equals(c)) { newLineCounter++; } } @@ -216,17 +217,66 @@ public static int getNumberOfNewLines(String text, boolean isWhitespace, String } /** - * Given a string will give back a non null string that is either - * the given string, or an empty string. + * Given a string will give back a non null string that is either the given + * string, or an empty string. * * @param text * @return */ public static String getDefaultString(String text) { - if(text != null) { + if (text != null) { return text; } return ""; } + /** + * Traverses backwards from the endOffset until it finds a whitespace character. + * + * The offset of the character after the whitespace is returned. + * + * (text = "abcd efg|h", endOffset = 8) -> 5 + * + * + * @param text + * @param endOffset non-inclusive + * @return Start offset directly after the first whitespace. + */ + 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; + } + + public static String cleanPathForWindows(String pathString) { + if(pathString.startsWith("/") ) { + if(pathString.length() > 3) { + char letter = pathString.charAt(1); + char colon = pathString.charAt(2); + if(Character.isLetter(letter) && ':' == colon) { + pathString = pathString.substring(1); + } + } + + } + pathString = pathString.replace("/", "\\"); + return pathString; + } + } 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 99c16bdb1..478b4228e 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/XMLAssert.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/XMLAssert.java index cfd25f41b..4b37b9396 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/XMLAssert.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/XMLAssert.java @@ -121,13 +121,14 @@ public static void testCompletionFor(XMLLanguageService xmlLanguageService, Stri public static void testCompletionFor(XMLLanguageService xmlLanguageService, String value, String catalogPath, Consumer customConfiguration, String fileURI, Integer expectedCount, CompletionSettings completionSettings, CompletionItem... expectedItems) throws BadLocationException { - testCompletionFor(xmlLanguageService, value, catalogPath, customConfiguration, fileURI, expectedCount, completionSettings, new XMLFormattingOptions(4, true), expectedItems); + testCompletionFor(xmlLanguageService, value, catalogPath, customConfiguration, fileURI, expectedCount, + completionSettings, new XMLFormattingOptions(4, true), expectedItems); } public static void testCompletionFor(XMLLanguageService xmlLanguageService, String value, String catalogPath, Consumer customConfiguration, String fileURI, Integer expectedCount, - CompletionSettings completionSettings, XMLFormattingOptions formattingSettings, CompletionItem... expectedItems) - throws BadLocationException { + CompletionSettings completionSettings, XMLFormattingOptions formattingSettings, + CompletionItem... expectedItems) throws BadLocationException { int offset = value.indexOf('|'); value = value.substring(0, offset) + value.substring(offset + 1); @@ -205,7 +206,7 @@ private static void assertCompletion(CompletionList completions, CompletionItem Assert.assertEquals(expected.getFilterText(), match.getFilterText()); } - if(expected.getDetail() != null) { + if (expected.getDetail() != null) { Assert.assertEquals(expected.getDetail(), match.getDetail()); } @@ -251,7 +252,7 @@ public static void testTagCompletion(String value, String expected) throws BadLo DOMDocument htmlDoc = DOMParser.getInstance().parse(document, ls.getResolverExtensionManager()); AutoCloseTagResponse response = ls.doTagComplete(htmlDoc, position); - if(expected == null) { + if (expected == null) { Assert.assertNull(response); return; } @@ -287,7 +288,7 @@ public static void testDiagnosticsFor(String xml, String catalogPath, Consumer configuration, String fileURI, boolean filter, ContentModelSettings settings, Diagnostic... expected) { TextDocument document = new TextDocument(xml, fileURI != null ? fileURI : "test.xml"); @@ -305,7 +306,7 @@ public static void testDiagnosticsFor(String xml, String catalogPath, Consumer actual = xmlLanguageService.doDiagnostics(xmlDocument, () -> { }, settings.getValidation()); - if(expected == null) { + if (expected == null) { assertTrue(actual.isEmpty()); return; } @@ -319,7 +320,7 @@ public static void assertDiagnostics(List actual, Diagnostic... expe public static void assertDiagnostics(List actual, List expected, boolean filter) { List received = actual; final boolean filterMessage; - if(expected != null && !expected.isEmpty() && !StringUtils.isEmpty(expected.get(0).getMessage())) { + if (expected != null && !expected.isEmpty() && !StringUtils.isEmpty(expected.get(0).getMessage())) { filterMessage = true; } else { filterMessage = false; @@ -328,7 +329,7 @@ public static void assertDiagnostics(List actual, List e received = actual.stream().map(d -> { Diagnostic simpler = new Diagnostic(d.getRange(), ""); simpler.setCode(d.getCode()); - if(filterMessage) { + if (filterMessage) { simpler.setMessage(d.getMessage()); } return simpler; @@ -346,7 +347,8 @@ public static Diagnostic d(int startLine, int startCharacter, int endCharacter, return d(startLine, startCharacter, startLine, endCharacter, code); } - public static Diagnostic d(int startLine, int startCharacter, int endLine, int endCharacter, IXMLErrorCode code, String message) { + public static Diagnostic d(int startLine, int startCharacter, int endLine, int endCharacter, IXMLErrorCode code, + String message) { // Diagnostic on 1 line return new Diagnostic(r(startLine, startCharacter, endLine, endCharacter), message, null, null, code.getCode()); } @@ -427,11 +429,11 @@ public static void testCodeActionsFor(String xml, Diagnostic diagnostic, String SharedSettings settings = new SharedSettings(); settings.setFormattingSettings(new XMLFormattingOptions(4, false)); testCodeActionsFor(xml, diagnostic, catalogPath, settings, expected); - + } - public static void testCodeActionsFor(String xml, Diagnostic diagnostic, String catalogPath, SharedSettings sharedSettings, - CodeAction... expected) { + public static void testCodeActionsFor(String xml, Diagnostic diagnostic, String catalogPath, + SharedSettings sharedSettings, CodeAction... expected) { TextDocument document = new TextDocument(xml.toString(), FILE_URI); XMLLanguageService xmlLanguageService = new XMLLanguageService(); @@ -450,14 +452,12 @@ public static void testCodeActionsFor(String xml, Diagnostic diagnostic, String DOMDocument xmlDoc = DOMParser.getInstance().parse(document, xmlLanguageService.getResolverExtensionManager()); XMLFormattingOptions formattingSettings; - if(sharedSettings != null && sharedSettings.formattingSettings != null) { + if (sharedSettings != null && sharedSettings.formattingSettings != null) { formattingSettings = sharedSettings.formattingSettings; - } - else { + } else { formattingSettings = new XMLFormattingOptions(4, false); } - List actual = xmlLanguageService.doCodeActions(context, range, xmlDoc, - formattingSettings); + List actual = xmlLanguageService.doCodeActions(context, range, xmlDoc, formattingSettings); assertCodeActions(actual, expected); } 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 000000000..8fc929c9f --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/general/FilePathCompletionTest.java @@ -0,0 +1,263 @@ +/******************************************************************************* +* 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.OSUtils.SLASH; +import static org.eclipse.lsp4xml.utils.OSUtils.isWindows; + +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 { + + private static final String userDir = System.getProperty("user.dir"); // C:..\..\folderName || /bin/.../java + private static final String userDirBackSlash = userDir.replace("/", "\\"); + private static final String userDirForwardSlash = userDir.replace("\\", "/"); + private static final String fileScheme = "file://"; + private static final String fileSchemeWithRoot = fileScheme + "/"; + private static final String userDirURI = fileSchemeWithRoot + userDirForwardSlash; // C:/../../folderName + + private static final int userDirURILength = userDirURI.length(); + + @Test + public void testFilePathCompletion() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 10, 11, "folderA", "folderB", "NestedA"); + testCompletionFor(xml, items); + } + + @Test + public void testFilePathCompletionBackSlash() throws BadLocationException { + + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 10, 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"); + testCompletionFor(xml, items); + } + + @Test + public void testFilePathCompletionFolderABackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, items); + } + + + @Test + public void testFilePathCompletionFolderB() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 16, 17, "xsdB1.xsd", "xmlB1.xml"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionFolderBBackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 16, 17, "xsdB1.xsd", "xmlB1.xml"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionFolderBAbsolutePath() throws BadLocationException { + 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); + } + + @Test + public void testFilePathCompletionFolderBAbsolutePathBackSlash() throws BadLocationException { + if (!isWindows) { + return; + } + 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); + } + + @Test + public void testFilePathCompletionFolderBAbsolutePathWithFileScheme() throws BadLocationException { + String filePath = (isWindows ? fileSchemeWithRoot : fileScheme) + userDirForwardSlash + "/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); + } + + @Test + public void testFilePathCompletionNestedA() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 16, 17, "NestedB"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionNestedABackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 16, 17, "NestedB"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionNestedBIncomplete() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 24, 28, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionNestedBIncompleteBackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 24, 28, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionExtraTextInValue() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 44, 45, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionExtraTextInValueBackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 44, 45, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @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\\"; + int filePathLength = filePath.length(); + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 29 + filePathLength - 1, 29 + filePathLength, "nestedXSD.xsd"); + testCompletionFor(xml, 1, items); + } + + @Test + public void testFilePathCompletionBadFolder() throws BadLocationException { + String xml = ""; + testCompletionFor(xml, 0); + } + + @Test + public void testFilePathCompletionBadFolderBackSlash() throws BadLocationException { + String xml = ""; + testCompletionFor(xml, 0); + } + + @Test + public void testFilePathCompletionStartWithDotDot() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 38, 39, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionStartWithDotDotBackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 38, 39, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionStartWithDot() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("/", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionStartWithDotBackSlash() throws BadLocationException { + String xml = ""; + CompletionItem[] items = getCompletionItemList("\\", 0, 18, 19, "xsdA1.xsd", "xsdA2.xsd"); + testCompletionFor(xml, 2, items); + } + + @Test + public void testFilePathCompletionEndsWithFileAndSlash() throws BadLocationException { + String xml = ""; + testCompletionFor(xml, 0); + } + + @Test + public void testFilePathCompletionEndsWithFileAndBackSlash() throws BadLocationException { + String xml = ""; + testCompletionFor(xml, 0); + } + + @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 userDir = System.getProperty("user.dir").replace("\\", "/"); + String fileURI = "file://" + userDir + "/src/test/resources/filePathCompletion/main.xml"; + XMLAssert.testCompletionFor(xml, null, fileURI, expectedItemCount, + expectedItems); + } + + private CompletionItem[] getCompletionItemList(int line, int startChar, int endChar, String... fileOrFolderNames) { + return getCompletionItemList(SLASH, line, startChar, endChar, fileOrFolderNames); + + } + + private 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); + } + + return items; + + } + +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelperTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelperTest.java new file mode 100644 index 000000000..cebc19f33 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/CompletionSortTextHelperTest.java @@ -0,0 +1,37 @@ +/******************************************************************************* +* 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 static org.junit.Assert.assertEquals; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.junit.Test; + +/** + * CompletionSortTextHelperTest + */ +public class CompletionSortTextHelperTest { + + @Test + public void testCompletionSortTextHelperProperty() { + CompletionSortTextHelper sort = new CompletionSortTextHelper(CompletionItemKind.Property); + assertEquals("aa1", sort.next()); + assertEquals("aa2", sort.next()); + assertEquals("aa3", sort.next()); + } + + @Test + public void testCompletionSortTextHelperFile() { + CompletionSortTextHelper sort = new CompletionSortTextHelper(CompletionItemKind.File); + assertEquals("ab1", sort.next()); + assertEquals("ab2", sort.next()); + assertEquals("ab3", sort.next()); + } +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/FilesUtilsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/FilesUtilsTest.java index 4d47f6a94..ced91322e 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/FilesUtilsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/FilesUtilsTest.java @@ -11,13 +11,13 @@ package org.eclipse.lsp4xml.utils; +import static java.io.File.separator; import static org.junit.Assert.assertEquals; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.Test; -import static java.io.File.separator; /** * FilesUtilsTest @@ -43,4 +43,20 @@ public void normalizePathTest() { assertEquals(Paths.get(separator + "Folder").toString(), FilesUtils.normalizePath("/Test/../Folder")); assertEquals(Paths.get(separator + "Users", "Nikolas").toString(), FilesUtils.normalizePath("\\Users\\Nikolas\\")); } + + @Test + public void getFilePathSlashTest() { + assertEquals("/", FilesUtils.getFilePathSlash("src/a/b/c")); + assertEquals("\\", FilesUtils.getFilePathSlash("src\\a\\b\\c")); + assertEquals("/", FilesUtils.getFilePathSlash("src")); + assertEquals("/", FilesUtils.getFilePathSlash("")); + } + + @Test + public void cleanPathForWindows() { + assertEquals("C:\\Users\\Home\\Documents", FilesUtils.convertToWindowsPath("\\C:\\Users\\Home\\Documents")); + assertEquals("C:\\Users\\Home\\Documents\\", FilesUtils.convertToWindowsPath("\\C:\\Users\\Home\\Documents\\")); + assertEquals("C:\\Users\\Home\\Documents\\", FilesUtils.convertToWindowsPath("/C:/Users/Home/Documents/")); + assertEquals("C:\\Users\\Home\\Documents\\", FilesUtils.convertToWindowsPath("C:/Users/Home/Documents/")); + } } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/StringUtilsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/StringUtilsTest.java index 1e4073cd3..59ac4066f 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/StringUtilsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/StringUtilsTest.java @@ -11,6 +11,7 @@ package org.eclipse.lsp4xml.utils; import static org.eclipse.lsp4xml.utils.StringUtils.trimNewLines; +import static org.junit.Assert.assertEquals; import org.junit.Assert; import org.junit.Test; @@ -52,6 +53,15 @@ public void testTrimWhichContainsNewLine() { assertTrimNewLines(" abc\rdef", " abc\rdef"); } + @Test + public void testGetOffsetAfterWhitespace() { + assertEquals(4, StringUtils.getOffsetAfterWhitespace("abc 123", 7)); + assertEquals(4, StringUtils.getOffsetAfterWhitespace("abc 123", 6)); + assertEquals(-1, StringUtils.getOffsetAfterWhitespace("abc 123", 4)); + assertEquals(0, StringUtils.getOffsetAfterWhitespace("123", 3)); + assertEquals(-1, StringUtils.getOffsetAfterWhitespace("123", 0)); + } + private static void assertTrimNewLines(String valueToTrim, String expected) { String actual = trimNewLines(valueToTrim); Assert.assertEquals(expected, actual); 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 000000000..e69de29bb 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 000000000..e69de29bb 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 000000000..e69de29bb 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 000000000..e69de29bb 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 000000000..e69de29bb 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 000000000..e69de29bb