FXMisc/RichTextFX

Bug: text area removes selection on text append

Opened this issue · 10 comments

This is my code:

public class JavaFxTest8 extends Application {

   @Override
    public void start(Stage primaryStage) {
        var codeArea = new CodeArea("Some text is here.");
        var button1 = new Button("Jmi");
        codeArea.selectedTextProperty().addListener((ov, oldV, newV) -> System.err.println(newV.length()));

        button1.setOnAction(e -> {
            codeArea.appendText("Appended text");
        });

        Scene scene = new Scene(new VBox(codeArea, button1), 400, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
        codeArea.selectRange(0, 4);
    }

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

If you click the button, then selection will be removed. I consider it is a bug because text appending have nothing with text selection. I tried to save selection and restore it after text appending but then I get two events in selectedTextProperty. So, one problem leads to another.

I think I agree with your assessment, I'll have a look and see ....

You can use the following as a work-around or alternative:

    button1.setOnAction(e -> {
        var mc = codeArea.createMultiChange();
        mc.insertText( codeArea.getLength(), "Appended text" );
        mc.commit();
    });

@Jugen I am trying to use your workaround, but I can't understand how I can do mutlichange insert with styles. I mean, now I do:

textArea.appendText(sb.toString());
textArea.setStyleSpans(oldLength, ssb.create());

There is insert(int position, StyledDocument<PS,SEG,S> document) method, but I don't know how create styled document from text and styles.

It would look something like this:

StyleSpansBuilder<Collection<String>> styleBld;
ReadOnlyStyledDocumentBuilder<Collection<String>,String,Collection<String>> docBld;

styleBld = new StyleSpansBuilder<>();
styleBld.add( Collections.EMPTY_LIST, 9 );
styleBld.add( Collections.singletonList("highlighted"), 4 );

docBld = new ReadOnlyStyledDocumentBuilder<>( textArea.getSegOps(), Collections.EMPTY_LIST );
docBld.addParagraph( "Appended text", styleBld.create() );

var mc = textArea.createMultiChange();
mc.insert( textArea.getLength(), docBld.build() );
mc.commit();

@Jugen Thank you very much for your help. But this workaround is very, very slow on appending.

Okay, let's try something else then. Since the problem is that the selection highlight is being removed when the text changes would it work if you used your own highlight instead which doesn't get removed ? You can add any number of highlights like so:

var mySelection = new SelectionImpl<>( "myselection", textArea, 6, 13 );
textArea.addSelection( mySelection );

There are other constructors as well, and if you want to style it do this:

var styledSelection = new SelectionImpl<>( "styledSelection", textArea, path -> path.getStyleClass().add("selectionStyle") );

You can also deselect it or change its location/range.

@Jugen Thank you very much for your help. I've checked your solution. It works, but it has different logic. When there are find matches then current match is selected, for example, to be replaced:

Peek 2024-06-04 13-01

But I can wait until this issue is resolved, as there is a lot of work to do and the project won't be released in the next couple of months.

Shouldn't that type of selection be done by adding a selection in any case, and not by using the user selection ?

@Jugen As I understand, there can be different variants. I decided to use user selection textArea.selectRange(start, end) because it lets user modify selection if he needs it. The same solution is used in NB:

Peek 2024-06-04 14-45

I've given this "bug" some thought and for inserts or replacements before or after the main selection it will be easy to adjust. However for inserts or replacements that overlap the main selection the behavior is not clear as to what should be done.
Also since the current behavior has been the way it is for a very long time I'm reluctant to change it.

Having looked into it though you can easily change this behavior in your custom text area class like so:

@Override
public void replace(int start, int end, StyledDocument<String,String,MyStyle> replacement) {

    var range = codeArea.getCaretSelectionBind().getRange();
    getContent().replace(start, end, replacement);

    if (end < range.getStart()) {
        // insert/replace is before selection, so adjust selection
        int newCaretPos = end - start + replacement.length();
        selectRange(newCaretPos, newCaretPos + range.getLength());
    }
    else if (start < range.getEnd()) {
        // insert/replace overlaps or is inside the selection
        // TODO
    }
}