diff options
| author | Paul Pazderski | 2019-03-17 10:59:41 +0000 |
|---|---|---|
| committer | Paul Pazderski | 2019-06-29 12:38:29 +0000 |
| commit | 1962d9672d5af1a8c4d749a819be3d9784bf9ee0 (patch) | |
| tree | 918aef20b14995aeb6d0a2afdc21c869a0472698 | |
| parent | 2bbc97e134a0eca8598bf25de1ccc8ebc6636b05 (diff) | |
| download | eclipse.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>
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()); + } } |
