RichTextFX
RichTextFX copied to clipboard
How can I implement some code folding between brackets?
I just want to implement code folding just like other ide-s : intellij, eclipse, netbeans. I reviewed java keyboard sample. The folding is controlled from context menu. I want indicate fold unfold icon on the line indicator? How to do it with codearea? Can you give me some code hints? Thank you.
If you are using either CodeArea, InlineCssTextArea, or StyleClassedTextArea then just use the following API:
foldText( int start, int end )
foldSelectedParagraphs() // paragraph in this context is a line/block of text ending with a line break.
foldParagraphs( int startPar, int endPar )
unfoldParagraphs( int startingFromPar )
unfoldText( int startingFromPos )
You can also look at JavaKeywordsDemo that uses a context menu to demo folding.
I want indicate fold unfold icon on the line indicator?
I think you'll have to provide your own custom LineNumberFactory and then add it to the area with codeArea.setParagraphGraphicFactory( .... )
Can you give me some example implementation of custom LineNumberFactory class?
If you are using either CodeArea, InlineCssTextArea, or StyleClassedTextArea then just use the following API:
foldText( int start, int end ) foldSelectedParagraphs() // paragraph in this context is a line/block of text ending with a line break. foldParagraphs( int startPar, int endPar ) unfoldParagraphs( int startingFromPar ) unfoldText( int startingFromPos )You can also look at JavaKeywordsDemo that uses a context menu to demo folding.
I know this example. This works just for selected text. But I need to similar functionality for a method folding or unfolding
Here is a custom LineNumberFactory implementation for you:
import java.util.function.IntFunction;
import java.util.function.Predicate;
import org.fxmisc.richtext.InlineCssTextArea;
import org.reactfx.collection.LiveList;
import org.reactfx.value.Val;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
/**
* Graphic factory that produces labels containing line numbers and a "+" to indicate folded paragraphs.
* To customize appearance, use {@code .lineno} and {@code .fold-indicator} style classes in CSS stylesheets.
*/
public class InlineCssLineNumberFactory<PS> implements IntFunction<Node> {
private static final Insets DEFAULT_INSETS = new Insets(0.0, 5.0, 0.0, 5.0);
private static final Paint DEFAULT_TEXT_FILL = Color.web("#666");
private static final Font DEFAULT_FONT = Font.font("monospace", FontPosture.ITALIC, 13);
private static final Font DEFAULT_FOLD_FONT = Font.font("monospace", FontWeight.BOLD, 13);
private static final Background DEFAULT_BACKGROUND = new Background(new BackgroundFill(Color.web("#ddd"), null, null));
public static IntFunction<Node> get(InlineCssTextArea area) {
return get(area, digits -> "%1$" + digits + "s");
}
/**
* @param <PS> The paragraph style type being used by the text area
* @param format Given an int convert to a String for the line number.
*/
public static <PS> IntFunction<Node> get( InlineCssTextArea area, IntFunction<String> format )
{
return new InlineCssLineNumberFactory<>( area, format );
}
private final Val<Integer> nParagraphs;
private final IntFunction<String> format;
private final InlineCssTextArea area;
private final Predicate<String> isFoldedCheck = pstyle -> pstyle != null && pstyle.contains( "collapse" );
private InlineCssLineNumberFactory( InlineCssTextArea area, IntFunction<String> format )
{
nParagraphs = LiveList.sizeOf(area.getParagraphs());
this.format = format;
this.area = area;
area.getParagraphs().sizeProperty().addListener( (ob,ov,nv) -> {
if ( nv <= ov ) Platform.runLater( () -> deleteParagraphCheck() );
else Platform.runLater( () -> insertParagraphCheck() );
});
}
@Override
public Node apply(int idx) {
Val<String> formatted = nParagraphs.map(n -> format(idx+1, n));
Label lineNo = new Label();
lineNo.setFont(DEFAULT_FONT);
lineNo.setBackground(DEFAULT_BACKGROUND);
lineNo.setTextFill(DEFAULT_TEXT_FILL);
lineNo.setPadding(DEFAULT_INSETS);
lineNo.setAlignment(Pos.TOP_RIGHT);
lineNo.getStyleClass().add("lineno");
// bind label's text to a Val that stops observing area's paragraphs
// when lineNo is removed from scene
lineNo.textProperty().bind(formatted.conditionOnShowing(lineNo));
Label foldIndicator = new Label( " " );
foldIndicator.setTextFill( Color.BLUE ); // Prevents CSS errors
foldIndicator.setFont( DEFAULT_FOLD_FONT );
lineNo.setContentDisplay( ContentDisplay.RIGHT );
lineNo.setGraphic( foldIndicator );
if ( area.getParagraphs().size() > idx+1 ) {
if ( isFoldedCheck.test( area.getParagraph( idx+1 ).getParagraphStyle() )
&& ! isFoldedCheck.test( area.getParagraph( idx ).getParagraphStyle() ) ) {
foldIndicator.setOnMouseClicked( ME -> area.unfoldParagraphs( idx ) );
foldIndicator.getStyleClass().add( "fold-indicator" );
foldIndicator.setCursor( Cursor.HAND );
foldIndicator.setText( "+" );
}
}
return lineNo;
}
private String format(int x, int max) {
int digits = (int) Math.floor(Math.log10(max)) + 1;
return String.format(format.apply(digits), x);
}
private void deleteParagraphCheck()
{
int p = area.getCurrentParagraph();
// Was the deleted paragraph in the viewport ?
if ( p >= area.firstVisibleParToAllParIndex() && p <= area.lastVisibleParToAllParIndex() )
{
int col = area.getCaretColumn();
// Delete was pressed on an empty paragraph, and so the cursor is now at the start of the next paragraph.
if ( col == 0 ) {
// Check if the now current paragraph is folded.
if ( isFoldedCheck.test( area.getParagraph( p ).getParagraphStyle() ) ) {
p = Math.max( p-1, 0 ); // Adjust to previous paragraph.
area.recreateParagraphGraphic( p ); // Show fold/unfold indicator on previous paragraph.
area.moveTo( p, 0 ); // Move cursor to previous paragraph.
}
}
// Backspace was pressed on an empty paragraph, and so the cursor is now at the end of the previous paragraph.
else if ( col == area.getParagraph( p ).length() ) {
area.recreateParagraphGraphic( p ); // Shows fold/unfold indicator on current paragraph if needed.
}
// In all other cases the paragraph graphic is created/updated automatically.
}
}
private void insertParagraphCheck()
{
int p = area.getCurrentParagraph();
// Is the inserted paragraph in the viewport ?
if ( p > area.firstVisibleParToAllParIndex() && p <= area.lastVisibleParToAllParIndex() ) {
// Check limits, as p-1 and p+1 are accessed ...
if ( p > 0 && p+1 < area.getParagraphs().size() ) {
// Now check if the inserted paragraph is before a folded block ?
if ( isFoldedCheck.test( area.getParagraph( p+1 ).getParagraphStyle() ) ) {
area.recreateParagraphGraphic( p-1 ); // Remove the unfold indicator.
}
}
}
}
}
The "java keywords" foldable demo is all a bit misleading. It has nothing to do with Java or Java code structure, it's just folding/unfolding any randomly selected text.
What the OP was looking for was a way to show in the line number bar (in a CodeArea, not a InlineCssTextArea), collapsible areas, such as methods, etc. Basically like a tree but without the ugly-ness of a standard tree control.