Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes in the Lingua Franca Language Server to support improvements in the VSCode extension #2370

Merged
merged 18 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lsp/src/main/java/org/lflang/diagram/lsp/LFLanguageClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.lflang.diagram.lsp;

import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.services.LanguageClient;

public interface LFLanguageClient extends KGraphLanguageClient, LanguageClient {

@JsonNotification("notify/sendLibraryReactors")
public void sendLibraryReactors(LibraryFile libraryFile);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
package org.lflang.diagram.lsp;

import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.xtext.ide.server.ILanguageServerAccess;
import org.eclipse.xtext.ide.server.ILanguageServerExtension;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.lflang.LFRuntimeModule;
import org.lflang.LFStandaloneSetup;
import org.lflang.ast.ToSExpr;
import org.lflang.generator.GeneratorResult;
import org.lflang.generator.GeneratorResult.Status;
import org.lflang.generator.IntegratedBuilder;
import org.lflang.lf.Model;
import org.lflang.lf.Reactor;
import org.lflang.lf.TargetDecl;
import org.lflang.util.LFCommand;

/**
Expand All @@ -30,17 +40,23 @@ class LFLanguageServerExtension implements ILanguageServerExtension {
.getInstance(IntegratedBuilder.class);

/** The access point for reading documents, communicating with the language client, etc. */
private LanguageClient client;
private LFLanguageClient client;

@Inject Injector injector;

@Override
public void initialize(ILanguageServerAccess access) {
// This method is never invoked.
}

public void setClient(LanguageClient client) {
public void setClient(LFLanguageClient client) {
this.client = client;
}

public XtextResourceSet getXtextResourceSet(final URI uri) {
return injector.getInstance(XtextResourceSet.class);
}

@JsonRequest("parser/ast")
public CompletableFuture<String> getAst(String uri) {
return CompletableFuture.supplyAsync(
Expand Down Expand Up @@ -84,6 +100,85 @@ public CompletableFuture<String> build(BuildArgs args) {
});
}

/**
* Manage requests to retrieve a hierarchical structure of reactor libraries based on the provided
* {@code filePath}.
*
* @param filePath the URI of the LF file of interest
* @return A {@code CompletableFuture<LibraryFile>} representing the asynchronous computation * of
* the parsed reactor structure. If an error occurs during parsing, the future will * complete
* with {@code null}.
*/
@JsonRequest("generator/getLibraryReactors")
public CompletableFuture<LibraryFile> getLibraryReactors(String filePath) {
return CompletableFuture.supplyAsync(
() -> {
try {
// LF program file parsing
URI uri = URI.createURI(filePath);
// Return a list of reactors within the file at the specific uri
return parseLibraryReactors(uri);
} catch (Exception e) {
return null;
}
});
}

/**
* Retrieve the target position specified in the LF program file at the given path.
*
* @param path The path to the LF program file.
* @return A {@code CompletableFuture<NodePosition>} containing the NodePosition object
* representing the position of the target, or null if an error occurs during parsing or if
* the target position is not found.
*/
@JsonRequest("generator/getTargetPosition")
public CompletableFuture<NodePosition> getTargetPosition(String path) {
return CompletableFuture.supplyAsync(
() -> {
NodePosition targetPosition;
try {
URI uri = URI.createURI(path);
// LF program file parsing
Resource resource = getXtextResourceSet(uri).getResource(uri, true);
Model m = (Model) resource.getContents().get(0);
TargetDecl target = m.getTarget();
INode node = NodeModelUtils.getNode(target);
targetPosition = new NodePosition(node.getStartLine(), node.getEndLine());
return targetPosition;
} catch (Exception e) {
return null;
}
});
}

/**
* Parse a library of reactors specified by the provided URI and construct a hierarchical
* libraryFile representation.
*
* @param uri The URI specifying the location of the library.
* @return A {@code LibraryFile} object representing the hierarchical structure of the reactor
* library, or {@code null} if an error occurs during parsing.
*/
public LibraryFile parseLibraryReactors(URI uri) {
LibraryFile res = new LibraryFile(uri.toString());
try {
Resource resource = getXtextResourceSet(uri).getResource(uri, true);
Model m = (Model) resource.getContents().get(0);
Stream<Reactor> reactors =
m.getReactors().stream().filter(r -> r.getName() != null && !r.getName().isEmpty());
reactors.forEach(
r -> {
INode node = NodeModelUtils.getNode(r);
NodePosition nodePosition = new NodePosition(node.getStartLine(), node.getEndLine());
res.getChildren().add(new ReactorNode(r.getName(), res.getUri(), nodePosition));
});
} catch (Exception e) {
return null;
vinzbarbuto marked this conversation as resolved.
Show resolved Hide resolved
}
return res;
}

/**
* Handles a request for the most complete build of the specified Lingua Franca file that can be
* done in a limited amount of time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,16 @@ public List<ILanguageServerExtension> getLanguageServerExtensions() {

@Override
public Class<? extends KGraphLanguageClient> getRemoteInterface() {
return KGraphLanguageClient.class;
return LFLanguageClient.class;
}

@Override
public void onConnect() {
super.onConnect();
constraints.setClient((KGraphLanguageClient) languageClient);
rectPack.setClient((KGraphLanguageClient) languageClient);
rectPack.setClient((LFLanguageClient) languageClient);
LanguageServerMessageReporter.setClient(languageClient);
lfExtension.setClient(languageClient);
lfExtension.setClient((LFLanguageClient) languageClient);
// The following is needed because VS Code treats System.err like System.out and System.out
// like a shout
// into the void.
Expand Down
53 changes: 53 additions & 0 deletions lsp/src/main/java/org/lflang/diagram/lsp/LibraryFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.lflang.diagram.lsp;

import java.util.ArrayList;
import java.util.List;

/**
* Represents a Lingua Franca (LF) file. This file can contain multiple ReactorNode elements, with
* each node potentially having zero or more of these reactors.
*/
public class LibraryFile {
private String label; // The label (name) associated with the LF file.
private String uri; // The URI specifying the location of the LF file.
private List<ReactorNode> children; // The list of reactors included within the LF file.

/**
* Constructs a new LibraryFile with the specified URI.
*
* @param uri The URI specifying the location of the LF file.
*/
public LibraryFile(String uri) {
this.uri = uri;
String[] splits = uri.split("/");
this.label = splits[splits.length - 1];
this.children = new ArrayList<>();
}

/**
* Retrieves the label (name) of the LF file.
*
* @return The label (name) of the LF file.
*/
public String getLabel() {
return label;
}

/**
* Retrieves the URI specifying the location of the LF file.
*
* @return The URI specifying the location of the LF file.
*/
public String getUri() {
return uri;
}

/**
* Retrieves the list of children nodes of the tree.
*
* @return The list of children nodes of the tree.
*/
public List<ReactorNode> getChildren() {
return children;
}
}
39 changes: 39 additions & 0 deletions lsp/src/main/java/org/lflang/diagram/lsp/NodePosition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.lflang.diagram.lsp;

/**
* @brief Position of a node within a text document or code file.
* <p>A position is a pair of integers representing the starting and ending line numbers.
*/
public class NodePosition {
private int start; // The starting position of the node.
private int end; // The ending position of the node.

/**
* Constructs a new NodePosition with the specified start and end positions.
*
* @param start The starting position of the node.
* @param end The ending position of the node.
*/
public NodePosition(int start, int end) {
this.start = start;
this.end = end;
}

/**
* Retrieves the starting position of the node.
*
* @return The starting position of the node.
*/
public int getStart() {
return start;
}

/**
* Retrieves the ending position of the node.
*
* @return The ending position of the node.
*/
public int getEnd() {
return end;
}
}
54 changes: 54 additions & 0 deletions lsp/src/main/java/org/lflang/diagram/lsp/ReactorNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.lflang.diagram.lsp;

/**
* Represents a node in a hierarchical structure where each node has a label, a URI, and positional
* information. This node typically represents a reactor within a Lingua Franca (LF) file.
*
* @see LibraryFile
*/
public class ReactorNode {

private String label; // The name or label of the reactor.
private String uri; // The URI indicating the location of the LF file that contains the reactor.
private NodePosition position; // The position of the reactor within the LF file.

/**
* Constructs a new ReactorNode with the specified label, URI, and position.
*
* @param label The label or name of the reactor.
* @param uri The URI that specifies the location of the LF file containing the reactor.
* @param position The position of the reactor within the LF file.
*/
public ReactorNode(String label, String uri, NodePosition position) {
this.label = label;
this.uri = uri;
this.position = position;
}

/**
* Returns the label of the reactor.
*
* @return The label of the reactor.
*/
public String getLabel() {
return label;
}

/**
* Returns the URI of the LF file that contains the reactor.
*
* @return The URI of the LF file.
*/
public String getUri() {
return uri;
}

/**
* Returns the position information of the reactor within the LF file.
*
* @return The position information of the reactor.
*/
public NodePosition getPosition() {
return position;
}
}
Loading