-
Notifications
You must be signed in to change notification settings - Fork 63
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
Aldenysq
wants to merge
3
commits into
master
Choose a base branch
from
extension-cpp-lsp
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+309
−4
Draft
Extension cpp lsp #1499
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
274 changes: 274 additions & 0 deletions
274
org.lflang.diagram/src/org/lflang/diagram/lsp/CppLanguageServer.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,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); | ||
} | ||
} |
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
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
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
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.