Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Pazderski2019-03-17 10:59:41 +0000
committerPaul Pazderski2019-06-29 12:38:29 +0000
commit1962d9672d5af1a8c4d749a819be3d9784bf9ee0 (patch)
tree918aef20b14995aeb6d0a2afdc21c869a0472698
parent2bbc97e134a0eca8598bf25de1ccc8ebc6636b05 (diff)
downloadeclipse.platform.debug-1962d9672d5af1a8c4d749a819be3d9784bf9ee0.tar.gz
eclipse.platform.debug-1962d9672d5af1a8c4d749a819be3d9784bf9ee0.tar.xz
eclipse.platform.debug-1962d9672d5af1a8c4d749a819be3d9784bf9ee0.zip
Bug 548356 - [console] Fix input handling in IOConsoleI20190702-0930I20190702-0610I20190701-1805
Existing IOConsole and especially IOConsolePartitioner have various problems with user input handling e.g. if existing input surrounded by output is modified or a character composition is started inside an output partition. Change-Id: I3ea4f19553d6363e0d3cdc5280b577be57b45bd9 Signed-off-by: Paul Pazderski <paul-eclipse@ppazderski.de>
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTestUtil.java11
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/IOConsoleTests.java87
-rw-r--r--org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartition.java160
-rw-r--r--org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java341
4 files changed, 437 insertions, 162 deletions
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 b2e391b15..ee08f8105 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
@@ -345,6 +345,17 @@ public final class IOConsoleTestUtil {
}
/**
+ * Set caret to offset relative to start of current line.
+ *
+ * @param offset relative offset to line start
+ * @return this {@link IOConsoleTestUtil} to chain methods
+ */
+ public IOConsoleTestUtil setCaretLineRelative(int offset) {
+ moveCaretToLineStart().moveCaret(offset);
+ return this;
+ }
+
+ /**
* Move caret by given amount forth or back.
*
* @param amount steps to set caret forth (positive value) or back (negative
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 18971d36f..031f3c473 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
@@ -183,9 +183,21 @@ public class IOConsoleTests extends AbstractDebugTest {
*/
public void testConsoleClear() throws Exception {
final IOConsoleTestUtil c = getTestUtil("Test clear");
+
+ c.writeAndVerify("Hello World!");
+ c.getDocument().replace(0, c.getContentLength(), "");
+ c.waitForScheduledJobs();
+ c.verifyContent("").verifyPartitions();
+
c.writeAndVerify("New console content.");
c.clear();
- closeConsole(c);
+ assertEquals("Unexpected partition type.", IOConsoleTestUtil.inputPartitionType(), c.getPartitioner().getContentType(0));
+
+ c.insertAndVerify("wrong").write("out").verifyContent("wrongout").verifyPartitions(2);
+ c.clear().insertTypingAndVerify("i").write("ooo").verifyContent("iooo").verifyPartitions();
+ c.enter().clear();
+
+ closeConsole(c, "i");
}
/**
@@ -265,6 +277,53 @@ public class IOConsoleTests extends AbstractDebugTest {
c.enter().clear();
expectedInput.add("+more inputinput");
+ // inserted input is shorter than existing input partition
+ c.writeAndVerify("foo");
+ c.insertTyping("><--input-partition--").setCaretOffset(4);
+ c.writeAndVerify("bar");
+ c.insertTypingAndVerify("short");
+ c.verifyContent("foo>short<--input-partition--bar").verifyPartitions(3);
+ c.enter().clear();
+ expectedInput.add(">short<--input-partition--");
+
+ // inserted input is longer than existing input partition
+ c.writeAndVerify("Hello");
+ c.insertTyping("><").moveCaret(-1);
+ c.writeAndVerify("World");
+ c.insertTypingAndVerify("user input");
+ c.verifyContent("Hello>user input<World").verifyPartitions(3);
+ c.enter().clear();
+ expectedInput.add(">user input<");
+
+ // replace and remove input
+ c.writeAndVerify("oooo");
+ c.insertTyping("input");
+ c.writeAndVerify("output");
+ c.verifyContent("ooooinputoutput").verifyPartitions(3);
+ c.select(4, 5).insertAndVerify("iiii");
+ c.verifyContent("ooooiiiioutput").verifyPartitions(3);
+ c.select(4, 4).backspace();
+ c.verifyContent("oooooutput").verifyPartitions(2);
+ c.enter().clear();
+ expectedInput.add("");
+
+ // insert alternating into distinct input partitions
+ c.insertTypingAndVerify("ac").writeAndVerify("ooo");
+ c.setCaretOffset(1).insertTypingAndVerify("b");
+ c.moveCaretToEnd().insertTypingAndVerify("123");
+ c.verifyContent("abcooo123").verifyPartitions(3);
+ c.enter().clear();
+ expectedInput.add("abc123");
+
+ // insert alternating into distinct input partitions
+ c.insertTypingAndVerify("abc").writeAndVerify("ooo");
+ c.moveCaretToEnd().insertTypingAndVerify("13").writeAndVerify("OOO");
+ c.moveCaret(-1).insertTyping("2");
+ c.moveCaretToStart().insertTyping("ABC");
+ c.verifyContent("ABCabcooo123OOO").verifyPartitions(4);
+ c.enter().clear();
+ expectedInput.add("ABCabc123");
+
closeConsole(c, expectedInput.toArray(new String[0]));
}
@@ -330,4 +389,30 @@ public class IOConsoleTests extends AbstractDebugTest {
}
closeConsole(c);
}
+
+ /**
+ * Some extra tests for IOConsolePartitioner.
+ */
+ public void testIOPartitioner() throws Exception {
+ final IOConsoleTestUtil c = getTestUtil("Test partitioner");
+
+ c.writeAndVerify("output");
+ c.getDocument().replace(2, 1, ":::");
+ c.verifyPartitions();
+
+ c.clear().insertAndVerify("input").enter();
+ c.getDocument().replace(3, 0, "()");
+ c.verifyInputPartitions(0, c.getContentLength());
+
+ c.clear().writeAndVerify("><");
+ c.getDocument().replace(1, 0, "a\nb\r\nc");
+ c.verifyContent(">a\nb\r\nc<").verifyPartitions();
+
+ c.clear().writeAndVerify(")");
+ c.getDocument().replace(0, 0, "(");
+ c.verifyContent("()").verifyPartitions();
+
+ closeConsole(c);
+ assertEquals("Test triggered errors in IOConsole.", 0, loggedErrors.get());
+ }
}
diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartition.java b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartition.java
index 935b09283..3f43137d0 100644
--- a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartition.java
+++ b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartition.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2004, 2013 IBM Corporation and others.
+ * Copyright (c) 2004, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Paul Pazderski - Bug 548356: Removed error prone handling of input content from input partition.
*******************************************************************************/
package org.eclipse.ui.internal.console;
@@ -26,73 +27,83 @@ import org.eclipse.ui.console.IOConsoleOutputStream;
* @since 3.1
*/
public class IOConsolePartition implements ITypedRegion {
+ /** Type for input partitions. */
public static final String OUTPUT_PARTITION_TYPE = ConsolePlugin.getUniqueIdentifier() + ".io_console_output_partition_type"; //$NON-NLS-1$
+ /** Type for output partitions. */
public static final String INPUT_PARTITION_TYPE = ConsolePlugin.getUniqueIdentifier() + ".io_console_input_partition_type"; //$NON-NLS-1$
- /**
- * The data contained by this partition.
- */
- private StringBuffer buffer;
- private String type;
private int offset;
+ private int length;
+ private String type;
+
/**
- * Output partitions are all read only.
- * Input partitions are read only once they have been appended to the console's input stream.
+ * Output partitions are all read only. Input partitions are read only once they
+ * have been appended to the console's input stream.
*/
private boolean readOnly;
/**
- * Only one of inputStream or outputStream will be null depending on the partitions type.
+ * Only one of inputStream or outputStream will be <code>null</code> depending
+ * on the partitions type.
*/
private IOConsoleOutputStream outputStream;
private IOConsoleInputStream inputStream;
- private int length;
/**
- * Creates a new partition to contain output to console.
+ * Partition of console output.
+ *
+ * @param offset offset where this partition starts
+ * @param outputStream source stream for this partition
*/
- public IOConsolePartition(IOConsoleOutputStream outputStream, int length) {
+ public IOConsolePartition(int offset, IOConsoleOutputStream outputStream) {
this.outputStream = outputStream;
- this.length = length;
+ this.offset = offset;
this.type = OUTPUT_PARTITION_TYPE;
this.readOnly = true;
}
/**
- * Creates a new partition to contain input from a console
+ * Partition of console input.
+ *
+ * @param offset offset where this partition starts
+ * @param inputStream source stream for this partition
*/
- public IOConsolePartition(IOConsoleInputStream inputStream, String text) {
+ public IOConsolePartition(int offset, IOConsoleInputStream inputStream) {
this.inputStream = inputStream;
- buffer = new StringBuffer(text);
- length = text.length();
+ this.offset = offset;
this.type = INPUT_PARTITION_TYPE;
this.readOnly = false;
}
/**
- * Inserts a string into this partition.
- *
- * @param s The string to insert
- * @param insertOffset the offset in the partition
+ * Partition of console output.
+ *
+ * @param outputStream source stream for this partition
+ * @param length length of the partition
+ * @deprecated use {@link #IOConsolePartition(int, IOConsoleOutputStream)} and
+ * {@link #setLength(int)} instead
*/
- public void insert(String s, int insertOffset) {
- if (insertOffset < 0) {
- insertOffset = 0;
- } else if (insertOffset > buffer.length()) {
- insertOffset = buffer.length();
- }
- buffer.insert(insertOffset, s);
- length += s.length();
+ @Deprecated
+ public IOConsolePartition(IOConsoleOutputStream outputStream, int length) {
+ this(0, outputStream);
+ setLength(length);
}
/**
- * Deletes data from this partition.
- * @param delOffset
- * @param delLength
- */
- public void delete(int delOffset, int delLength) {
- buffer.delete(delOffset, delOffset+delLength);
- length -= delLength;
+ * Partition of console input.
+ *
+ * @param inputStream source stream for this partition
+ * @param text text of the input partition
+ * @deprecated use {@link #IOConsolePartition(int, IOConsoleInputStream)} and
+ * {@link #setLength(int)} instead. Also note: input partitions do
+ * not explicitly store the partitioned input anymore. Instead
+ * request the input from the partitioned document using input
+ * partition's offset and length.
+ */
+ @Deprecated
+ public IOConsolePartition(IOConsoleInputStream inputStream, String text) {
+ this(0, inputStream);
+ setLength(text == null ? 0 : text.length());
}
@Override
@@ -122,32 +133,28 @@ public class IOConsolePartition implements ITypedRegion {
/**
* Sets this partition's length.
*
- * @param length
+ * @param length new length of partition
*/
public void setLength(int length) {
this.length = length;
}
/**
- * Returns the data contained in this partition.
- * @return The data contained in this partition.
- */
- public String getString() {
- return buffer.toString();
- }
-
- /**
- * Returns a StyleRange object which may be used for setting the style
- * of this partition in a viewer.
+ * Returns a StyleRange object which may be used for setting the style of this
+ * partition in a viewer.
+ *
+ * @param rangeOffset offset for the style range
+ * @param rangeLength length of style range
+ * @return style range for this partition
*/
public StyleRange getStyleRange(int rangeOffset, int rangeLength) {
return new StyleRange(rangeOffset, rangeLength, getColor(), null, getFontStyle());
}
/**
- * Returns the font of the input stream if the type of the partition
- * is <code>INPUT_PARTITION_TYPE</code>, otherwise it returns the output
- * stream font
+ * Returns the font of the input stream if the type of the partition is
+ * <code>INPUT_PARTITION_TYPE</code>, otherwise it returns the output stream
+ * font
*
* @return the font of one of the backing streams
*/
@@ -159,9 +166,9 @@ public class IOConsolePartition implements ITypedRegion {
}
/**
- * Returns the colour of the input stream if the type of the partition
- * is <code>INPUT_PARTITION_TYPE</code>, otherwise it returns the output
- * stream colour
+ * Returns the colour of the input stream if the type of the partition is
+ * <code>INPUT_PARTITION_TYPE</code>, otherwise it returns the output stream
+ * colour
*
* @return the colour of one of the backing streams
*/
@@ -192,18 +199,55 @@ public class IOConsolePartition implements ITypedRegion {
}
/**
- * Clears the contents of the buffer
+ * Returns the underlying output stream.
+ * <p>
+ * Always <code>null</code> for input partitions.
+ * </p>
+ *
+ * @return the underlying output stream
+ * @deprecated use {@link #getOutputStream()} instead
*/
- public void clearBuffer() {
- buffer.setLength(0);
+ @Deprecated
+ IOConsoleOutputStream getStream() {
+ return outputStream;
}
/**
- * Returns the underlying output stream
+ * Returns the underlying output stream.
+ * <p>
+ * Always <code>null</code> for input partitions.
+ * </p>
*
* @return the underlying output stream
*/
- IOConsoleOutputStream getStream() {
+ IOConsoleOutputStream getOutputStream() {
return outputStream;
}
+
+ /**
+ * Returns the underlying input stream.
+ * <p>
+ * Always <code>null</code> for output partitions.
+ * </p>
+ *
+ * @return the underlying input stream
+ */
+ IOConsoleInputStream getInputStream() {
+ return inputStream;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(40);
+ sb.append(INPUT_PARTITION_TYPE.equals(type) ? "[Input" : "[Output"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (!readOnly) {
+ sb.append("+"); //$NON-NLS-1$
+ }
+ sb.append("]"); //$NON-NLS-1$
+ sb.append(" Offset: "); //$NON-NLS-1$
+ sb.append(offset);
+ sb.append(" Length: "); //$NON-NLS-1$
+ sb.append(length);
+ return sb.toString();
+ }
}
diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java
index f3248ffda..edb7af0b0 100644
--- a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java
+++ b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/IOConsolePartitioner.java
@@ -10,7 +10,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Paul Pazderski - Bug 547064: use binary search for getPartition
+ * Paul Pazderski - Contributions for:
+ * Bug 547064: use binary search for getPartition
+ * Bug 548356: fixed user input handling
*******************************************************************************/
package org.eclipse.ui.internal.console;
@@ -21,6 +23,7 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -32,6 +35,8 @@ import org.eclipse.jface.text.IDocumentPartitionerExtension;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.jface.text.TypedRegion;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.ConsolePlugin;
@@ -44,10 +49,15 @@ import org.eclipse.ui.progress.WorkbenchJob;
/**
* Partitions an IOConsole's document
- * @since 3.1
*
+ * @since 3.1
*/
public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocumentPartitionerExtension {
+ /**
+ * If true validate partitioning after changes and do other additional
+ * assertions. Useful for developing/debugging.
+ */
+ private static final boolean ASSERT = false;
/**
* Comparator to sort or search {@link IRegion}s by {@link IRegion#getOffset()}.
@@ -60,7 +70,7 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
/**
* List of all partitions. Must always be sorted ascending by
* {@link IRegion#getOffset()} and not contain <code>null</code> or 0-length
- * elements.
+ * elements. (see also {@link #checkPartitions()})
*/
private ArrayList<IOConsolePartition> partitions;
/** Blocks of data that have not yet been appended to the document. */
@@ -70,16 +80,12 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
*/
private ArrayList<PendingPartition> updatePartitions;
/**
- * The last partition appended to the document
- */
- private IOConsolePartition lastPartition;
- /**
* Job that appends pending partitions to the document.
*/
private QueueProcessingJob queueJob;
- /**
- * The input stream attached to this document.
- */
+ /** Job that trims console content if it exceeds {@link #highWaterMark}. */
+ private TrimJob trimJob = new TrimJob();
+ /** The input stream attached to this document. */
private IOConsoleInputStream inputStream;
/**
* Flag to indicate that the updateJob is updating the document.
@@ -103,7 +109,6 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
/** The partitioned {@link IOConsole}. */
private IOConsole console;
- private TrimJob trimJob = new TrimJob();
/**
* Lock for appending to and removing from the document - used
* to synchronize addition of new text/partitions in the update
@@ -237,19 +242,7 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
@Override
public ITypedRegion getPartition(int offset) {
final ITypedRegion partition = getIOPartition(offset);
- if (partition != null) {
- return partition;
- }
-
- if (lastPartition == null) {
- synchronized (partitions) {
- lastPartition = new IOConsolePartition(inputStream, ""); //$NON-NLS-1$
- lastPartition.setOffset(offset);
- partitions.add(lastPartition);
- inputPartitions.add(lastPartition);
- }
- }
- return lastPartition;
+ return partition != null ? partition : new TypedRegion(offset, 0, IOConsolePartition.INPUT_PARTITION_TYPE);
}
/**
@@ -333,33 +326,30 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
if (document == null) {
return null; //another thread disconnected the partitioner
}
- if (document.getLength() == 0) { //document cleared
- if (lastPartition != null && lastPartition.getType().equals(IOConsolePartition.INPUT_PARTITION_TYPE)) {
- synchronized (partitions) {
- partitions.remove(lastPartition);
- inputPartitions.remove(lastPartition);
- }
+ if (document.getLength() == 0) { // document cleared
+ synchronized (partitions) {
+ partitions.clear();
+ inputPartitions.clear();
}
- lastPartition = null;
return new Region(0, 0);
}
-
if (updateInProgress) {
synchronized(partitions) {
if (updatePartitions != null) {
+ IOConsolePartition lastPartition = getPartitionByIndex(partitions.size() - 1);
for (PendingPartition pp : updatePartitions) {
if (pp == consoleClosedPartition) {
continue;
}
int ppLen = pp.text.length();
- if (lastPartition != null && lastPartition.getStream() == pp.stream) {
+ if (lastPartition != null && lastPartition.getOutputStream() == pp.stream) {
int len = lastPartition.getLength();
lastPartition.setLength(len + ppLen);
} else {
- IOConsolePartition partition = new IOConsolePartition(pp.stream, ppLen);
- partition.setOffset(firstOffset);
+ IOConsolePartition partition = new IOConsolePartition(firstOffset, pp.stream);
+ partition.setLength(ppLen);
lastPartition = partition;
partitions.add(partition);
}
@@ -367,78 +357,167 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
}
}
}
- } else {// user input.
- int amountDeleted = event.getLength() ;
-
- if (amountDeleted > 0) {
- int offset = event.fOffset;
- IOConsolePartition partition = (IOConsolePartition) getPartition(offset);
- if (partition == lastPartition && IOConsolePartition.INPUT_PARTITION_TYPE.equals(partition.getType())) {
- partition.delete(event.fOffset-partition.getOffset(), amountDeleted);
- }
+ } else {
+ synchronized (partitions) {
+ return applyUserInput(event);
+ }
+ }
+ return new Region(event.fOffset, event.fText.length());
+ }
+
+ /**
+ * Update partitioning due to document change. All document change events not
+ * triggered by this partitioner are considered user input and therefore
+ * partitioned as input.
+ * <p>
+ * This method does not care if the document event removed or replaced parts of
+ * read-only partitions. It assumes manipulating read-only partitions is valid
+ * or is blocked before this method is used.
+ * </p>
+ *
+ * @param event the event describing the document change
+ * @return the region of the document in which the partition type changed or
+ * <code>null</code>
+ */
+ private IRegion applyUserInput(DocumentEvent event) {
+ final int eventTextLength = event.getText() != null ? event.getText().length() : 0;
+ final int offset = event.getOffset();
+ final int amountDeleted = event.getLength();
+
+ if (amountDeleted == 0 && eventTextLength == 0) {
+ // event did not changed document
+ return null;
+ }
+
+ final int eventPartitionIndex = findPartitionCandidate(offset);
+ int lastPartitionWithValidOffset = eventPartitionIndex;
+
+ if (amountDeleted > 0 && eventPartitionIndex >= 0) {
+ // adjust length of all partitions affected by replace/remove event
+ int toDelete = amountDeleted;
+ for (int i = eventPartitionIndex; i < partitions.size() && toDelete > 0; i++) {
+ final IOConsolePartition partition = partitions.get(i);
+ final int removeLength = Math.min(partition.getLength(), toDelete);
+ partition.setLength(partition.getLength() - removeLength);
+ toDelete -= removeLength;
+ }
+ if (ASSERT) {
+ Assert.isTrue(toDelete == 0, "Tried to delete outside partitioned range."); //$NON-NLS-1$
}
+ lastPartitionWithValidOffset--; // update one more since first affected partition may be empty now
+ }
- synchronized(partitions) {
- if (lastPartition == null || lastPartition.isReadOnly()) {
- lastPartition = new IOConsolePartition(inputStream, event.fText);
- lastPartition.setOffset(event.fOffset);
- partitions.add(lastPartition);
- inputPartitions.add(lastPartition);
- } else {
- lastPartition.insert(event.fText, (event.fOffset-lastPartition.getOffset()));
+ if (eventTextLength > 0) {
+ // find best partition for event text
+ int inputPartitionIndex = eventPartitionIndex;
+ IOConsolePartition inputPartition = getPartitionByIndex(inputPartitionIndex);
+ if (inputPartition != null && inputPartition.isReadOnly() && offset == inputPartition.getOffset()) {
+ // if we could not reuse partition at event offset we may append the partition
+ // right before our event offset (e.g. if input is at end of document)
+ inputPartitionIndex--;
+ lastPartitionWithValidOffset--;
+ inputPartition = getPartitionByIndex(inputPartitionIndex);
+ }
+ if (inputPartition == null || inputPartition.isReadOnly()) {
+ if (inputPartition != null && offset < inputPartition.getOffset() + inputPartition.getLength()) {
+ // input is inside an existing read-only partition
+ splitPartition(offset);
}
+ inputPartition = new IOConsolePartition(offset, inputStream);
+ inputPartitionIndex++;
+ partitions.add(inputPartitionIndex, inputPartition);
+ inputPartitions.add(inputPartition);
+ lastPartitionWithValidOffset++; // new input partitions get build with correct offsets
+ }
+ inputPartition.setLength(inputPartition.getLength() + eventTextLength);
+ }
- int lastLineDelimiter = -1;
- String partitionText = lastPartition.getString();
- for (int i = 0; i < lld.length; i++) {
- String ld = lld[i];
- int index = partitionText.lastIndexOf(ld);
- if (index != -1) {
- index += ld.length();
- }
- if (index > lastLineDelimiter) {
- lastLineDelimiter = index;
- }
+ // repair partition offsets
+ int newOffset = 0;
+ if (lastPartitionWithValidOffset >= 0) {
+ // reduce number of partition to update by skipping still valid entries
+ final IOConsolePartition partition = partitions.get(lastPartitionWithValidOffset);
+ newOffset = partition.getOffset() + partition.getLength();
+ }
+ final Iterator<IOConsolePartition> it = partitions.listIterator(lastPartitionWithValidOffset + 1);
+ while (it.hasNext()) {
+ final IOConsolePartition partition = it.next();
+ if (partition.getLength() <= 0) {
+ if (ASSERT) {
+ Assert.isTrue(partition.getLength() == 0);
}
- if (lastLineDelimiter != -1) {
- StringBuilder input = new StringBuilder();
- Iterator<IOConsolePartition> it = inputPartitions.iterator();
- while (it.hasNext()) {
- IOConsolePartition partition = it.next();
- if (partition.getOffset() + partition.getLength() <= event.fOffset + lastLineDelimiter) {
- if (partition == lastPartition) {
- lastPartition = null;
- }
- input.append(partition.getString());
- partition.clearBuffer();
- partition.setReadOnly();
- it.remove();
- } else {
- //create a new partition containing everything up to the line delimiter
- //and append that to the string buffer.
- String contentBefore = partitionText.substring(0, lastLineDelimiter);
- IOConsolePartition newPartition = new IOConsolePartition(inputStream, contentBefore);
- newPartition.setOffset(partition.getOffset());
- newPartition.setReadOnly();
- newPartition.clearBuffer();
- int index = partitions.indexOf(partition);
- partitions.add(index, newPartition);
- input.append(contentBefore);
- //delete everything that has been appended to the buffer.
- partition.delete(0, lastLineDelimiter);
- partition.setOffset(lastLineDelimiter + partition.getOffset());
- lastLineDelimiter = 0;
- }
- }
- if (input.length() > 0) {
- inputStream.appendData(input.toString());
+ it.remove();
+ if (isInputPartition(partition)) {
+ final boolean removed = inputPartitions.remove(partition);
+ if (ASSERT) {
+ Assert.isTrue(removed);
}
+ }
+ } else {
+ partition.setOffset(newOffset);
+ newOffset += partition.getLength();
+ }
+ }
+ // send pending input if event contains line delimiter
+ final int[] result = TextUtilities.indexOf(lld, event.getText(), 0);
+ if (result[1] >= 0) {
+ inputPartitions.sort(CMP_REGION_BY_OFFSET);
+ final StringBuilder inputLine = new StringBuilder();
+ for (IOConsolePartition p : inputPartitions) {
+ try {
+ final String fragment = document.get(p.getOffset(), p.getLength());
+ inputLine.append(fragment);
+ } catch (BadLocationException e) {
+ log(e);
}
+ p.setReadOnly();
}
+ inputPartitions.clear();
+ if (ASSERT) {
+ Assert.isTrue(inputLine.length() > 0);
+ }
+ inputStream.appendData(inputLine.toString());
}
- return new Region(event.fOffset, event.fText.length());
+ if (ASSERT) {
+ checkPartitions();
+ }
+ return new Region(0, document.getLength());
+ }
+
+ /**
+ * Split an existing partition at offset. The offset must not be the first or
+ * last offset of the existing partition because this leads to empty partitions
+ * not bearable by this partitioner.
+ * <p>
+ * New partition is added to {@link #partitions} (always) and
+ * {@link #inputPartitions} (if applicable).
+ * </p>
+ *
+ * @param offset the offset where the existing partition will end after split
+ * and a new partition will start
+ * @return the newly created partition (i.e. the right side of the split)
+ */
+ private IOConsolePartition splitPartition(int offset) {
+ final int partitionIndex = findPartitionCandidate(offset);
+ final IOConsolePartition existingPartition = partitions.get(partitionIndex);
+ final IOConsolePartition newPartition;
+ if (isInputPartition(existingPartition)) {
+ newPartition = new IOConsolePartition(offset, existingPartition.getInputStream());
+ if (existingPartition.isReadOnly()) {
+ newPartition.setReadOnly();
+ }
+ if (inputPartitions.contains(existingPartition)) {
+ inputPartitions.add(newPartition);
+ }
+ } else {
+ newPartition = new IOConsolePartition(offset, existingPartition.getOutputStream());
+ }
+ newPartition.setLength((existingPartition.getOffset() + existingPartition.getLength()) - offset);
+ existingPartition.setLength(offset - existingPartition.getOffset());
+ partitions.add(partitionIndex + 1, newPartition);
+ return newPartition;
}
private void setUpdateInProgress(boolean b) {
@@ -446,12 +525,14 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
}
/**
- * A stream has been appended, add to pendingPartions list and schedule updateJob.
- * updateJob is scheduled with a slight delay, this allows the console to run the job
- * less frequently and update the document with a greater amount of data each time
- * the job is run
+ * A stream has been appended, add to pendingPartions list and schedule
+ * updateJob. updateJob is scheduled with a slight delay, this allows the
+ * console to run the job less frequently and update the document with a greater
+ * amount of data each time the job is run
+ *
* @param stream The stream that was written to.
- * @param s The string that should be appended to the document.
+ * @param s The string that should be appended to the document.
+ * @throws IOException if partitioner is not connected to a document
*/
public void streamAppended(IOConsoleOutputStream stream, String s) throws IOException {
if (document == null) {
@@ -520,6 +601,9 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
processQueue();
+ if (ASSERT) {
+ checkPartitions();
+ }
return Status.OK_STATUS;
}
@@ -627,7 +711,6 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
setUpdateInProgress(true);
document.set(""); //$NON-NLS-1$
setUpdateInProgress(false);
- partitions.clear();
} else {
// overflow
int cutoffLine = document.getLineOfOffset(truncateOffset);
@@ -654,6 +737,9 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
offset += p.getLength();
}
}
+ if (ASSERT) {
+ checkPartitions();
+ }
} catch (BadLocationException e) {
}
}
@@ -664,7 +750,8 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
@Override
public boolean isReadOnly(int offset) {
- return ((IOConsolePartition)getPartition(offset)).isReadOnly();
+ final IOConsolePartition partition = getIOPartition(offset);
+ return partition != null ? partition.isReadOnly() : false;
}
@Override
@@ -681,4 +768,52 @@ public class IOConsolePartitioner implements IConsoleDocumentPartitioner, IDocum
}
return styles;
}
+
+ /**
+ * Get a partition by its index. Safe from out of bounds exceptions.
+ *
+ * @param index index of requested partition
+ * @return the requested partition or <code>null</code> if index is invalid
+ */
+ private IOConsolePartition getPartitionByIndex(int index) {
+ return (index >= 0 && index < partitions.size()) ? partitions.get(index) : null;
+ }
+
+ /**
+ * Check if given partition is from type input partition.
+ *
+ * @param partition partition to check (not <code>null</code>)
+ * @return true if partition is an input partition
+ */
+ private static boolean isInputPartition(IOConsolePartition partition) {
+ return IOConsolePartition.INPUT_PARTITION_TYPE.equals(partition.getType());
+ }
+
+ private static void log(Throwable t) {
+ ConsolePlugin.log(t);
+ }
+
+ /**
+ * For debug purpose. Check if whole document is partitioned, partitions are
+ * ordered by offset, every partition has length greater 0 and all writable
+ * input partitions are listed in {@link #inputPartitions}.
+ */
+ private void checkPartitions() {
+ if (!connected) {
+ return;
+ }
+ final List<IOConsolePartition> knownInputPartitions = new ArrayList<>(inputPartitions);
+ int offset = 0;
+ for (IOConsolePartition partition : partitions) {
+ Assert.isTrue(offset == partition.getOffset());
+ Assert.isTrue(partition.getLength() > 0);
+ offset += partition.getLength();
+
+ if (isInputPartition(partition) && !partition.isReadOnly()) {
+ Assert.isTrue(knownInputPartitions.remove(partition));
+ }
+ }
+ Assert.isTrue(offset == document.getLength());
+ Assert.isTrue(knownInputPartitions.isEmpty());
+ }
}

Back to the top