Skip to content

Commit

Permalink
Merge pull request #2370 from lf-lang/extending
Browse files Browse the repository at this point in the history
Changes in the Lingua Franca Language Server to support improvements in the VSCode extension
  • Loading branch information
lhstrh authored Aug 29, 2024
2 parents c584965 + ec2b7a6 commit 4f69124
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 5 deletions.
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;
}
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;
}
}

0 comments on commit 4f69124

Please sign in to comment.