forked from eclipse/lemminx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes eclipse#1464 Signed-off-by: azerr <[email protected]>
- Loading branch information
1 parent
def27a7
commit abefe2f
Showing
16 changed files
with
904 additions
and
453 deletions.
There are no files selected for viewing
101 changes: 101 additions & 0 deletions
101
...eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/FilePathPlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2019 Red Hat Inc. and others. | ||
* All rights reserved. This program and the accompanying materials | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Red Hat Inc. - initial API and implementation | ||
*******************************************************************************/ | ||
package org.eclipse.lemminx.extensions.filepath; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import org.eclipse.lemminx.dom.DOMDocument; | ||
import org.eclipse.lemminx.extensions.colors.settings.XMLColorsSettings; | ||
import org.eclipse.lemminx.extensions.filepath.participants.FilePathCompletionParticipant; | ||
import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; | ||
import org.eclipse.lemminx.extensions.filepath.settings.FilePaths; | ||
import org.eclipse.lemminx.extensions.filepath.settings.FilePathsSettings; | ||
import org.eclipse.lemminx.services.extensions.IXMLExtension; | ||
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; | ||
import org.eclipse.lemminx.services.extensions.save.ISaveContext; | ||
import org.eclipse.lsp4j.InitializeParams; | ||
|
||
/** | ||
* FilePathPlugin | ||
*/ | ||
public class FilePathPlugin implements IXMLExtension { | ||
|
||
private final FilePathCompletionParticipant completionParticipant; | ||
private FilePathsSettings filePathsSettings; | ||
|
||
public FilePathPlugin() { | ||
completionParticipant = new FilePathCompletionParticipant(this); | ||
} | ||
|
||
@Override | ||
public void doSave(ISaveContext context) { | ||
if (context.getType() != ISaveContext.SaveContextType.DOCUMENT) { | ||
// Settings | ||
updateSettings(context); | ||
} | ||
} | ||
|
||
private void updateSettings(ISaveContext saveContext) { | ||
Object initializationOptionsSettings = saveContext.getSettings(); | ||
FilePathsSettings settings = FilePathsSettings | ||
.getFilePathsSettings(initializationOptionsSettings); | ||
updateSettings(settings, saveContext); | ||
} | ||
|
||
private void updateSettings(FilePathsSettings settings, ISaveContext context) { | ||
this.filePathsSettings = settings; | ||
} | ||
|
||
@Override | ||
public void start(InitializeParams params, XMLExtensionsRegistry registry) { | ||
registry.registerCompletionParticipant(completionParticipant); | ||
} | ||
|
||
@Override | ||
public void stop(XMLExtensionsRegistry registry) { | ||
registry.unregisterCompletionParticipant(completionParticipant); | ||
} | ||
|
||
public FilePathsSettings getFilePathsSettings() { | ||
return filePathsSettings; | ||
} | ||
|
||
/** | ||
* Return the list of {@link FilePathExpression} for the given document and an | ||
* empty list otherwise. | ||
* | ||
* @param xmlDocument the DOM document | ||
* | ||
* @return the list of {@link FilePathExpression} for the given document and an | ||
* empty list otherwise. | ||
*/ | ||
public List<FilePathExpression> findFilePathExpression(DOMDocument xmlDocument) { | ||
FilePathsSettings settings = getFilePathsSettings(); | ||
if (settings == null) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
List<FilePaths> filePathsDef = settings.getFilePaths(); | ||
if (filePathsDef == null) { | ||
return Collections.emptyList(); | ||
} | ||
List<FilePathExpression> expressions = new ArrayList<>(); | ||
for (FilePaths filePaths : filePathsDef) { | ||
if (filePaths.matches(xmlDocument.getDocumentURI())) { | ||
expressions.addAll(filePaths.getExpressions()); | ||
} | ||
} | ||
return expressions; | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
...a/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2023 Red Hat Inc. and others. | ||
* All rights reserved. This program and the accompanying materials | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Red Hat Inc. - initial API and implementation | ||
*******************************************************************************/ | ||
|
||
package org.eclipse.lemminx.extensions.filepath.participants; | ||
|
||
import static org.eclipse.lemminx.utils.platform.Platform.isWindows; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.DirectoryStream; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
import org.eclipse.lemminx.dom.DOMAttr; | ||
import org.eclipse.lemminx.dom.DOMDocument; | ||
import org.eclipse.lemminx.dom.DOMNode; | ||
import org.eclipse.lemminx.dom.DOMRange; | ||
import org.eclipse.lemminx.dom.DTDDeclParameter; | ||
import org.eclipse.lemminx.extensions.filepath.FilePathPlugin; | ||
import org.eclipse.lemminx.extensions.filepath.settings.FilePathExpression; | ||
import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; | ||
import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest; | ||
import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; | ||
import org.eclipse.lemminx.utils.CompletionSortTextHelper; | ||
import org.eclipse.lemminx.utils.FilesUtils; | ||
import org.eclipse.lemminx.utils.XMLPositionUtility; | ||
import org.eclipse.lsp4j.CompletionItem; | ||
import org.eclipse.lsp4j.CompletionItemKind; | ||
import org.eclipse.lsp4j.Range; | ||
import org.eclipse.lsp4j.TextEdit; | ||
import org.eclipse.lsp4j.jsonrpc.CancelChecker; | ||
import org.eclipse.lsp4j.jsonrpc.messages.Either; | ||
|
||
/** | ||
* Extension to support completion for file, folder path in: | ||
* | ||
* <ul> | ||
* <li>attribute value: | ||
* | ||
* <pre> | ||
* <item path="file:///C:/folder" /> | ||
* <item path="file:///C:/folder file:///C:/file.txt" /> | ||
* <item path="/folder" /> | ||
* </pre> | ||
* | ||
* </li> | ||
* <li>DTD DOCTYPE SYSTEM | ||
* | ||
* <pre> | ||
* <!DOCTYPE parent SYSTEM "file.dtd"> | ||
* </pre> | ||
* | ||
* </li> | ||
* | ||
* </ul> | ||
* | ||
* <p> | ||
* | ||
* </p> | ||
*/ | ||
public class FilePathCompletionParticipant extends CompletionParticipantAdapter { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(FilePathCompletionParticipant.class.getName()); | ||
|
||
private final FilePathPlugin filePathPlugin; | ||
|
||
public FilePathCompletionParticipant(FilePathPlugin filePathPlugin) { | ||
this.filePathPlugin = filePathPlugin; | ||
} | ||
|
||
@Override | ||
public void onAttributeValue(String value, ICompletionRequest request, ICompletionResponse response, | ||
CancelChecker cancelChecker) throws Exception { | ||
// File path completion on attribute value | ||
List<FilePathExpression> expressions = filePathPlugin.findFilePathExpression(request.getXMLDocument()); | ||
if (expressions.isEmpty()) { | ||
return; | ||
} | ||
DOMNode node = request.getNode(); | ||
DOMAttr attr = node.findAttrAt(request.getOffset()); | ||
DOMDocument xmlDocument = request.getXMLDocument(); | ||
for (FilePathExpression expression : expressions) { | ||
if (expression.match(attr)) { | ||
DOMRange attrValueRange = attr.getNodeAttrValue(); | ||
addFileCompletionItems(xmlDocument, attrValueRange.getStart() + 1 /* increment to be after the quote */, | ||
attrValueRange.getEnd() - 1, request.getOffset(), expression.getSeparator(), | ||
response); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) | ||
throws Exception { | ||
// File path completion on text node | ||
List<FilePathExpression> expressions = filePathPlugin.findFilePathExpression(request.getXMLDocument()); | ||
if (expressions.isEmpty()) { | ||
return; | ||
} | ||
DOMNode node = request.getNode(); | ||
DOMDocument xmlDocument = request.getXMLDocument(); | ||
for (FilePathExpression expression : expressions) { | ||
if (expression.match(node)) { | ||
DOMRange textRange = node; | ||
addFileCompletionItems(xmlDocument, textRange.getStart(), textRange.getEnd(), request.getOffset(), | ||
expression.getSeparator(), | ||
response); | ||
} | ||
} | ||
|
||
} | ||
|
||
@Override | ||
public void onDTDSystemId(String value, ICompletionRequest request, ICompletionResponse response, | ||
CancelChecker cancelChecker) throws Exception { | ||
// File path completion on DTD DOCTYPE SYSTEM | ||
DOMDocument xmlDocument = request.getXMLDocument(); | ||
DTDDeclParameter systemId = xmlDocument.getDoctype().getSystemIdNode(); | ||
addFileCompletionItems(xmlDocument, systemId.getStart() + 1 /* increment to be after the quote */, | ||
systemId.getEnd() - 1, request.getOffset(), null, response); | ||
} | ||
|
||
private static void addFileCompletionItems(DOMDocument xmlDocument, int startOffset, int endOffset, | ||
int completionOffset, | ||
Character separator, ICompletionResponse response) | ||
throws Exception { | ||
FilePathCompletionResult result = FilePathCompletionResult.create(xmlDocument.getText(), | ||
xmlDocument.getDocumentURI(), startOffset, endOffset, completionOffset, separator); | ||
Path baseDir = result.getBaseDir(); | ||
if (baseDir == null) { | ||
return; | ||
} | ||
String slash = ""; | ||
Range replaceRange = XMLPositionUtility.createRange(result.getStart(), result.getEnd(), xmlDocument); | ||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(baseDir)) { | ||
for (Path entry : stream) { | ||
createFilePathCompletionItem(entry.toFile(), replaceRange, response, slash); | ||
} | ||
} catch (IOException e) { | ||
LOGGER.log(Level.SEVERE, "Error while getting files/directories", e); | ||
} | ||
} | ||
|
||
private static void createFilePathCompletionItem(File f, Range replaceRange, ICompletionResponse response, | ||
String slash) { | ||
CompletionItem item = new CompletionItem(); | ||
String fName = FilesUtils.encodePath(f.getName()); | ||
if (isWindows && fName.isEmpty()) { // Edge case for Windows drive letter | ||
fName = f.getPath(); | ||
fName = fName.substring(0, fName.length() - 1); | ||
} | ||
String insertText; | ||
insertText = slash + fName; | ||
item.setLabel(insertText); | ||
|
||
CompletionItemKind kind = f.isDirectory() ? CompletionItemKind.Folder : CompletionItemKind.File; | ||
item.setKind(kind); | ||
|
||
item.setSortText(CompletionSortTextHelper.getSortText(kind)); | ||
item.setFilterText(insertText); | ||
item.setTextEdit(Either.forLeft(new TextEdit(replaceRange, insertText))); | ||
response.addCompletionItem(item); | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
...n/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package org.eclipse.lemminx.extensions.filepath.participants; | ||
|
||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.function.Predicate; | ||
|
||
import org.eclipse.lemminx.utils.FilesUtils; | ||
import org.eclipse.lemminx.utils.StringUtils; | ||
|
||
public class FilePathCompletionResult { | ||
|
||
private static final Predicate<Character> isStartValidChar = (c) -> c != '/' && c != '\\'; | ||
|
||
private final int start; | ||
|
||
private final int end; | ||
|
||
private final Path baseDir; | ||
|
||
public FilePathCompletionResult(int start, int end, Path baseDir) { | ||
super(); | ||
this.start = start; | ||
this.end = end; | ||
this.baseDir = baseDir; | ||
} | ||
|
||
public int getStart() { | ||
return start; | ||
} | ||
|
||
public int getEnd() { | ||
return end; | ||
} | ||
|
||
public Path getBaseDir() { | ||
return baseDir; | ||
} | ||
|
||
public static FilePathCompletionResult create(String content, String fileUri, int startNodeOffset, | ||
int endNodeOffset, int completionOffset, Character separator) { | ||
boolean isMultiFilePath = separator != null; | ||
Predicate<Character> isStartValidChar2 = isStartValidChar; | ||
int endPathOffset = endNodeOffset; | ||
if (isMultiFilePath) { | ||
// multiple file path | ||
isStartValidChar2 = c -> c != separator && isStartValidChar.test(c); | ||
endPathOffset = StringUtils.findEndWord(content, completionOffset, endNodeOffset, c -> c != separator); | ||
if (endPathOffset == -1) { | ||
endPathOffset = endNodeOffset; | ||
} | ||
} | ||
int startPathOffset = StringUtils.findStartWord(content, completionOffset, startNodeOffset, isStartValidChar2); | ||
|
||
int startBaseDirOffset = startNodeOffset; | ||
if (isMultiFilePath && !isStartValidChar.test(content.charAt(startPathOffset - 1))) { | ||
int tmp = StringUtils.findStartWord(content, completionOffset, startNodeOffset, c -> c != separator); | ||
if (tmp != -1) { | ||
startBaseDirOffset = tmp; | ||
} | ||
} | ||
|
||
Path baseDir = getBaseDir(content, fileUri, startBaseDirOffset, startPathOffset); | ||
if (baseDir == null || !Files.exists(baseDir)) { | ||
baseDir = null; | ||
} | ||
return new FilePathCompletionResult(startPathOffset, endPathOffset, baseDir); | ||
} | ||
|
||
private static Path getBaseDir(String content, String fileUri, int start, int end) { | ||
if (end > start) { | ||
String basePath = content.substring(start, end); | ||
if (!hasPathBeginning(basePath)) { | ||
try { | ||
Path baseDir = FilesUtils.getPath(basePath); | ||
if (Files.exists(baseDir)) { | ||
return baseDir; | ||
} | ||
} catch (Exception e) { | ||
|
||
} | ||
} | ||
try { | ||
return FilesUtils.getPath(fileUri).getParent().resolve(basePath); | ||
} catch (Exception e) { | ||
return null; | ||
} | ||
} | ||
return FilesUtils.getPath(fileUri).getParent(); | ||
} | ||
|
||
private static boolean hasPathBeginning(String currentText) { | ||
if (currentText.startsWith("/") | ||
|| currentText.startsWith("./") | ||
|| currentText.startsWith("../") | ||
|| currentText.startsWith("..\\") | ||
|| currentText.startsWith(".\\")) { | ||
return true; | ||
} | ||
return isAbsoluteWindowsPath(currentText); | ||
} | ||
|
||
private static boolean isAbsoluteWindowsPath(String currentText) { | ||
if (currentText.length() < 3) { | ||
return false; | ||
} | ||
if (!Character.isLetter(currentText.charAt(0))) { | ||
return false; | ||
} | ||
return currentText.charAt(1) == ':' && (currentText.charAt(2) == '\\' || currentText.charAt(2) == '/'); | ||
} | ||
|
||
} |
Oops, something went wrong.