diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java index 9385c8a2e81..33b9c6468d9 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java @@ -398,7 +398,7 @@ outer: return; } try { - if ( marker != null && outputStream instanceof IErrorMarkeredOutputStream ) { + if ( outputStream instanceof IErrorMarkeredOutputStream ) { IErrorMarkeredOutputStream s = (IErrorMarkeredOutputStream) outputStream; s.write(l, marker); } else { diff --git a/core/org.eclipse.cdt.ui.tests/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.ui.tests/META-INF/MANIFEST.MF index 032741b8d06..623e29aa080 100644 --- a/core/org.eclipse.cdt.ui.tests/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.ui.tests/META-INF/MANIFEST.MF @@ -38,7 +38,9 @@ Require-Bundle: org.eclipse.jface.text, org.eclipse.ltk.core.refactoring;bundle-version="3.4.0", org.eclipse.core.filesystem;bundle-version="1.2.0", org.eclipse.ltk.ui.refactoring, - org.eclipse.osgi + org.eclipse.osgi, + org.mockito, + org.hamcrest Bundle-ActivationPolicy: lazy Bundle-Vendor: Eclipse CDT Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/buildconsole/BuildConsolePartitionerEditDataTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/buildconsole/BuildConsolePartitionerEditDataTest.java new file mode 100644 index 00000000000..7b0ce1e13da --- /dev/null +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/buildconsole/BuildConsolePartitionerEditDataTest.java @@ -0,0 +1,136 @@ +package org.eclipse.cdt.ui.tests.buildconsole; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import org.eclipse.cdt.core.ProblemMarkerInfo; + +import org.eclipse.cdt.internal.ui.buildconsole.BuildConsolePartition; +import org.eclipse.cdt.internal.ui.buildconsole.BuildConsolePartitionerEditData; +import org.eclipse.cdt.internal.ui.buildconsole.BuildConsolePartitionerEditData.UpdateUIData; +import org.eclipse.cdt.internal.ui.buildconsole.IBuildConsoleStreamDecorator; + +public class BuildConsolePartitionerEditDataTest { + + private static final int DEFAULT_MAX_LINES = 100; + private BuildConsolePartitionerEditData data; + private IBuildConsoleStreamDecorator stream1 = mock(IBuildConsoleStreamDecorator.class); + private IBuildConsoleStreamDecorator stream2 = mock(IBuildConsoleStreamDecorator.class); + private ProblemMarkerInfo marker1 = new ProblemMarkerInfo(null, 0, null, 0, null); + private ProblemMarkerInfo marker2 = new ProblemMarkerInfo(null, 0, null, 0, null); + + @Before + public void before() { + data = new BuildConsolePartitionerEditData(DEFAULT_MAX_LINES); + } + + @Test + public void testBasicOperation() { + data.clear(); + UpdateUIData update0 = data.getUpdate(); + assertThat(update0.getNewContents(), is("")); + assertThat(update0.getStreamsNeedingNotifcation(), is(empty())); + assertThat(update0.needsClearDocumentMarkerManager(), is(true)); + + data.append("Line of text\n", stream1, null); + UpdateUIData update1 = data.getUpdate(); + assertThat(update1.getNewContents(), is("Line of text\n")); + assertThat(update1.getStreamsNeedingNotifcation(), is(Arrays.asList(stream1))); + assertThat(update1.needsClearDocumentMarkerManager(), is(false)); + + data.append("Another line of text\n", stream2, null); + UpdateUIData update2 = data.getUpdate(); + assertThat(update2.getNewContents(), is("Line of text\nAnother line of text\n")); + assertThat(update2.getStreamsNeedingNotifcation(), is(Arrays.asList(stream2))); + assertThat(update2.needsClearDocumentMarkerManager(), is(false)); + } + + @Test + public void testOverflow() { + for (int i = 0; i < DEFAULT_MAX_LINES * 4; i++) { + data.append("Line " + i + "\n", stream1, null); + } + + UpdateUIData update = data.getUpdate(); + assertThat(update.needsClearDocumentMarkerManager(), is(true)); + assertThat(update.getNewPartitions().size(), is(lessThanOrEqualTo(2))); + + String contents = update.getNewContents(); + int newlines = (int) contents.chars().filter(ch -> ch == '\n').count(); + assertThat(newlines, is(lessThan(DEFAULT_MAX_LINES * 2))); + assertThat(newlines, is(greaterThanOrEqualTo(DEFAULT_MAX_LINES))); + + int lastLine = DEFAULT_MAX_LINES * 4 - 1; + assertThat(contents, endsWith("Line " + lastLine + "\n")); + int firstLine = lastLine - newlines + 1; + assertThat(contents, startsWith("Line " + firstLine + "\n")); + } + + @Test + public void testPartitionsCombine() { + data.append("Line\n", stream1, null); + data.append("Line\n", stream1, null); + UpdateUIData update = data.getUpdate(); + + assertThat(update.getNewPartitions(), is(Arrays.asList( + new BuildConsolePartition(stream1, 0, 10, BuildConsolePartition.CONSOLE_PARTITION_TYPE, null, 1)))); + } + + @Test + public void testPartitionsDontCombineOnDifferentStreams() { + data.append("Line\n", stream1, null); + data.append("Line\n", stream2, null); + UpdateUIData update = data.getUpdate(); + + assertThat(update.getNewPartitions(), is(Arrays.asList( + new BuildConsolePartition(stream1, 0, 5, BuildConsolePartition.CONSOLE_PARTITION_TYPE, null, 1), + new BuildConsolePartition(stream2, 5, 5, BuildConsolePartition.CONSOLE_PARTITION_TYPE, null, 1)))); + } + + @Test + public void testPartitionsDontCombineOnDifferentMarkersA() { + data.append("Line\n", stream1, marker1); + data.append("Line\n", stream1, marker2); + UpdateUIData update = data.getUpdate(); + + assertThat(update.getNewPartitions(), is(Arrays.asList( + new BuildConsolePartition(stream1, 0, 5, BuildConsolePartition.INFO_PARTITION_TYPE, marker1, 1), + new BuildConsolePartition(stream1, 5, 5, BuildConsolePartition.INFO_PARTITION_TYPE, marker2, 1)))); + } + + @Test + public void testPartitionsDontCombineOnDifferentMarkersB() { + data.append("Line\n", stream1, null); + data.append("Line\n", stream1, marker2); + UpdateUIData update = data.getUpdate(); + + assertThat(update.getNewPartitions(), is(Arrays.asList( + new BuildConsolePartition(stream1, 0, 5, BuildConsolePartition.CONSOLE_PARTITION_TYPE, null, 1), + new BuildConsolePartition(stream1, 5, 5, BuildConsolePartition.INFO_PARTITION_TYPE, marker2, 1)))); + } + + @Test + public void testPartitionsDontCombineOnDifferentMarkersC() { + data.append("Line\n", stream1, marker1); + data.append("Line\n", stream1, null); + UpdateUIData update = data.getUpdate(); + + assertThat(update.getNewPartitions(), + is(Arrays.asList( + new BuildConsolePartition(stream1, 0, 5, BuildConsolePartition.INFO_PARTITION_TYPE, marker1, 1), + new BuildConsolePartition(stream1, 5, 5, BuildConsolePartition.CONSOLE_PARTITION_TYPE, null, 1)))); + } + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/CPluginResources.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/CPluginResources.properties index c6ecfc88cd9..8e69e557a00 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/CPluginResources.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/CPluginResources.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2005, 2013 IBM Corporation and others. +# Copyright (c) 2005, 2017 IBM Corporation and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at @@ -22,6 +22,7 @@ ConsolePreferencePage.autoOpenConsole.label=Open console when building ConsolePreferencePage.consoleOnTop.label=Bring console to top when building (if present) ConsolePreferencePage.consoleWrapLines.label=Wrap lines on the console ConsolePreferencePage.consoleLines.label=Limit console output (number of lines): +ConsolePreferencePage.consoleLines.tooltip=This is a fuzzy limit, optimized for best performance. The actual limit will be between this value and 2 times this value. ConsolePreferencePage.consoleLines.errorMessage=Value must be an integer between 10 and 2147483647 ConsolePreferencePage.tabWidth.label=Display tab width: ConsolePreferencePage.tabWidth.errorMessage=Value must be an integer between 1 and 100 diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleManager.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleManager.java index 0545767cb90..fe99ece5efc 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleManager.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleManager.java @@ -373,7 +373,7 @@ public class BuildConsoleManager implements IBuildConsoleManager, IResourceChang }); } - public BuildConsoleStreamDecorator getStreamDecorator(int type) throws CoreException { + public IBuildConsoleStreamDecorator getStreamDecorator(int type) throws CoreException { switch (type) { case BUILD_STREAM_TYPE_ERROR : return errorStream; diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePage.java index 936780cf1f4..f31e5422ad8 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePage.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2002, 2015 QNX Software Systems and others. + * Copyright (c) 2002, 2017 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -280,8 +280,8 @@ public class BuildConsolePage extends Page final Object source = event.getSource(); final String property = event.getProperty(); - if (BuildConsole.P_STREAM_COLOR.equals(property) && source instanceof BuildConsoleStreamDecorator) { - BuildConsoleStreamDecorator stream = (BuildConsoleStreamDecorator)source; + if (BuildConsole.P_STREAM_COLOR.equals(property) && source instanceof IBuildConsoleStreamDecorator) { + IBuildConsoleStreamDecorator stream = (IBuildConsoleStreamDecorator)source; if (stream.getConsole().equals(getConsole()) && getControl() != null) { Display display = getControl().getDisplay(); display.asyncExec(new Runnable() { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartition.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartition.java index 399d7fc5b51..f460d167ad3 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartition.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartition.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2002, 2010 QNX Software Systems and others. + * Copyright (c) 2002, 2017 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -8,6 +8,7 @@ * Contributors: * QNX Software Systems - Initial API and implementation * Dmitry Kozlov (CodeSourcery) - Build error highlighting and navigation + * Jonah Graham (Kichwa Coders) - Significant rewrite, changed model (Bug 314428) *******************************************************************************/ package org.eclipse.cdt.internal.ui.buildconsole; @@ -18,11 +19,14 @@ import org.eclipse.jface.text.TypedRegion; public class BuildConsolePartition extends TypedRegion { /** Associated stream */ - private BuildConsoleStreamDecorator fStream; + private IBuildConsoleStreamDecorator fStream; /** Marker associated with this partition if any */ private ProblemMarkerInfo fMarker; + /** Number of newlines in this region */ + private int fNewlines; + /** Partition type */ public static final String CONSOLE_PARTITION_TYPE = CUIPlugin.getPluginId() + ".CONSOLE_PARTITION_TYPE"; //$NON-NLS-1$ @@ -31,16 +35,12 @@ public class BuildConsolePartition extends TypedRegion { public static final String INFO_PARTITION_TYPE = CUIPlugin.getPluginId() + ".INFO_PARTITION_TYPE"; //$NON-NLS-1$ public static final String WARNING_PARTITION_TYPE = CUIPlugin.getPluginId() + ".WARNING_PARTITION_TYPE"; //$NON-NLS-1$ - public BuildConsolePartition(BuildConsoleStreamDecorator stream, int offset, int length, String type) { - super(offset, length, type); - fStream = stream; - } - - public BuildConsolePartition(BuildConsoleStreamDecorator stream, int offset, int length, String type, - ProblemMarkerInfo marker) { + public BuildConsolePartition(IBuildConsoleStreamDecorator stream, int offset, int length, String type, + ProblemMarkerInfo marker, int newlines) { super(offset, length, type); fStream = stream; fMarker = marker; + fNewlines = newlines; } /** @@ -67,66 +67,23 @@ public class BuildConsolePartition extends TypedRegion { * * @return this partition's stream */ - public BuildConsoleStreamDecorator getStream() { + public IBuildConsoleStreamDecorator getStream() { return fStream; } - /** - * Returns whether this partition is allowed to be combined with the given - * partition. - * - * @param partition - * @return boolean - */ - public boolean canBeCombinedWith(BuildConsolePartition partition) { - // Error partitions never can be combined together - String type = getType(); - if (isProblemPartitionType(type)) { - return false; - } - - int start = getOffset(); - int end = start + getLength(); - int otherStart = partition.getOffset(); - int otherEnd = otherStart + partition.getLength(); - boolean overlap = (otherStart >= start && otherStart <= end) - || (start >= otherStart && start <= otherEnd); - return getStream() != null && overlap && type.equals(partition.getType()) - && getStream().equals(partition.getStream()); - } - - /** - * Returns a new partition representing this and the given parition - * combined. - * - * @param partition - * @return partition - */ - public BuildConsolePartition combineWith(BuildConsolePartition partition) { - int start = getOffset(); - int end = start + getLength(); - int otherStart = partition.getOffset(); - int otherEnd = otherStart + partition.getLength(); - int theStart = Math.min(start, otherStart); - int theEnd = Math.max(end, otherEnd); - return createNewPartition(theStart, theEnd - theStart, CONSOLE_PARTITION_TYPE); - } - - /** - * Creates a new partition of this type with the given offset, and length. - * - * @param offset - * @param length - * @return a new partition with the given range - */ - public BuildConsolePartition createNewPartition(int offset, int length, String type) { - return new BuildConsolePartition(getStream(), offset, length, type, getMarker()); - } - public ProblemMarkerInfo getMarker() { return fMarker; } + /** + * Return number of newlines represented in this partition. + * + * @return number of newlines + */ + public int getNewlines() { + return fNewlines; + } + public static boolean isProblemPartitionType(String type) { return type == BuildConsolePartition.ERROR_PARTITION_TYPE || type == BuildConsolePartition.WARNING_PARTITION_TYPE diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitioner.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitioner.java index 150052c243b..16aab00f3ad 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitioner.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitioner.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2002, 2016 QNX Software Systems and others. + * Copyright (c) 2002, 2017 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,25 +11,21 @@ * Andrew Gvozdev (Quoin Inc.) - Copy build log (bug 306222) * Alex Collins (Broadcom Corp.) - Global console * Sergey Prigogin (Google) - Performance improvements + * Jonah Graham (Kichwa Coders) - Significant rewrite, changed model (Bug 314428) *******************************************************************************/ package org.eclipse.cdt.internal.ui.buildconsole; import java.io.IOException; import java.io.OutputStream; import java.net.URI; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; -import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; -import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; @@ -48,308 +44,64 @@ import org.eclipse.cdt.core.resources.IConsole; import org.eclipse.cdt.core.resources.ResourcesUtil; import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.internal.ui.buildconsole.BuildConsolePartitionerEditData.UpdateUIData; import org.eclipse.cdt.internal.ui.preferences.BuildConsolePreferencePage; +/* + * XXX the wrap lines is way too slow to be usable on large {@link #fMaxLines} + * (not sure limit, 500 seems ok, 10000 is a problem) Best idea may be to do the + * wrapping "fixed" within the + * {@link #appendToDocument(String, BuildConsoleStreamDecorator, ProblemMarkerInfo)} + */ public class BuildConsolePartitioner implements IDocumentPartitioner, IDocumentPartitionerExtension, IConsole, IPropertyChangeListener { - private static class SynchronizedDeque implements Deque { - private final Deque deque; - - public SynchronizedDeque(Deque deque) { - this.deque = deque; - } - - @Override - public synchronized boolean isEmpty() { - return deque.isEmpty(); - } - - @Override - public synchronized void addFirst(E e) { - deque.addFirst(e); - } - - @Override - public synchronized void addLast(E e) { - deque.addLast(e); - } - - @Override - public synchronized Object[] toArray() { - return deque.toArray(); - } - - @Override - public synchronized T[] toArray(T[] a) { - return deque.toArray(a); - } - - @Override - public synchronized boolean offerFirst(E e) { - return deque.offerFirst(e); - } - - @Override - public synchronized boolean offerLast(E e) { - return deque.offerLast(e); - } - - @Override - public synchronized E removeFirst() { - return deque.removeFirst(); - } - - @Override - public synchronized E removeLast() { - return deque.removeLast(); - } - - @Override - public synchronized E pollFirst() { - return deque.pollFirst(); - } - - @Override - public synchronized E pollLast() { - return deque.pollLast(); - } - - @Override - public synchronized E getFirst() { - return deque.getFirst(); - } - - @Override - public synchronized E getLast() { - return deque.getLast(); - } - - @Override - public synchronized E peekFirst() { - return deque.peekFirst(); - } - - @Override - public synchronized E peekLast() { - return deque.peekLast(); - } - - @Override - public synchronized boolean removeFirstOccurrence(Object o) { - return deque.removeFirstOccurrence(o); - } - - @Override - public synchronized boolean containsAll(Collection c) { - return deque.containsAll(c); - } - - @Override - public synchronized boolean removeLastOccurrence(Object o) { - return deque.removeLastOccurrence(o); - } - - @Override - public synchronized boolean addAll(Collection c) { - return deque.addAll(c); - } - - @Override - public synchronized boolean add(E e) { - return deque.add(e); - } - - @Override - public synchronized boolean removeAll(Collection c) { - return deque.removeAll(c); - } - - @Override - public synchronized boolean offer(E e) { - return deque.offer(e); - } - - @Override - public synchronized boolean retainAll(Collection c) { - return deque.retainAll(c); - } - - @Override - public synchronized E remove() { - return deque.remove(); - } - - @Override - public synchronized E poll() { - return deque.poll(); - } - - @Override - public synchronized E element() { - return deque.element(); - } - - @Override - public synchronized void clear() { - deque.clear(); - } - - @Override - public synchronized boolean equals(Object o) { - return deque.equals(o); - } - - @Override - public synchronized E peek() { - return deque.peek(); - } - - @Override - public synchronized void push(E e) { - deque.push(e); - } - - @Override - public synchronized E pop() { - return deque.pop(); - } - - @Override - public synchronized int hashCode() { - return deque.hashCode(); - } - - @Override - public synchronized boolean remove(Object o) { - return deque.remove(o); - } - - @Override - public synchronized boolean contains(Object o) { - return deque.contains(o); - } - - @Override - public synchronized int size() { - return deque.size(); - } - - @Override - public synchronized Iterator iterator() { - return deque.iterator(); - } - - @Override - public synchronized Iterator descendingIterator() { - return deque.descendingIterator(); - } - } - private IProject fProject; - private int openStreamCount = 0; - /** - * List of partitions + * Active list of partitions, must only be accessed form UI thread which + * provides implicit lock */ - List fPartitions = new ArrayList(5); - - private int fMaxLines; - + List fPartitions = new ArrayList(); /** - * The stream that was last appended to + * Active document, must only be accessed form UI thread which provides + * implicit lock */ - BuildConsoleStreamDecorator fLastStream; - BuildConsoleDocument fDocument; + + /** + * Provides core implementation of partitioner. + */ + BuildConsolePartitionerEditData fEditData; + + /** + * Set to true if there is an asyncExec for the UI update already scheduled. + */ + private AtomicBoolean fEditUiPending = new AtomicBoolean(false); + DocumentMarkerManager fDocumentMarkerManager; - boolean killed; BuildConsoleManager fManager; /** - * A queue of stream entries written to standard out and standard err. - * Entries appended to the end of the queue and removed from the front. + * Encapsulation of variables for log files that must be accessed + * synchronized on fLogFile. The key part we want to synchronize is the + * writes to fLogStream so that different sources (stderr/stdout) don't get + * unnecessarily intermixed. */ - private final Deque fQueue = new SynchronizedDeque( - new ArrayDeque()); - - private URI fLogURI; - private OutputStream fLogStream; - - private class StreamEntry { - public static final int EVENT_APPEND = 0; - public static final int EVENT_OPEN_LOG = 1; - public static final int EVENT_CLOSE_LOG = 2; - public static final int EVENT_OPEN_APPEND_LOG = 3; - - /** Identifier of the stream written to. */ - private BuildConsoleStreamDecorator fStream; - /** The text written */ - private StringBuilder fText = null; - /** Problem marker corresponding to the line of text */ - private ProblemMarkerInfo fMarker; - /** Type of event **/ - private int eventType; - - public StreamEntry(String text, BuildConsoleStreamDecorator stream, ProblemMarkerInfo marker) { - fText = new StringBuilder(text); - fStream = stream; - fMarker = marker; - eventType = EVENT_APPEND; - } - + private static class LogFile { + private OutputStream fLogStream; + private int openStreamCount = 0; /** - * This constructor is used for special events such as clear console or - * close log. - * - * @param event - * - kind of event. + * This value can be obtained independently without a lock. */ - public StreamEntry(int event) { - fText = null; - fStream = null; - fMarker = null; - eventType = event; - } - - /** - * Returns the stream identifier - */ - public BuildConsoleStreamDecorator getStream() { - return fStream; - } - - public void appendText(String text) { - fText.append(text); - } - - public int size() { - return fText.length(); - } - - /** - * Returns the text written - */ - public String getText() { - return fText.toString(); - } - - /** - * Returns error marker - */ - public ProblemMarkerInfo getMarker() { - return fMarker; - } - - /** - * Returns type of event - */ - public int getEventType() { - return eventType; - } + private URI fLogURI; } + /** + * All operations on the log files need to be synchronized on this object + */ + private LogFile fLogFile = new LogFile(); + /** * Construct a partitioner that is not associated with a specific project */ @@ -360,14 +112,11 @@ public class BuildConsolePartitioner public BuildConsolePartitioner(IProject project, BuildConsoleManager manager) { fProject = project; fManager = manager; - fMaxLines = BuildConsolePreferencePage.buildConsoleLines(); + fEditData = new BuildConsolePartitionerEditData(BuildConsolePreferencePage.buildConsoleLines()); fDocument = new BuildConsoleDocument(); fDocument.setDocumentPartitioner(this); fDocumentMarkerManager = new DocumentMarkerManager(fDocument, this); connect(fDocument); - - fLogURI = null; - fLogStream = null; } /** @@ -375,8 +124,10 @@ public class BuildConsolePartitioner * Should be called when opening the output stream. */ public void setStreamOpened() { - fQueue.add(new StreamEntry(StreamEntry.EVENT_OPEN_LOG)); - asyncProcessQueue(); + synchronized (fLogFile) { + fLogFile.openStreamCount++; + logOpen(false); + } } /** @@ -385,8 +136,7 @@ public class BuildConsolePartitioner * has been closed, without emptying the log file. */ public void setStreamAppend() { - fQueue.add(new StreamEntry(StreamEntry.EVENT_OPEN_APPEND_LOG)); - asyncProcessQueue(); + logOpen(true); } /** @@ -396,191 +146,168 @@ public class BuildConsolePartitioner * in the background. */ public void setStreamClosed() { - fQueue.add(new StreamEntry(StreamEntry.EVENT_CLOSE_LOG)); - asyncProcessQueue(); + synchronized (fLogFile) { + fLogFile.openStreamCount--; + if (fLogFile.openStreamCount <= 0) { + fLogFile.openStreamCount = 0; + if (fLogFile.fLogStream != null) { + try { + fLogFile.fLogStream.close(); + } catch (IOException e) { + CUIPlugin.log(e); + } finally { + ResourcesUtil.refreshWorkspaceFiles(fLogFile.fLogURI); + } + fLogFile.fLogStream = null; + } + } + } + } + + /** + * Open the log + * + * @param append + * Set to true if the log should be opened for appending, false + * for overwriting. + */ + private void logOpen(boolean append) { + synchronized (fLogFile) { + fLogFile.fLogURI = fManager.getLogURI(fProject); + if (fLogFile.fLogURI != null) { + try { + IFileStore logStore = EFS.getStore(fLogFile.fLogURI); + // Ensure the directory exists before opening the file + IFileStore dir = logStore.getParent(); + if (dir != null) + dir.mkdir(EFS.NONE, null); + int opts = append ? EFS.APPEND : EFS.NONE; + fLogFile.fLogStream = logStore.openOutputStream(opts, null); + } catch (CoreException e) { + CUIPlugin.log(e); + } finally { + ResourcesUtil.refreshWorkspaceFiles(fLogFile.fLogURI); + } + } + } + } + + private void log(String text) { + synchronized (fLogFile) { + if (fLogFile.fLogStream != null) { + try { + fLogFile.fLogStream.write(text.getBytes()); + fLogFile.fLogStream.flush(); + } catch (IOException e) { + CUIPlugin.log(e); + } + } + } + } + + /** + * Update the UI after a short delay. The reason for a short delay is to try + * and reduce the "frame rate" of the build console updates, this reduces + * the total load on the main thread. User's won't be able to tell that + * there is an extra delay. + * + * A too short time has little effect and a too long time starts to be + * visible to the user. With my experiments to get under 50% CPU utilization + * on the main thread requires at least 35 msec delay between updates. 250 + * msec leads to visible delay to user and ~20% utilization. And finally the + * chosen value, 75 msec leads to ~35% utilization and no user visible + * delay. + */ + private static final int UDPATE_DELAY_MS = 75; + + /** + * @see #UDPATE_DELAY_MS + */ + private void scheduleUpdate() { + Display display = CUIPlugin.getStandardDisplay(); + if (display != null) { + display.timerExec(UDPATE_DELAY_MS, this::updateUI); + } + } + + private void updateUI() { + fEditUiPending.set(false); + UpdateUIData update = fEditData.getUpdate(); + + /* + * We refresh the log file here although not technically a UI operation. + * We used to refresh the file on every log write, but that is very + * expensive and this call has to search the whole workspace to map the + * URI to the corresponding IFile. (At the time everything was done in + * the UI thread, not just the refresh, so this is an improvement.) + * + * XXX: Consider caching the IFile. + * + * XXX: Consider doing the refresh asynchronously in another thread. + * Keep in mind that the log file can easily be written at rate 10x + * faster than Eclipse's refresh mechanism can detect, which is 1ms. + */ + ResourcesUtil.refreshWorkspaceFiles(fLogFile.fLogURI); + + // notify all streams with data we are about to update + update.getStreamsNeedingNotifcation().forEach(this::warnOfContentChange); + + /* + * The order of these statements matters, the setting the contents of + * the document causes listeners to eventually come back and get the + * partitions, so the new partitions have to be in place first + */ + fPartitions = update.getNewPartitions(); + + if (update.needsClearDocumentMarkerManager()) { + fDocumentMarkerManager.clear(); + } + + /* + * This call is slow, it updates the UI as a side effect. + * + * XXX: Doing a set on the whole document means that all the line + * numbers need to be recalculated. This can be optimized further by + * keeping track of what needs to be edited. However, for now this + * optimization has not been done because although this leads to + * increased CPU usage, it does not lead to a delay in total processing + * time, but rather to a decrease in frame rate. Furthermore, if the + * document overflows, the document's line numbers need to be + * recalculated anyway, so little benefit. + */ + fDocument.set(update.getNewContents()); } /** * Adds the new text to the document. * * @param text - * - the text to append. + * the text to append, cannot be null. * @param stream - * - the stream to append to. + * the stream to append to, null means to clear + * everything. + * @param marker + * the marker associated with this line of console output, can be + * null */ - public void appendToDocument(String text, BuildConsoleStreamDecorator stream, ProblemMarkerInfo marker) { - boolean addToQueue = true; - synchronized (fQueue) { - StreamEntry entry = fQueue.peekLast(); - if (entry != null) { - // If last stream is the same and the size of the queued entry - // has not exceeded - // the batch size, avoid creating a new entry and append the new - // text to the last - // entry in the queue. The batch size is adaptive and grows with - // the length of - // the queue. - if (entry.getStream() == stream && entry.getEventType() == StreamEntry.EVENT_APPEND - && entry.getMarker() == marker && entry.size() < 2000 * fQueue.size()) { - entry.appendText(text); - addToQueue = false; - } - } - if (addToQueue) { - fQueue.add(new StreamEntry(text, stream, marker)); - } + public void appendToDocument(String text, IBuildConsoleStreamDecorator stream, ProblemMarkerInfo marker) { + // Log the output to file ASAP, no need to fEditData lock + log(text); + if (stream == null) { + fEditData.clear(); + } else { + fEditData.append(text, stream, marker); } - if (addToQueue) { - asyncProcessQueue(); - } - } - - /** - * Asynchronous processing of stream entries to append to console. Note that - * all these are processed by the same thread - the user-interface thread as - * of {@link Display#asyncExec(Runnable)}. - */ - private void asyncProcessQueue() { - Runnable r = new Runnable() { - @Override - public void run() { - StreamEntry entry; - entry = fQueue.pollFirst(); - if (entry == null) - return; - - switch (entry.getEventType()) { - case StreamEntry.EVENT_OPEN_LOG: - openStreamCount++; - //$FALL-THROUGH$ - case StreamEntry.EVENT_OPEN_APPEND_LOG: - logOpen(entry.getEventType() == StreamEntry.EVENT_OPEN_APPEND_LOG); - break; - case StreamEntry.EVENT_APPEND: - fLastStream = entry.getStream(); - try { - warnOfContentChange(fLastStream); - - if (fLastStream == null) { - // special case to empty document - fPartitions.clear(); - fDocumentMarkerManager.clear(); - fDocument.set(""); //$NON-NLS-1$ - } - String text = entry.getText(); - if (text.length() > 0) { - addStreamEntryToDocument(entry); - log(text); - boolean allowSlack = false; - entry = fQueue.peekFirst(); - if (entry != null && entry.getEventType() == StreamEntry.EVENT_APPEND) { - // Buffer truncation is an expensive operation. - // Allow some slack - // if more data is coming and we will be - // truncating the buffer - // again soon. - allowSlack = true; - } - checkOverflow(allowSlack); - } - } catch (BadLocationException e) { - } - break; - case StreamEntry.EVENT_CLOSE_LOG: - openStreamCount--; - if (openStreamCount <= 0) { - openStreamCount = 0; - logClose(); - } - break; - } - } - - /** - * Open the log - * - * @param append - * Set to true if the log should be opened for appending, - * false for overwriting. - */ - private void logOpen(boolean append) { - fLogURI = fManager.getLogURI(fProject); - if (fLogURI != null) { - try { - IFileStore logStore = EFS.getStore(fLogURI); - // Ensure the directory exists before opening the file - IFileStore dir = logStore.getParent(); - if (dir != null) - dir.mkdir(EFS.NONE, null); - int opts = append ? EFS.APPEND : EFS.NONE; - fLogStream = logStore.openOutputStream(opts, null); - } catch (CoreException e) { - CUIPlugin.log(e); - } finally { - ResourcesUtil.refreshWorkspaceFiles(fLogURI); - } - } - } - - private void log(String text) { - if (fLogStream != null) { - try { - fLogStream.write(text.getBytes()); - if (fQueue.isEmpty()) { - fLogStream.flush(); - } - } catch (IOException e) { - CUIPlugin.log(e); - } finally { - ResourcesUtil.refreshWorkspaceFiles(fLogURI); - } - } - } - - private void logClose() { - if (fLogStream != null) { - try { - fLogStream.close(); - } catch (IOException e) { - CUIPlugin.log(e); - } finally { - ResourcesUtil.refreshWorkspaceFiles(fLogURI); - } - fLogStream = null; - } - } - - }; Display display = CUIPlugin.getStandardDisplay(); if (display != null) { - display.asyncExec(r); - } - } - - private void addStreamEntryToDocument(StreamEntry entry) throws BadLocationException { - ProblemMarkerInfo marker = entry.getMarker(); - if (marker == null) { - // It is plain unmarkered console output - addPartition(new BuildConsolePartition(fLastStream, fDocument.getLength(), - entry.getText().length(), BuildConsolePartition.CONSOLE_PARTITION_TYPE)); - } else { - // this text line in entry is markered with ProblemMarkerInfo, - // create special partition for it. - String errorPartitionType; - if (marker.severity == IMarker.SEVERITY_INFO) { - errorPartitionType = BuildConsolePartition.INFO_PARTITION_TYPE; - } else if (marker.severity == IMarker.SEVERITY_WARNING) { - errorPartitionType = BuildConsolePartition.WARNING_PARTITION_TYPE; - } else { - errorPartitionType = BuildConsolePartition.ERROR_PARTITION_TYPE; + if (!fEditUiPending.getAndSet(true)) { + display.asyncExec(this::scheduleUpdate); } - addPartition(new BuildConsolePartition(fLastStream, fDocument.getLength(), - entry.getText().length(), errorPartitionType, marker)); } - fDocument.replace(fDocument.getLength(), 0, entry.getText()); + } - void warnOfContentChange(BuildConsoleStreamDecorator stream) { + void warnOfContentChange(IBuildConsoleStreamDecorator stream) { if (stream != null) { ConsolePlugin.getDefault().getConsoleManager().warnOfContentChange(stream.getConsole()); } @@ -592,8 +319,7 @@ public class BuildConsolePartitioner } public void setDocumentSize(int nLines) { - fMaxLines = nLines; - checkOverflow(false); + fEditData.setMaxLines(nLines); } @Override @@ -605,7 +331,6 @@ public class BuildConsolePartitioner public void disconnect() { fDocument.setDocumentPartitioner(null); CUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this); - killed = true; } @Override @@ -680,8 +405,9 @@ public class BuildConsolePartitioner public IRegion documentChanged2(DocumentEvent event) { String text = event.getText(); if (getDocument().getLength() == 0) { - // cleared fPartitions.clear(); + fDocumentMarkerManager.clear(); + fEditData.clear(); return new Region(0, 0); } ITypedRegion[] affectedRegions = computePartitioning(event.getOffset(), text.length()); @@ -700,85 +426,6 @@ public class BuildConsolePartitioner return new Region(affectedRegions[0].getOffset(), affectedLength); } - /** - * Checks to see if the console buffer has overflowed, and empties the - * overflow if needed, updating partitions and hyperlink positions. - */ - protected void checkOverflow(boolean allowSlack) { - if (fMaxLines <= 0) - return; - int nLines = fDocument.getNumberOfLines(); - if (nLines <= (allowSlack ? fMaxLines * 2 : fMaxLines) + 1) - return; - - int overflow = 0; - try { - overflow = fDocument.getLineOffset(nLines - fMaxLines); - } catch (BadLocationException e) { - } - // Update partitions - List newParitions = new ArrayList(fPartitions.size()); - Iterator partitions = fPartitions.iterator(); - while (partitions.hasNext()) { - ITypedRegion region = partitions.next(); - if (region instanceof BuildConsolePartition) { - BuildConsolePartition messageConsolePartition = (BuildConsolePartition) region; - - ITypedRegion newPartition = null; - int offset = region.getOffset(); - String type = messageConsolePartition.getType(); - if (offset < overflow) { - int endOffset = offset + region.getLength(); - if (endOffset < overflow || BuildConsolePartition.isProblemPartitionType(type)) { - // Remove partition, - // partitions with problem markers can't be split - - // remove them too. - } else { - // Split partition - int length = endOffset - overflow; - newPartition = messageConsolePartition.createNewPartition(0, length, type); - } - } else { - // Modify partition offset - offset = messageConsolePartition.getOffset() - overflow; - newPartition = messageConsolePartition.createNewPartition(offset, - messageConsolePartition.getLength(), type); - } - if (newPartition != null) { - newParitions.add(newPartition); - } - } - } - fPartitions = newParitions; - fDocumentMarkerManager.moveToFirstError(); - - try { - fDocument.replace(0, overflow, ""); //$NON-NLS-1$ - } catch (BadLocationException e) { - } - } - - /** - * Adds a new partition, combining with the previous partition if possible. - */ - private BuildConsolePartition addPartition(BuildConsolePartition partition) { - if (fPartitions.isEmpty()) { - fPartitions.add(partition); - } else { - int index = fPartitions.size() - 1; - BuildConsolePartition last = (BuildConsolePartition) fPartitions.get(index); - if (last.canBeCombinedWith(partition)) { - // replace with a single partition - partition = last.combineWith(partition); - fPartitions.set(index, partition); - } else { - // different kinds - add a new parition - fPartitions.add(partition); - } - } - return partition; - } - public IConsole getConsole() { return this; } @@ -792,13 +439,16 @@ public class BuildConsolePartitioner @Override public void start(final IProject project) { + synchronized (fLogFile) { + fLogFile.fLogStream = null; + fLogFile.fLogURI = null; + } + Display display = CUIPlugin.getStandardDisplay(); if (display != null) { display.asyncExec(new Runnable() { @Override public void run() { - fLogStream = null; - fLogURI = null; fManager.startConsoleActivity(project); } }); @@ -827,43 +477,11 @@ public class BuildConsolePartitioner fManager.getStreamDecorator(BuildConsoleManager.BUILD_STREAM_TYPE_ERROR)); } - /** This method is useful for future debugging and bug-fixing */ - @SuppressWarnings({ "unused", "nls" }) - private void printDocumentPartitioning() { - System.out.println("Document partitioning: "); - for (ITypedRegion tr : fPartitions) { - BuildConsolePartition p = (BuildConsolePartition) tr; - int start = p.getOffset(); - int end = p.getOffset() + p.getLength(); - String text; - String isError = "U"; - String type = p.getType(); - if (type == BuildConsolePartition.ERROR_PARTITION_TYPE) { - isError = "E"; - } else if (type == BuildConsolePartition.WARNING_PARTITION_TYPE) { - isError = "W"; - } else if (type == BuildConsolePartition.INFO_PARTITION_TYPE) { - isError = "I"; - } else if (type == BuildConsolePartition.CONSOLE_PARTITION_TYPE) { - isError = "C"; - } - try { - text = fDocument.get(p.getOffset(), p.getLength()); - } catch (BadLocationException e) { - text = "N/A"; - } - if (text.endsWith("\n")) { - text = text.substring(0, text.length() - 1); - } - System.out.println(" " + isError + " " + start + "-" + end + ":[" + text + "]"); - } - } - /** * @return {@link URI} location of log file. */ public URI getLogURI() { - return fLogURI; + return fLogFile.fLogURI; } IProject getProject() { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitionerEditData.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitionerEditData.java new file mode 100644 index 00000000000..d3af3f96e0b --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsolePartitionerEditData.java @@ -0,0 +1,327 @@ +/******************************************************************************* + * Copyright (c) 2016, 2017 Kichwa Coders and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Jonah Graham (Kichwa Coders) - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.buildconsole; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.text.ITypedRegion; + +import org.eclipse.cdt.core.ProblemMarkerInfo; + +public class BuildConsolePartitionerEditData { + + /** + * Return data for an update. + */ + public interface UpdateUIData { + /** + * The contents have been changed in such a way that an associated + * DocumentMarkerManager needs to clear its state. This normally happens + * when the document overflows (and older lines are discarded) or when + * document is cleared. + */ + boolean needsClearDocumentMarkerManager(); + + /** + * New contents for the build console. + */ + String getNewContents(); + + /** + * New partitions that match the new contents. + */ + List getNewPartitions(); + + /** + * All the streams that have been written to since the last update. + */ + List getStreamsNeedingNotifcation(); + } + + /** + * The maximum number of lines the document is allowed to have. This is a + * soft limit. 0 or less for unlimited. + */ + private int fMaxLines; + + /** + * Editable partitions, all modifications are made to this copy of the + * partitions, then the UI thread occasionally gets these updates + */ + private List fEditPartitions = new ArrayList(); + + /** + * Set to true when an edit causes the document marker manager to need to be + * cleared out on next UI update. + */ + private boolean fClearDocumentMarkerManager = false; + + /** + * Editable document, all modifications are made to this copy of the + * document, then the UI thread occasionally gets these updates + */ + private StringBuilder fEditStringBuilder = new StringBuilder(); + + /** + * Total number of lines in document + */ + private int fEditLineCount = 0; + + /** + * Set of streams that have been updated since the last UI update. + */ + private Set fEditStreams = new HashSet<>(); + + public BuildConsolePartitionerEditData(int maxLines) { + fMaxLines = maxLines; + } + + public int getMaxLines() { + return fMaxLines; + } + + public void setMaxLines(int fMaxLines) { + this.fMaxLines = fMaxLines; + } + + /** + * Clear the entire document. + */ + public void clear() { + synchronized (this) { + fEditPartitions.clear(); + fClearDocumentMarkerManager = true; + fEditStringBuilder.setLength(0); + fEditLineCount = 0; + } + } + + /** + * + * Adds the new text to the document. + * + * @param text + * the text to append, cannot be null. + * @param stream + * the stream to append to, cannot be null. + * @param marker + * the marker associated with this line of console output, can be + * null + * + */ + public void append(String text, IBuildConsoleStreamDecorator stream, ProblemMarkerInfo marker) { + int newlines = (int) text.chars().filter(ch -> ch == '\n').count(); + synchronized (this) { + fEditStreams.add(stream); + + if (text.length() > 0) { + String partitionType; + if (marker == null) { + partitionType = BuildConsolePartition.CONSOLE_PARTITION_TYPE; + } else if (marker.severity == IMarker.SEVERITY_INFO) { + partitionType = BuildConsolePartition.INFO_PARTITION_TYPE; + } else if (marker.severity == IMarker.SEVERITY_WARNING) { + partitionType = BuildConsolePartition.WARNING_PARTITION_TYPE; + } else { + partitionType = BuildConsolePartition.ERROR_PARTITION_TYPE; + } + if (fEditPartitions.isEmpty()) { + fEditPartitions.add(new BuildConsolePartition(stream, fEditStringBuilder.length(), + text.length(), partitionType, marker, newlines)); + } else { + int index = fEditPartitions.size() - 1; + BuildConsolePartition last = fEditPartitions.get(index); + /* + * Don't permit partitions with markers to be combined + */ + boolean canBeCombined = marker == null && last.getMarker() == null; + /* + * Don't permit a single partition to exceed the maximum + * number of lines of the whole document, this significantly + * simplifies the logic of checkOverflow. + */ + canBeCombined = canBeCombined + && (fMaxLines <= 0 || (last.getNewlines() + newlines < fMaxLines)); + /* + * Don't permit different partition types to be combined + */ + canBeCombined = canBeCombined && Objects.equals(last.getType(), partitionType); + /* + * Don't permit different streams to be combined + */ + canBeCombined = canBeCombined && Objects.equals(last.getStream(), stream); + + if (canBeCombined) { + // replace with a single partition + int combinedOffset = last.getOffset(); + int combinedLength = last.getLength() + text.length(); + int combinedNewlines = last.getNewlines() + newlines; + BuildConsolePartition partition2 = new BuildConsolePartition(last.getStream(), + combinedOffset, combinedLength, BuildConsolePartition.CONSOLE_PARTITION_TYPE, + null, combinedNewlines); + fEditPartitions.set(index, partition2); + } else { + // different kinds - add a new parition + fEditPartitions.add(new BuildConsolePartition(stream, fEditStringBuilder.length(), + text.length(), partitionType, marker, newlines)); + } + } + fEditStringBuilder.append(text); + fEditLineCount += newlines; + + checkOverflow(); + } + } + } + + /** + * Checks to see if the console buffer has overflowed, and empties the + * overflow if needed, updating partitions and hyperlink positions. + */ + public void checkOverflow() { + if (fMaxLines <= 0) { + return; + } + + synchronized (this) { + + /* + * We actually limit the number of lines to 2 x max lines, bringing + * it back to max lines when it overflows. This prevents + * recalculating on every update + */ + if (fEditLineCount <= fMaxLines * 2) + return; + + // Update partitions + + int newHeadIndex = fEditPartitions.size(); + int newNewlineCount = 0; + while (newHeadIndex > 0 && newNewlineCount < fMaxLines) { + newHeadIndex--; + BuildConsolePartition part = fEditPartitions.get(newHeadIndex); + newNewlineCount += part.getNewlines(); + } + + if (newHeadIndex == 0) { + // Nothing to do + return; + } + + int newPartCount = fEditPartitions.size() - newHeadIndex; + int offsetToOffset = fEditPartitions.get(newHeadIndex).getOffset(); + List newParitions = new ArrayList<>(newPartCount); + Iterator partitions = fEditPartitions.listIterator(newHeadIndex); + while (partitions.hasNext()) { + BuildConsolePartition partition = partitions.next(); + + BuildConsolePartition newPartition = new BuildConsolePartition(partition.getStream(), + partition.getOffset() - offsetToOffset, partition.getLength(), partition.getType(), + partition.getMarker(), partition.getNewlines()); + + newParitions.add(newPartition); + } + + fEditPartitions = newParitions; + fClearDocumentMarkerManager = true; + + fEditStringBuilder.delete(0, offsetToOffset); + fEditLineCount = newNewlineCount; + + } + } + + /** + * This method is useful for future debugging and bug-fixing + */ + @SuppressWarnings("nls") + public void printDocumentPartitioning() { + // Non synchronized access, used only for debugging + System.out.println("Document partitioning: "); + for (ITypedRegion tr : fEditPartitions) { + BuildConsolePartition p = (BuildConsolePartition) tr; + int start = p.getOffset(); + int end = p.getOffset() + p.getLength(); + String text; + String isError = "U"; + String type = p.getType(); + if (type == BuildConsolePartition.ERROR_PARTITION_TYPE) { + isError = "E"; + } else if (type == BuildConsolePartition.WARNING_PARTITION_TYPE) { + isError = "W"; + } else if (type == BuildConsolePartition.INFO_PARTITION_TYPE) { + isError = "I"; + } else if (type == BuildConsolePartition.CONSOLE_PARTITION_TYPE) { + isError = "C"; + } + text = fEditStringBuilder.substring(p.getOffset(), p.getLength()); + + if (text.endsWith("\n")) { + text = text.substring(0, text.length() - 1); + } + System.out.println(" " + isError + " " + start + "-" + end + ":[" + text + "]"); + } + } + + /** + * Obtain the next snapshot of data. This update must be processed by the + * UI. i.e. don't call this method unless you are going to handle the update + * now. + * + * @return see {@link UpdateUIData} for details on individual values + * returned. + */ + public UpdateUIData getUpdate() { + boolean clearDocumentMarkerManager; + String newConents; + List newPartitions; + List streamsNeedingNotifcation; + + synchronized (this) { + newConents = fEditStringBuilder.toString(); + newPartitions = new ArrayList<>(fEditPartitions); + clearDocumentMarkerManager = fClearDocumentMarkerManager; + fClearDocumentMarkerManager = false; + streamsNeedingNotifcation = new ArrayList<>(fEditStreams); + fEditStreams.clear(); + } + + return new UpdateUIData() { + + @Override + public boolean needsClearDocumentMarkerManager() { + return clearDocumentMarkerManager; + } + + @Override + public List getStreamsNeedingNotifcation() { + return streamsNeedingNotifcation; + } + + @Override + public List getNewPartitions() { + return newPartitions; + } + + @Override + public String getNewContents() { + return newConents; + } + }; + } + +} \ No newline at end of file diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleStreamDecorator.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleStreamDecorator.java index 363d1d07360..f0c9a095dee 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleStreamDecorator.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildConsoleStreamDecorator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2010 QNX Software Systems and others. + * Copyright (c) 2009, 2017 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -12,13 +12,13 @@ package org.eclipse.cdt.internal.ui.buildconsole; import org.eclipse.swt.graphics.Color; +import org.eclipse.ui.console.IConsole; - -public class BuildConsoleStreamDecorator { +public class BuildConsoleStreamDecorator implements IBuildConsoleStreamDecorator { private BuildConsole fConsole = null; - + private Color fColor = null; - + /** * Constructs a new stream connected to the given console. * @@ -33,7 +33,8 @@ public class BuildConsoleStreamDecorator { /** * Sets the color of this message stream * - * @param color color of this message stream, possibly null + * @param color + * color of this message stream, possibly null */ public void setColor(Color color) { Color old = fColor; @@ -42,23 +43,14 @@ public class BuildConsoleStreamDecorator { fConsole.firePropertyChange(this, BuildConsole.P_STREAM_COLOR, old, color); } } - - /** - * Returns the color of this message stream, or null - * if default. - * - * @return the color of this message stream, or null - */ + + @Override public Color getColor() { return fColor; } - - /** - * Returns the console this stream is connected to. - * - * @return the console this stream is connected to - */ - public BuildConsole getConsole() { + + @Override + public IConsole getConsole() { return fConsole; } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildOutputStream.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildOutputStream.java index 25bd5cbf64a..b415602026f 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildOutputStream.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/BuildOutputStream.java @@ -26,11 +26,11 @@ import org.eclipse.cdt.internal.core.IErrorMarkeredOutputStream; */ public class BuildOutputStream extends ConsoleOutputStream implements IErrorMarkeredOutputStream { - final BuildConsoleStreamDecorator fStream; + final IBuildConsoleStreamDecorator fStream; private BuildConsolePartitioner fPartitioner; public BuildOutputStream(BuildConsolePartitioner partitioner, - BuildConsoleStreamDecorator stream) { + IBuildConsoleStreamDecorator stream) { fPartitioner = partitioner; if (fPartitioner.getProject() == null) // Note: The global console log stream should have been diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/IBuildConsoleStreamDecorator.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/IBuildConsoleStreamDecorator.java new file mode 100644 index 00000000000..f4de7f533fc --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/buildconsole/IBuildConsoleStreamDecorator.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2017 Kichwa Coders and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Jonah Graham (Kichwa Coders) - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.buildconsole; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.ui.console.IConsole; + +public interface IBuildConsoleStreamDecorator { + + /** + * Returns the color of this message stream, or null if + * default. + * + * @return the color of this message stream, or null + */ + Color getColor(); + + /** + * Returns the console this stream is connected to. + * + * @return the console this stream is connected to + */ + IConsole getConsole(); + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/BuildConsolePreferencePage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/BuildConsolePreferencePage.java index a54d8262287..0acc7ec4301 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/BuildConsolePreferencePage.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/BuildConsolePreferencePage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 QNX Software Systems and others. + * Copyright (c) 2000, 2017 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -81,6 +81,8 @@ public class BuildConsolePreferencePage extends FieldEditorPreferencePage implem IntegerFieldEditor buildCount = new IntegerFieldEditor(PREF_BUILDCONSOLE_LINES, CUIPlugin.getResourceString("ConsolePreferencePage.consoleLines.label"), parent); //$NON-NLS-1$ + buildCount.getLabelControl(parent).setToolTipText(CUIPlugin.getResourceString("ConsolePreferencePage.consoleLines.tooltip")); //$NON-NLS-1$ + buildCount.getTextControl(parent).setToolTipText(CUIPlugin.getResourceString("ConsolePreferencePage.consoleLines.tooltip")); //$NON-NLS-1$ buildCount.setErrorMessage(CUIPlugin.getResourceString("ConsolePreferencePage.consoleLines.errorMessage")); //$NON-NLS-1$ buildCount.setValidRange(10, Integer.MAX_VALUE); addField(buildCount);