How to indicate fold unfold icon at the line indicator in codearea of richtextfx control java

112 views Asked by At

I just want to implement some code folding between brackets or carets like other java IDEs: eclipse, netbeans, intellij using codearea control of richtextfx library. Please give me some code hints? thank you.

1

There are 1 answers

0
swpalmer On

The trick is to implement your own line number factory that adds a control to the line number label (e.g. the graphic of a Label) that indicates a spot where the lines can be folded, or that they are already folded.

When that graphic is clicked you would call: codeArea.foldParagraphs(startLine, endLine); to fold the lines, or codeArea.unfoldParagraphs(line); to unfold the lines.

How you determine if a line is foldable and how many lines to fold is out of scope for a StackOverflow answer. It can be a simple as checking if the line ("paragraph") ends with a '{' and then scan ahead to the first '}'. But that will fail in some cases, like a "}" that is part of a string or character literal or something like that.

Here is a skeleton of what the code might look like:

public class FoldingLineNumberFactory implements IntFunction<Node> {
    private IntFunction<Node> defaultFactory;
    private CodeArea editor;

    public FoldingLineNumberFactory(CodeArea editor) {
        this.editor = editor;
        defaultFactory = LineNumberFactory.get(editor);
    }

    @Override
    public Node apply(int lineNum) {
        Node n = defaultFactory.apply(lineNum);
        if (n instanceof Label lab) {
            lab.setContentDisplay(ContentDisplay.RIGHT);
            lab.setGraphic(graphic(lineNum));
        }
        return label;
    }

    private Node graphic(int line) {
        // Folding support should be factored out into another class
        if (beginsFoldableRegion(line)) {
            boolean folded = isLineFolded(line);
            var g = getFoldableGraphic(folded);
            g.setUserData(line);
            g.addEventFilter(MouseEvent.MOUSE_CLICKED, this::mouseClicked);
            g.setCursor(Cursor.DEFAULT);
            return g;
        }
        return nullGraphic(); // takes the same space, but is invisible
    }

    private void mouseClicked(MouseEvent e) {
        if (e.getSource() instanceof Node n) {
            if (n.getUserData() instanceof Integer line) {
                if (isFoldedAt(line)) {
                    unfold(e);
                } else {
                    fold(e);
                }
                e.consume();
            }
        }
    }

    private void fold(int line) {
        // TODO: record somewhere that this line is folded
        int lastLine = endLineForRegionStartingAt(line);
        editor.foldParagraphs(line, lastLine);
    }

    private void unfold(int line) {
        // TODO: record that this line isn't folded anymore
        editor.unfoldParagraphs(line);
        if (editor.getParagraphGraphic(line) instanceof Label lab) {
            lab.setGraphic(graphic(line));
        } else {
            System.out.println("paragraph graphic not a Label");
        }
    }

    protected boolean isLineFolded(int line) {
        return false; // You need to implement this
    }

    protected boolean beginsFoldableRegion(int line) {
        return false; // You need to implement this
    }
    
    private int endLineForRegionStartingAt(int line) {
        // TODO: implement this
        return -1;
    }

    private Node getFoldableGraphic(boolean folded) {
        if (folded) {
            return getFoldGraphic(); // TODO
        } else {
            return getUnfoldGraphic(); // TODO
        }
    }

    private Node nullGraphic() {
        var r =  new Rectangle(10, 10, Color.TRANSPARENT); // just guessing at size
        // setting stroke changes final size
        r.setStroke(Color.TRANSPARENT);
        return r;
    }

How you fill in the rest depends on how you determine what is foldable, and what you want the folding control to look like.