X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.scl.ui%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fui%2Fconsole%2FAbstractCommandConsole.java;fp=bundles%2Forg.simantics.scl.ui%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fui%2Fconsole%2FAbstractCommandConsole.java;h=a8931ecffa22dbf57be5054b60022b2cbb624a55;hp=b0019688386fcbbf8f7d019dad052630c75410d0;hb=501ad95ad5ca980ef4c6e65af1451a0d7b63cddc;hpb=6a8b529bc5ceff5f1455968c03e65d140ffb6e39 diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java index b00196883..a8931ecff 100644 --- a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java @@ -2,12 +2,16 @@ package org.simantics.scl.ui.console; 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; @@ -22,6 +26,7 @@ import org.eclipse.jface.window.ToolTip; 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; @@ -67,7 +72,7 @@ public abstract class AbstractCommandConsole extends Composite { protected final int options; - StyledText output; + StyledText output; Sash sash; StyledText deco; protected StyledText input; @@ -86,7 +91,39 @@ public abstract class AbstractCommandConsole extends Composite { 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; @@ -169,10 +206,12 @@ public abstract class AbstractCommandConsole extends Composite { 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); } }); } @@ -426,15 +465,16 @@ public abstract class AbstractCommandConsole extends Composite { private StringBuilder outputBuffer = new StringBuilder(); private ArrayList styleRanges = new ArrayList(); - 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(() -> { @@ -442,7 +482,7 @@ public abstract class AbstractCommandConsole extends Composite { String outputText; StyleRange[] styleRangeArray; synchronized(outputBuffer) { - outputScheduled = false; + outputScheduled.set(false); outputText = outputBuffer.toString(); outputBuffer = new StringBuilder(); @@ -450,14 +490,62 @@ public abstract class AbstractCommandConsole extends Composite { 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); } @@ -536,7 +624,7 @@ public abstract class AbstractCommandConsole extends Composite { syncSetErrorAnnotations(forCommand, annotations); }); } - + private boolean readPreferences() { IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); @@ -547,6 +635,13 @@ public abstract class AbstractCommandConsole extends Composite { commandHistory = new ArrayList(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; }