diff options
6 files changed, 256 insertions, 75 deletions
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java index d659beea9..9e3efa615 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java @@ -19,6 +19,7 @@ import org.eclipse.debug.tests.breakpoint.BreakpointOrderingTests; import org.eclipse.debug.tests.console.ConsoleDocumentAdapterTests; import org.eclipse.debug.tests.console.ConsoleManagerTests; import org.eclipse.debug.tests.console.ConsoleTests; +import org.eclipse.debug.tests.console.IOConsoleFixedWidthTests; import org.eclipse.debug.tests.console.IOConsoleTests; import org.eclipse.debug.tests.console.ProcessConsoleManagerTests; import org.eclipse.debug.tests.console.ProcessConsoleTests; @@ -59,7 +60,7 @@ import junit.framework.TestSuite; public class AutomatedSuite extends TestSuite { /** - * Returns the suite. This is required to use the JUnit Launcher. + * Returns the suite. This is required to use the JUnit Launcher. * * @return the test suite */ @@ -117,6 +118,7 @@ public class AutomatedSuite extends TestSuite { addTest(new TestSuite(ConsoleManagerTests.class)); addTest(new TestSuite(ConsoleTests.class)); addTest(new TestSuite(IOConsoleTests.class)); + addTest(new TestSuite(IOConsoleFixedWidthTests.class)); addTest(new TestSuite(ProcessConsoleManagerTests.class)); addTest(new TestSuite(ProcessConsoleTests.class)); addTest(new TestSuite(StreamsProxyTests.class)); diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleDocumentAdapterTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleDocumentAdapterTests.java index 02205f1d6..ffcd759c8 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleDocumentAdapterTests.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleDocumentAdapterTests.java @@ -199,7 +199,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { // add text starting at fixed width border addText = "kl"; - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 0, 1)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, docAdapter.getCharCount(), addText, 0, addText.length(), 0, 1)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(docAdapter.getCharCount(), 0, addText); assertNumberOfLines(docAdapter, 5); @@ -213,7 +213,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { // insert text starting at fixed width border addText = ">"; offset = docAdapter.getCharCount() - 2; - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 1)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, addText, 0, addText.length(), 0, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, 0, addText); assertNumberOfLines(docAdapter, 5); @@ -225,7 +225,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { checkLineMapping(docAdapter, rand); // delete character at fixed width border - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 1)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, "", 1, 0, 0, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, 1, ""); assertNumberOfLines(docAdapter, 5); @@ -338,7 +338,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { docAdapter.replaceTextRange(offset, 0, addText); replaceText = "uvwxyz_-=|"; offset = docAdapter.getCharCount() - 3; - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 1)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, replaceText, 3, replaceText.length(), 0, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, 3, replaceText); assertNumberOfLines(docAdapter, 7); @@ -357,7 +357,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { // remove last wrapped line offset = docAdapter.getOffsetAtLine(docAdapter.getLineCount() - 1); - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 0)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, "", 10, 0, 1, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, 10, ""); assertNumberOfLines(docAdapter, 6); @@ -376,7 +376,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { // remove 5th line including it's real line delimiter offset = docAdapter.getOffsetAtLine(5 - 1); - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, null, null)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, "", 11, 0, 1, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, 11, ""); assertNumberOfLines(docAdapter, 5); @@ -637,7 +637,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { // remove at fixed width border offset = 10; remove = 1; - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 0)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, "", remove, 0, 1, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, remove, ""); assertNumberOfLines(docAdapter, 1); @@ -763,6 +763,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { assertLine(docAdapter, 3, "#B"); checkLineMapping(docAdapter, rand); + // replace last (real) line with new content addText = "*+"; offset = docAdapter.getCharCount() - 2; length = 1; @@ -774,6 +775,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { assertLine(docAdapter, 3, "*+B"); checkLineMapping(docAdapter, rand); + // replace last character (at fixed width border) with new content docAdapter.setText(""); docAdapter.replaceTextRange(docAdapter.getCharCount(), 0, "0123456789"); docAdapter.replaceTextRange(docAdapter.getCharCount(), 0, "ABCDEFGHIJ"); @@ -781,7 +783,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { addText = "$b"; offset = docAdapter.getCharCount() - 1; length = 1; - eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, null, null, null, null, 1, 1)); + eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGING, offset, addText, length, addText.length(), 0, 0)); eventListener.addExpectation(new TextEventExpectation(TextChangeEventType.CHANGED)); docAdapter.replaceTextRange(offset, length, addText); assertNumberOfLines(docAdapter, 3); @@ -1164,6 +1166,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { * and {@link #docAdapter} is set do some additional validations. */ TextChangingEvent lastEvent; + int eventLineBeforeChange = -1; int linesBeforeReplace = -1; int lengthBeforeReplace = -1; @@ -1180,31 +1183,30 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { @Override public void textChanging(TextChangingEvent event) { final TextEventExpectation expectation = checkCommon(TextChangeEventType.CHANGING); - if (expectation == null) { - return; - } - - if (expectation.start != null) { - assertEquals("event.start", (int) expectation.start, event.start); - } - if (expectation.newText != null) { - assertEquals("event.newText", expectation.newText, event.newText); - } - if (expectation.replaceCharCount != null) { - assertEquals("event.replaceCharCount", (int) expectation.replaceCharCount, event.replaceCharCount); - } - if (expectation.newCharCount != null) { - assertEquals("event.newCharCount", (int) expectation.newCharCount, event.newCharCount); - } - if (expectation.replaceLineCount != null) { - assertEquals("event.replaceLineCount", (int) expectation.replaceLineCount, event.replaceLineCount); - } - if (expectation.newLineCount != null) { - assertEquals("event.newLineCount", (int) expectation.newLineCount, event.newLineCount); + if (expectation != null) { + if (expectation.start != null) { + assertEquals("event.start", (int) expectation.start, event.start); + } + if (expectation.newText != null) { + assertEquals("event.newText", expectation.newText, event.newText); + } + if (expectation.replaceCharCount != null) { + assertEquals("event.replaceCharCount", (int) expectation.replaceCharCount, event.replaceCharCount); + } + if (expectation.newCharCount != null) { + assertEquals("event.newCharCount", (int) expectation.newCharCount, event.newCharCount); + } + if (expectation.replaceLineCount != null) { + assertEquals("event.replaceLineCount", (int) expectation.replaceLineCount, event.replaceLineCount); + } + if (expectation.newLineCount != null) { + assertEquals("event.newLineCount", (int) expectation.newLineCount, event.newLineCount); + } } if (!allowUnexpectedEvents && docAdapter != null) { lastEvent = event; + eventLineBeforeChange = docAdapter.getLineAtOffset(event.start); linesBeforeReplace = docAdapter.getLineCount(); lengthBeforeReplace = docAdapter.getCharCount(); @@ -1223,7 +1225,7 @@ public class ConsoleDocumentAdapterTests extends AbstractDebugTest { if (docAdapter != null && lastEvent != null) { final int lastEventOffset = lastEvent.start; final int lastEventLineIndex = docAdapter.getLineAtOffset(lastEventOffset); - assertEquals("Line of offset " + lastEventOffset + " has changed after text change.", lastEventLineIndex, docAdapter.getLineAtOffset(lastEventOffset)); + assertTrue("Line of event offset " + lastEventOffset + " has moved up.", eventLineBeforeChange <= lastEventLineIndex); // check if predicted changes are correct final int predictedDocLength = lengthBeforeReplace - lastEvent.replaceCharCount + lastEvent.newCharCount; diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleFixedWidthTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleFixedWidthTests.java new file mode 100644 index 000000000..dba2f11cc --- /dev/null +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleFixedWidthTests.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2019 Paul Pazderski 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: + * Paul Pazderski - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import org.eclipse.debug.tests.TestUtil; + +/** + * Same as {@link IOConsoleTests} but with fixed width console enabled. + */ +public class IOConsoleFixedWidthTests extends IOConsoleTests { + + public IOConsoleFixedWidthTests() { + super(IOConsoleFixedWidthTests.class.getSimpleName()); + } + + public IOConsoleFixedWidthTests(String name) { + super(name); + } + + @Override + protected IOConsoleTestUtil getTestUtil(String title) { + final IOConsoleTestUtil c = super.getTestUtil(title); + // Varying the width may reveal new bugs. There is no width value + // which is invalid. (but remember most test output is quite short) + // And try the most beautiful width of 1 aka the vertical console. + c.getConsole().setConsoleWidth(3); + c.setIgnoreFixedConsole(true); + // console width is applied asynchronous + TestUtil.waitForJobs(getName(), 50, 1000); + return c; + } + + // the actual tests are inherited from IOConsoleTests +} diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTestUtil.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTestUtil.java index ee08f8105..ce67942cc 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTestUtil.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTestUtil.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.tests.TestUtil; import org.eclipse.jface.text.BadLocationException; @@ -24,9 +25,11 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Event; import org.eclipse.ui.console.IConsoleDocumentPartitioner; import org.eclipse.ui.console.IOConsole; @@ -55,6 +58,24 @@ public final class IOConsoleTestUtil { * initialized. */ private IOConsoleOutputStream defaultOut = null; + /** + * This utility was initial written to be used with disabled fixed width + * console. If fixed width is enabled some methods like 'set caret to line + * end' may behave unexpected if a test case was written with non fixed + * width console in mind. + * <p> + * If this field is set to <code>true</code> all methods which may behave + * problematic in fixed width are executed in a way so that there result is + * the same as if the console has no fixed width enabled. E.g. 'set caret to + * line end' ignores virtual line wraps and jumps to the next real line + * delimiter. + * </p> + * <p> + * If this field is set to <code>true</code> any test written for non fixed + * width mode should succeed regardless of the fixed width setting. + * </p> + */ + private boolean ignoreFixedConsole = false; /** * Create a new testing helper. @@ -304,7 +325,34 @@ public final class IOConsoleTestUtil { */ public IOConsoleTestUtil backspace(int repeat) { for (int i = 0; i < repeat; i++) { - textPanel.invokeAction(ST.DELETE_PREVIOUS); + if (ignoreFixedConsole) { + // Note: this backspace simulation can not handle multibyte line + // delimiter. It would only delete one byte with one backspace. + final Event e = new Event(); + e.doit = true; + e.text = ""; + final IRegion selection = getSelection(); + if (selection.getLength() == 0) { + final int caretOffset = getCaretOffset(); + e.start = caretOffset - 1; + e.end = caretOffset; + if (caretOffset <= 0) { + e.doit = false; + } + } else { + e.start = selection.getOffset(); + e.end = selection.getOffset() + selection.getLength(); + } + if (e.doit) { + textPanel.notifyListeners(SWT.Verify, e); + } + if (e.doit) { + textPanel.replaceTextRange(e.start, e.end - e.start, e.text); + setCaretOffset(e.start); + } + } else { + textPanel.invokeAction(ST.DELETE_PREVIOUS); + } } return this; } @@ -393,7 +441,17 @@ public final class IOConsoleTestUtil { * @return this {@link IOConsoleTestUtil} to chain methods */ public IOConsoleTestUtil moveCaretToLineStart() { - textPanel.invokeAction(ST.LINE_START); + if (ignoreFixedConsole) { + try { + final int currentOffset = getCaretOffset(); + final int docLineStart = getDocument().getLineInformationOfOffset(currentOffset).getOffset(); + setCaretOffset(docLineStart); + } catch (BadLocationException e) { + TestUtil.log(IStatus.ERROR, name, "Failed to set caret to line start in wrapped line mode.", e); + } + } else { + textPanel.invokeAction(ST.LINE_START); + } return this; } @@ -403,11 +461,31 @@ public final class IOConsoleTestUtil { * @return this {@link IOConsoleTestUtil} to chain methods */ public IOConsoleTestUtil moveCaretToLineEnd() { - textPanel.invokeAction(ST.LINE_END); + if (ignoreFixedConsole) { + try { + final int currentOffset = getCaretOffset(); + final IRegion docLine = getDocument().getLineInformationOfOffset(currentOffset); + setCaretOffset(docLine.getOffset() + docLine.getLength()); + } catch (BadLocationException e) { + TestUtil.log(IStatus.ERROR, name, "Failed to set caret to line end in wrapped line mode.", e); + } + } else { + textPanel.invokeAction(ST.LINE_END); + } return this; } /** + * Get the selected text region in console. + * + * @return the selected region + */ + public IRegion getSelection() { + final Point selection = textPanel.getSelection(); + return new Region(selection.x, selection.y - selection.x); + } + + /** * Select text in console. * * @param offset selection start position. May be negative to select @@ -653,6 +731,30 @@ public final class IOConsoleTestUtil { } /** + * If <code>true</code> the util will work as if console is not in fixed + * width mode. E.g. {@link #moveCaretToLineStart()} will move caret to + * document line start not to widget line start. + * + * @see #ignoreFixedConsole + */ + public boolean isIgnoreFixedConsole() { + return ignoreFixedConsole; + } + + /** + * Enable compatibility mode. If set to <code>true</code> written for + * console without fixed width should work with any fixed width. Commands + * like {@link #moveCaretToLineStart()} are modified to not move to begin of + * widget line (maybe wrapped line) but to start it would have without fixed + * width. + * + * @see #ignoreFixedConsole + */ + public void setIgnoreFixedConsole(boolean ignoreWrappeding) { + this.ignoreFixedConsole = ignoreWrappeding; + } + + /** * Get identifier for output {@link IOConsolePartition}s. * * @return output partition identifier diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java index ce203b915..e244d2fbb 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java @@ -121,7 +121,7 @@ public class IOConsoleTests extends AbstractDebugTest { * @param title console title * @return util to help testing console functions */ - private IOConsoleTestUtil getTestUtil(String title) { + protected IOConsoleTestUtil getTestUtil(String title) { final IOConsole console = new IOConsole(title, "", null, StandardCharsets.UTF_8.name(), true); consoleFinished.set(false); console.addPropertyChangeListener((PropertyChangeEvent event) -> { @@ -150,7 +150,7 @@ public class IOConsoleTests extends AbstractDebugTest { * @param c the test util containing the console to close * @param expected content this {@link IOConsole} input stream has received */ - private void closeConsole(IOConsoleTestUtil c, String... expectedInputLines) throws IOException { + protected void closeConsole(IOConsoleTestUtil c, String... expectedInputLines) throws IOException { if (consoleFinished.get()) { // This should only happen if no output streams where used and the // user input stream was explicit closed before diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleDocumentAdapter.java b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleDocumentAdapter.java index 88bc3197c..89a545a95 100644 --- a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleDocumentAdapter.java +++ b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleDocumentAdapter.java @@ -135,6 +135,47 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen private int[] widgetLineOffsets = new int[1]; /** + * The fact that wrapped lines are new lines without a newline delimiter leads + * to some hard to handle edge cases since all involved interfaces (implicit) + * assume a newline has its unique offset. + * <p> + * Consider a fixed with of 10 and a console filled with (automatically wrapped) + * content of: + * </p> + * + * <pre> + * 0123456789 + * 0 + * </pre> + * + * <p> + * If we remove the last character ('0') we must set replaceLineCount to 1 since + * there is one line less due to the unwrapped line. The replaceLineCount is + * necessary so that StyledTextRenderer updates the lines which possible follow + * below and have changed due to moved content. + * </p> + * <p> + * But StyledTextRenderer presumes that the line index where the event occurs is + * the same before and after the text change. This is not the case for our auto + * wrapped lines since getLineAtOffset(10) is 1 before text change and 0 after + * </p> + * <p> + * To solve this unlucky situation we will lie to StyledText(Renderer) for the + * short time between text changing event and actual text change. If we know the + * same (start) offset will yield a different line index before and after change + * we will return for this offset the line index it will have after the change + * already before the change is applied. In this example if StyledText ask for + * getLineAtOffset(10) we return 0 already before the text is actual changed. + * </p> + * <p> + * This field store the offset for which we should preempt the new line index. + * It must be an offset at fixed width border (or negative for none) because + * only those have the potential for two different line indexes at same offset. + * </p> + */ + private int preemptLineWrapChange = -1; + + /** * New {@link ConsoleDocumentAdapter} with no {@link IDocument} connected yet. * * @param width fixed console width to enforce text wrap or <= 0 to disable @@ -264,6 +305,13 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen // is the index before the insertion index. widgetLine = (-widgetLine) - 2; } + if (offset == preemptLineWrapChange) { + // The requested offset is at fixed width border. In some text change situations + // we must return the line index it will have after the change even if the + // change is not applied yet. See #preemptLineWrapChange Javadoc for more + // details. + widgetLine--; + } return widgetLine; } } @@ -385,10 +433,10 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen * @throws BadLocationException if document event is invalid */ private TextChangingEvent generateTextChangingEvent(DocumentEvent event) throws BadLocationException { - String newText = event.getText() == null ? "" : event.getText(); //$NON-NLS-1$ - int newTextLength = newText.length(); - int eventOffset = event.getOffset(); - int eventLength = event.getLength(); + final String newText = event.getText() == null ? "" : event.getText(); //$NON-NLS-1$ + final int newTextLength = newText.length(); + final int eventOffset = event.getOffset(); + final int eventLength = event.getLength(); int replaceLineCount = 0; int newLineCount = 0; @@ -400,45 +448,14 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen // single character inserted at first offset can, from StyledText-Widgets // perspective, change the content of every line. - if (newTextLength >= 0 || eventLength > 0) { + if (newTextLength > 0 || eventLength > 0) { // In this method first and last refer to the first and last line affected by // the current document event - final int firstDocLineIndex = document.getLineOfOffset(eventOffset); - final int firstDocLineOffset = document.getLineOffset(firstDocLineIndex); - - if (eventOffset != firstDocLineOffset && (eventOffset - firstDocLineOffset) % fixedConsoleWidth == 0) { - // event start is at fixed width border - // XXX: the trick here is important to do the impossible - // The fact that wrapped lines are new lines without a newline delimiter leads - // to some (nearly) impossible edge cases since all involved interfaces - // (implicit) assume a newline has its unique offset. - // - // Consider a fixed with of 10 and a console filled with (automatically wrapped) - // content of: - // 0123456789 - // 0 - // If we remove the last character ('0') we must set replaceLineCount to 1 since - // there is one line less due to the unwrapped line. The replaceLineCount is - // necessary so that StyledTextRenderer updates the lines which possible follow - // below and have changed due to moved content. - // But StyledTextRenderer presumes that the line index where the event occurs is - // the same before and after the text change. This is not the case for our auto - // wrapped lines since getLineAtOffset(10) is 1 before text change and 0 after. - // - // To solve this unlucky situation we simply never send text change event - // occurring at the fixed width wrap border. If such an document change happens - // we expand the text change to include also the character before the wrap - // border. - eventOffset--; - eventLength++; - newText = document.get(eventOffset, 1) + newText; - newTextLength++; - } - final int eventEnd = eventOffset + eventLength; final int firstWidgetLineIndex = getLineOfOffset(eventOffset); final int firstWidgetLineOffset = getLineOffset(firstWidgetLineIndex); + final int firstInsertLength; final int lastInsertLength; int lastDocLineLengthDiff = -eventLength; @@ -446,6 +463,7 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen Match newLineMatch = docLegalLineDelimiterMatcher.indexOf(newText, newTextOffset); if (newLineMatch == null) { // single line insert + firstInsertLength = newTextLength; lastInsertLength = eventOffset - firstWidgetLineOffset + newTextLength; lastDocLineLengthDiff += newTextLength; } else { @@ -458,7 +476,7 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen // 3. Last line: everything (including) last line delimiter to end of inserted // text - final int firstInsertLength = newLineMatch.getOffset(); + firstInsertLength = newLineMatch.getOffset(); // newLineCount here is numbers of lines required if text is wrapped -1 because // we start inserting in an existing line and +1 for the first line delimiter we // had found @@ -512,6 +530,18 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen lastAffectedWidgetLineIndex--; } replaceLineCount = lastAffectedWidgetLineIndex - firstWidgetLineIndex; + + if (firstInsertLength == 0 && eventLength > 0 && affectedContentAfterRange == 0) { + final int firstDocLineOffset = document.getLineInformationOfOffset(eventOffset).getOffset(); + if (eventOffset != firstDocLineOffset && (eventOffset - firstDocLineOffset) % fixedConsoleWidth == 0) { + // Text change produce a tricky wrapped line change. Change start at fixed width + // border and is at start of wrapped line because there is wrapped content after + // event start. After change there is no more wrapped content after so the same + // event start offset is now one widget line above. + replaceLineCount++; + preemptLineWrapChange = eventOffset; + } + } } final TextChangingEvent changingEvent = new TextChangingEvent(this); @@ -550,7 +580,7 @@ public class ConsoleDocumentAdapter implements IDocumentAdapter, IDocumentListen @Override public synchronized void documentChanged(DocumentEvent event) { - + preemptLineWrapChange = -1; updateWidgetOffsets(event.getOffset()); TextChangedEvent changeEvent = new TextChangedEvent(this); |