From f8f3d588e145fa4e9ef0a1dbb08159408cbdb0a3 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 22 Oct 2025 13:31:24 -0700 Subject: [PATCH 1/4] line endings --- .../richtext/notebook/CodeCellTextModel.java | 4 +- .../richtext/notebook/TextCellTextModel.java | 4 +- .../richtext/StringBuilderStyledOutput.java | 19 +++----- .../control/richtext/util/RichUtils.java | 14 +++++- .../scene/control/richtext/CodeArea.java | 45 ++++++++++++++++++- .../scene/control/richtext/LineEnding.java | 40 +++++++++++++++++ .../model/PlainTextFormatHandler.java | 12 ++--- .../control/richtext/model/StyledOutput.java | 15 ++++++- .../richtext/model/StyledTextModel.java | 22 +++++++++ .../scene/control/richtext/CodeAreaTest.java | 24 ++++++++++ .../control/richtext/RichTextAreaTest.java | 26 ++++++++++- .../richtext/model/TestRichTextModel.java | 17 +++++++ .../control/richtext/support/RTUtil.java | 22 +++++++++ 13 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/LineEnding.java diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java index 3ccffb7d9f9..c8b6ef60534 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -72,7 +72,7 @@ public void setText(String text) { public String getText() { try { - StyledOutput out = StyledOutput.forPlainText(); + StyledOutput out = StyledOutput.forPlainText(getLineEnding()); TextPos end = getDocumentEnd(); export(TextPos.ZERO, end, out); return out.toString(); diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java index aebee7fd5e5..3634c1924e0 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -70,7 +70,7 @@ public void setText(String text) { public String getPlainText() { try { - StyledOutput out = StyledOutput.forPlainText(); + StyledOutput out = StyledOutput.forPlainText(getLineEnding()); TextPos end = getDocumentEnd(); export(TextPos.ZERO, end, out); return out.toString(); diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StringBuilderStyledOutput.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StringBuilderStyledOutput.java index 72608ad5435..6282c57d398 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StringBuilderStyledOutput.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StringBuilderStyledOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,23 +26,18 @@ package com.sun.jfx.incubator.scene.control.richtext; import java.io.IOException; +import com.sun.jfx.incubator.scene.control.richtext.util.RichUtils; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.model.StyledOutput; import jfx.incubator.scene.control.richtext.model.StyledSegment; public class StringBuilderStyledOutput implements StyledOutput { private final StringBuilder sb; - private String newline = System.getProperty("line.separator"); + private final String newline; - public StringBuilderStyledOutput(int initialCapacity) { - sb = new StringBuilder(initialCapacity); - } - - public StringBuilderStyledOutput() { - this(1024); - } - - public void setLineSeparator(String s) { - newline = s; + public StringBuilderStyledOutput(LineEnding lineEnding) { + sb = new StringBuilder(1024); + newline = RichUtils.getLineEnding(lineEnding); } @Override diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/util/RichUtils.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/util/RichUtils.java index eca7f8e5699..dc9864cfd96 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/util/RichUtils.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/util/RichUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,6 +61,7 @@ import com.sun.javafx.scene.text.TextFlowHelper; import com.sun.javafx.scene.text.TextLayout; import com.sun.javafx.scene.text.TextLine; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.model.StyleAttributeMap; /** @@ -160,6 +161,17 @@ public static boolean isTouchSupported() { return Platform.isSupported(ConditionalFeature.INPUT_TOUCH); } + public static String getLineEnding(LineEnding v) { + if(v == null) { + return System.getProperty("line.separator"); + } + return switch(v) { + case CR -> "\r"; + case CRLF -> "\r\n"; + case LF -> "\n"; + }; + } + public static int getTextLength(TextFlow f) { int len = 0; for (Node n : f.getChildrenUnmodifiable()) { diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 3ee442abc5c..ca1e310c0b3 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -32,6 +32,7 @@ import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.WritableValue; import javafx.css.CssMetaData; import javafx.css.FontCssMetaData; @@ -86,6 +87,7 @@ * @since 24 */ public class CodeArea extends RichTextArea { + private SimpleObjectProperty lineEnding; private BooleanProperty lineNumbers; private StyleableIntegerProperty tabSize; private StyleableObjectProperty font; @@ -115,6 +117,9 @@ protected void validateModel(StyledTextModel m) { if ((m != null) && (!(m instanceof CodeTextModel))) { throw new IllegalArgumentException("CodeArea accepts models that extend CodeTextModel"); } + if (m != null) { + m.setLineEnding(getLineEnding()); + } } @Override @@ -425,8 +430,7 @@ public final String getText() { return ""; } TextPos end = m.getDocumentEnd(); - try (StringBuilderStyledOutput out = new StringBuilderStyledOutput()) { - out.setLineSeparator("\n"); + try (StringBuilderStyledOutput out = new StringBuilderStyledOutput(m.getLineEnding())) { m.export(TextPos.ZERO, end, out); return out.toString(); } catch (IOException e) { @@ -449,4 +453,41 @@ public final void setText(String text) { private CodeTextModel codeModel() { return (CodeTextModel)getModel(); } + + /** + * Specifies the line ending characters. + * A {@code null} value results in the platform line ending as reported by + * the {@code line.separator} system property. + *

+ * Modifying this property causes corresponding update in the underlying model, if the latter is not {@code null}. + * + * @return the line ending property + * @since 26 + * @defaultValue null + */ + public final ObjectProperty lineEndingProperty() { + if (lineEnding == null) { + lineEnding = new SimpleObjectProperty<>(this, "lineEnding") { + @Override + protected void invalidated() { + StyledTextModel m = getModel(); + if (m != null) { + m.setLineEnding(get()); + } + } + }; + } + return lineEnding; + } + + public final LineEnding getLineEnding() { + if (lineEnding == null) { + return null; + } + return lineEnding.get(); + } + + public final void setLineEnding(LineEnding v) { + lineEndingProperty().set(v); + } } diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/LineEnding.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/LineEnding.java new file mode 100644 index 00000000000..744f9ea44b4 --- /dev/null +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/LineEnding.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jfx.incubator.scene.control.richtext; + +/** + * Specifies line separator (line ending) characters. + * + * @since 26 + */ +public enum LineEnding { + /** Classic Mac OS */ + CR, + /** Windows */ + CRLF, + /** macOS/Unix */ + LF +} diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/PlainTextFormatHandler.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/PlainTextFormatHandler.java index 3928ee762b9..d96091854a9 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/PlainTextFormatHandler.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/PlainTextFormatHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,9 +27,10 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javafx.scene.input.DataFormat; import com.sun.jfx.incubator.scene.control.richtext.StringBuilderStyledOutput; +import com.sun.jfx.incubator.scene.control.richtext.util.RichUtils; import jfx.incubator.scene.control.richtext.StyleResolver; import jfx.incubator.scene.control.richtext.TextPos; @@ -61,15 +62,14 @@ public StyledInput createStyledInput(String text, StyleAttributeMap attr) { @Override public Object copy(StyledTextModel m, StyleResolver resolver, TextPos start, TextPos end) throws IOException { - StringBuilderStyledOutput out = new StringBuilderStyledOutput(); + StringBuilderStyledOutput out = new StringBuilderStyledOutput(m.getLineEnding()); m.export(start, end, out); return out.toString(); } @Override public void save(StyledTextModel m, StyleResolver resolver, TextPos start, TextPos end, OutputStream out) throws IOException { - Charset charset = Charset.forName("utf-8"); - byte[] newline = System.getProperty("line.separator").getBytes(charset); + byte[] newline = RichUtils.getLineEnding(m.getLineEnding()).getBytes(StandardCharsets.UTF_8); StyledOutput so = new StyledOutput() { @Override @@ -80,7 +80,7 @@ public void consume(StyledSegment seg) throws IOException { break; case TEXT: String text = seg.getText(); - byte[] b = text.getBytes(charset); + byte[] b = text.getBytes(StandardCharsets.UTF_8); out.write(b); break; } diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledOutput.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledOutput.java index 090697b732d..68941737458 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledOutput.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ import java.io.Closeable; import java.io.IOException; import com.sun.jfx.incubator.scene.control.richtext.StringBuilderStyledOutput; +import jfx.incubator.scene.control.richtext.LineEnding; /** * Class represents a consumer of styled text segments for the purposes of @@ -55,6 +56,16 @@ public interface StyledOutput extends Closeable { * @return the instance of a plain text StyledOutput */ public static StyledOutput forPlainText() { - return new StringBuilderStyledOutput(); + return new StringBuilderStyledOutput(null); + } + + /** + * Creates an instance of a plain text StyledOutput with the specified line ending characters. + * @param lineEnding the line ending characters + * @return the instance of a plain text StyledOutput + * @since 26 + */ + public static StyledOutput forPlainText(LineEnding lineEnding) { + return new StringBuilderStyledOutput(lineEnding); } } diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index f62ae75f30d..db6ab0faa79 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -48,6 +48,7 @@ import com.sun.jfx.incubator.scene.control.richtext.StyleAttributeMapHelper; import com.sun.jfx.incubator.scene.control.richtext.UndoableChange; import com.sun.jfx.incubator.scene.control.richtext.util.RichUtils; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.Marker; import jfx.incubator.scene.control.richtext.StyleResolver; import jfx.incubator.scene.control.richtext.TextPos; @@ -284,6 +285,7 @@ private record FHKey(DataFormat format, boolean forExport) { } // TODO should it hold WeakReferences? private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); private final HashMap handlers = new HashMap<>(2); + private LineEnding lineEnding; private final Markers markers = new Markers(); private final UndoableChange head = UndoableChange.createHead(); private final ReadOnlyBooleanWrapper undoable = new ReadOnlyBooleanWrapper(this, "undoable", false); @@ -1007,4 +1009,24 @@ private void checkWritable() { throw new UnsupportedOperationException("the model is not writeable"); } } + + /** + * Specifies the line ending characters. + * A {@code null} value results in the platform line ending as reported by + * the {@code line.separator} system property. + * + * @return the line ending value + * @defaultValue null + */ + public final LineEnding getLineEnding() { + return lineEnding; + } + + /** + * Sets the line ending characters. + * @param value the line ending value, can be null + */ + public final void setLineEnding(LineEnding value) { + lineEnding = value; + } } diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java index 7cb5ad8ce2f..02690323329 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java @@ -33,12 +33,15 @@ import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.scene.Scene; +import javafx.scene.input.Clipboard; import javafx.scene.text.Font; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import jfx.incubator.scene.control.richtext.CodeArea; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.RichTextArea; +import jfx.incubator.scene.control.richtext.TextPos; import jfx.incubator.scene.control.richtext.model.CodeTextModel; import jfx.incubator.scene.control.richtext.model.RichTextModel; import jfx.incubator.scene.control.richtext.skin.CodeAreaSkin; @@ -198,4 +201,25 @@ public void modelAcceptable() { control.setModel(m); assertTrue(control.getModel() == m); } + + @Test + public void lineEnding() { + String nl = System.getProperty("line.separator"); + control.setText("1\n2\n3"); + assertEquals(3, control.getParagraphCount()); + t(null, "1" + nl + "2" + nl + "3"); + t(LineEnding.CR, "1\r2\r3"); + t(LineEnding.CRLF, "1\r\n2\r\n3"); + t(LineEnding.LF, "1\n2\n3"); + } + + private void t(LineEnding lineEnding, String expected) { + control.setLineEnding(lineEnding); + assertEquals(lineEnding, control.getLineEnding()); + assertEquals(lineEnding, control.lineEndingProperty().get()); + assertEquals(expected, control.getText()); + control.select(TextPos.ZERO, control.getDocumentEnd()); + control.copy(); + assertEquals(expected, Clipboard.getSystemClipboard().getString()); + } } diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index 8ca8a98a30d..834e60b3d7b 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.sun.jfx.incubator.scene.control.richtext.VFlow; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.RichTextArea; import jfx.incubator.scene.control.richtext.RichTextAreaShim; import jfx.incubator.scene.control.richtext.SelectionSegment; @@ -56,6 +57,7 @@ import jfx.incubator.scene.control.richtext.model.RichTextFormatHandler; import jfx.incubator.scene.control.richtext.model.RichTextModel; import jfx.incubator.scene.control.richtext.model.StyleAttributeMap; +import jfx.incubator.scene.control.richtext.model.StyledTextModel; import jfx.incubator.scene.control.richtext.skin.RichTextAreaSkin; import test.jfx.incubator.scene.control.richtext.support.RTUtil; import test.jfx.incubator.scene.control.richtext.support.TestStyledInput; @@ -74,6 +76,7 @@ public class RichTextAreaTest { private RichTextArea control; private static final StyleAttributeMap BOLD = StyleAttributeMap.builder().setBold(true).build(); + private static final String NL = System.getProperty("line.separator"); @BeforeEach public void beforeEach() { @@ -331,8 +334,27 @@ public void copy() { control.appendText("\n4"); control.select(new TextPos(0, 3, 2, false), control.getDocumentEnd()); control.copy(); - String nl = System.getProperty("line.separator"); - assertEquals(nl + "4", Clipboard.getSystemClipboard().getString()); + assertEquals(NL + "4", Clipboard.getSystemClipboard().getString()); + } + + @Test + public void copyLineEnding() { + control.appendText("1\n2\n3"); + assertEquals(3, control.getParagraphCount()); + t(null, "1" + NL + "2" + NL + "3"); + t(LineEnding.CR, "1\r2\r3"); + t(LineEnding.CRLF, "1\r\n2\r\n3"); + t(LineEnding.LF, "1\n2\n3"); + } + + private void t(LineEnding lineEnding, String expected) { + StyledTextModel m = control.getModel(); + m.setLineEnding(lineEnding); + assertEquals(lineEnding, m.getLineEnding()); + assertEquals(expected, text()); + control.select(TextPos.ZERO, control.getDocumentEnd()); + control.copy(); + assertEquals(expected, Clipboard.getSystemClipboard().getString()); } @Test diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java index 80fe8ad2553..49e41a5dcaf 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.sun.jfx.incubator.scene.control.richtext.SegmentStyledInput; +import jfx.incubator.scene.control.richtext.LineEnding; import jfx.incubator.scene.control.richtext.TextPos; import jfx.incubator.scene.control.richtext.model.RichParagraph; import jfx.incubator.scene.control.richtext.model.RichTextModel; @@ -40,6 +41,7 @@ import jfx.incubator.scene.control.richtext.model.StyledInput; import jfx.incubator.scene.control.richtext.model.StyledSegment; import jfx.incubator.scene.control.richtext.model.StyledTextModel; +import test.jfx.incubator.scene.control.richtext.support.RTUtil; /** * Tests RichTextModel. @@ -272,4 +274,19 @@ public void clamp() { assertEquals(TextPos.ofLeading(2, 0), m.clamp(TextPos.ofLeading(2, 100))); } + + @Test + public void lineEnding() { + String nl = System.getProperty("line.separator"); + RichTextModel m = createModel("1\n2\n3"); + assertEquals(3, m.size()); + m.setLineEnding(null); + assertEquals("1" + nl + "2" + nl + "3", RTUtil.getText(m)); + m.setLineEnding(LineEnding.CR); + assertEquals("1\r2\r3", RTUtil.getText(m)); + m.setLineEnding(LineEnding.CRLF); + assertEquals("1\r\n2\r\n3", RTUtil.getText(m)); + m.setLineEnding(LineEnding.LF); + assertEquals("1\n2\n3", RTUtil.getText(m)); + } } diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java index c5ecabea72e..568c5b04c9f 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java @@ -34,6 +34,7 @@ import com.sun.javafx.tk.Toolkit; import jfx.incubator.scene.control.richtext.RichTextArea; import jfx.incubator.scene.control.richtext.TextPos; +import jfx.incubator.scene.control.richtext.model.StyledTextModel; /** * Utilities for RichTextArea-based tests. @@ -75,6 +76,27 @@ public static String getText(RichTextArea control) { } } + /** + * Extracts plain text from the supplied StyledTextModel, using {@code write(DataFormat.PLAIN_TEXT)} method. + * + * @param model the model + * @return the plain text + */ + public static String getText(StyledTextModel model) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + try { + model.write(null, DataFormat.PLAIN_TEXT, out); + byte[] b = out.toByteArray(); + return new String(b, StandardCharsets.UTF_8); + } finally { + out.close(); + } + } catch (IOException e) { + throw new AssertionError(e); + } + } + /** * Clears the system Clipboard. */ From fd279ec4e0df310ea30e23b5e31ed05592417544 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 22 Oct 2025 14:08:19 -0700 Subject: [PATCH 2/4] tests --- .../scene/control/richtext/CodeAreaTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java index 02690323329..2f4228a2470 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/CodeAreaTest.java @@ -157,6 +157,7 @@ public void getControlCssMetaData() { @Test public void getText() { control.setText("123"); + control.setLineEnding(LineEnding.LF); String s = control.getText(); assertEquals("123", s); @@ -222,4 +223,22 @@ private void t(LineEnding lineEnding, String expected) { control.copy(); assertEquals(expected, Clipboard.getSystemClipboard().getString()); } + + @Test + public void setText() { + String expected = "1\n2\n3\n4"; + String[] variants = { + "1\n2\n3\n4", + "1\r2\r3\r4", + "1\r\n2\r\n3\r\n4", + "1\r2\n3\r\n4" + }; + control.setLineEnding(LineEnding.LF); + for (int i = 0; i < variants.length; i++) { + String s = variants[i]; + control.setText(s); + String text = control.getText(); + assertEquals(expected, text, "variant=" + i); + } + } } From 7c3b107956d546fe0096639820ec82b0c3a3e099 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 30 Oct 2025 12:49:26 -0700 Subject: [PATCH 3/4] since --- .../incubator/scene/control/richtext/model/StyledTextModel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index db6ab0faa79..978c1fae4f9 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -1017,6 +1017,7 @@ private void checkWritable() { * * @return the line ending value * @defaultValue null + * @since 26 */ public final LineEnding getLineEnding() { return lineEnding; @@ -1025,6 +1026,7 @@ public final LineEnding getLineEnding() { /** * Sets the line ending characters. * @param value the line ending value, can be null + * @since 26 */ public final void setLineEnding(LineEnding value) { lineEnding = value; From 994e6788f8000649b0a29f2f21427266fac222b2 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 31 Oct 2025 11:41:56 -0700 Subject: [PATCH 4/4] get text --- .../jfx/incubator/scene/control/richtext/RichTextArea.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 7da3c96d3dc..26c13530130 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1451,9 +1451,7 @@ private final boolean getText(TextPos start, TextPos end, StringBuilder sb, int end = tmp; } - // TODO JDK-8370140 (line separator property) - String lineSeparator = System.getProperty("line.separator"); - + String lineSeparator = RichUtils.getLineEnding(m.getLineEnding()); int toCopy = limit; int index = start.index(); boolean first = true;