From c5b610f8739a68fec1244b13c4b7bbef4c0148f8 Mon Sep 17 00:00:00 2001 From: Justus Garbe Date: Tue, 16 Jul 2024 20:35:01 +0200 Subject: [PATCH] feat: make error lines now --- .../services/compile/CompilerDiagnostic.java | 4 +- .../recaf/services/compile/JavacCompiler.java | 1 + .../services/script/JavacScriptEngine.java | 3 +- .../recaf/ui/control/richtext/Editor.java | 17 ++- .../ui/control/richtext/problem/Problem.java | 3 +- .../problem/ProblemGraphicFactory.java | 127 ++++++++---------- 6 files changed, 75 insertions(+), 80 deletions(-) diff --git a/recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerDiagnostic.java b/recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerDiagnostic.java index beff0fa57..a6d404b38 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerDiagnostic.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerDiagnostic.java @@ -16,7 +16,7 @@ * * @author Matt Coley */ -public record CompilerDiagnostic(int line, int column, @Nonnull String message, @Nonnull Level level) { +public record CompilerDiagnostic(int line, int column, int length, @Nonnull String message, @Nonnull Level level) { /** * @param line * New line number. @@ -25,7 +25,7 @@ public record CompilerDiagnostic(int line, int column, @Nonnull String message, */ @Nonnull public CompilerDiagnostic withLine(int line) { - return new CompilerDiagnostic(line, column, message, level); + return new CompilerDiagnostic(line, column, length, message, level); } @Override diff --git a/recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompiler.java index 0fedf5760..388de3af7 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompiler.java @@ -180,6 +180,7 @@ public void report(@Nonnull Diagnostic diagnostic) { diagnostics.add(new CompilerDiagnostic( (int) diagnostic.getLineNumber(), (int) diagnostic.getColumnNumber(), + (int) diagnostic.getEndPosition() - (int) diagnostic.getPosition(), diagnostic.getMessage(Locale.getDefault()), mapKind(diagnostic.getKind()) )); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/script/JavacScriptEngine.java b/recaf-core/src/main/java/software/coley/recaf/services/script/JavacScriptEngine.java index c50232c8c..d9cf45ba0 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/script/JavacScriptEngine.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/script/JavacScriptEngine.java @@ -213,7 +213,8 @@ private GenerateResult generateStandardClass(@Nonnull String source) { source = source.replace("\t" + originalName + "(", "\t" + modifiedName + "("); } else { return new GenerateResult(null, List.of( - new CompilerDiagnostic(-1, -1, "Could not determine name of class", CompilerDiagnostic.Level.ERROR))); + new CompilerDiagnostic(-1, -1, 0, + "Could not determine name of class", CompilerDiagnostic.Level.ERROR))); } // Compile the class diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java index 78cc0e5dc..49d4cf285 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java @@ -568,21 +568,28 @@ public double computeWidthUntilCharacter(int paragraph, int character) { double width = 0; int index = 0; + double lastWidth = 0; + double lastX = 0; + for (Text textNode : textNodes) { String text = textNode.getText(); double boundWidth = textNode.getBoundsInLocal().getWidth(); + if (index + text.length() < character) { - width += boundWidth; index += text.length(); } else { - int length = character - index; + double lastStart = lastX + lastWidth; double charWidth = boundWidth / StringUtil.getTabAdjustedLength(text); - width += charWidth * length; - break; + + // Compute the width of the text until the character. + return lastStart + charWidth * (character - index); } + + lastWidth = boundWidth; + lastX = textNode.getLayoutX(); } - return width; + return 0L; } /** diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/Problem.java b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/Problem.java index 632a9ea02..ca30c1784 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/Problem.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/Problem.java @@ -17,13 +17,12 @@ public record Problem(int line, int column, int length, ProblemLevel level, Prob */ @Nonnull public static Problem fromDiagnostic(@Nonnull CompilerDiagnostic diagnostic) { - // TODO: refactor CompilerDiagnostic to also have a length field ProblemLevel level = switch (diagnostic.level()) { case WARNING -> ProblemLevel.WARN; case INFO -> ProblemLevel.INFO; default -> ProblemLevel.ERROR; }; - return new Problem(diagnostic.line(), diagnostic.column(), 1, + return new Problem(diagnostic.line(), diagnostic.column(), diagnostic.length(), level, ProblemPhase.BUILD, diagnostic.message()); } diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemGraphicFactory.java b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemGraphicFactory.java index 3aad7469b..a9469f724 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemGraphicFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemGraphicFactory.java @@ -1,27 +1,20 @@ package software.coley.recaf.ui.control.richtext.problem; -import jakarta.annotation.Nonnull; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Tooltip; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import org.kordamp.ikonli.carbonicons.CarbonIcons; -import software.coley.recaf.ui.control.FontIconView; -import software.coley.recaf.ui.control.richtext.Editor; -import software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory; -import software.coley.recaf.ui.control.richtext.linegraphics.LineContainer; +import software.coley.recaf.ui.control.richtext.linegraphics.AbstractTextBoundLineGraphicFactory; import software.coley.recaf.ui.control.richtext.linegraphics.LineGraphicFactory; + /** * Graphic factory that adds overlays to line graphics indicating the problem status of the line. * * @author Matt Coley * @see ProblemTracking */ -public class ProblemGraphicFactory extends AbstractLineGraphicFactory { - private Editor editor; - +public class ProblemGraphicFactory extends AbstractTextBoundLineGraphicFactory { /** * New graphic factory. */ @@ -30,67 +23,61 @@ public ProblemGraphicFactory() { } @Override - public void install(@Nonnull Editor editor) { - this.editor = editor; - } + protected void apply(StackPane pane, int paragraph) { + if (editor.getProblemTracking() == null) + return; - @Override - public void uninstall(@Nonnull Editor editor) { - this.editor = null; - } + Problem problem = editor.getProblemTracking().getProblem(paragraph + 1); - @Override - public void apply(@Nonnull LineContainer container, int paragraph) { - ProblemTracking problemTracking = editor.getProblemTracking(); - - // Always null if no bracket tracking is registered for the editor. - if (problemTracking == null) return; - - // Add problem graphic overlay to lines with problems. - int line = paragraph + 1; - Problem problem = problemTracking.getProblem(line); - if (problem != null) { - ProblemLevel level = problem.getLevel(); - Color levelColor = switch (level) { - case ERROR -> Color.RED; - case WARN -> Color.YELLOW; - default -> Color.TURQUOISE; - }; - Node graphic = switch (level) { - case ERROR -> new FontIconView(CarbonIcons.ERROR, levelColor); - case WARN -> new FontIconView(CarbonIcons.WARNING_ALT, levelColor); - default -> new FontIconView(CarbonIcons.INFORMATION, levelColor); - }; - - Rectangle shape = new Rectangle(); - shape.widthProperty().bind(container.widthProperty()); - shape.heightProperty().bind(container.heightProperty()); - shape.setCursor(Cursor.HAND); - shape.setFill(levelColor); - shape.setOpacity(0.33); - - Tooltip tooltip = new Tooltip(formatTooltipMessage(problem)); - tooltip.setGraphic(graphic); - switch (level) { - case ERROR -> tooltip.getStyleClass().add("error-text"); - case WARN -> tooltip.getStyleClass().add("warn-text"); - } - Tooltip.install(shape, tooltip); - container.addTopLayer(shape); - } + if (problem == null) + return; + + // compute the width of the draw canvas and offset required + int index = problem.column() + 1; + double toStart = editor.computeWidthUntilCharacter(paragraph, index); + double toEnd = editor.computeWidthUntilCharacter(paragraph, index + problem.length()); + toEnd = Math.max(toEnd, toStart + 6); + double errorWidth = toEnd - toStart; + + // Create the overlay + Canvas canvas = new Canvas(toEnd, containerHeight); + canvas.setManaged(false); + canvas.setMouseTransparent(true); + canvas.setTranslateY(3); + canvas.setTranslateX(toStart + 1.7); + + pane.getChildren().add(canvas); + + drawWaves(canvas, errorWidth); } - private static String formatTooltipMessage(Problem problem) { - StringBuilder sb = new StringBuilder(); - int line = problem.getLine(); - if (line > 0) { - int column = problem.getColumn(); - if (column >= 0) { - sb.append("Column ").append(column); - } - } else { - sb.append("Unknown line"); + private void drawWaves(Canvas canvas, double errorWidth) { + var gc = canvas.getGraphicsContext2D(); + + // make a solid red line + gc.setStroke(Color.RED); + gc.setLineWidth(1); + + gc.beginPath(); + + final double scalingFactor = .7; + final double waveUp = 3 * scalingFactor; + final double waveDown = 6 * scalingFactor; + final double stop = errorWidth - scalingFactor; + + // draw sawtooth pattern + for (double offset = 0; offset < stop; offset += waveDown) { + gc.moveTo(offset, containerHeight - waveUp); + if (offset + waveUp < stop) + gc.lineTo(offset + waveUp, containerHeight - waveDown); + if (offset + waveDown < stop) + gc.lineTo(offset + waveDown, containerHeight - waveUp); } - return sb.append('\n').append(problem.getMessage()).toString(); + + // draw one last wave up + gc.lineTo(stop, containerHeight - waveUp); + + gc.stroke(); + gc.closePath(); } } \ No newline at end of file