From b5b841bc8561166b7a11b8d2f241ff288a101da6 Mon Sep 17 00:00:00 2001 From: Simeon Andreev Date: Tue, 24 Feb 2026 13:10:21 +0200 Subject: [PATCH] Add hard wrap limit option to console This change adds the following options to org.eclipse.debug.ui: * Console.limitLongLines (boolean) * Console.limitLongLinesWrap (boolean) * Console.limitLongLinesLength (int) When limitLongLines is set, long console lines will be trimmed or wrapped. If limitLongLinesWrap is set, lines are wrapped. Otherwise lines are trimmed. The length at which long lines are trimmed/wrapped is limitLongLinesLength. The preferences apply to output before its appended to the console document. In contrast, the existing console word wrapping is applied after the output is appended to the console document. Fixes: #2479 --- .../META-INF/MANIFEST.MF | 3 +- .../eclipse/debug/tests/AutomatedSuite.java | 6 +- .../ConsoleOutputLineTruncateTest.java | 127 ++++++++++++++++ .../console/ConsoleOutputLineWrapTest.java | 140 ++++++++++++++++++ .../debug/tests/console/IOConsoleTests.java | 119 +++++++++++++++ .../ui/DebugUIPreferenceInitializer.java | 3 + .../ui/preferences/ConsolePreferencePage.java | 38 +++++ .../preferences/DebugPreferencesMessages.java | 3 + .../DebugPreferencesMessages.properties | 3 + .../IDebugPreferenceConstants.java | 8 + .../ui/views/console/ProcessConsole.java | 15 ++ .../src/org/eclipse/ui/console/IOConsole.java | 18 ++- .../console/ConsoleOutputLineTruncate.java | 93 ++++++++++++ .../console/ConsoleOutputLineWrap.java | 85 +++++++++++ .../console/IConsoleOutputModifier.java | 34 +++++ .../console/IOConsolePartitioner.java | 51 +++++++ 16 files changed, 742 insertions(+), 4 deletions(-) create mode 100644 debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineTruncateTest.java create mode 100644 debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineWrapTest.java create mode 100644 debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineTruncate.java create mode 100644 debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineWrap.java create mode 100644 debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IConsoleOutputModifier.java diff --git a/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF b/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF index 7016e231f40..1dd65e70878 100644 --- a/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF +++ b/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF @@ -31,10 +31,11 @@ Export-Package: org.eclipse.debug.tests, Import-Package: org.assertj.core.api;version="3.24.2", org.assertj.core.api.iterable, org.junit.jupiter.api;version="[5.14.0,6.0.0)", - org.junit.jupiter.api.io;version="[5.14.0,6.0.0)", org.junit.jupiter.api.extension;version="[5.14.0,6.0.0)", org.junit.jupiter.api.function;version="[5.14.0,6.0.0)", + org.junit.jupiter.api.io;version="[5.14.0,6.0.0)", org.junit.jupiter.params;version="[5.14.0,6.0.0)", + org.junit.jupiter.params.provider;version="[5.14.0,6.0.0)", org.junit.platform.suite.api;version="[1.14.0,2.0.0)", org.opentest4j;version="[1.3.0,2.0.0)" Eclipse-BundleShape: dir diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java index 52be8447f02..efd2349e0c8 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java @@ -19,8 +19,10 @@ import org.eclipse.debug.tests.breakpoint.BreakpointTests; import org.eclipse.debug.tests.breakpoint.SerialExecutorTest; import org.eclipse.debug.tests.console.ConsoleDocumentAdapterTests; -import org.eclipse.debug.tests.console.ConsoleShowHideTests; import org.eclipse.debug.tests.console.ConsoleManagerTests; +import org.eclipse.debug.tests.console.ConsoleOutputLineTruncateTest; +import org.eclipse.debug.tests.console.ConsoleOutputLineWrapTest; +import org.eclipse.debug.tests.console.ConsoleShowHideTests; import org.eclipse.debug.tests.console.ConsoleTests; import org.eclipse.debug.tests.console.FileLinkTests; import org.eclipse.debug.tests.console.IOConsoleFixedWidthTests; @@ -122,6 +124,8 @@ ConsoleManagerTests.class, // ConsoleTests.class, // IOConsoleTests.class, // + ConsoleOutputLineWrapTest.class, // + ConsoleOutputLineTruncateTest.class, // IOConsoleFixedWidthTests.class, // ProcessConsoleManagerTests.class, // ProcessConsoleTests.class, // diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineTruncateTest.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineTruncateTest.java new file mode 100644 index 00000000000..28737d98061 --- /dev/null +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineTruncateTest.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.eclipse.ui.internal.console.ConsoleOutputLineTruncate; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link ConsoleOutputLineTruncate} handling chunks of input, breaking + * input lines at a specific length limit. + */ +public class ConsoleOutputLineTruncateTest { + + /** + * Parameters of a test for {@link ConsoleOutputLineTruncate}. + * + * @param limit the line length limit for the test + * @param chunks how many times the {@code input} is repeated + * @param repeat how many times {@code output} is repeated in the expected + * output, line breaks are inserted between concatenated + * {@code output} + * @param input input string passed to the tested + * {@link ConsoleOutputLineTruncate} + * @param output expected output, repeated {@code repeat} times + * @param nl the newline character sequences + */ + record Parameters(int limit, int chunks, int repeat, String input, String output, String... nl) { + } + + private static Parameters test(int limit, String input, String output, String... nl) { + return test(limit, 1, 1, input, output, nl); + } + + private static Parameters test(int limit, int chunks, int repeat, String input, String output, String... nl) { + return new Parameters(limit, chunks, repeat, input, output, nl); + } + + private static final Parameters[] TESTS = { + // Unix newlines + test(4, "\n========", "\n==== ...\n", "\n"), + test(10, "========", "========", "\n"), + test(10, 10, 1, "========", "========== ...\n", "\n"), + test(4, "========", "==== ...\n", "\n"), + test(4, 10, 1, "========", "==== ...\n", "\n"), + test(4, "====\n====", "====\n====", "\n"), + test(4, 5, 1, "====\n====", "====\n==== ...\n==== ...\n==== ...\n==== ...\n====", "\n"), + test(2, 5, 1, "=======\n==", "== ...\n== ...\n== ...\n== ...\n== ...\n==", "\n"), + test(2, "====\n====", "== ...\n== ...\n", "\n"), + test(2, 5, 1, "====\n====", "== ...\n== ...\n== ...\n== ...\n== ...\n== ...\n", "\n"), + test(2, "=========", "== ...\n", "\n"), + test(2, "=======\n==", "== ...\n==", "\n"), + test(3, "=========", "=== ...\n", "\n"), + test(3, 5, 1, "=========", "=== ...\n", "\n"), + test(3, "========\n=", "=== ...\n=", "\n"), + test(2, "======\n======", "== ...\n== ...\n", "\n"), + test(2, 5, 1, "======\n======", "== ...\n== ...\n== ...\n== ...\n== ...\n== ...\n", "\n"), + test(3, 3, 1, "========\n=", "=== ...\n=== ...\n=== ...\n=" , "\n"), + test(4, 3, 1, "========\n=", "==== ...\n==== ...\n==== ...\n=", "\n"), + + // Windows newlines + test(4, "\r\n========", "\r\n==== ...\r\n", "\r\n"), + test(10, "========", "========", "\r\n"), + test(10, 10, 1, "========", "========== ...\r\n", "\r\n"), + test(4, "========", "==== ...\r\n", "\r\n"), + test(4, 10, 1, "========", "==== ...\r\n", "\r\n"), + test(4, "====\r\n====", "====\r\n====", "\r\n"), + test(4, 5, 1, "====\r\n====", "====\r\n==== ...\r\n==== ...\r\n==== ...\r\n==== ...\r\n====", "\r\n"), + test(2, 5, 1, "=======\r\n==", "== ...\r\n== ...\r\n== ...\r\n== ...\r\n== ...\r\n==", "\r\n"), + test(2, "====\r\n====", "== ...\r\n== ...\r\n", "\r\n"), + test(2, 5, 1, "====\r\n====", "== ...\r\n== ...\r\n== ...\r\n== ...\r\n== ...\r\n== ...\r\n", "\r\n"), + test(2, "=========", "== ...\r\n", "\r\n"), + test(2, "=======\r\n==", "== ...\r\n==", "\r\n"), + test(3, "=========", "=== ...\r\n", "\r\n"), + test(3, 5, 1, "=========", "=== ...\r\n", "\r\n"), + test(3, "========\r\n=", "=== ...\r\n=", "\r\n"), + test(2, "======\r\n======", "== ...\r\n== ...\r\n", "\r\n"), + test(2, 5, 1, "======\r\n======", "== ...\r\n== ...\r\n== ...\r\n== ...\r\n== ...\r\n== ...\r\n", "\r\n"), + test(3, 3, 1, "========\r\n=", "=== ...\r\n=== ...\r\n=== ...\r\n=", "\r\n"), + test(4, 3, 1, "========\r\n=", "==== ...\r\n==== ...\r\n==== ...\r\n=", "\r\n"), + + // multiple newlines + test(3, 3, 1, "========\r\n=", "=== ...\r\n=== ...\r\n=== ...\r\n=", "\r\n", "\r", "\n"), + test(4, 3, 1, "========\r\n=", "==== ...\r\n==== ...\r\n==== ...\r\n=", "\r\n", "\r", "\n"), + test(3, 3, 1, "========\r\n=", "=== ...\r\n=== ...\r\n=== ...\r\n=", "\r", "\n", "\r\n"), + test(4, 3, 1, "========\r\n=", "==== ...\r\n==== ...\r\n==== ...\r\n=", "\r", "\n", "\r\n"), + }; + + private static Stream tests() { + return Arrays.stream(TESTS).map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("tests") + public void test(Parameters p) { + ConsoleOutputLineTruncate truncate = new ConsoleOutputLineTruncate(p.limit, p.nl); + StringBuilder c = new StringBuilder(); + for (int i = 0; i < p.chunks; ++i) { + StringBuilder s = new StringBuilder(p.input); + CharSequence text = truncate.modify(s); + c.append(text); + } + String expected = repeat(p.output, p.repeat, p.nl[0]); + assertEquals(expected, c.toString()); + } + + private static String repeat(String s, int r, String nl) { + return (nl + s).repeat(r).substring(nl.length()); + } +} diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineWrapTest.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineWrapTest.java new file mode 100644 index 00000000000..bcd57599237 --- /dev/null +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleOutputLineWrapTest.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.eclipse.ui.internal.console.ConsoleOutputLineWrap; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link ConsoleOutputLineWrap} handling chunks of input, breaking input + * lines at a specific length limit. + */ +public class ConsoleOutputLineWrapTest { + + /** + * Parameters of a test for {@link ConsoleOutputLineWrap}. + * + * @param limit the line length limit for the test + * @param chunks how many times the {@code input} is repeated + * @param repeat how many times {@code output} is repeated in the expected + * output, line breaks are inserted between concatenated + * {@code output} + * @param input input string passed to the tested + * {@link ConsoleOutputLineWrap} + * @param output expected output, repeated {@code repeat} times + * @param nl the newline character sequences + */ + record Parameters(int limit, int chunks, int repeat, String input, String output, String... nl) { + } + + private static Parameters test(int limit, String input, String output, String... nl) { + return test(limit, 1, 1, input, output, nl); + } + + private static Parameters test(int limit, int chunks, String input, String output, String... nl) { + return test(limit, chunks, chunks, input, output, nl); + } + + private static Parameters test(int limit, int chunks, int repeat, String input, String output, String... nl) { + return new Parameters(limit, chunks, repeat, input, output, nl); + } + + private static final Parameters[] TESTS = { + // Unix newlines + test(4, "\n========", "\n====\n====", "\n"), + + test(10, "========" , "========" , "\n"), + test(10, 10, 8, "========" , "==========", "\n"), + test( 4, "========" , "====\n====", "\n"), + test( 4, 10, "========" , "====\n====", "\n"), + test( 4, "====\n====" , "====\n====", "\n"), + test( 4, 10, "====\n====" , "====\n====", "\n"), + + test( 2, 10, "=======\n==" , "==\n==\n==\n=\n==", "\n"), + test( 2, "====\n====" , "==\n==\n==\n==" , "\n"), + test( 2, 10, "====\n====" , "==\n==\n==\n==" , "\n"), + test( 2, "=========" , "==\n==\n==\n==\n=", "\n"), + test( 2, "=======\n==" , "==\n==\n==\n=\n==", "\n"), + test( 3, "=========" , "===\n===\n===" , "\n"), + test( 3, 10, "=========" , "===\n===\n===" , "\n"), + test( 3, "========\n=" , "===\n===\n==\n=" , "\n"), + + test( 2, "======\n======", "==\n==\n==\n==\n==\n==", "\n"), + test( 2, 10, "======\n======", "==\n==\n==\n==\n==\n==", "\n"), + + test( 3, 3, 1, "========\n=" , "===\n===\n==\n===\n===\n===\n===\n===\n===\n=", "\n"), + test( 4, 3, 1, "========\n=" , "====\n====\n====\n====\n=\n====\n====\n=\n=" , "\n"), + + // Windows newlines + test(4, "\r\n========", "\r\n====\r\n====", "\r\n"), + + test(10, "========" , "========" , "\r\n"), + test(10, 10, 8, "========" , "==========" , "\r\n"), + test( 4, "========" , "====\r\n====", "\r\n"), + test( 4, 10, "========" , "====\r\n====", "\r\n"), + test( 4, "====\r\n====" , "====\r\n====", "\r\n"), + test( 4, 10, "====\r\n====" , "====\r\n====", "\r\n"), + + test( 2, 10, "=======\r\n==" , "==\r\n==\r\n==\r\n=\r\n==", "\r\n"), + test( 2, "====\r\n====" , "==\r\n==\r\n==\r\n==" , "\r\n"), + test( 2, 10, "====\r\n====" , "==\r\n==\r\n==\r\n==" , "\r\n"), + test( 2, "=========" , "==\r\n==\r\n==\r\n==\r\n=", "\r\n"), + test( 2, "=======\r\n==" , "==\r\n==\r\n==\r\n=\r\n==", "\r\n"), + test( 3, "=========" , "===\r\n===\r\n===" , "\r\n"), + test( 3, 10, "=========" , "===\r\n===\r\n===" , "\r\n"), + test( 3, "========\r\n=" , "===\r\n===\r\n==\r\n=" , "\r\n"), + + test( 2, "======\r\n======", "==\r\n==\r\n==\r\n==\r\n==\r\n==", "\r\n"), + test( 2, 10, "======\r\n======", "==\r\n==\r\n==\r\n==\r\n==\r\n==", "\r\n"), + + test( 3, 3, 1, "========\r\n=" , "===\r\n===\r\n==\r\n===\r\n===\r\n===\r\n===\r\n===\r\n===\r\n=", "\r\n"), + test( 4, 3, 1, "========\r\n=" , "====\r\n====\r\n====\r\n====\r\n=\r\n====\r\n====\r\n=\r\n=" , "\r\n"), + + // multiple newlines + test( 3, 3, 1, "========\r\n=" , "===\r\n===\r\n==\r\n===\r\n===\r\n===\r\n===\r\n===\r\n===\r\n=", "\r\n", "\r", "\n"), + test( 4, 3, 1, "========\r\n=" , "====\r\n====\r\n====\r\n====\r\n=\r\n====\r\n====\r\n=\r\n=" , "\r\n", "\r", "\n"), + + test( 3, 3, 1, "========\r\n=" , "===\r===\r==\r\n===\r===\r===\r\n===\r===\r===\r\n=", "\r", "\n", "\r\n"), + test( 4, 3, 1, "========\r\n=" , "====\r====\r\n====\r====\r=\r\n====\r====\r=\r\n=" , "\r", "\n", "\r\n"), + }; + + private static Stream tests() { + return Arrays.stream(TESTS).map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("tests") + public void test(Parameters p) { + ConsoleOutputLineWrap lineBreak = new ConsoleOutputLineWrap(p.limit, p.nl); + StringBuilder c = new StringBuilder(); + for (int i = 0; i < p.chunks; ++i) { + StringBuilder s = new StringBuilder(p.input); + CharSequence text = lineBreak.modify(s); + c.append(text); + } + String expected = repeat(p.output, p.repeat, p.nl[0]); + assertEquals(expected, c.toString()); + } + + private static String repeat(String s, int r, String nl) { + return (nl + s).repeat(r).substring(nl.length()); + } +} diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java index 945f4c8c8bf..d9393986eeb 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java @@ -80,6 +80,7 @@ */ @ExtendWith(DebugTestExtension.class) public class IOConsoleTests { + /** * The console view used for the running test. Required to obtain access to * consoles {@link StyledText} widget to simulate user input. @@ -761,6 +762,124 @@ public void testTrimSurrogateCharacters() throws Exception { } } + /** + * Check trimming of long lines. + */ + @Test + public void testTrimLongLine() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test trim long line"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + c.getConsole().setLimitLineLength(false, 8); + c.writeFast("first\n"); + c.writeFast("0123456789\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n01234567 ...\nlast\n"); + closeConsole(c); + } + } + + /** + * Check that trimming long lines doesn't split at a newline. + */ + @Test + public void testTrimLongLineNewline() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test trim long line with newline"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + c.getConsole().setLimitLineLength(false, 8); + c.writeFast("first\n"); + c.writeFast("0123456\r\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n0123456\r\nlast\n"); + closeConsole(c); + } + } + + /** + * Check that trimming long lines doesn't split surrogate pairs, e.g. + * emojis. + */ + @Test + public void testTrimLongLineSurrogateCharacters() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test trim long line with emoji"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + c.getConsole().setLimitLineLength(false, 8); + c.writeFast("first\n"); + c.writeFast("01234😀😀😀\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n01234😀 ...\nlast\n"); + closeConsole(c); + } + } + + /** + * Check wrapping of long lines. + */ + @Test + public void testWrapLongLine() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test wrap long line"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + String nl = c.getConsole().getDocument().getLegalLineDelimiters()[0]; + c.getConsole().setLimitLineLength(true, 8); + c.writeFast("first\n"); + c.writeFast("0123456789\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n01234567" + nl + "89\nlast\n"); + closeConsole(c); + } + } + + /** + * Check that wrapping long lines doesn't split newlines. + */ + @Test + public void testWrapLongLineNewline() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test wrap long line with newline"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + c.getConsole().setLimitLineLength(true, 8); + c.writeFast("first\n"); + c.writeFast("0123456\r\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n0123456\r\nlast\n"); + closeConsole(c); + } + } + + /** + * Check that wrapping long lines doesn't split surrogate pairs, e.g. + * emojis. + */ + @Test + public void testWrapLongLineSurrogateCharacters() throws Exception { + final IOConsoleTestUtil c = getTestUtil("Test wrap long line with emoji"); + try (IOConsoleOutputStream out = c.getDefaultOutputStream()) { + String nl = c.getConsole().getDocument().getLegalLineDelimiters()[0]; + c.getConsole().setLimitLineLength(true, 8); + c.writeFast("first\n"); + c.writeFast("0123456😀😀\n", out); + c.write("last\n"); + c.verifyContentByLine("first", 0).verifyContentByLine("last", -2); + assertTrue(c.getDocument().getNumberOfLines() > 2, "Document not filled."); + c.waitForScheduledJobs(); + c.verifyContent("first\n0123456" + nl + "😀😀\nlast\n"); + closeConsole(c); + } + } + /** * Some extra tests for IOConsolePartitioner. */ diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/DebugUIPreferenceInitializer.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/DebugUIPreferenceInitializer.java index 79ac70b3cd2..3ff13f5e296 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/DebugUIPreferenceInitializer.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/DebugUIPreferenceInitializer.java @@ -86,6 +86,9 @@ public void initializeDefaultPreferences() { prefs.setDefault(IDebugPreferenceConstants.CONSOLE_LIMIT_CONSOLE_OUTPUT, true); prefs.setDefault(IDebugPreferenceConstants.CONSOLE_LOW_WATER_MARK, 80000); prefs.setDefault(IDebugPreferenceConstants.CONSOLE_HIGH_WATER_MARK, 100000); + prefs.setDefault(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES, false); + prefs.setDefault(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_WRAP, false); + prefs.setDefault(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_LENGTH, 1000); prefs.setDefault(IDebugPreferenceConstants.CONSOLE_TAB_WIDTH, 8); prefs.setDefault(IDebugPreferenceConstants.CONSOLE_INTERPRET_CONTROL_CHARACTERS, false); prefs.setDefault(IDebugPreferenceConstants.CONSOLE_INTERPRET_CR_AS_CONTROL_CHARACTER, true); diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/ConsolePreferencePage.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/ConsolePreferencePage.java index a0f6b7b4d8b..427f4a8c818 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/ConsolePreferencePage.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/ConsolePreferencePage.java @@ -92,6 +92,10 @@ protected void clearErrorMessage() { private BooleanFieldEditor2 fUseBufferSize; private ConsoleIntegerFieldEditor fBufferSizeEditor; + private BooleanFieldEditor2 fLimitLines; + private BooleanFieldEditor2 fLimitLineWrap; + private ConsoleIntegerFieldEditor fLimitLineLength; + private ConsoleIntegerFieldEditor fTabSizeEditor; private BooleanFieldEditor autoScrollLockEditor; @@ -165,6 +169,24 @@ public void widgetSelected(SelectionEvent e) { } ); + fLimitLines = new BooleanFieldEditor2(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES, DebugPreferencesMessages.ConsolePreferencePage_Limit_console_lines, SWT.NONE, getFieldEditorParent()); + addField(fLimitLines); + fLimitLines.getChangeControl(getFieldEditorParent()).addSelectionListener( + new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateLineLimitControls(); + } + } + ); + + fLimitLineWrap = new BooleanFieldEditor2(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_WRAP, DebugPreferencesMessages.ConsolePreferencePage_Limit_console_lines_wrap, SWT.NONE, getFieldEditorParent()); + addField(fLimitLineWrap); + + fLimitLineLength = new ConsoleIntegerFieldEditor(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_LENGTH, DebugPreferencesMessages.ConsolePreferencePage_Limit_console_lines_length, getFieldEditorParent()); + fLimitLineLength.setValidRange(1, Integer.MAX_VALUE - 100000); + addField(fLimitLineLength); + fTabSizeEditor = new ConsoleIntegerFieldEditor(IDebugPreferenceConstants.CONSOLE_TAB_WIDTH, DebugPreferencesMessages.ConsolePreferencePage_12, getFieldEditorParent()); addField(fTabSizeEditor); fTabSizeEditor.setValidRange(1,100); @@ -277,6 +299,7 @@ protected void initialize() { updateWidthEditor(); updateAutoScrollLockEditor(); updateBufferSizeEditor(); + updateLineLimitControls(); updateInterpretCrAsControlCharacterEditor(); updateWordWrapEditorFromConsolePreferences(); } @@ -308,6 +331,17 @@ protected void updateBufferSizeEditor() { fBufferSizeEditor.getLabelControl(getFieldEditorParent()).setEnabled(b.getSelection()); } + /** + * Update enablement for line length limits based on enablement of 'limit + * console lines' editor. + */ + protected void updateLineLimitControls() { + Button b = fLimitLines.getChangeControl(getFieldEditorParent()); + fLimitLineWrap.setEnabled(b.getSelection(), getFieldEditorParent()); + fLimitLineLength.getTextControl(getFieldEditorParent()).setEnabled(b.getSelection()); + fLimitLineLength.getLabelControl(getFieldEditorParent()).setEnabled(b.getSelection()); + } + /** * Update enablement of carriage return interpretation based on general control * character interpretation. @@ -333,6 +367,7 @@ protected void performDefaults() { super.performDefaults(); updateWidthEditor(); updateBufferSizeEditor(); + updateLineLimitControls(); updateInterpretCrAsControlCharacterEditor(); updateElapsedTimePreferences(); @@ -361,6 +396,9 @@ public void propertyChange(PropertyChangeEvent event) { if (fBufferSizeEditor != null && event.getSource() != fBufferSizeEditor) { fBufferSizeEditor.refreshValidState(); } + if (fLimitLineLength != null && event.getSource() != fLimitLineLength) { + fLimitLineLength.refreshValidState(); + } if (fTabSizeEditor != null && event.getSource() != fTabSizeEditor) { fTabSizeEditor.refreshValidState(); } diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java index b9df13a90fe..bb3b0e0b7e9 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java @@ -31,6 +31,9 @@ public class DebugPreferencesMessages extends NLS { public static String ConsolePreferencePage_Console_width; public static String ConsolePreferencePage_Limit_console_output_1; public static String ConsolePreferencePage_Console_buffer_size__characters___2; + public static String ConsolePreferencePage_Limit_console_lines; + public static String ConsolePreferencePage_Limit_console_lines_wrap; + public static String ConsolePreferencePage_Limit_console_lines_length; public static String ConsolePreferencePage_ConsoleAutoPinEnable; public static String ConsolePreferencePage_The_console_buffer_size_must_be_at_least_1000_characters__1; diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties index 4e8a8e8c256..527067ef407 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties @@ -23,6 +23,9 @@ ConsolePreferencePage_Wrap_text_1=Fixed &width console ConsolePreferencePage_Console_width=&Maximum character width: ConsolePreferencePage_Limit_console_output_1=&Limit console output ConsolePreferencePage_Console_buffer_size__characters___2=Console &buffer size (characters): +ConsolePreferencePage_Limit_console_lines=Limit console lines +ConsolePreferencePage_Limit_console_lines_wrap=Wrap console line at limit +ConsolePreferencePage_Limit_console_lines_length=Console line length limit ConsolePreferencePage_ConsoleAutoPinEnable=Pin current and the new Console views when a new Console view is opened ConsolePreferencePage_The_console_buffer_size_must_be_at_least_1000_characters__1=Buffer size must be between 1000 and {0} inclusive. ConsolePreferencePage_console_width=Character width must be between 80 and 1000 inclusive. diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/IDebugPreferenceConstants.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/IDebugPreferenceConstants.java index 8984d5d6548..66006d38594 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/IDebugPreferenceConstants.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/IDebugPreferenceConstants.java @@ -73,6 +73,14 @@ public interface IDebugPreferenceConstants { String CONSOLE_LOW_WATER_MARK = "Console.lowWaterMark"; //$NON-NLS-1$ String CONSOLE_HIGH_WATER_MARK = "Console.highWaterMark"; //$NON-NLS-1$ + /** + * Limit for long lines, lines longer than the limit can be truncated or + * wrapped. + */ + String CONSOLE_LIMIT_LINES = "Console.limitLongLines"; //$NON-NLS-1$ + String CONSOLE_LIMIT_LINES_WRAP = "Console.limitLongLinesWrap"; //$NON-NLS-1$ + String CONSOLE_LIMIT_LINES_LENGTH = "Console.limitLongLinesLength"; //$NON-NLS-1$ + /** * Integer preference specifying the number of spaces composing a * tab in the console. diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java index f5b4bee8990..155dcbb4ee1 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java @@ -534,6 +534,15 @@ public void propertyChange(PropertyChangeEvent evt) { } else { setWaterMarks(-1, -1); } + } else if (property.equals(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES) || property.equals(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_LENGTH) || property.equals(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_WRAP)) { + boolean limitLines = store.getBoolean(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES); + if (limitLines) { + boolean wrap = store.getBoolean(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_WRAP); + int length = store.getInt(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_LENGTH); + setLimitLineLength(wrap, length); + } else { + setLimitLineLength(false, -1); + } } else if (property.equals(IDebugPreferenceConstants.CONSOLE_TAB_WIDTH)) { int tabWidth = store.getInt(IDebugPreferenceConstants.CONSOLE_TAB_WIDTH); setTabWidth(tabWidth); @@ -687,6 +696,12 @@ protected void init() { setWaterMarks(lowWater, highWater); } + if (store.getBoolean(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES)) { + boolean lineWrap = store.getBoolean(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_WRAP); + int lineLimitLength = store.getInt(IDebugPreferenceConstants.CONSOLE_LIMIT_LINES_LENGTH); + setLimitLineLength(lineWrap, lineLimitLength); + } + setHandleControlCharacters(store.getBoolean(IDebugPreferenceConstants.CONSOLE_INTERPRET_CONTROL_CHARACTERS)); setCarriageReturnAsControlCharacter(store.getBoolean(IDebugPreferenceConstants.CONSOLE_INTERPRET_CR_AS_CONTROL_CHARACTER)); diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java index 7a03913cea2..91ab534668a 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java @@ -254,8 +254,22 @@ public void setWaterMarks(int low, int high) { } /** - * Check if all streams connected to this console are closed. If so, - * notify the partitioner that this console is finished. + * Sets a line character length limit for the console. If the length limit is + * positive, lines are wrapped or truncated at this length. + * + * @param wrap {@code true} if the line should be wrapped, {@code false} if it + * should be truncated. No effect if {@code length} is negative. + * @param length The length at which lines are wrapped or truncated. Negative if + * the line should be left unchanged. + * @since 3.17 + */ + public void setLimitLineLength(boolean wrap, int length) { + partitioner.setLimitLineLength(wrap, length); + } + + /** + * Check if all streams connected to this console are closed. If so, notify the + * partitioner that this console is finished. */ private void checkFinished() { if (openStreams.isEmpty()) { diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineTruncate.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineTruncate.java new file mode 100644 index 00000000000..c90aabacbdc --- /dev/null +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineTruncate.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.console; + +import org.eclipse.jface.text.MultiStringMatcher; +import org.eclipse.jface.text.MultiStringMatcher.Match; + +/** + * Processes chunks of character sequences. Truncates lines longer than a + * specified length. + */ +public class ConsoleOutputLineTruncate implements IConsoleOutputModifier { + + private static final String DOTS = " ..."; //$NON-NLS-1$ + private final int lineLimit; + private final MultiStringMatcher newlineMatcher; + private final String nl; + private int currentLineLength = 0; + + public ConsoleOutputLineTruncate(int lineLimit, String... newlines) { + this.lineLimit = lineLimit; + newlineMatcher = MultiStringMatcher.create(newlines); + nl = newlines[0]; + } + + @Override + public void reset() { + currentLineLength = 0; + } + + @Override + public CharSequence modify(CharSequence t) { + StringBuilder text = new StringBuilder(t); + Match newlineMatch = newlineMatcher.indexOf(text, 0); + int currentNewline = matchIndex(newlineMatch); + int start = 0; + int end = currentNewline; + while (end >= 0) { + int diff = truncateLine(text, newlineMatch.getText(), start, end); + start = end + newlineMatch.getText().length() + diff; + newlineMatch = newlineMatcher.indexOf(text, start); + end = matchIndex(newlineMatch); + currentLineLength = 0; + } + int n = text.length(); + truncateLine(text, null, start, n); + return text; + } + + private static int matchIndex(Match match) { + return match != null ? match.getOffset() : -1; + } + + private int truncateLine(StringBuilder text, String newlineMatch, int s, int e) { + int previousLineLength = currentLineLength; + int n = e - s; + currentLineLength += n; + int d = 0; + if (previousLineLength > lineLimit) { + int e1 = e; + if (newlineMatch != null) { + e1 += newlineMatch.length(); + n += newlineMatch.length(); + } + text.replace(s, e1, ""); //$NON-NLS-1$ + d = -n; + } else if (currentLineLength > lineLimit) { + int s1 = s + lineLimit - previousLineLength; + if (s1 > 0 && Character.isLowSurrogate(text.charAt(s1))) { + --s1; + } + String dots = DOTS; + if (newlineMatch == null) { + dots += nl; + } + text.replace(s1, e, dots); + n = e - s1; + d = dots.length() - n; + } + return d; + } +} diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineWrap.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineWrap.java new file mode 100644 index 00000000000..bd39c718537 --- /dev/null +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleOutputLineWrap.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.console; + +import org.eclipse.jface.text.MultiStringMatcher; +import org.eclipse.jface.text.MultiStringMatcher.Match; + +/** + * Processes chunks of character sequences. Wraps lines longer than a specified + * length. + */ +public class ConsoleOutputLineWrap implements IConsoleOutputModifier { + + private final int lineLimit; + private final MultiStringMatcher newlineMatcher; + private final String nl; + private int currentLineLength = 0; + + public ConsoleOutputLineWrap(int lineLimit, String... newlines) { + this.lineLimit = lineLimit; + newlineMatcher = MultiStringMatcher.create(newlines); + nl = newlines[0]; + } + + @Override + public void reset() { + currentLineLength = 0; + } + + @Override + public CharSequence modify(CharSequence t) { + StringBuilder text = new StringBuilder(t); + Match newlineMatch = newlineMatcher.indexOf(text, 0); + int currentNewline = matchIndex(newlineMatch); + int start = 0; + int end = currentNewline; + while (end >= 0) { + int breaks = breakLine(text, start, end); + start = end + newlineMatch.getText().length() + breaks; + newlineMatch = newlineMatcher.indexOf(text, start); + end = matchIndex(newlineMatch); + currentLineLength = 0; + } + int n = text.length(); + breakLine(text, start, n); + return text; + } + + private static int matchIndex(Match match) { + return match != null ? match.getOffset() : -1; + } + + private int breakLine(StringBuilder text, int s, int e) { + int previousLineLength = currentLineLength; + currentLineLength += e - s; + int b = 0; + if (currentLineLength > lineLimit) { + int s1 = s + lineLimit - previousLineLength; + char c = text.charAt(s1); + if (s1 > 0 && (Character.isLowSurrogate(c))) { + --s1; + } + int e1 = e; + for (int i = s1; i < e1; i += lineLimit) { + text.insert(i, nl); + currentLineLength = e1 - i; + i += nl.length(); + e1 += nl.length(); + b += nl.length(); + } + } + return b; + } +} diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IConsoleOutputModifier.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IConsoleOutputModifier.java new file mode 100644 index 00000000000..48a5b4ba8b7 --- /dev/null +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IConsoleOutputModifier.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.console; + +/** + * Processes character sequences split into chunks. Modifies long lines in the + * input. Lines can span multiple chunks, chunks can contain multiple lines. + */ +public interface IConsoleOutputModifier { + + /** + * Resets state to handle a new set of chunks. + */ + void reset(); + + /** + * Processes a chunk, modifying long lines. + * + * @param chunk the current chunk of input + * @return the potentially modified chunk + */ + CharSequence modify(CharSequence chunk); +} diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java index dd12bf2c546..bc9f3c46da4 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java @@ -162,6 +162,21 @@ private enum DocUpdateType { * this many characters are remain in console. */ private int lowWaterMark = -1; + /** + * If {@link #lineLengthLimit} is non-negative, will either wrap or cut lines + * depending on this value. + */ + private volatile boolean lineLimitWrap = false; + /** + * Line limit length, lines longer than this are either wrapped or truncated. + * Negative to disable. + */ + private volatile int lineLengthLimit = -1; + /** + * Truncates or wraps console lines if {@link #lineLengthLimit} is positive, + * depending on {@link #lineLimitWrap}. + */ + private IConsoleOutputModifier lineModifier; /** The partitioned {@link IOConsole}. */ private final IOConsole console; @@ -219,6 +234,7 @@ public void connect(IDocument doc) { inputPartitions = new ArrayList<>(); document = doc; legalLineDelimiterMatcher = MultiStringMatcher.create(document.getLegalLineDelimiters()); + setLineModifier(); } } } @@ -269,6 +285,20 @@ public void setWaterMarks(int low, int high) { ConsolePlugin.getStandardDisplay().asyncExec(this::checkBufferSize); } + /** + * Set handling for long lines. Lines can be wrapped, truncated or left + * unchanged. + * + * @param wrap whether to wrap the line or truncate line limit + * @param length length at which to wrap or truncate lines, negative to disable + * @see IOConsole#setLimitLineLength(boolean, int) + */ + public void setLimitLineLength(boolean wrap, int length) { + lineLimitWrap = wrap; + lineLengthLimit = length; + setLineModifier(); + } + /** * Notification from the console that all of its streams have been closed. */ @@ -793,6 +823,17 @@ private void processPendingPartitions() { if (pendingCopy.isEmpty()) { return; } + + if (lineModifier != null) { + List modified = new ArrayList<>(pendingCopy.size()); + for (PendingPartition partition : pendingCopy) { + CharSequence t = lineModifier.modify(partition.text); + PendingPartition m = new PendingPartition(partition.stream, t); + modified.add(m); + } + pendingCopy = modified; + } + int pendingSize = 0; IOConsoleOutputStream stream = pendingCopy.get(0).stream; for (PendingPartition p : pendingCopy) { @@ -1508,4 +1549,14 @@ private void checkPartitions() { Assert.isTrue(knownInputPartitions.isEmpty()); } } + + private void setLineModifier() { + if (document != null && lineLengthLimit > 0) { + String[] lineDelimiters = document.getLegalLineDelimiters(); + lineModifier = lineLimitWrap ? new ConsoleOutputLineWrap(lineLengthLimit, lineDelimiters) + : new ConsoleOutputLineTruncate(lineLengthLimit, lineDelimiters); + } else { + lineModifier = null; + } + } }