Richtextfx: Code Formatting Support

Created on 24 Oct 2014  Â·  32Comments  Â·  Source: FXMisc/RichTextFX

Is there anyway i can add code formatting support to codeArea? Similar to Alt+Shift+F format option in Netbeans And Ctrl+Shilft+F option in Eclipse. In this way i want to improve the representation of code in my code editor. :)

Most helpful comment

Back to first problem (text change listener). Do you have some sample code to detect textchanges??

Does this work for you?
````java
Subscription s = area.plainTextChanges().subscribe(tc -> {
String removed = tc.getRemoved();
String inserted = tc.getInserted();

if (!removed.isEmpty() && inserted.isEmpty()) {
    // deletion
} else if (!inserted.isEmpty() && removed.isEmpty()) {
    // insertion
} else {
    // replacement
}

});
````

I totally agree with you. I wanted/want to achieve this. But the problem is detecting TextChanges and caret's Location

I'd probably use a State Machine to do the above code.

The state would consist of two things: an enum that represents the action that should be taken, and a String that filters the options that the autcomplete box can show. The code would look something like...
````java
class AutocompleteState {

private static final String EMPTY_STRING = "";

private String filterText;
private State state;

public static enum State { SHOW, HIDE, REFILTER, INSERTION }

private AutocompleteState(String filterText, State state) {
    this.filterText = filterText;
    this.state = state;
}

public static AutocompleteState initial() {
    return new AutocompleteState(EMPTY_STRING, HIDE);
}

public void showBox() {
    type = SHOW;
}

public void hideBox() {
    type = HIDE;
    filterText = EMPTY_STRING;
}

// Note: this method's implementation is probably wrong because a PlainTextChange
//  can be merged with a previous one. Although I'm assuming a textChange in this case
//  only inserts or deletes one letter at a time, this may not be true in its actual usage.
public void updateFilter(PlainTextChange textChange) {
    // Note: textChange.isInsertion() is pseudo code to demonstrate idea
    if (textChange.isInsertion()) {
        filterText += textChange.getInserted();
    } else if (textChange.isDeletion()) {
        filterText -= textChange.getRemoved();
    }
    type = !filterText.isEmpty() ? REFILTER : HIDE;
}

public void insertSelected() {
    type = INSERTION;
}

}

EventStream appearanceTriggers = ...;
EventStream disappearanceTriggers = ...;
EventStream textModifications = area.plainTextChanges().conditionOn(popup.showingProperty());
EventStream insertionTriggers = ...;
EventStream boxEvents = StateMachine
.init(AutocompleteState.initial())
.on(appearanceTriggers).transition((appearance, state) -> state.showBox())
.on(disappearanceTriggers).transition((ignore, state) -> state.hideBox())
.on(textModifications).transition((textChange, state) -> state.updateFilter(textChange))
.on(insertionTriggers).transition((ignore, state) -> state.insertSelected())
.toEventStream();

Subscription sub = boxEvents.subscribe(state -> {
switch (state.getType()) {
case: show
// show popup at caret bounds
Optional caretBounds = area.getCaretBounds();
if (caretBounds.isPresent()) { popup.show( /* code /); }
break;
case: hide
popup.hide()
break;
case: refilter
// assumes popup is already displayed
autocompleteBox.refilter(state.getFilterText());
// move autocompleteBox to new caret location
popup.show( /
new location */ );
default: case: insertion
area.insertText(state.getStartPosition(), autocompleteBox.getSelectedItem());
popu.hide();
}
})
````

All 32 comments

you should be able to listen to key events and then do pattern matching to
find caret positions on text to look for the keywords, then finally insert
new lines, and tabs at caret positions :)
On Oct 24, 2014 6:19 AM, "deepsidhu1313" [email protected] wrote:

Is there anyway i can add code formatting support to codeArea? Similar to
Alt+Shift+F format option in Netbeans And Ctrl+Shilft+F option in Eclipse.
In this way i want to improve the representation of code in my code editor.
:)

—
Reply to this email directly or view it on GitHub
https://github.com/TomasMikula/RichTextFX/issues/91.

As Maher said, you can add a keyboard shortcut and do anything you want in the handler, e.g. replace the whole text with the new, formatted version.

Since version 0.5, it is quite easy to add a keyboard shortcut. Here is how you can do that:

import static javafx.scene.input.KeyCode.*;
import static javafx.scene.input.KeyCombination.*;
import static org.fxmisc.wellbehaved.event.EventPattern.*;
import org.fxmisc.wellbehaved.event.EventHandlerHelper;

EventHandler<? super KeyEvent> keyPressedHandler = EventHandlerHelper
        .on(keyPressed(F, CONTROL_DOWN, SHIFT_DOWN)).act(event -> {
            codeArea.replaceText(formatCode(codeArea.getText()));
        })
        .create();

EventHandlerHelper.install(codeArea.onKeyPressedProperty(), keyPressedHandler);

String formatCode(String code) {
    // ...
}

I think there has been some misunderstanding :P :) and may be that's my fault, i know how to add Event handlers :) . The question i was asking is there any inbuilt library/api/method/class in RichTextFX which can do the code formatting ?

Like you wrote above:

String formatCode(String code) {
// ...
}

Apologies for misunderstanding :)

No there isn't :)
This is because code area is agnostic, its not done for a specific
language. For example formatting python requires different setup than other
languages.
What I ended up doing is building my own code area version that extends the
one in RichTextFX and added all sorts of auto completion, syntax
highlighting, formatting etc.

is there any chance i can get the code of your modified codeArea version? :)

its okay if you dont want to share your code. Can you tell me what things i need to develop a similar code editor, like external libraries required to develop code suggestions and formatting options ? :)

Check https://github.com/antlr/grammars-v4. If your grammar is already supported it is quite easy to create a syntax formatter, auto-complete etc.

Hi, lol. Not at all. I was just caught up with work. I'll post it over next
weekend. It needs to be cleaned up. I went back and checked my code and
realized I had put the reactions somewhere else in another class. As I
said, It was a quick and dirty code for a prototype. Just give me a couple
of days to clean it up a little :)
On Oct 29, 2014 12:14 AM, "deepsidhu1313" [email protected] wrote:

its okay if you dont want to share your code. Can you tell me what things
i need to develop a similar code editor, like external libraries required
to develop code suggestions and formatting options ? :)

—
Reply to this email directly or view it on GitHub
https://github.com/TomasMikula/RichTextFX/issues/91#issuecomment-60882553
.

LOL Okkk sir :)

Take a look at Miho's suggestion. Also, this https://github.com/jrguenther/Xanthic, which extends Antlr and shows how to set it up on CodeArea.

My implementation was based on the CodeArea Sample. You listen to textProperty changes on CodeArea, and call a pattern matching method, which looks for keywords, find their start and end indexes, and assign a color style. It is pretty much the same code. I just added something like 'if you find curly bracket, then insert "\n\t\n\t\n\t\n\t\" and then insert a closing bracket. I hope this helps for now. I would go with Miho's suggestion and look at Xanthic, you will be better off investing your time looking at proper examples ( as opposed to quick-n-dirty code). I'll post it here once I get time to clean it up a little.

I second the suggestions of using a parser. Then code formatting is just an easy transformation of the syntax tree to text.

Thanks @TomasMikula. As i am still trying to achieve these features, and I am trying to add autocomplete feature to my project. And i was trying to create a version of [TextAreaDemo] (http://docs.oracle.com/javase/tutorial/uiswing/examples/components/TextAreaDemoProject/src/components/TextAreaDemo.java) in JavaFX using your CodeArea. But i am unable to find any equivalent methods to support the following code

   `textArea.getDocument().addDocumentListener(this);

    InputMap im = textArea.getInputMap();
    ActionMap am = textArea.getActionMap();
    im.put(KeyStroke.getKeyStroke("ENTER"), COMMIT_ACTION);
    am.put(COMMIT_ACTION, new CommitAction());`.

My code is available at https://github.com/deepsidhu1313/SyntaxTextAreaFX. Can you please guide me in the procedure to add support for Autocomplete using RichTextFX, by suggesting me equivalent methods ??

@deepsidhu1313 I'm not familiar with Swing, so could you explain what those methods do? Then I'd be able to help you know what the RichTextFX equivalent is or how you would need to build upon it to do that.

@JordanMartinez The code uses a document Listener. I checked JDK's in-built StyledDocument class, it provides document listener , but RichtextFX doesn't. Then Inputmap is action/keyevent listener and actionmap is the action to perform on certain trigger, and in the example code is COMMIT_ACTION which is a string will be added to document on pressing enter and then will be replaced by appropriate suggested keyword or "\n". I hope my understanding of the code is right, i am also not an expert. I have intermediate programming skills. I was just looking for best practise to add autocomplete support to CodeArea.

What does a document listener do? Does it notify you when content within the document has been changed? If so, that's the same as RTFX's plainTextChanges() and richTextChanges().

Ok, I get InputMap and actionmap, which have direct equivalents in RTFX via WellBehavedFX. Have you taken a look at StyledTextAreaBehavior and its demo?

Yes, DocumentListener do that. Methods removeUpdate(), insertUpdate() and changedUpdate() are used to listen these DocumentEvents. I am not sure about the equivalency to RTFX's plainTextChanges() and richTextChanges().

No i didn't , but thanks i will implement these.

Also is there a way to get caret's location in XY pixels wrt to screen and scene ?? So that i can properly display popup.

Methods removeUpdate(), insertUpdate() and changedUpdate() are used to listen these DocumentEvents. I am not sure about the equivalency to RTFX's plainTextChanges() and richTextChanges().

Let me explain. plainTextChanges() simply emits a PlainTextChange (PTC). A PTC stores three things: the starting position of the text that was removed, the text that was removed, and the text that was inserted. Thus, depending on the removed and inserted values, you can quickly determine what type of change it is: text was removed (inserted = ""; removed = "text"), inserted (inserted = "text"; removed = ""), or replaced (inserted="apple"; removed = "banana").

A PTC is different from richTextChanges() in that it will emit a RichTextChange (RTC) when plainTextChanges() emits an event, but also when no text is inserted/removed/replace but simply restyled. So you probably want to use plainTextChanges if your using it for syntax highlighting.

Also is there a way to get caret's location in XY pixels wrt to screen and scene ?? So that i can properly display popup.

Yes, you can use the new API that's in the as yet unreleased master branch via #410. Or use the currently deprecated Popup API in the most recent release (see the same PR above for the API to which I'm referring).

@JordanMartinez I am confused a little right now. I understand the role of PTC and plainTextChanges(). But which method will return PTC, so that i can observe the inserted values.

And also may i ask which will be better adding EventHandler or InputMap, because they seem same to me ?? My apologies for the trouble.

But which method will return PTC, so that i can observe the inserted values.

Now I'm confused. Were you expecting plainTextChanges() to adhere to the Observable pattern? If so, it doesn't. It adheres to the reactive programming paradigm via ReactFX (see the wiki for a tutorial).
If that isn't it, then I'm not sure what's confusing you and I will need you to clarify.

And also may i ask which will be better adding EventHandler or InputMap, because they seem same to me ?? My apologies for the trouble.

I think InputMap is better because a user of your code can always override a small or large aspect of your area's default behavior whereas EventHandler seems to be an all-or-nothing approach. StyledTextArea is an example of the former (aside from a few cases that involve the caret that aren't really overrideable).

My apologies for the trouble.

No trouble at all.

Ok, to avoid confusion. How will you implement Autocomplete feature with a popup suggestion list in Code area?

For the event handler part, I guess i will add a method s that user can setup trigger keys for popup list. Somethings like setAutocompleteListTrigger(EventHandler e) or setAutocompleteListTrigger(KeyCombo kc).

How will you implement Autocomplete feature with a popup suggestion list in Code area?

Well, here's the general principles that make an auto-complete box work:

  • A trigger causes the auto-complete box to appear with all its options (usually be pressing a key that inserts a letter, though calling it up without any text inserted could work, too).
  • Every time text is inserted/deleted, the text used to filter through all those options is recalculated and used again to filter them. Then, it redisplays those updated options in the box.
  • When certain keys are pressed during the time the box is displayed, some action should occur

    • Escape: hides the box and stops the process

    • Navigation keys: chooses which item to select

    • Enter: hides the box and inserts the text that was selected in the auto-completed box.

  • When all text is deleted, the box should disappear

I'd provide the functionality that does most of this framework code and delegate the actual specific implementations to the user of the area via functional programming.

For the event handler part, I guess i will add a method s that user can setup trigger keys for popup list. Somethings like setAutocompleteListTrigger(EventHandler e) or setAutocompleteListTrigger(KeyCombo kc).

There are a number of options that need to be configured for this to work:

  • what input (e.g. typing a letter at first) causes the box to initially appear?
  • what input (e.g. typing escape) causes the box to disappear?
  • what input (e.g. typing enter) causes the selected option to be inserted

So, you could have setters for such input like setAutocompleteListTrigger, but I feel like this misses the point. Shouldn't such a configuration be in the constructor of your area since it will never change once set?

Wow, :smile: i didn't expect that much detail.

I totally agree with you. I wanted/want to achieve this. But the problem is detecting TextChanges and caret's Location, i am not Java expert, but implementing old DocumentListener seems easier.

I have checked Popup Demo which is answer to my caret's location problem (i guess in next release, as it is not working now).

Back to first problem (text change listener). Do you have some sample code to detect textchanges??

I can add EventListeners for showing or hiding Popup.

I feel that by default only syntax highlighting (as basic feature) should be enabled, because enabling keyword suggestion and code parsing by default will have its own overhead. If someone wants to implement these feature in their advance text editor apps or IDE like apps they will be able to do so by simply setting some boolean values.

Back to first problem (text change listener). Do you have some sample code to detect textchanges??

Does this work for you?
````java
Subscription s = area.plainTextChanges().subscribe(tc -> {
String removed = tc.getRemoved();
String inserted = tc.getInserted();

if (!removed.isEmpty() && inserted.isEmpty()) {
    // deletion
} else if (!inserted.isEmpty() && removed.isEmpty()) {
    // insertion
} else {
    // replacement
}

});
````

I totally agree with you. I wanted/want to achieve this. But the problem is detecting TextChanges and caret's Location

I'd probably use a State Machine to do the above code.

The state would consist of two things: an enum that represents the action that should be taken, and a String that filters the options that the autcomplete box can show. The code would look something like...
````java
class AutocompleteState {

private static final String EMPTY_STRING = "";

private String filterText;
private State state;

public static enum State { SHOW, HIDE, REFILTER, INSERTION }

private AutocompleteState(String filterText, State state) {
    this.filterText = filterText;
    this.state = state;
}

public static AutocompleteState initial() {
    return new AutocompleteState(EMPTY_STRING, HIDE);
}

public void showBox() {
    type = SHOW;
}

public void hideBox() {
    type = HIDE;
    filterText = EMPTY_STRING;
}

// Note: this method's implementation is probably wrong because a PlainTextChange
//  can be merged with a previous one. Although I'm assuming a textChange in this case
//  only inserts or deletes one letter at a time, this may not be true in its actual usage.
public void updateFilter(PlainTextChange textChange) {
    // Note: textChange.isInsertion() is pseudo code to demonstrate idea
    if (textChange.isInsertion()) {
        filterText += textChange.getInserted();
    } else if (textChange.isDeletion()) {
        filterText -= textChange.getRemoved();
    }
    type = !filterText.isEmpty() ? REFILTER : HIDE;
}

public void insertSelected() {
    type = INSERTION;
}

}

EventStream appearanceTriggers = ...;
EventStream disappearanceTriggers = ...;
EventStream textModifications = area.plainTextChanges().conditionOn(popup.showingProperty());
EventStream insertionTriggers = ...;
EventStream boxEvents = StateMachine
.init(AutocompleteState.initial())
.on(appearanceTriggers).transition((appearance, state) -> state.showBox())
.on(disappearanceTriggers).transition((ignore, state) -> state.hideBox())
.on(textModifications).transition((textChange, state) -> state.updateFilter(textChange))
.on(insertionTriggers).transition((ignore, state) -> state.insertSelected())
.toEventStream();

Subscription sub = boxEvents.subscribe(state -> {
switch (state.getType()) {
case: show
// show popup at caret bounds
Optional caretBounds = area.getCaretBounds();
if (caretBounds.isPresent()) { popup.show( /* code /); }
break;
case: hide
popup.hide()
break;
case: refilter
// assumes popup is already displayed
autocompleteBox.refilter(state.getFilterText());
// move autocompleteBox to new caret location
popup.show( /
new location */ );
default: case: insertion
area.insertText(state.getStartPosition(), autocompleteBox.getSelectedItem());
popu.hide();
}
})
````

I guess i have to wait for next stable release to merge this code. Thanks for the help. It is more than what i wanted.

By stable release, do you mean the 0.7.0 release? Or are you referring to the next milestone release?

I mean 0.7.0. Plus will try to use Latest JDK instead 1.8u40.

Yeah... that might be a while....

I can wait. :smile:. It is not like that somebody is waiting for me to implement these. My project is for me and others like me, who want to learn and explore Java.

No doubt RichTextFX is in the center of my many projects. But releasing simple and stable API's are my priority.

You can always contribute some code if you're willing ;-)

I would love too. And believe me i want to. But i am still learning advance concepts. I don't make promises that i can't keep. Also i already am working on many projects. SyntaxAreaFX is part of bigger project i promised to my friend.

If you find any basic stuff/coding that i can contribute, please let me know. You can check my novice skills on my repos. :smile:

:laughing: You should see some of my first PRs to this project, haha! Tomas' critiques and code modeled good programming for me in a number of ways.

If you find any basic stuff/coding that i can contribute, please let me know. You can check my novice skills on my repos. :smile:

How about coming up with a list of TestFX tests in #373? Even if you don't write the tests, it would help me know what else to write to insure RTFX has a stable API. If you wanted to write those tests, that would help, too.

I think #150 would be easy to implement. It's not a bug, but it adds a nice feature. As far as I understand, you would need to add the method via reflection (just follow the examples in the code) to TextExt and then add methods in the higher object that delegate to the lower object's method from StyledTextArea to ParagraphBox to TextExt to get it.

I think the rest of the bugs and enhancements would require a deeper understanding of RTFX and how it works, which I will currently assume is a further than you are currently willing to go.

Simple way 😎 if your CodeArea language is Java
you can add code formatting support to codeArea by listen for shortcut like Alt+Shift+L
then use google-java-format library for example :

String formattedSource = new Formatter().formatSource(codeArea.getText()); codeArea.replaceText(0, codeArea.getLength(), formattedSource);

Was this page helpful?
0 / 5 - 0 ratings