Skip to content

Conversation

@andy-goryachev-oracle
Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle commented Oct 22, 2025

Adds control of line endings (newline separators) in StyledTextModel, RichTextArea, and CodeArea.

The impacted areas are:

  • saving to plain text
  • copying to plain text
  • IME

This feature is implemented as a regular field in the StyledTextModel (since it is ultimately an attribute of the model), with convenience setter and getter in the RichTextArea.

NOTES


Progress

  • Change must not contain extraneous whitespace
  • Change requires CSR request JDK-8370973 to be approved
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issues

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1944/head:pull/1944
$ git checkout pull/1944

Update a local copy of the PR:
$ git checkout pull/1944
$ git pull https://git.openjdk.org/jfx.git pull/1944/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1944

View PR using the GUI difftool:
$ git pr show -t 1944

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1944.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 22, 2025

👋 Welcome back angorya! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Oct 22, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 2
/csr

@openjdk openjdk bot added the rfr Ready for review label Oct 22, 2025
@openjdk
Copy link

openjdk bot commented Oct 22, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Oct 22, 2025
@openjdk
Copy link

openjdk bot commented Oct 22, 2025

@andy-goryachev-oracle has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@andy-goryachev-oracle please create a CSR request for issue JDK-8370140 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@mlbridge
Copy link

mlbridge bot commented Oct 22, 2025

@kevinrushforth kevinrushforth self-requested a review October 23, 2025 16:01
@kevinrushforth
Copy link
Member

Reviewers: @kevinrushforth @jayathirthrao

Copy link
Member

@jayathirthrao jayathirthrao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code change looks good to me. I have minor suggestions.
Also played around with demos everything works fine.

public void setLineSeparator(String s) {
newline = s;
public StringBuilderStyledOutput(LineEnding lineEnding) {
sb = new StringBuilder(1024);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better to name this constant.

return System.getProperty("line.separator");
}
return switch(v) {
case CR -> "\r";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing indentation.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked over the proposed API and left some inline comments. I'll repeat my two main points here:

  1. Rather than a Nullable enum, create an explicit enum value for "use the line.separator property"
  2. Why is the new lineEnding property on the CodeArea subclass and not on RichTextArea?

*
* @since 26
*/
public enum LineEnding {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an explicit enum value that means "use the value of System.getProperty("line.separator")". Relying on null to mean this is not clean for two reasons: 1) it is more difficult to document (case in point, it isn't documented here); and 2) using an enum that might be null in a switch statement without first checking for null will cause an NPE.

Possible names: NATIVE? LINE_SEPARATOR? something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possible name: DEFAULT

I'm not sure which one I like best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PLATFORM_DEFAULT maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. Or maybe SYSTEM_DEFAULT (since it comes from Java's system properties)?

Comment on lines 34 to 35
/** Classic Mac OS */
CR,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I question the need for this enum value. CR was only used prior to macOS 10.0, which is so old as to be uninteresting without a good reason (for comparison, the very first version of Java for macOS was for macOS 10.5). Do you have a use case in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the use case is for application developers to have full control of the line endings. This might be useful for programming editors that deal with legacy formats.

Comment on lines 36 to 39
/** Windows */
CRLF,
/** macOS/Unix */
LF
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this shows up in the javadoc-generated API docs, it might be worth expanding this a bit.

*
* @return the line ending property
* @since 26
* @defaultValue null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a good reason for this to be a Nullable property. Instead, define a new enum value that means "use the value of the line.separator system property". It seems much cleaner (I don't like relying on null for out of band enum values, since it will cause an NPE if you switch on it without checking for null first).

Separately, should "use line.separator" should be the default value or should it use "\n" instead. Probably using "line.separator" is a better choice, which is what this PR does. The reason I bring this up is so that we make a conscious decision.

* @since 26
* @defaultValue null
*/
public final ObjectProperty<LineEnding> lineEndingProperty() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a property of RichTextArea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, RichTextArea might have a model that has no concept of line ending, or has its own idea of line ending - that's why it's an attribute of the model rather than the property in the control.

CodeArea, on the other hand, deals with plain text as an underlying data, so line ending is a property of the control, just like font and tabSize.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike font and tabSize, the lineEnding property is used when exporting to an external format where you want line endings, which also applies to RichTextArea. This is done implicitly by various operations (copy/paste/export) or explicitly when calling getText. I realize that getText is not currently a public method in RichTextArea, but I understand is likely to be proposed in the future. Once you add that method to the public API it will seem odd that users of CodeArea set the property in the control, but users of RichTextArea itself would need to set it in the model.

Do you still think it belongs in the CodeArea subclass? I suppose it can be moved to the superclass later, if needed (without breaking anything), but I at least want you to think through all the issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I thought of a bigger concern. Having a writable attribute in both the control and the model is not going to give coherent results in the case where you have more than one view on the same model. So you might need to rethink the idea of having a writable property on the view (the control) while also having state (via a writable attribute) on the model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!
What do you think we should do then?

I should probably move the setter and getter to the RichTextArea class then, and remove the property altogether.

Any custom UI can always create its own property for line ending and deal with the model directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the property in the model be the primary and having the RTA setter/getter forward to the model makes it a lot like setUndoRedoEnable, which seems like a good choice. The state is only in one place (the model) with convenience methods in the control.

* @since 26
* @defaultValue null
*/
public final ObjectProperty<LineEnding> lineEndingProperty() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike font and tabSize, the lineEnding property is used when exporting to an external format where you want line endings, which also applies to RichTextArea. This is done implicitly by various operations (copy/paste/export) or explicitly when calling getText. I realize that getText is not currently a public method in RichTextArea, but I understand is likely to be proposed in the future. Once you add that method to the public API it will seem odd that users of CodeArea set the property in the control, but users of RichTextArea itself would need to set it in the model.

Do you still think it belongs in the CodeArea subclass? I suppose it can be moved to the superclass later, if needed (without breaking anything), but I at least want you to think through all the issues.

/** macOS/Unix line ending, sequence of CR/LF (0x0d 0x0a)*/
LF,
/** Platform default line ending, using the value of {@code System.getProperty("line.separator")} */
SYSTEM_DEFAULT;
Copy link
Member

@kevinrushforth kevinrushforth Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe just SYSTEM? Now that I look at it, I'm not sure we need the "DEFAULT" suffix (nor the word "default" in the description).

* @since 26
*/
public enum LineEnding {
/** Classic Mac OS line ending, ASCII CR (0x0d) */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say "Legacy" rather than "Classic", and maybe add a sentence that this was used on Mac systems prior to macOS 10.

Comment on lines 36 to 37
/** Windows line ending, ASCII LF (0x0a) */
CRLF,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a . at the end of the sentence (here and for all the enums)?

*/
public static StyledOutput forPlainText() {
return new StringBuilderStyledOutput();
return new StringBuilderStyledOutput(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null is not allowed. Pass LineEnding.SYSTEM_DEFAULT.

@Override
protected void invalidated() {
LineEnding v = get();
if(v == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: space after if

protected void invalidated() {
LineEnding v = get();
if(v == null) {
set(old);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to unbind if bound.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good with a couple fairly minor comments inline.

/** Legacy Mac OS line ending, ASCII CR (0x0d). */
CR,
/** Windows */
/** Windows line ending, ASCII LF (0x0a). */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "ASCII LF (0x0a)" bit belongs to the LF enum value, not here.

CRLF,
/** macOS/Unix */
LF
/** macOS/Unix line ending, sequence of CR/LF (0x0d 0x0a). */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The The "CR/LF (0x0d 0x0a)" bit belongs to the CRLF enum value, not here. While you are at it, maybe add "ASCII" before "CR/LF" for consistency?

Comment on lines 1021 to 1023
if (lineEnding == null) {
lineEnding = LineEnding.SYSTEM;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed, right? lineEnding can never be null as long as you initialize it to LineEnding.SYSTEM , since you check and throw on an attempt to set it to null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried to do a lazy init, but it's not that essential.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I left one minor comment, but will approve as is. Will reapprove if you decide to make changes.

Comment on lines 86 to 88
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: you don't need two levels of try here. Even better, you can use try with resources instead of adding finally clause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

csr Need approved CSR to integrate pull request rfr Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants