Skip to content

Commit

Permalink
File completion in attribute value
Browse files Browse the repository at this point in the history
Provides file completion when the user starts typing a path in an attribute value

Only displays `.xsd` and folders as of now.

Fixes eclipse-lemminx#345

Signed-off-by: Nikolas Komonen <[email protected]>
  • Loading branch information
NikolasKomonen committed May 31, 2019
1 parent f9d5253 commit 5732226
Show file tree
Hide file tree
Showing 13 changed files with 472 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -38,7 +38,7 @@ public final class ParentProcessWatcher implements Runnable, Function<MessageCon
*/
private static final int FORCED_EXIT_CODE = 1;

private static final boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("win") >= 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*******************************************************************************
* 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.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 {

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);

if(parentAttributeValuePath == null) {
return;
}

File f = new File(parentAttributeValuePath.toString());

if(f.isFile()) {
return;
}
String slash = StringUtils.getFilePathSlash(currentValue);

Range replaceRange = adjustReplaceRange(xmlDocument, fullRange, currentValue, slash);

createNextValidCompletionPaths(parentAttributeValuePath, currentValue, replaceRange, response, xsdFilter);
}

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();
Position replaceEnd = new Position(currentEnd.getLine(), currentEnd.getCharacter() - 1);
int startOffset;
try {
startOffset = xmlDocument.offsetAt(fullRange.getStart()) + 1;
} 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(replaceEnd);
}
replaceRange.setEnd(replaceEnd);

return replaceRange;
}

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;
// }
insertText = slash + fName;
item.setLabel(insertText);
CompletionItemKind kind = f.isFile()? CompletionItemKind.File : CompletionItemKind.Folder;
item.setKind(kind);

item.setSortText(CompletionSortTextHelper.getSortText(kind));
item.setFilterText(insertText);
item.setTextEdit(new TextEdit(replaceRange, insertText));
response.addCompletionItem(item);
}





}
Original file line number Diff line number Diff line change
@@ -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";
}
}

}
Loading

0 comments on commit 5732226

Please sign in to comment.