searchResultClass="org.simantics.scl.ui.search.SCLSearchResult">
</viewPage>
</extension>
+ <extension
+ point="org.eclipse.core.runtime.preferences">
+ <initializer
+ class="org.simantics.scl.ui.console.SCLConsolePreferenceInitializer">
+ </initializer>
+ </extension>
+ <extension
+ point="org.eclipse.ui.preferencePages">
+ <page
+ name="SCL"
+ class="org.simantics.scl.ui.preference.SCLPreferencePage"
+ id="org.simantics.scl.ui.preferences.root">
+ </page>
+ <page
+ name="Console"
+ class="org.simantics.scl.ui.console.SCLConsolePreferencePage"
+ category="org.simantics.scl.ui.preferences.root"
+ id="org.simantics.scl.ui.preferences.console">
+ </page>
+ </extension>
</plugin>
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;
}
private static final String BUNDLE_NAME = "org.simantics.scl.ui.console.messages"; //$NON-NLS-1$
public static String ConsoleActions_ClearConsole;
public static String ConsoleActions_InterruptCurrentCommand;
+ public static String SCLConsolePreferencePage_Description;
+ public static String SCLConsolePreferencePage_Limit_console_output_Label;
+ public static String SCLConsolePreferencePage_Console_buffer_size_Label;
+ public static String SCLConsolePreferencePage_Console_buffer_size_Error;
public static String SCLConsoleView_ManageImports;
public static String SCLConsoleView_RefreshAutomatically;
public static String SCLConsoleView_RefreshCompleted;
*/
public final class Preferences {
+ /**
+ * Console buffer high and low water marks
+ */
+ public static final String CONSOLE_LIMIT_CONSOLE_OUTPUT = "SCLConsole.limitConsoleOutput"; //$NON-NLS-1$
+
+ public static final String CONSOLE_LOW_WATER_MARK = "SCLConsole.lowWaterMark"; //$NON-NLS-1$
+
+ public static final String CONSOLE_HIGH_WATER_MARK = "SCLConsole.highWaterMark"; //$NON-NLS-1$
+
+ /**
+ * By default console output buffer size is limited.
+ */
+ public static final boolean CONSOLE_LIMIT_CONSOLE_OUTPUT_DEFAULT = true;
+
+ /**
+ * The console low water mark default value {@value #CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE}.
+ */
+ public static final int CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE = 5000000;
+
+ /**
+ * The console low water mark default value {@value #CONSOLE_HIGH_WATER_MARK_DEFAULT_VALUE}.
+ */
+ public static final int CONSOLE_HIGH_WATER_MARK_DEFAULT_VALUE = highWatermarkForLow(CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE);
+
+ /**
+ * The console low water mark maximum value {@value #CONSOLE_LOW_WATER_MARK_MAX_VALUE}.
+ */
+ public static final int CONSOLE_LOW_WATER_MARK_MAX_VALUE = 10000000;
+
+ public static int highWatermarkForLow(int low) {
+ return low + 80*100;
+ }
+
public static final String COMMAND_HISTORY = "COMMAND_HISTORY"; //$NON-NLS-1$
private static final String DELIMITER = "¤¤¤¤"; //$NON-NLS-1$
--- /dev/null
+package org.simantics.scl.ui.console;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.simantics.scl.ui.Activator;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 1.42.0
+ */
+public class SCLConsolePreferenceInitializer extends AbstractPreferenceInitializer {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences()
+ */
+ @Override
+ public void initializeDefaultPreferences() {
+ IPreferenceStore prefs = Activator.getInstance().getPreferenceStore();
+
+ prefs.setDefault(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT, Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT_DEFAULT);
+ prefs.setDefault(Preferences.CONSOLE_LOW_WATER_MARK, Preferences.CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE);
+ prefs.setDefault(Preferences.CONSOLE_HIGH_WATER_MARK, Preferences.CONSOLE_HIGH_WATER_MARK_DEFAULT_VALUE);
+ }
+
+}
--- /dev/null
+package org.simantics.scl.ui.console;
+
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.FieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.IntegerFieldEditor;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.simantics.scl.ui.Activator;
+
+/**
+ * A page to set the preferences for the SCL console.
+ *
+ * @author Tuukka Lehtonen
+ * @since 1.42.0
+ * @see SCLConsolePreferenceConstants
+ */
+public class SCLConsolePreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+
+ /**
+ * A boolean field editor that provides access to this editors boolean button.
+ *
+ * Copied from
+ * <code>org.eclipse.debug.internal.ui.preferences.BooleanFieldEditor2</code>.
+ */
+ public class BooleanFieldEditor2 extends BooleanFieldEditor {
+ private Button fChangeControl;
+
+ public BooleanFieldEditor2(
+ String name,
+ String labelText,
+ int style,
+ Composite parent) {
+ super(name, labelText, style, parent);
+ }
+
+ @Override
+ public Button getChangeControl(Composite parent) {
+ if (fChangeControl == null) {
+ fChangeControl = super.getChangeControl(parent);
+ }
+ return fChangeControl;
+ }
+ }
+
+ /**
+ * This class exists to provide visibility to the <code>refreshValidState</code>
+ * method and to perform more intelligent clearing of the error message.
+ */
+ protected class ConsoleIntegerFieldEditor extends IntegerFieldEditor {
+
+ public ConsoleIntegerFieldEditor(String name, String labelText, Composite parent) {
+ super(name, labelText, parent);
+ }
+
+ /**
+ * @see org.eclipse.jface.preference.FieldEditor#refreshValidState()
+ */
+ @Override
+ protected void refreshValidState() {
+ super.refreshValidState();
+ }
+
+ /**
+ * Clears the error message from the message line if the error
+ * message is the error message from this field editor.
+ */
+ @Override
+ protected void clearErrorMessage() {
+ if (canClearErrorMessage()) {
+ super.clearErrorMessage();
+ }
+ }
+ }
+
+ private BooleanFieldEditor2 fUseBufferSize = null;
+ private ConsoleIntegerFieldEditor fBufferSizeEditor = null;
+
+ /**
+ * Create the console page.
+ */
+ public SCLConsolePreferencePage() {
+ super(GRID);
+ setDescription(Messages.SCLConsolePreferencePage_Description);
+ setPreferenceStore(Activator.getInstance().getPreferenceStore());
+ }
+
+ /**
+ * Create all field editors for this page
+ */
+ @Override
+ public void createFieldEditors() {
+ fUseBufferSize = new BooleanFieldEditor2(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT, Messages.SCLConsolePreferencePage_Limit_console_output_Label, SWT.NONE, getFieldEditorParent());
+ addField(fUseBufferSize);
+
+ fBufferSizeEditor = new ConsoleIntegerFieldEditor(Preferences.CONSOLE_LOW_WATER_MARK, Messages.SCLConsolePreferencePage_Console_buffer_size_Label, getFieldEditorParent());
+ addField(fBufferSizeEditor);
+ fBufferSizeEditor.setValidRange(1000, 10000000);
+ fBufferSizeEditor.setErrorMessage(Messages.SCLConsolePreferencePage_Console_buffer_size_Error);
+
+ fUseBufferSize.getChangeControl(getFieldEditorParent()).addSelectionListener(
+ SelectionListener.widgetSelectedAdapter(e -> updateBufferSizeEditor()));
+ }
+
+ /**
+ * @see IWorkbenchPreferencePage#init(IWorkbench)
+ */
+ @Override
+ public void init(IWorkbench workbench) {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.preference.IPreferencePage#performOk()
+ */
+ @Override
+ public boolean performOk() {
+ boolean ok = super.performOk();
+
+ // update high water mark to be (about) 100 lines (100 * 80 chars) greater than low water mark
+ IPreferenceStore store = Activator.getInstance().getPreferenceStore();
+ int low = store.getInt(Preferences.CONSOLE_LOW_WATER_MARK);
+ store.setValue(Preferences.CONSOLE_HIGH_WATER_MARK, Preferences.highWatermarkForLow(low));
+
+ return ok;
+ }
+
+ /**
+ * @see org.eclipse.jface.preference.FieldEditorPreferencePage#initialize()
+ */
+ @Override
+ protected void initialize() {
+ super.initialize();
+ updateBufferSizeEditor();
+ }
+
+ /**
+ * Update enablement of buffer size editor based on enablement of 'limit
+ * console output' editor.
+ */
+ protected void updateBufferSizeEditor() {
+ Button b = fUseBufferSize.getChangeControl(getFieldEditorParent());
+ fBufferSizeEditor.getTextControl(getFieldEditorParent()).setEnabled(b.getSelection());
+ fBufferSizeEditor.getLabelControl(getFieldEditorParent()).setEnabled(b.getSelection());
+ }
+
+ /**
+ * @see org.eclipse.jface.preference.PreferencePage#performDefaults()
+ */
+ @Override
+ protected void performDefaults() {
+ super.performDefaults();
+ updateBufferSizeEditor();
+ }
+
+ protected boolean canClearErrorMessage() {
+ return fBufferSizeEditor.isValid();
+ }
+
+ /**
+ * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (event.getProperty().equals(FieldEditor.IS_VALID)) {
+ boolean newValue = ((Boolean) event.getNewValue()).booleanValue();
+ // If the new value is true then we must check all field editors.
+ // If it is false, then the page is invalid in any case.
+ if (newValue) {
+ if (fBufferSizeEditor != null && event.getSource() != fBufferSizeEditor) {
+ fBufferSizeEditor.refreshValidState();
+ }
+ checkState();
+ } else {
+ super.propertyChange(event);
+ }
+
+ } else {
+ super.propertyChange(event);
+ }
+ }
+
+}
ConsoleActions_ClearConsole=Clear Console
ConsoleActions_InterruptCurrentCommand=Interrupt Current Command
+SCLConsolePreferencePage_Description=SCL Console Settings
+SCLConsolePreferencePage_Limit_console_output_Label=&Limit console output
+SCLConsolePreferencePage_Console_buffer_size_Label=Console &buffer size (characters):
+SCLConsolePreferencePage_Console_buffer_size_Error=Buffer size must be between 1000 and 10000000 inclusive.
SCLConsoleView_ManageImports=Manage Imports
SCLConsoleView_RefreshAutomatically=Refresh Automatically
SCLConsoleView_RefreshCompleted=refresh completed\n
--- /dev/null
+package org.simantics.scl.ui.preference;
+
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.simantics.scl.ui.Activator;
+
+/**
+ * A so far blank root level SCL preference page.
+ *
+ * @author Tuukka Lehtonen
+ * @since 1.42.0
+ */
+public class SCLPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+
+ /**
+ * Create the console page.
+ */
+ public SCLPreferencePage() {
+ super(GRID);
+ setPreferenceStore(Activator.getInstance().getPreferenceStore());
+ }
+
+ /**
+ * Create all field editors for this page
+ */
+ @Override
+ public void createFieldEditors() {
+ }
+
+ /**
+ * @see IWorkbenchPreferencePage#init(IWorkbench)
+ */
+ @Override
+ public void init(IWorkbench workbench) {
+ }
+
+}