mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-07-24 09:25:31 +02:00
Bug 314428: speedup build console - limit UI thread
Re-wrote the BuildConsolePartitioner to do as many operations as possible in the non-UI thread and only to update the UI thread in batches. Key changes: - The UI thread "pulls" changes when it is ready, this means that many lines of output are updated in one go. This compares to the "push" that the non-UI thread used to do. The previous code pushed a change to the UI thread on every line of build output. - The limit on console size is now soft, rather than recalcuating the entire document on each line (i.e. add line at bottom, remove line from top) the document is truncated to the limit size as it reaches 2x the limit size. This calculation is also done more effeciently by tracking the number of lines each partition contains rather than recalculating them. - Folded most of the implementation of BuildConsolePartition into BuildConsolePartitioner. This is because the partioner has a global view and is much more efficient at determining if adjacent partitions can be combined. In addition, rather than having the complexity of splitting partitions that were too long, instead make sure we don't create such partitions in the first place. Change-Id: I47543db3fef754e779684cae44d3316982f1bc0a
This commit is contained in:
parent
f14ee6a61d
commit
b564575d51
13 changed files with 744 additions and 676 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))));
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<E> implements Deque<E> {
|
||||
private final Deque<E> deque;
|
||||
|
||||
public SynchronizedDeque(Deque<E> 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> 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<? extends E> 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<E> iterator() {
|
||||
return deque.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Iterator<E> 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<ITypedRegion> fPartitions = new ArrayList<ITypedRegion>(5);
|
||||
|
||||
private int fMaxLines;
|
||||
|
||||
List<ITypedRegion> fPartitions = new ArrayList<ITypedRegion>();
|
||||
/**
|
||||
* 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<StreamEntry> fQueue = new SynchronizedDeque<StreamEntry>(
|
||||
new ArrayDeque<StreamEntry>());
|
||||
|
||||
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 <code>null</code>.
|
||||
* @param stream
|
||||
* - the stream to append to.
|
||||
* the stream to append to, <code>null</code> means to clear
|
||||
* everything.
|
||||
* @param marker
|
||||
* the marker associated with this line of console output, can be
|
||||
* <code>null</code>
|
||||
*/
|
||||
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<ITypedRegion> newParitions = new ArrayList<ITypedRegion>(fPartitions.size());
|
||||
Iterator<ITypedRegion> 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() {
|
||||
|
|
|
@ -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<ITypedRegion> getNewPartitions();
|
||||
|
||||
/**
|
||||
* All the streams that have been written to since the last update.
|
||||
*/
|
||||
List<IBuildConsoleStreamDecorator> 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<BuildConsolePartition> fEditPartitions = new ArrayList<BuildConsolePartition>();
|
||||
|
||||
/**
|
||||
* 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<IBuildConsoleStreamDecorator> 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 <code>null</code>.
|
||||
* @param stream
|
||||
* the stream to append to, cannot be <code>null</code>.
|
||||
* @param marker
|
||||
* the marker associated with this line of console output, can be
|
||||
* <code>null</code>
|
||||
*
|
||||
*/
|
||||
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<BuildConsolePartition> newParitions = new ArrayList<>(newPartCount);
|
||||
Iterator<BuildConsolePartition> 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<ITypedRegion> newPartitions;
|
||||
List<IBuildConsoleStreamDecorator> 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<IBuildConsoleStreamDecorator> getStreamsNeedingNotifcation() {
|
||||
return streamsNeedingNotifcation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ITypedRegion> getNewPartitions() {
|
||||
return newPartitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewContents() {
|
||||
return newConents;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <code>null</code>
|
||||
* @param color
|
||||
* color of this message stream, possibly <code>null</code>
|
||||
*/
|
||||
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 <code>null</code>
|
||||
* if default.
|
||||
*
|
||||
* @return the color of this message stream, or <code>null</code>
|
||||
*/
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <code>null</code> if
|
||||
* default.
|
||||
*
|
||||
* @return the color of this message stream, or <code>null</code>
|
||||
*/
|
||||
Color getColor();
|
||||
|
||||
/**
|
||||
* Returns the console this stream is connected to.
|
||||
*
|
||||
* @return the console this stream is connected to
|
||||
*/
|
||||
IConsole getConsole();
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue