Skip to content

Commit

Permalink
Report diagnostics in the code editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Thihup committed May 7, 2024
1 parent 6d654df commit d8b2b89
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 21 deletions.
152 changes: 142 additions & 10 deletions dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/Main.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
package dev.thihup.jvisualg.ide;

import dev.thihup.jvisualg.lsp.VisualgLauncher;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Popup;
import javafx.stage.Stage;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.event.MouseOverTextEvent;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import org.reactfx.Subscription;

import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main extends Application {

private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final ExecutorService executor = Executors.newFixedThreadPool(3);

@FXML
private CodeArea codeArea;

private Subscription subscribe;
private Launcher<LanguageServer> clientLauncher;
private Future<Void> lspServer;
private Future<Void> lspClient;

@Override
public void start(Stage stage) throws Exception {
Expand Down Expand Up @@ -77,25 +98,131 @@ public void start(Stage stage) throws Exception {
stage.setScene(scene);
stage.show();


PipedInputStream inClient = new PipedInputStream();
PipedOutputStream outClient = new PipedOutputStream();
PipedInputStream inServer = new PipedInputStream();
PipedOutputStream outServer = new PipedOutputStream();

inClient.connect(outServer);
outClient.connect(inServer);

lspServer = VisualgLauncher.startServer(inServer, outServer, executor);
clientLauncher = LSPLauncher.createClientLauncher(new VisualgLanguageClient(), inClient, outClient, executor, null);

lspClient = clientLauncher.startListening();

Popup popup = new Popup();
Label popupMsg = new Label();
popupMsg.setStyle(
"-fx-background-color: black;" +
"-fx-text-fill: white;" +
"-fx-padding: 5;");
popup.getContent().add(popupMsg);

codeArea.setMouseOverTextDelay(Duration.ofMillis(200));
codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> {
int chIdx = e.getCharacterIndex();
Point2D pos = e.getScreenPosition();
if (codeArea.getText().isEmpty() || diagnostics == null)
return;
diagnostics.stream()
.filter(diagnostic -> {
int start = toOffset(codeArea.getText(), diagnostic.getRange().getStart());
int end = toOffset(codeArea.getText(), diagnostic.getRange().getEnd());
return chIdx >= start && chIdx <= end;
})
.findFirst().ifPresent(diagnostic -> {
popupMsg.setText(diagnostic.getMessage());
popup.show(codeArea, pos.getX(), pos.getY() + 10);
});

});
codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> {
popup.hide();
});


if (Boolean.getBoolean("autoClose"))
Platform.runLater(stage::close);
}


private List<Diagnostic> diagnostics;

class VisualgLanguageClient implements LanguageClient {


@Override
public void telemetryEvent(Object object) {

}

@Override
public void publishDiagnostics(PublishDiagnosticsParams diagnostics) {
Main.this.diagnostics = diagnostics.getDiagnostics();
diagnostics.getDiagnostics().forEach(diagnostic -> {
System.out.println(diagnostic.getMessage());
Platform.runLater(() -> {
if (codeArea.getText().isEmpty())
return;
int offsetStart = toOffset(codeArea.getText(), diagnostic.getRange().getStart());
int offsetEnd = toOffset(codeArea.getText(), diagnostic.getRange().getEnd()) + 1;
codeArea.setStyleClass(offsetStart, offsetEnd, "error");
});

});
}

@Override
public void showMessage(MessageParams messageParams) {

}

@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
return null;
}

@Override
public void logMessage(MessageParams message) {
System.out.println(message.getMessage());
}
}

@Override
public void stop() throws Exception {
if (subscribe != null) {
subscribe.unsubscribe();
}
if (lspServer != null) {
lspServer.cancel(true);
}
if (lspClient != null) {
lspClient.cancel(true);
}
executor.shutdown();
}

private int toOffset(String text, Position position) {
String[] lines = text.split("\n");
int offset = 0;
for (int i = 0; i < lines.length; i++) {
if (i == position.getLine()) {
return offset + position.getCharacter();
}
offset += lines[i].length() + 1;
}
return 0;
}


public static void main(String[] args) {
launch(args);
}

private static final List<String> KEYWORDS = List.of(
"Div", "Mod", "Se", "Entao", "Então", "Senao", "Senão", "FimSe", "Para", "De", "Ate", "Até", "Passo", "FimPara", "Faca", "Faça", "Enquanto", "FimEnquanto", "Retorne", "E", "Ou", "Nao", "Não", "Escolha", "FimEscolha", "Repita", "FimRepita", "Caso", "OutroCaso",
"Div", "Mod", "Se", "Entao", "Então", "Senao", "Senão", "FimSe", "Para", "De", "Ate", "Até", "Passo", "FimPara", "Faca", "Faça", "Enquanto", "FimEnquanto", "Retorne", "E", "Ou", "Nao", "Não", "Escolha", "FimEscolha", "Repita", "FimRepita", "Caso", "OutroCaso",
"abs", "arccos", "arcsen", "arctan", "asc", "carac", "caracpnum", "compr", "copia", "cos", "cotan", "exp", "grauprad", "int", "log", "logn", "maiusc", "minusc", "numpcarac", "pos", "pi", "quad", "radpgrau", "raizq", "rand", "randi"

);
Expand All @@ -105,7 +232,7 @@ public static void main(String[] args) {
);

private static final List<String> SPECIAL_KEYWORDS = List.of(
"Escreva", "Escreval", "Leia", "Algoritmo", "FimAlgoritmo", "Funcao", "Função", "FimFuncao", "FimFunção", "Procedimento", "FimProcedimento","Inicio", "Var"
"Escreva", "Escreval", "Leia", "Algoritmo", "FimAlgoritmo", "Funcao", "Função", "FimFuncao", "FimFunção", "Procedimento", "FimProcedimento", "Inicio", "Var"
);


Expand All @@ -119,17 +246,22 @@ public static void main(String[] args) {
private static final Pattern PATTERN = Pattern.compile(

"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
+ "|(?<NUMBER>" + NUMBER_PATTERN + ")"
+ "|(?<TYPE>" + TYPES_PATTERN + ")"
+ "|(?<SPECIAL>" + SPECIAL_PATTERN + ")");
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
+ "|(?<NUMBER>" + NUMBER_PATTERN + ")"
+ "|(?<TYPE>" + TYPES_PATTERN + ")"
+ "|(?<SPECIAL>" + SPECIAL_PATTERN + ")");

private final LongAdder counter = new LongAdder();

private Task<StyleSpans<Collection<String>>> computeHighlightingAsync() {
String text = codeArea.getText();
Task<StyleSpans<Collection<String>>> task = new Task<>() {
@Override
protected StyleSpans<Collection<String>> call() throws Exception {
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier("untitled://file", counter.intValue());
DidChangeTextDocumentParams didChangeTextDocumentParams = new DidChangeTextDocumentParams(versionedTextDocumentIdentifier, Collections.singletonList(new TextDocumentContentChangeEvent(text)));
clientLauncher.getRemoteProxy().getTextDocumentService().didChange(didChangeTextDocumentParams);
return computeHighlighting(text);
}
};
Expand All @@ -145,7 +277,7 @@ private StyleSpans<Collection<String>> computeHighlighting(String text) {
Matcher matcher = PATTERN.matcher(text);
int lastKwEnd = 0;
StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();
while(matcher.find()) {
while (matcher.find()) {
List<String> style = switch (matcher) {

case Matcher _ when matcher.group("KEYWORD") != null -> List.of("keyword");
Expand Down
5 changes: 4 additions & 1 deletion dev.thihup.jvisualg.ide/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
requires javafx.fxml;
requires org.fxmisc.richtext;
requires reactfx;

requires dev.thihup.jvisualg.lsp;
requires org.eclipse.lsp4j;
requires org.eclipse.lsp4j.jsonrpc;
requires javafx.controls;
exports dev.thihup.jvisualg.ide to javafx.graphics;

opens dev.thihup.jvisualg.ide to javafx.fxml;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
.text0 {
-fx-fill: #000000;
#codeArea {
-fx-font-family: "Courier New";
-fx-font-size: 10pt;
}

.background0 {
-fx-background-color: #FFFFFF;
.error {
-rtfx-background-color: #f0f0f0;
-rtfx-underline-color: red;
-rtfx-underline-dash-array: 2 2;
-rtfx-underline-width: 1;
-rtfx-underline-cap: butt;
-fx-fill: red;
}

.italic {
Expand All @@ -26,7 +32,6 @@
-fx-fill: #FF0000;
}


.keyword {
-fx-fill: #0000FF;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient;

import java.io.*;
import java.util.Timer;
import java.util.TimerTask;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class VisualgLauncher {
public static void main(String[] args) throws Throwable {
startServer(System.in, System.out, Executors.newSingleThreadExecutor());
}

public static Future<Void> startServer(InputStream inputStream, OutputStream outputStream, ExecutorService executor) {
VisualgLanguageServer server = new VisualgLanguageServer();

Launcher<LanguageClient> serverLauncher = LSPLauncher.createServerLauncher(server, System.in, System.out, Executors.newSingleThreadExecutor(), null);
Launcher<LanguageClient> serverLauncher = LSPLauncher.createServerLauncher(server, inputStream, outputStream, executor, null);
server.connect(serverLauncher.getRemoteProxy());

Future<Void> voidFuture = serverLauncher.startListening();
return serverLauncher.startListening();
}
}
2 changes: 2 additions & 0 deletions dev.thihup.jvisualg.lsp/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
requires org.eclipse.lsp4j.jsonrpc;
requires com.google.gson;
requires dev.thihup.jvisualg.frontend;

exports dev.thihup.jvisualg.lsp to dev.thihup.jvisualg.ide;
}

0 comments on commit d8b2b89

Please sign in to comment.