Search code examples
javafxbreakpointsline-numbersrichtextfx

Show breakpoint at line number in RichTextFx CodeArea


I'm working with RichTextFx's CodeArea to highlight custom mini language code.

Now while executing this code I want to show a small arrow in front of current executed line. I know the specific line number but can't get anything to happen with the line number label.

Since github project claims showing line numbers or breakpoint toggles as a feature this can't be very difficult. But can't get anything to work...

Thanks in advance


Solution

  • To show any graphic in front of the line, you need to set the "paragraph graphic factory" of the CodeArea. This graphic factory is just a function int -> Node: given the line number, it returns a Node that will be displayed in front of the line.

    Here is a graphic factory that produces a green triangle pointing at the line. It will only be shown when the line is equal to the given integer property shownLine.

    class ArrowFactory implements IntFunction<Node> {
        private final ObservableValue<Integer> shownLine;
    
        ArrowFactory(ObservableValue<Integer> shownLine) {
            this.shownLine = shownLine;
        }
    
        @Override
        public Node apply(int lineNumber) {
            Polygon triangle = new Polygon(0.0, 0.0, 10.0, 5.0, 0.0, 10.0);
            triangle.setFill(Color.GREEN);
    
            ObservableValue<Boolean> visible = Val.map(
                    shownLine,
                    sl -> sl == lineNumber);
    
            triangle.visibleProperty().bind(visible.conditionOnShowing(t‌​riangle));
    
            return triangle;
        }
    }
    

    Each graphic (i.e. little green triangle) you create will be observing the given shownLine property to decide whether it should be visible. As lines, and therefore line graphics, come and go, it is important to remove the listener of shownLine when the graphic is no longer used. visible.conditionOnShowing(t‌​riangle) is a new property that will stop observing the visible property (and automatically also the shownLine property, thanks to ReactFX's lazy binding semantics) and reset to constant false whenever the triangle is not part of a showing window. So we don't cause memory or CPU leaks due to uncleaned listeners.

    Here is a complete runnable demo that uses this ArrowFactory combined with the LineNumberFactory provided by RichTextFX to show both line numbers and a little triangle. This demo uses the CodeArea's current line as the shownLine property. You will want to substitute it for a property that contains the current line of execution.

    import java.util.function.IntFunction;
    
    import javafx.application.Application;
    import javafx.beans.value.ObservableValue;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Polygon;
    import javafx.stage.Stage;
    
    import org.fxmisc.richtext.CodeArea;
    import org.fxmisc.richtext.LineNumberFactory;
    import org.reactfx.value.Val;
    
    public class CodeAreaWithLineIndicator extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
            CodeArea codeArea = new CodeArea();
    
            IntFunction<Node> numberFactory = LineNumberFactory.get(codeArea);
            IntFunction<Node> arrowFactory = new ArrowFactory(codeArea.currentParagraphProperty());
            IntFunction<Node> graphicFactory = line -> {
                HBox hbox = new HBox(
                    numberFactory.apply(line),
                    arrowFactory.apply(line));
                hbox.setAlignment(Pos.CENTER_LEFT);
                return hbox;
            };
            codeArea.setParagraphGraphicFactory(graphicFactory);
    
            primaryStage.setScene(new Scene(new StackPane(codeArea), 600, 400));
            primaryStage.show();
        }
    }
    
    class ArrowFactory implements IntFunction<Node> {
        private final ObservableValue<Integer> shownLine;
    
        ArrowFactory(ObservableValue<Integer> shownLine) {
            this.shownLine = shownLine;
        }
    
        @Override
        public Node apply(int lineNumber) {
            Polygon triangle = new Polygon(0.0, 0.0, 10.0, 5.0, 0.0, 10.0);
            triangle.setFill(Color.GREEN);
    
            ObservableValue<Boolean> visible = Val.map(
                    shownLine,
                    sl -> sl == lineNumber);
    
            triangle.visibleProperty().bind(visible.conditionOnShowing(t‌​riangle));
    
            return triangle;
        }
    }
    

    And this is the result:

    CodeArea with current line indicator