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..3d4178f284 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java @@ -0,0 +1,165 @@ +/******************************************************************************* +* 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 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.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.StringUtils; + +/** + * FilePathCompletionParticipant + */ +public class FilePathCompletionParticipant implements ICompletionParticipant { + + 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(); + // +- 1 since it includes the quotations + int startOffset = xmlDocument.offsetAt(fullRange.getStart()) + 1; + int endOffset = xmlDocument.offsetAt(fullRange.getEnd()) - 1; + String currentValue = xmlDocument.getText().substring(startOffset, endOffset); + + 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".equals(scheme))) { + return; + } + + directoryOfFile = uri.getPath(); + + Path parentAttributeValuePath = getNormalizedPath(directoryOfFile, currentValue); + + File f = new File(parentAttributeValuePath.toString()); + if(parentAttributeValuePath == null || f.isFile()) { + return; + } + String slash = StringUtils.getFilePathSlash(currentValue); + + //In the case the currently typed file/directory needs to be overwritten + Position replaceStart = null; + Position currentEnd = fullRange.getEnd(); + Position replaceEnd = new Position(currentEnd.getLine(), currentEnd.getCharacter() - 1); + int lastSlashIndex = currentValue.lastIndexOf(slash); + if(lastSlashIndex > -1) { + replaceStart = xmlDocument.positionAt(startOffset + lastSlashIndex); + } + Range replaceRange = new Range(); + if(replaceStart != null) { + replaceRange.setStart(replaceStart); + } + else { + replaceRange.setStart(replaceEnd); + } + replaceRange.setEnd(replaceEnd); + + createNextValidCompletionPaths(parentAttributeValuePath, currentValue, replaceRange, response, xsdFilter); + } + + private void createNextValidCompletionPaths(Path pathToAttributeDirectory, String attributePath, Range replaceRange, ICompletionResponse response, + FilenameFilter filter) { + + String slash = StringUtils.getFilePathSlash(attributePath); + + File[] proposedFiles = gatherFiles(pathToAttributeDirectory, filter); + if (proposedFiles != null) { + for (File child : proposedFiles) { + if (child != null) { + createFilePathCompletionItem(pathToAttributeDirectory, attributePath, child, replaceRange, response, slash); + } + } + } + } + + 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, String attributePath, File f, Range replaceRange, ICompletionResponse response, String slash) { + CompletionItem item = new CompletionItem(); + String fName = f.getName(); + String insertText; + if(f.isDirectory()) { + insertText = slash + fName + slash; + } + else if(f.isFile()) { + insertText = slash + fName; + } + else { + return; + } + + item.setLabel(insertText); + item.setKind(f.isFile()? CompletionItemKind.File : CompletionItemKind.Folder); + Sor + item.setSortText(sortText); + 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/StringUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/StringUtils.java index ee7a31a2a9..c97575281d 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,70 @@ public static String getDefaultString(String text) { return ""; } + public static Path getNormalizedPath(String parentDirectory, String givenPath) { + + Path p; + if(givenPath == null) { + return null; + } + String newPath; + File f; + + if(givenPath.startsWith(SLASH)) { //givenPath is already absolute + p = Paths.get(givenPath).normalize(); + newPath = p.toString(); + f = new File(newPath); + if(f.exists()) { + return p; + } + } + else { + String combinedPath = parentDirectory + SLASH + givenPath; + p = Paths.get(combinedPath).normalize(); + newPath = p.toString(); + f = new File(newPath); + if(f.exists()) { + return p; + } + } + + int lastIndexOfSlash = givenPath.lastIndexOf(SLASH); + if(lastIndexOfSlash != -1) { + //incase the given path is incomplete + givenPath = givenPath.substring(0, lastIndexOfSlash); + } + + if(givenPath.startsWith(SLASH)) { //givenPath is already absolute + p = Paths.get(givenPath).normalize(); + newPath = p.toString(); + f = new File(newPath); + if(f.exists()) { + return p; + } + } + else { + String combinedPath = parentDirectory + SLASH + givenPath; + p = Paths.get(combinedPath).normalize(); + newPath = p.toString(); + 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; + } + } + } 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