Limit SCL Console buffer size to 5M characters by default 31/3731/1
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 20 Dec 2019 11:15:56 +0000 (13:15 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 20 Dec 2019 11:15:56 +0000 (13:15 +0200)
Low watermark limit for the buffer size can be configured from the new
SCL / Console preference page. High watermark is always set to 100 80
character rows larger than the low watermark.

Buffer size limiting can also be disabled/enabled entirely from the same
preference page.

gitlab #104

Change-Id: I8be203e60ae49fce53c39a4d251d2575a64b2543

bundles/org.simantics.scl.ui/plugin.xml
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/Messages.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/Preferences.java
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferenceInitializer.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferencePage.java [new file with mode: 0644]
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/messages.properties
bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/preference/SCLPreferencePage.java [new file with mode: 0644]

index bb69f55bfb30f329d98dfa2dce241e9d55fdc3bb..b7d5c9fb2005941f82c9cb028214da8f53775237 100644 (file)
             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>
index b0019688386fcbbf8f7d019dad052630c75410d0..a8931ecffa22dbf57be5054b60022b2cbb624a55 100644 (file)
@@ -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<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(() -> {
@@ -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<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;
     }
 
index ecbb2be227bd4052af8ba3aeb10ec2a4e152d3f0..b83ff3e532730f5930de28754a29e261accb4219 100644 (file)
@@ -6,6 +6,10 @@ public class Messages extends NLS {
        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;
index 1d55d1fe3139558be1a334c9c513cb9fa4187021..6b733146b8105254571afebaa7d232d6da0c316d 100644 (file)
@@ -12,6 +12,39 @@ import java.util.TreeSet;
  */
 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$
diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferenceInitializer.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferenceInitializer.java
new file mode 100644 (file)
index 0000000..fb804eb
--- /dev/null
@@ -0,0 +1,25 @@
+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);
+    }
+
+}
diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferencePage.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/SCLConsolePreferencePage.java
new file mode 100644 (file)
index 0000000..3f1f307
--- /dev/null
@@ -0,0 +1,188 @@
+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);
+        }
+    }
+
+}
index fad5ea9f24c6570c1303d349c142724b638787fe..429fe77b4e63812f7062985059eb6d4face7f470 100644 (file)
@@ -1,5 +1,9 @@
 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
diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/preference/SCLPreferencePage.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/preference/SCLPreferencePage.java
new file mode 100644 (file)
index 0000000..3883502
--- /dev/null
@@ -0,0 +1,38 @@
+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) {
+    }
+
+}