import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Deque;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Color;
protected final int options;
- StyledText output;
+ StyledText output;
Sash sash;
StyledText deco;
protected StyledText input;
int commandHistoryPos = 0;
boolean outputModiLock = false;
-
+
+ boolean limitConsoleOutput;
+
+ /**
+ * The amount of buffered characters to adjust {@link #output} to when trimming
+ * the console buffer after its length has exceeded {@link #highWatermark}.
+ */
+ int lowWatermark;
+ /**
+ * The maximum amount of buffered characters allowed in {@link #output} before
+ * the buffer is pruned to under {@link #lowWatermark} characters.
+ */
+ int highWatermark;
+
+ /**
+ * The console preference scope listened to.
+ */
+ IEclipsePreferences preferences;
+
+ /**
+ * The console preference listener.
+ */
+ IPreferenceChangeListener preferenceListener = e -> {
+ String k = e.getKey();
+ if (Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT.equals(k)) {
+ limitConsoleOutput = preferences.getBoolean(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT, Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT_DEFAULT);
+ } else if (Preferences.CONSOLE_LOW_WATER_MARK.equals(k)) {
+ lowWatermark = preferences.getInt(Preferences.CONSOLE_LOW_WATER_MARK, Preferences.CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE);
+ } else if (Preferences.CONSOLE_HIGH_WATER_MARK.equals(k)) {
+ highWatermark = preferences.getInt(Preferences.CONSOLE_HIGH_WATER_MARK, Preferences.CONSOLE_HIGH_WATER_MARK_DEFAULT_VALUE);
+ }
+ };
+
/*
Shell tip = null;
Label label = null;
addListener(SWT.Dispose, event -> {
if (fontRegistry != null)
fontRegistry.removeListener(fontRegistryListener);
+ if (preferences != null)
+ preferences.removePreferenceChangeListener(preferenceListener);
try {
writePreferences();
} catch (IOException e) {
- e.printStackTrace();
+ getLogger().error("Failed to store command history in preferences", e);
}
});
}
private StringBuilder outputBuffer = new StringBuilder();
private ArrayList<StyleRange> styleRanges = new ArrayList<StyleRange>();
- private volatile boolean outputScheduled = false;
+ private AtomicBoolean outputScheduled = new AtomicBoolean(false);
public void appendOutput(final String text, final Color foreground, final Color background) {
+ boolean scheduleOutput = false;
synchronized (outputBuffer) {
styleRanges.add(new StyleRange(outputBuffer.length(), text.length(), foreground, background));
outputBuffer.append(text);
+ scheduleOutput = outputScheduled.compareAndSet(false, true);
}
- if(!outputScheduled) {
- outputScheduled = true;
+ if(scheduleOutput) {
final Display display = Display.getDefault();
if(display.isDisposed()) return;
display.asyncExec(() -> {
String outputText;
StyleRange[] styleRangeArray;
synchronized(outputBuffer) {
- outputScheduled = false;
+ outputScheduled.set(false);
outputText = outputBuffer.toString();
outputBuffer = new StringBuilder();
styleRangeArray = styleRanges.toArray(new StyleRange[styleRanges.size()]);
styleRanges.clear();
}
- int pos = output.getCharCount();
- outputModiLock = true;
- output.replaceTextRange(pos, 0, outputText);
- outputModiLock = false;
+ int addedLength = outputText.length();
+ int currentLength = output.getCharCount();
+ int insertPos = currentLength;
+ int newLength = insertPos + addedLength;
+
+ if (limitConsoleOutput && newLength > highWatermark) {
+ // Test for corner case: buffer overflows and more text is incoming than fits low watermark
+ if (addedLength > lowWatermark) {
+ // Prune the new input text first if it is too large to fit in the buffer even on its own to be < lowWatermark
+ int removedCharacters = addedLength - lowWatermark;
+
+ outputText = outputText.substring(removedCharacters);
+ addedLength = outputText.length();
+ newLength = insertPos + addedLength;
+
+ // Prune new incoming style ranges also
+ int firstStyleRangeToCopy = 0;
+ for (int i = 0; i < styleRangeArray.length; ++i, ++firstStyleRangeToCopy) {
+ StyleRange sr = styleRangeArray[i];
+ if ((sr.start + sr.length) > removedCharacters) {
+ if (sr.start < removedCharacters)
+ sr.start = removedCharacters;
+ break;
+ }
+ }
+ styleRangeArray = Arrays.copyOfRange(styleRangeArray, firstStyleRangeToCopy, styleRangeArray.length);
+ for (StyleRange sr : styleRangeArray)
+ sr.start -= removedCharacters;
+ }
+
+ int minimallyRemoveFromBegin = Math.min(currentLength, newLength - lowWatermark);
+
+ // Find the next line change to prune the text until then
+ StyledTextContent content = output.getContent();
+ int lineCount = content.getLineCount();
+ int lastRemovedLine = content.getLineAtOffset(minimallyRemoveFromBegin);
+ int removeUntilOffset = lastRemovedLine >= (lineCount-1)
+ ? currentLength
+ : content.getOffsetAtLine(lastRemovedLine + 1);
+
+ insertPos -= removeUntilOffset;
+
+ outputModiLock = true;
+ output.replaceTextRange(0, removeUntilOffset, "");
+ output.replaceTextRange(insertPos, 0, outputText);
+ outputModiLock = false;
+ } else {
+ // Buffer does not need to be pruned, just append at end
+ outputModiLock = true;
+ output.replaceTextRange(insertPos, 0, outputText);
+ outputModiLock = false;
+ }
- for(StyleRange styleRange : styleRangeArray) {
- styleRange.start += pos;
+ for (StyleRange styleRange : styleRangeArray) {
+ styleRange.start += insertPos;
output.setStyleRange(styleRange);
}
syncSetErrorAnnotations(forCommand, annotations);
});
}
-
+
private boolean readPreferences() {
IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID);
commandHistory = new ArrayList<String>(recentImportPaths);
commandHistoryPos = commandHistory.size();
+ limitConsoleOutput = store.getBoolean(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT);
+ lowWatermark = store.getInt(Preferences.CONSOLE_LOW_WATER_MARK);
+ highWatermark = store.getInt(Preferences.CONSOLE_HIGH_WATER_MARK);
+
+ preferences = InstanceScope.INSTANCE.getNode(PLUGIN_ID);
+ preferences.addPreferenceChangeListener(preferenceListener);
+
return true;
}