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

Extension cpp lsp #1499

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions org.lflang.diagram/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies {
exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt.*'
exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt'
}
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'
}

apply plugin: 'application'
Expand Down
274 changes: 274 additions & 0 deletions org.lflang.diagram/src/org/lflang/diagram/lsp/CppLanguageServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
package org.lflang.diagram.lsp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import org.lflang.generator.CodeMap;
import java.nio.charset.StandardCharsets;
import java.io.OutputStream;
import java.io.InputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
import java.util.Collections;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.MarkedString;
import java.util.Map;
import org.lflang.generator.Position;
import org.lflang.generator.Range;
import java.util.NavigableMap;
import java.util.HashMap;
import java.util.HashSet;
import java.io.File;

import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.xtext.ide.server.hover.IHoverService;
import org.eclipse.xtext.util.CancelIndicator;

import org.lflang.generator.GeneratorResult;

public class CppLanguageServer{
static OutputStream stdin = null;
static Process pr;
static InputStream stdout = null;
static BufferedReader reader = null;
static BufferedWriter writer = null;
static BufferedReader errorReader = null;
static Path generatedPath;
static String generatedDirectory;
static Map<Path, GeneratorResult> storedGeneratedResults;
static Map<Path, ProjectBuildResult> storedBuildResults;


public static void init() {
process_init();
clangd_init();
readOutput();
storedBuildResults = new HashMap();
System.out.println("Initialized language server");
}

public static Hover hoverRequest(HoverParams params) {
Path lfPath = Paths.get(params.getTextDocument().getUri().substring(5));
if (!storedBuildResults.containsKey(lfPath)) {
Hover preHover = new Hover();
List<Either<String, MarkedString>> content = Collections.singletonList(Either.forLeft("You first need to build a project"));
preHover.setContents(content);
return preHover;
}
try {
Map<Path, CodeMap> result;
GeneratorResult genResult;
ProjectBuildResult buildResult = storedBuildResults.get(lfPath);
result = buildResult.getGeneratorResult().getCodeMaps();
generatedDirectory = buildResult.getGeneratorResult().getCommand().directory().toString() + "/src-gen";
check_and_add_cmake_file();
Position codePos = findPosition(params.getPosition().getLine(), params.getPosition().getCharacter(), lfPath, result);
if (codePos.getOneBasedLine() == 1 && codePos.getOneBasedColumn() == 1) {
return IHoverService.EMPTY_HOVER; // hover to LF code
}
if (!buildResult.isOpen(generatedPath)) {
buildResult.open(generatedPath);
}
return clangdHover(codePos);
} catch (Exception e) {
return IHoverService.EMPTY_HOVER; // Fail silently
}
}

private static Position findPosition(int line, int col, Path lfFile, Map<Path, CodeMap> generatedResult) {
for (Path sourcePath : generatedResult.keySet()) {
CodeMap codeMap = generatedResult.get(sourcePath);
for (int brutLine = 1; brutLine <= 500; brutLine++) { // TODO change brute force positions
for (int brutChar = 1; brutChar <= 100; brutChar++) {
Position generatedPos = Position.fromOneBased(brutLine, brutChar);
Position p = codeMap.adjusted(lfFile, generatedPos);
if (p.getOneBasedLine() == line && p.getOneBasedColumn() == col) {
generatedPath = sourcePath;
return generatedPos;
}
}
}
}
return Position.fromOneBased(1, 1);
}

private static Hover clangdHover(Position params) {
send_hover_request(params.getOneBasedLine(), params.getOneBasedColumn());
Hover resultHover = new Hover();
try {
String jsonResponse = readOutput();
jsonResponse = jsonResponse.substring(jsonResponse.indexOf(System.getProperty("line.separator"))+1);// remove first line since contains the header
jsonResponse = jsonResponse.substring(jsonResponse.indexOf(System.getProperty("line.separator"))+1);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonResponse);
String jsonValue = jsonNode.get("result").get("contents").get("value").textValue();
List<Either<String, MarkedString>> content = Collections.singletonList(Either.forLeft(jsonValue));
resultHover.setContents(content);
// int lvalue_c = jsonNode.get("result").get("range").get("start").get("character").intValue();
// int lvalue_l = jsonNode.get("result").get("range").get("start").get("line").intValue();
// int rvalue_c = jsonNode.get("result").get("range").get("end").get("character").intValue();
// int rvalue_l = jsonNode.get("result").get("range").get("end").get("line").intValue();
return resultHover;
} catch (Exception e) {
// most often gets here because there is no fields that jsonNode.get() is looking for (i.e. result is null)
return IHoverService.EMPTY_HOVER;
}
}

private static void check_and_add_cmake_file() {
try {
File cmake_file = new File(generatedDirectory + "compile_commands.json");
if (!cmake_file.exists()) {
ProcessBuilder cmake_command = new ProcessBuilder("cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=1");
cmake_command.directory(new File(generatedDirectory));
cmake_command.start();
}
} catch (Exception e) {
System.out.println("failed creating cmake file");
}
}

private static String readOutput() {
char[] chars = new char[8192];
try {
for(int len; (len = reader.read(chars)) > 0 && reader.ready();) {

}
} catch (Exception e) {
System.out.println("failed reading output");
}
return String.valueOf(chars);
}

private static void process_init() {
try {
ProcessBuilder clangd = new ProcessBuilder("clangd");
pr = clangd.start();

stdin = pr.getOutputStream();
stdout = pr.getInputStream();
reader = new BufferedReader (new InputStreamReader(stdout));
writer = new BufferedWriter(new OutputStreamWriter(stdin));

errorReader = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
} catch (Exception e) {
System.out.println("failed process init");
}
}

private static void readErrorOutput() {
try {
String str = null;
while ((str = errorReader.readLine()) != null && errorReader.ready()) {
System.out.println(str);
}
} catch (Exception e) {
System.out.println("failed reading error output");
}
}

private static void send_hover_request(int line, int character) {
try {
String body = "{\"method\": \"textDocument/hover\",\"jsonrpc\": \"2.0\",\"id\": 1,\"params\": {\"textDocument\": {\"uri\": \"file://" + generatedPath.toString() + "\"} ,\"position\": {\"line\": "+line+",\"character\": "+character+"}}}\r\n";
String header = "Content-Length: " + body.length() +"\r\n\r\n";
String cmd = header + body;
writer.write(cmd);
writer.flush();
} catch (Exception e) {
System.out.println("failed hover request");
}

}

private static void clangd_init() {
try {
String body = "{\"jsonrpc\": \"2.0\", \"method\": \"initialize\", \"id\": 1, \"params\": {\"rootUri\": \"file://" + generatedDirectory + "\", \"capabilities\": {\"hover\": {\"dynamicRegistration\": false, \"contentFormat\": []}}}}\r\n";
String header = "Content-Length: " + body.length() +"\r\n\r\n";
String cmd = header + body;
writer.write(cmd);
writer.flush();
} catch (Exception e) {
System.out.println("failed clangd init");
}
}

private static void did_open_clangd(Path path) {

try {
String fileContent = Files.readString(path, StandardCharsets.UTF_8);
fileContent = fileContent.replaceAll("\"", "\\\\\"");
fileContent = fileContent.replaceAll("\n", "\\\\n");
String body = "{\"method\": \"textDocument/didOpen\", \"jsonrpc\": \"2.0\", \"params\": {\"textDocument\": {\"uri\": \"file://" + path + "\", \"languageId\": \"cpp\", \"version\": 1, \"text\": \""+fileContent+"\"}}}\r\n";
String header = "Content-Length: " + (body.length()) +"\r\n\r\n";
String cmd = header + body;
writer.write(cmd);
writer.flush();
} catch (Exception e) {
System.out.println("failed clangd did open");
}

}

private static void did_close_clangd(String filePath) {
try {
String body = "{\"method\": \"textDocument/didClose\", \"jsonrpc\": \"2.0\", \"params\": {\"textDocument\": {\"uri\": \"file://" + filePath + "\"}}}\r\n";
String header = "Content-Length: " + (body.length()) +"\r\n\r\n";
String cmd = header + body;
writer.write(cmd);
writer.flush();
} catch (Exception e) {
System.out.println("failed clangd did close");
}
}

static class ProjectBuildResult {
Path uri;
GeneratorResult generatorResult;
HashSet<Path> clangOpenFiles;

public ProjectBuildResult(Path u, GeneratorResult gr) {
uri = u;
generatorResult = gr;
clangOpenFiles = new HashSet();
}
public GeneratorResult getGeneratorResult() {
return generatorResult;
}
public String path() {
return uri.toString();
}

public boolean isOpen(Path p) {
return clangOpenFiles.contains(p);
}
public void open(Path p) {
did_open_clangd(p);
readOutput();
clangOpenFiles.add(p);
}
public void closeOpenFiles() {
for (Path gen_path : clangOpenFiles) {
did_close_clangd(gen_path.toString());
readOutput();
}
}

}

public static void addBuild(Path uri, GeneratorResult generatorResult) {
ProjectBuildResult buildResult = new ProjectBuildResult(uri, generatorResult);
if (storedBuildResults.containsKey(uri)) {
// update build if was re-built
ProjectBuildResult result = storedBuildResults.get(uri);
result.closeOpenFiles();
storedBuildResults.remove(uri);
}
storedBuildResults.put(uri, buildResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.eclipse.xtext.ide.server.hover.IHoverService;
import org.eclipse.xtext.util.CancelIndicator;

import org.lflang.diagram.lsp.CppLanguageServer;

/**
* The Lingua Franca language and diagram server.
*
Expand All @@ -18,17 +20,19 @@ public class LFLanguageServer extends KGraphLanguageServerExtension {
public void cancelProgress(WorkDoneProgressCancelParams params) {
Progress.cancel(params.getToken().getRight().intValue());
}
boolean initialized = false;

@Override
protected Hover hover(HoverParams params, CancelIndicator cancelIndicator) {
// This override is just a hacky little patch that is being applied downstream of the original mistake and
// upstream of the ungraceful handling (IndexOutOfBoundsException) of said mistake. This patch is applied here
// simply because it is easy. This would be done differently were it not for the fact that we plan to rebuild
// this infrastructure from scratch anyway.
try {
return super.hover(params, cancelIndicator);
} catch (IndexOutOfBoundsException e) {
return IHoverService.EMPTY_HOVER; // Fail silently
if (!initialized) {
CppLanguageServer.init();
initialized = true;
}
return CppLanguageServer.hoverRequest(params);
// return IHoverService.EMPTY_HOVER; // Fail silently
Comment on lines +31 to +36
Copy link
Collaborator

@petervdonovan petervdonovan Jan 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    try {
        if (!initialized) {
            CppLanguageServer.init();
            initialized = true;
        }
        return CppLanguageServer.hoverRequest(params);
    } catch (IndexOutOfBoundsException e) {
        return IHoverService.EMPTY_HOVER;  // Fail silently
    }

The old code which got deleted in this PR does "Fail silently," as it says, which is of course very bad. However, I want to keep it around (temporarily, until we move away from Xtext). See lf-lang/vscode-lingua-franca#63 (and the comment about the "hacky little patch") for background

Copy link
Collaborator

@petervdonovan petervdonovan Jan 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, never mind. If we don't use the default hover method, we don't have the IndexOutOfBoundsException from the framework, so that comment was not exactly right. But we do use the default hover method if we are not hovering over C++ code. So actually, the right thing to do is to use the old hacky thing only if we are not hovering over C++.

Edit: Actually, I should just stop talking and clarify what I mean when I have my wits about me and have read all the other code. You can disregard what I wrote.

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.lflang.diagram.lsp;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.ArrayList;
Expand Down Expand Up @@ -35,6 +36,9 @@ class LFLanguageServerExtension implements ILanguageServerExtension {

/** The access point for reading documents, communicating with the language client, etc. */
private LanguageClient client;
private static GeneratorResult buildResult = null;
private static int buildToken = 0;
private static Path buildPath = null;

@Override
public void initialize(ILanguageServerAccess access) {
Expand Down Expand Up @@ -76,6 +80,7 @@ public CompletableFuture<String> build(String uri) {
*/
@JsonNotification("generator/partialBuild")
public void partialBuild(String uri) {
System.out.println("building on " + uri);
if (client == null) return;
buildWithProgress(client, uri, false);
}
Expand All @@ -99,6 +104,18 @@ public CompletableFuture<String[]> buildAndRun(String uri) {
});
}

public static GeneratorResult getGeneratorResult() {
return buildResult;
}

public static int getBuildToken() {
return buildToken;
}

public static Path getBuildPath() {
return buildPath;
}

/**
* Describes a build process that has a progress.
*/
Expand All @@ -123,6 +140,11 @@ private GeneratorResult buildWithProgress(LanguageClient client, String uri, boo
} finally {
progress.end(result == null ? "An internal error occurred." : result.getUserMessage());
}

// buildResult = result;
// buildToken += 1;
// buildPath = Paths.get(uri.substring(5)); // remove "file:"
CppLanguageServer.addBuild(Paths.get(uri.substring(5)), result);
return result;
}
}
4 changes: 4 additions & 0 deletions org.lflang/src/org/lflang/generator/CodeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ public String getGeneratedCode() {
return generatedCode;
}

public Map<Path, NavigableMap<Range, Range>> getCodeMap() {
return map;
}

/**
* Returns the set of all paths to Lingua Franca files
* that are known to contain code that corresponds to
Expand Down