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;
*/
public static final int HIDE_INPUT = 1 << 0;
- public static final String PLUGIN_ID = "org.simantics.scl.ui";
+ public static final String PLUGIN_ID = "org.simantics.scl.ui"; //$NON-NLS-1$
public static final int COMMAND_HISTORY_SIZE = 50;
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;
// Initialize current text font
fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
fontRegistry.addListener(fontRegistryListener);
- FontDescriptor font = FontDescriptor.createFrom( fontRegistry.getFontData("org.simantics.scl.consolefont") );
+ FontDescriptor font = FontDescriptor.createFrom( fontRegistry.getFontData("org.simantics.scl.consolefont") ); //$NON-NLS-1$
setTextFont(font);
setLayout(new FormLayout());
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);
}
});
}
GC gc = new GC(deco);
int inputLeftPos = gc.getFontMetrics().getAverageCharWidth()*2;
gc.dispose();
- deco.setText(">");
+ deco.setText(">"); //$NON-NLS-1$
deco.setLayoutData( formData(sash, 100, 0, new Tuple2(0, inputLeftPos)) );
// Input area
input = new StyledText(this, SWT.MULTI);
input.setFont(textFont);
input.setLayoutData( formData(sash, 100, new Tuple2(0, inputLeftPos), 100) );
- adjustInputSize("");
+ adjustInputSize(""); //$NON-NLS-1$
input.addVerifyKeyListener(event -> {
switch(event.keyCode) {
case SWT.KEYPAD_CR:
}
});
input.addVerifyListener(e -> {
- if(e.text.contains("\n")) {
+ if(e.text.contains("\n")) { //$NON-NLS-1$
int lineId = input.getLineAtOffset(e.start);
int lineOffset = input.getOffsetAtLine(lineId);
int indentAmount;
for(indentAmount=0;
lineOffset+indentAmount < input.getCharCount() &&
- input.getTextRange(lineOffset+indentAmount, 1).equals(" ");
+ input.getTextRange(lineOffset+indentAmount, 1).equals(" "); //$NON-NLS-1$
++indentAmount);
StringBuilder indent = new StringBuilder();
indent.append('\n');
for(int i=0;i<indentAmount;++i)
indent.append(' ');
- e.text = e.text.replace("\n", indent);
+ e.text = e.text.replace("\n", indent); //$NON-NLS-1$
}
});
input.addModifyListener(e -> {
Tuple2 t = (Tuple2) o;
return new FormAttachment((Integer) t.c0, (Integer) t.c1);
}
- throw new IllegalArgumentException("argument not supported: " + o);
+ throw new IllegalArgumentException("argument not supported: " + o); //$NON-NLS-1$
}
private int getOffsetInInput(int x, int y) {
String validatedText;
- Job validationJob = new Job("SCL input validation") {
+ Job validationJob = new Job("SCL input validation") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
};
- Job preValidationJob = new Job("SCL input validation") {
+ Job preValidationJob = new Job("SCL input validation") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
if(!input.isDisposed()) {
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);
}
// Print it into output area
//appendOutput("> " + command.replace("\n", "\n ") + "\n", greenColor, null);
- input.setText("");
+ input.setText(""); //$NON-NLS-1$
// Execute
execute(command);
range.start = 0;
range.length = 1;
input.setStyleRange(range);
- getLogger().error("The following error message didn't have a proper location: {}", annotation.description, e);
+ getLogger().error("The following error message didn't have a proper location: {}", annotation.description, e); //$NON-NLS-1$
}
}
}
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;
}
public void clear() {
outputModiLock = true;
- output.setText("");
+ output.setText(""); //$NON-NLS-1$
outputModiLock = false;
}