X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scl.ui%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fui%2Fconsole%2FAbstractCommandConsole.java;h=a8931ecffa22dbf57be5054b60022b2cbb624a55;hb=refs%2Fchanges%2F31%2F3731%2F1;hp=a98d65715c7fa89a652ac6a0293667027bfda51a;hpb=d1c23bec0b9900d92fc522429ef5476757a2af93;p=simantics%2Fplatform.git 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 a98d65715..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,27 +2,36 @@ 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; import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.window.DefaultToolTip; 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; import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; @@ -36,8 +45,10 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Sash; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.preferences.ScopedPreferenceStore; import org.simantics.scl.runtime.tuple.Tuple2; +import org.slf4j.Logger; /** * A console with input and output area that can be embedded @@ -51,7 +62,7 @@ public abstract class AbstractCommandConsole extends Composite { */ 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; @@ -61,8 +72,9 @@ public abstract class AbstractCommandConsole extends Composite { protected final int options; - StyledText output; + StyledText output; Sash sash; + StyledText deco; protected StyledText input; int userInputHeight=0; @@ -70,13 +82,48 @@ public abstract class AbstractCommandConsole extends Composite { protected Color greenColor; protected Color redColor; - protected Font textFont; + + FontRegistry fontRegistry; + FontDescriptor textFontDescriptor; + Font textFont; ArrayList commandHistory = new ArrayList(); 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; @@ -97,7 +144,7 @@ public abstract class AbstractCommandConsole extends Composite { return true; } - private boolean hasOption(int mask) { + protected boolean hasOption(int mask) { return (options & mask) != 0; } @@ -105,7 +152,12 @@ public abstract class AbstractCommandConsole extends Composite { resourceManager = new LocalResourceManager(JFaceResources.getResources(), this); greenColor = resourceManager.createColor(new RGB(0, 128, 0)); redColor = resourceManager.createColor(new RGB(172, 0, 0)); - textFont = resourceManager.createFont( FontDescriptor.createFrom("Courier New", 12, SWT.NONE) ); + + // Initialize current text font + fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry(); + fontRegistry.addListener(fontRegistryListener); + FontDescriptor font = FontDescriptor.createFrom( fontRegistry.getFontData("org.simantics.scl.consolefont") ); //$NON-NLS-1$ + setTextFont(font); setLayout(new FormLayout()); @@ -152,30 +204,33 @@ public abstract class AbstractCommandConsole extends Composite { readPreferences(); 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); } }); } protected void createInputArea() { - // "> " Decoration - StyledText deco = new StyledText(this, SWT.MULTI | SWT.READ_ONLY); + deco = new StyledText(this, SWT.MULTI | SWT.READ_ONLY); deco.setFont(textFont); deco.setEnabled(false); 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: @@ -212,19 +267,19 @@ public abstract class AbstractCommandConsole extends Composite { } }); 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 { @@ -320,7 +375,7 @@ public abstract class AbstractCommandConsole extends Composite { 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) { @@ -350,7 +405,7 @@ public abstract class AbstractCommandConsole extends Composite { 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) { @@ -361,7 +416,7 @@ public abstract class AbstractCommandConsole extends Composite { }; - 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()) { @@ -410,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(() -> { @@ -426,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(); @@ -434,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; + } - for(StyleRange styleRange : styleRangeArray) { - styleRange.start += pos; + 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 += insertPos; output.setStyleRange(styleRange); } @@ -467,7 +571,7 @@ public abstract class AbstractCommandConsole extends Composite { // Print it into output area //appendOutput("> " + command.replace("\n", "\n ") + "\n", greenColor, null); - input.setText(""); + input.setText(""); //$NON-NLS-1$ // Execute execute(command); @@ -504,8 +608,7 @@ public abstract class AbstractCommandConsole extends Composite { range.start = 0; range.length = 1; input.setStyleRange(range); - System.err.println("The following error message didn't have a proper location:"); - System.err.println(annotation.description); + getLogger().error("The following error message didn't have a proper location: {}", annotation.description, e); //$NON-NLS-1$ } } } @@ -513,18 +616,15 @@ public abstract class AbstractCommandConsole extends Composite { private void asyncSetErrorAnnotations(final String forCommand, final ErrorAnnotation[] annotations) { if(input.isDisposed()) return; - input.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - if(input.isDisposed()) - return; - if(!input.getText().equals(forCommand)) - return; - syncSetErrorAnnotations(forCommand, annotations); - } + input.getDisplay().asyncExec(() -> { + if(input.isDisposed()) + return; + if(!input.getText().equals(forCommand)) + return; + syncSetErrorAnnotations(forCommand, annotations); }); } - + private boolean readPreferences() { IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); @@ -535,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; } @@ -554,7 +661,7 @@ public abstract class AbstractCommandConsole extends Composite { public void clear() { outputModiLock = true; - output.setText(""); + output.setText(""); //$NON-NLS-1$ outputModiLock = false; } @@ -562,4 +669,34 @@ public abstract class AbstractCommandConsole extends Composite { return output; } + IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + setTextFont( FontDescriptor.createFrom((FontData[]) event.getNewValue()) ); + } + }; + + private void setTextFont(FontDescriptor font) { + FontDescriptor oldFontDesc = textFontDescriptor; + textFont = resourceManager.createFont(font); + textFontDescriptor = font; + applyTextFont(textFont); + + // Only destroy old font after the new font has been set! + if (oldFontDesc != null) + resourceManager.destroyFont(oldFontDesc); + } + + private void applyTextFont(Font font) { + if (output != null) + output.setFont(font); + if (deco != null) + deco.setFont(font); + if (input != null) { + input.setFont(font); + adjustInputSize(input.getText()); + } + } + + public abstract Logger getLogger(); }