Skip to content

Commit

Permalink
Var processing 2.0 (#325)
Browse files Browse the repository at this point in the history
* multiple variable completion

Signed-off-by: Arun Venmany <[email protected]>

* multiple variable diagnostic

Signed-off-by: Arun Venmany <[email protected]>

* file watcher for config files alteration for variable re processing

Signed-off-by: Arun Venmany <[email protected]>

* changes for review comments

Signed-off-by: Arun Venmany <[email protected]>

* changes for review comments

Signed-off-by: Arun Venmany <[email protected]>

* correcting file watch path

Signed-off-by: Arun Venmany <[email protected]>

* correcting ci commons snapshot version

Signed-off-by: Arun Venmany <[email protected]>

* correcting ci commons snapshot version

Signed-off-by: Arun Venmany <[email protected]>

* adding file filters

Signed-off-by: Arun Venmany <[email protected]>

* adding more file filters and changing completion logic for multiple vars

Signed-off-by: Arun Venmany <[email protected]>

* adding more file filters and changing completion logic for multiple vars

Signed-off-by: Arun Venmany <[email protected]>

* updating one test based on review comments

Signed-off-by: Arun Venmany <[email protected]>

---------

Signed-off-by: Arun Venmany <[email protected]>
  • Loading branch information
arunvenmany-ibm authored Dec 24, 2024
1 parent 2dc07a5 commit 07eca75
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ public void testInvalidVariableDiagnostic() {
);

Diagnostic dup1 = new Diagnostic();
dup1.setRange(r(5, 36, 5, 55));
dup1.setRange(r(5, 34, 5, 56));
dup1.setCode("incorrect_variable");
dup1.setSource("liberty-lemminx");
dup1.setSeverity(DiagnosticSeverity.Error);
dup1.setMessage("ERROR: The variable \"default.httpsl.port\" does not exist.");
dup1.setData("default.httpsl.port");

Diagnostic dup2 = new Diagnostic();
dup2.setRange(r(7, 31, 7, 50));
dup2.setRange(r(7, 29, 7, 51));
dup2.setCode("incorrect_variable");
dup2.setSource("liberty-lemminx");
dup2.setSeverity(DiagnosticSeverity.Error);
Expand Down Expand Up @@ -156,7 +156,7 @@ public void testInvalidVariableDiagnosticWithCodeAction() throws IOException, Ba
);

Diagnostic invalid1 = new Diagnostic();
invalid1.setRange(r(7, 31, 7, 44));
invalid1.setRange(r(7, 29, 7, 45));
invalid1.setCode("incorrect_variable");
invalid1.setMessage("ERROR: The variable \"default.https\" does not exist.");
invalid1.setData("default.https");
Expand All @@ -181,7 +181,6 @@ public void testInvalidVariableDiagnosticWithCodeAction() throws IOException, Ba
.get(0).getLeft().getTextDocument()
.setUri(serverXmlFile.toURI().toString());
}

XMLAssert.testCodeActionsFor(serverXML, serverXmlFile.toURI().toString(), invalid1, codeActions.get(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import io.openliberty.tools.langserver.lemminx.models.feature.VariableLoc;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
Expand Down Expand Up @@ -53,21 +54,52 @@ public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICo
Properties variableProps = SettingsService.getInstance()
.getVariablesForServerXml(request.getXMLDocument()
.getDocumentURI());
String variableName = valuePrefix.replace("$", "")
.replace("{", "")
.replace("}", "");
variableProps.entrySet().stream().filter(it -> it.getKey().toString().toLowerCase().contains(variableName.toLowerCase()))
//getting all existing variables in current completion prefix string
List<VariableLoc> variables = LibertyUtils.getVariablesFromTextContent(valuePrefix);
String variablePrefix = "";
String completionPrefix;

if (!variables.isEmpty()) {
// for multiple variables
// find last variable last character and consider new variable completion from that location
VariableLoc lastVar = variables.get(variables.size() - 1);
// if last index of an existing variable is less than lastIndex of ${,
// this means that completion variable is started with ${
if (valuePrefix.lastIndexOf("${") > lastVar.getEndLoc()) {
// get whatever is after last ${ as variablePrefix for Completion
variablePrefix = valuePrefix.substring(valuePrefix.lastIndexOf("${") + 2);
completionPrefix = valuePrefix.substring(0, valuePrefix.lastIndexOf("${"));
} else {
variablePrefix = valuePrefix.substring(lastVar.getEndLoc() + 1);
// add char between last variable and start of completion variable to completionPrefix
completionPrefix = valuePrefix.substring(0,lastVar.getEndLoc()+1);
}
} else {
// for single variable,check ${ is specified
if (valuePrefix.contains("${")) {
// extract variable name with whatever is after ${
variablePrefix = valuePrefix.substring(valuePrefix.lastIndexOf("${") + 2);
// extract completionPrefix with whatever is before ${
completionPrefix = valuePrefix.substring(0, valuePrefix.lastIndexOf("${"));
} else {
// if no ${ specified, take all data as completion variable
variablePrefix = valuePrefix;
completionPrefix = "";
}
}
String finalVariableName = variablePrefix.replace("${","").replace("}","");
variableProps.entrySet().stream().filter(it -> it.getKey().toString().toLowerCase()
.contains(finalVariableName.toLowerCase()))
.forEach(variableProp -> {
String varValue = String.format("${%s}", variableProp.getKey());

String varValue = String.format("%s${%s}", completionPrefix, variableProp.getKey());
Either<TextEdit, InsertReplaceEdit> edit = Either.forLeft(new
TextEdit(request.getReplaceRange(), varValue));
CompletionItem completionItem = new CompletionItem();
completionItem.setLabel(varValue);
completionItem.setTextEdit(edit);
completionItem.setFilterText(variableProp.getKey().toString());
completionItem.setKind(CompletionItemKind.Value);
completionItem.setDocumentation(String.format("%s = %s", variableProp.getKey(),variableProp.getValue()));
completionItem.setDocumentation(String.format("%s = %s", variableProp.getKey(), variableProp.getValue()));
response.addCompletionItem(completionItem);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ private void validateVariables(DOMDocument domDocument, List<Diagnostic> diagnos
for (VariableLoc variable : variables) {
if (!variablesMap.containsKey(variable.getValue())) {
String variableInDoc = String.format("${%s}", variable.getValue());
Range range = XMLPositionUtility.createRange(variable.getStartLoc(),variable.getEndLoc(),
//range is used in ReplaceVariable to provide quick fix.
// we just need the variable value range here as ${} is added in replace variable message
Range range = XMLPositionUtility.createRange(variable.getStartLoc() - 2, variable.getEndLoc() + 1,
domDocument);
String message = "ERROR: The variable \"" + variable.getValue() + "\" does not exist.";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package io.openliberty.tools.langserver.lemminx;

import io.openliberty.tools.langserver.lemminx.services.FileWatchService;
import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace;
import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant;
import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant;
Expand All @@ -26,6 +27,9 @@
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.WorkspaceFolder;

import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

Expand Down Expand Up @@ -77,6 +81,23 @@ public void start(InitializeParams initializeParams, XMLExtensionsRegistry xmlEx
} catch (Exception e) {
throw new RuntimeException(e);
}

// for each workspace, a file alteration observer is added
for (LibertyWorkspace workspace : LibertyProjectsManager.getInstance().getLibertyWorkspaceFolders()) {
// checking for any changes in wlp user folder for gradle and maven
Path libertyUsrGradlePath = new File(workspace.getWorkspaceURI().getPath(),
"target").toPath();
Path libertyUsrMavenPath = new File(workspace.getWorkspaceURI().getPath(),
"build").toPath();
List<String> paths = Arrays.asList(libertyUsrMavenPath.toString(), libertyUsrGradlePath.toString());
try {
FileWatchService.getInstance()
.addFileAlterationObserver(workspace, paths);
} catch (Exception e) {
LOGGER.warning("unable to add file alteration observer for paths " + paths
+ " with error message " + e.getMessage());
}
}
}

@Override
Expand All @@ -90,6 +111,7 @@ public void stop(XMLExtensionsRegistry xmlExtensionsRegistry) {
xmlExtensionsRegistry.unregisterHoverParticipant(hoverParticipant);
xmlExtensionsRegistry.unregisterDiagnosticsParticipant(diagnosticsParticipant);
xmlExtensionsRegistry.unregisterCodeActionParticipant(codeActionsParticipant);
FileWatchService.getInstance().cleanFileMonitors();
}

// Do save is called on startup with a Settings update
Expand All @@ -103,17 +125,5 @@ public void doSave(ISaveContext saveContext) {
SettingsService.getInstance().updateLibertySettings(xmlSettings);
LOGGER.info("Liberty XML settings updated");
}
if (saveContext.getType() == SaveContextType.DOCUMENT) {
try {
LibertyWorkspace workspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(saveContext.getUri());
if (workspace != null) {
SettingsService.getInstance()
.populateVariablesForWorkspace(workspace);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
LOGGER.info("Liberty XML variables updated");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Hover onAttributeValue(IHoverRequest request, CancelChecker cancelChecker
stringBuilder.append(String.format("%s = %s", variable.getValue(), variableMap.get(variable.getValue())));
}
if (varIter.hasNext()) {
stringBuilder.append(System.lineSeparator());
stringBuilder.append("<br />");
}
}
if (!stringBuilder.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionRequest;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;

import java.util.List;
Expand Down Expand Up @@ -64,10 +66,6 @@ public void doCodeAction(ICodeActionRequest request, List<CodeAction> codeAction
String variableInDoc = String.format("${%s}", nextVariable.getKey().toString());
codeActions.add(CodeActionFactory.replace(title, diagnostic.getRange(), variableInDoc, document.getTextDocument(), diagnostic));
}
/*for (Map.Entry<Object, Object> nextVariable : existingVariables.entrySet()) {
String title = "Replace Variable with " + nextVariable.getKey() + " with value = " + nextVariable.getValue();
codeActions.add(CodeActionFactory.replace(title, diagnostic.getRange(), nextVariable.getKey().toString(), document.getTextDocument(), diagnostic));
}*/
}
} catch (Exception e) {
// BadLocationException not expected
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (c) 2024 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package io.openliberty.tools.langserver.lemminx.services;

import io.openliberty.tools.langserver.lemminx.util.LibertyConstants;
import io.openliberty.tools.langserver.lemminx.util.LibertyUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

public class FileWatchService {

private final Set<FileAlterationObserver> fileObservers = new HashSet<>();
private final Set<FileAlterationMonitor> monitors = new HashSet<>();

private static final FileWatchService instance = new FileWatchService();

public static FileWatchService getInstance() {
return instance;
}

private static final Logger LOGGER = Logger.getLogger(FileWatchService.class.getName());

private FileWatchService() {
}

/**
* add observer for file changes
*
* @param workspace workspace
* @param watchLocations parent locations
*/
public void addFileAlterationObserver(LibertyWorkspace workspace, List<String> watchLocations)
throws Exception {
for (String location:watchLocations) {
FileAlterationObserver observer = getFileAlterationObserver(location, workspace);
observer.initialize();
fileObservers.add(observer);
FileAlterationMonitor monitor = new FileAlterationMonitor();
monitor.addObserver(observer);
monitor.start();
monitors.add(monitor);
}
}

private FileAlterationObserver getFileAlterationObserver(final String parentPath, LibertyWorkspace workspace) {
//ignore files/directories with below suffixes and names
IOFileFilter notFileFilter = FileFilterUtils.notFileFilter(
new SuffixFileFilter(Arrays.asList(".class", ".lst", ".txt", ".log", ".manager", ".libertyls",
".sLock", ".jar", ".war", ".ear",".mf"),
IOCase.INSENSITIVE)
.or(new NameFileFilter(Arrays.asList("plugin-cfg.xml", "libs", "tmp", "classes",
"generated-sources", "generated-test-sources", "invoker-reports",
"it", "maven-status", "surefire-reports", "test-classes"), IOCase.INSENSITIVE)));
FileAlterationObserver observer = new FileAlterationObserver(parentPath, notFileFilter);
addFileAlterationListener(observer, workspace);
return observer;
}

private void addFileAlterationListener(FileAlterationObserver observer, LibertyWorkspace workspace) {
observer.addListener(new FileAlterationListenerAdaptor() {
@Override
public void onDirectoryCreate(File file) {
// not required
}

@Override
public void onDirectoryDelete(File file) {
// not required
}

@Override
public void onDirectoryChange(File file) {
// not required
}

@Override
public void onFileCreate(File file) {
onAlteration(file, workspace);
}

@Override
public void onFileDelete(File file) {
onAlteration(file, workspace);
}

@Override
public void onFileChange(File file) {
onAlteration(file, workspace);
}

/**
* update variables on file alteration, if modified file is a config
*
* @param file changed file
* @param workspace current workspace
*/
private void onAlteration(File file, LibertyWorkspace workspace) {
boolean watchedFileChanged = LibertyConstants.filesToWatch.stream().anyMatch(fileName -> file.getName().contains(fileName));
boolean isConfigXmlFile = false;
try {
isConfigXmlFile = LibertyUtils.isConfigXMLFile(file.getCanonicalPath());
} catch (IOException e) {
LOGGER.warning("Liberty XML variables cannot be updated for file path %s with error %s"
.formatted(file.getPath(), e.getMessage()));
}
if (watchedFileChanged || isConfigXmlFile) {
SettingsService.getInstance().populateVariablesForWorkspace(workspace);
LOGGER.info("Liberty XML variables updated for workspace URI " + workspace.getWorkspaceString());
}
}
});
}

/**
* clean all monitors for all workspaces
*/
public void cleanFileMonitors() {
fileObservers.clear();
try {
for (FileAlterationMonitor monitor : monitors) {
monitor.stop();
}
} catch (Exception e) {
LOGGER.warning("Issue while removing file monitors with error %s"
.formatted(e.getMessage()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
*******************************************************************************/
package io.openliberty.tools.langserver.lemminx.util;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class LibertyConstants {
Expand Down Expand Up @@ -74,4 +76,6 @@ private LibertyConstants() {

public static String changedFeatureNameDiagMessage="ERROR: The %s feature cannot be configured with the %s feature because they are two different versions of the same feature. " +
"The feature name changed from %s to %s for Jakarta EE. Remove one of the features.";

public static List<String> filesToWatch= Arrays.asList(SERVER_XML,"server.env","bootstrap.properties");
}
Loading

0 comments on commit 07eca75

Please sign in to comment.