]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLModuleEditor2.java
Add character highlighting to SCL module editor
[simantics/platform.git] / bundles / org.simantics.scl.ui / src / org / simantics / scl / ui / editor2 / SCLModuleEditor2.java
index d07c2566902a7ee169a3fd63630d380832cd2076..0e068d69bd3568387a8ba73fbbe1599d741e15a3 100644 (file)
-package org.simantics.scl.ui.editor2;\r
-\r
-import org.eclipse.jface.resource.JFaceResources;\r
-import org.eclipse.jface.resource.LocalResourceManager;\r
-import org.eclipse.jface.resource.ResourceManager;\r
-import org.eclipse.jface.text.IDocument;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.ui.IEditorInput;\r
-import org.eclipse.ui.IEditorSite;\r
-import org.eclipse.ui.PartInitException;\r
-import org.eclipse.ui.contexts.IContextService;\r
-import org.eclipse.ui.editors.text.TextEditor;\r
-import org.eclipse.ui.texteditor.ITextEditorActionConstants;\r
-import org.eclipse.ui.texteditor.StatusLineContributionItem;\r
-import org.simantics.scl.ui.editor.SCLSourceViewerConfigurationNew;\r
-import org.simantics.scl.ui.editor.completion.SCLTextEditorEnvironment;\r
-\r
-public class SCLModuleEditor2 extends TextEditor {\r
-    private boolean disposed = false;\r
-    ResourceManager resourceManager;\r
-\r
-    public SCLModuleEditor2() {\r
-        super();\r
-        resourceManager = new LocalResourceManager(JFaceResources.getResources());\r
-        SCLSourceViewerConfigurationNew sourceViewerConfiguration = new SCLSourceViewerConfigurationNew(resourceManager);\r
-        setDocumentProvider(new SCLModuleEditor2DocumentProvider(sourceViewerConfiguration));\r
-        setSourceViewerConfiguration(sourceViewerConfiguration);\r
-    }\r
-    \r
-    @Override\r
-    public boolean isTabsToSpacesConversionEnabled() {\r
-        return true;\r
-    }\r
-    \r
-    @Override\r
-    public void init(IEditorSite site, IEditorInput input)\r
-            throws PartInitException {\r
-        super.init(site, input);\r
-    }\r
-\r
-    @Override\r
-    public void createPartControl(Composite parent) {\r
-        super.createPartControl(parent);\r
-        StatusLineContributionItem statusLineContribution = new StatusLineContributionItem(\r
-                ITextEditorActionConstants.STATUS_CATEGORY_INPUT_POSITION,\r
-                true, 14);\r
-        setStatusField(statusLineContribution,\r
-                ITextEditorActionConstants.STATUS_CATEGORY_INPUT_POSITION);\r
-        getEditorSite().getActionBars().getStatusLineManager().add(statusLineContribution);\r
-        getEditorSite().getActionBars().updateActionBars();\r
-        getEditorSite().getService(IContextService.class).activateContext("org.simantics.scl.ui.editor");\r
-        updatePartName();\r
-    }\r
-\r
-    protected void updatePartName() {\r
-        setPartName(getEditorInput().getName());\r
-    }\r
-    \r
-    @Override\r
-    public void dispose() {\r
-        disposed = true;\r
-        super.dispose();\r
-        resourceManager.dispose();\r
-    }\r
-\r
-    public boolean isDisposed() {\r
-        return disposed;\r
-    }\r
-    \r
-    public SCLTextEditorEnvironment getSCLTextEditorEnvironment() {\r
-        return ((SCLSourceViewerConfigurationNew)getSourceViewerConfiguration())\r
-                .getSclTextEditorEnvironment();\r
-    }\r
-\r
-    public IDocument getDocument() {\r
-        return getSourceViewer().getDocument();\r
-    }\r
-}\r
+package org.simantics.scl.ui.editor2;
+
+import java.text.CharacterIterator;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.link.LinkedModeModel;
+import org.eclipse.jface.text.link.LinkedPosition;
+import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ST;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
+import org.eclipse.ui.texteditor.IUpdate;
+import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
+import org.eclipse.ui.texteditor.TextNavigationAction;
+import org.simantics.scl.ui.editor.SCLSourceViewerConfigurationNew;
+import org.simantics.scl.ui.editor.completion.SCLTextEditorEnvironment;
+import org.simantics.scl.ui.editor2.iterator.DocumentCharacterIterator;
+import org.simantics.scl.ui.editor2.iterator.JavaWordIterator;
+
+import com.ibm.icu.text.BreakIterator;
+
+public class SCLModuleEditor2 extends TextEditor {
+    
+    private static final char[] CHARS = new char[] { '(', ')', '{', '}', '[', ']', '<', '>' };
+    
+    private static final String MATCHING_BRACKETS = "matchingBrackets";
+    private static final String MATCHING_BRACKETS_COLOR = "matchingBracketsColor";
+    private static final String HIGHLIGHT_BRACKET_AT_CARET_LOCATION = "highlightBracketAtCaretLocation";
+    private static final String ENCLOSING_BRACKETS = "enclosingBrackets";
+    
+    private boolean disposed = false;
+    protected ResourceManager resourceManager;
+    private DefaultCharacterPairMatcher matcher;
+
+    public SCLModuleEditor2() {
+        super();
+        resourceManager = new LocalResourceManager(JFaceResources.getResources());
+        SCLSourceViewerConfigurationNew sourceViewerConfiguration = new SCLSourceViewerConfigurationNew(resourceManager);
+        setDocumentProvider(new SCLModuleEditor2DocumentProvider(sourceViewerConfiguration));
+        setSourceViewerConfiguration(sourceViewerConfiguration);
+    }
+    
+    @Override
+    public boolean isTabsToSpacesConversionEnabled() {
+        return true;
+    }
+    
+    @Override
+    public void init(IEditorSite site, IEditorInput input)
+            throws PartInitException {
+        super.init(site, input);
+        getPreferenceStore().setValue(MATCHING_BRACKETS, true);
+        getPreferenceStore().setValue(MATCHING_BRACKETS_COLOR, "192,192,192");
+        getPreferenceStore().setValue(HIGHLIGHT_BRACKET_AT_CARET_LOCATION, true);
+        getPreferenceStore().setValue(ENCLOSING_BRACKETS, true);
+    }
+
+    @Override
+    public void createPartControl(Composite parent) {
+        super.createPartControl(parent);
+        getEditorSite().getService(IContextService.class).activateContext("org.simantics.scl.ui.editor");
+        updatePartName();
+    }
+
+    @Override
+    protected void createNavigationActions() {
+        super.createNavigationActions();
+        
+        // Taken from org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.createNavigationActions()
+        final StyledText textWidget= getSourceViewer().getTextWidget();
+        
+        IAction action = new NavigatePreviousSubWordAction();
+        action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS);
+        setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action);
+        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);
+
+        action = new NavigateNextSubWordAction();
+        action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT);
+        setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action);
+        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);
+
+        action = new SelectPreviousSubWordAction();
+        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS);
+        setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action);
+        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);
+
+        action = new SelectNextSubWordAction();
+        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT);
+        setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action);
+        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);
+    }
+
+    protected void updatePartName() {
+        setPartName(getEditorInput().getName());
+    }
+
+    @Override
+    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
+        matcher = new DefaultCharacterPairMatcher(CHARS);
+        support.setCharacterPairMatcher(matcher);
+        support.setMatchingCharacterPainterPreferenceKeys(MATCHING_BRACKETS, MATCHING_BRACKETS_COLOR, HIGHLIGHT_BRACKET_AT_CARET_LOCATION, ENCLOSING_BRACKETS);
+        super.configureSourceViewerDecorationSupport(support);
+    }
+    
+    @Override
+    public void dispose() {
+        disposed = true;
+        super.dispose();
+        resourceManager.dispose();
+        matcher.dispose();
+    }
+
+    public boolean isDisposed() {
+        return disposed;
+    }
+    
+    public SCLTextEditorEnvironment getSCLTextEditorEnvironment() {
+        return ((SCLSourceViewerConfigurationNew)getSourceViewerConfiguration())
+                .getSclTextEditorEnvironment();
+    }
+
+    public IDocument getDocument() {
+        return getSourceViewer().getDocument();
+    }
+    
+    /**
+     * Text navigation action to navigate to the next sub-word.
+     *
+     * @since 3.0
+     */
+    protected abstract class NextSubWordAction extends TextNavigationAction {
+
+        protected JavaWordIterator fIterator= new JavaWordIterator();
+
+        /**
+         * Creates a new next sub-word action.
+         *
+         * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
+         */
+        protected NextSubWordAction(int code) {
+            super(getSourceViewer().getTextWidget(), code);
+        }
+
+        /*
+         * @see org.eclipse.jface.action.IAction#run()
+         */
+        @Override
+        public void run() {
+            final ISourceViewer viewer= getSourceViewer();
+            final IDocument document= viewer.getDocument();
+            try {
+                fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
+                int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
+                if (position == -1)
+                    return;
+
+                int next= findNextPosition(position);
+                if (isBlockSelectionModeEnabled() && document.getLineOfOffset(next) != document.getLineOfOffset(position)) {
+                    super.run(); // may navigate into virtual white space
+                } else if (next != BreakIterator.DONE) {
+                    setCaretPosition(next);
+                    getTextWidget().showSelection();
+                    fireSelectionChanged();
+                }
+            } catch (BadLocationException x) {
+                // ignore
+            }
+        }
+
+        /**
+         * Finds the next position after the given position.
+         *
+         * @param position the current position
+         * @return the next position
+         */
+        protected int findNextPosition(int position) {
+            ISourceViewer viewer= getSourceViewer();
+            int widget= -1;
+            int next= position;
+            while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize
+                next= fIterator.following(next);
+                if (next != BreakIterator.DONE)
+                    widget= modelOffset2WidgetOffset(viewer, next);
+            }
+
+            IDocument document= viewer.getDocument();
+            LinkedModeModel model= LinkedModeModel.getModel(document, position);
+            if (model != null && next != BreakIterator.DONE) {
+                LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
+                if (linkedPosition != null) {
+                    int linkedPositionEnd= linkedPosition.getOffset() + linkedPosition.getLength();
+                    if (position != linkedPositionEnd && linkedPositionEnd < next)
+                        next= linkedPositionEnd;
+                } else {
+                    LinkedPosition nextLinkedPosition= model.findPosition(new LinkedPosition(document, next, 0));
+                    if (nextLinkedPosition != null) {
+                        int nextLinkedPositionOffset= nextLinkedPosition.getOffset();
+                        if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next)
+                            next= nextLinkedPositionOffset;
+                    }
+                }
+            }
+
+            return next;
+        }
+
+        /**
+         * Sets the caret position to the sub-word boundary given with <code>position</code>.
+         *
+         * @param position Position where the action should move the caret
+         */
+        protected abstract void setCaretPosition(int position);
+    }
+
+    /**
+     * Text navigation action to navigate to the next sub-word.
+     *
+     * @since 3.0
+     */
+    protected class NavigateNextSubWordAction extends NextSubWordAction {
+
+        /**
+         * Creates a new navigate next sub-word action.
+         */
+        public NavigateNextSubWordAction() {
+            super(ST.WORD_NEXT);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(final int position) {
+            getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
+        }
+    }
+
+    /**
+     * Text operation action to delete the next sub-word.
+     *
+     * @since 3.0
+     */
+    protected class DeleteNextSubWordAction extends NextSubWordAction implements IUpdate {
+
+        /**
+         * Creates a new delete next sub-word action.
+         */
+        public DeleteNextSubWordAction() {
+            super(ST.DELETE_WORD_NEXT);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(final int position) {
+            if (!validateEditorInputState())
+                return;
+
+            final ISourceViewer viewer= getSourceViewer();
+            StyledText text= viewer.getTextWidget();
+            Point widgetSelection= text.getSelection();
+            if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
+                final int caret= text.getCaretOffset();
+                final int offset= modelOffset2WidgetOffset(viewer, position);
+
+                if (caret == widgetSelection.x)
+                    text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
+                else
+                    text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
+                text.invokeAction(ST.DELETE_NEXT);
+            } else {
+                Point selection= viewer.getSelectedRange();
+                final int caret, length;
+                if (selection.y != 0) {
+                    caret= selection.x;
+                    length= selection.y;
+                } else {
+                    caret= widgetOffset2ModelOffset(viewer, text.getCaretOffset());
+                    length= position - caret;
+                }
+
+                try {
+                    viewer.getDocument().replace(caret, length, ""); //$NON-NLS-1$
+                } catch (BadLocationException exception) {
+                    // Should not happen
+                }
+            }
+        }
+
+        /*
+         * @see org.eclipse.ui.texteditor.IUpdate#update()
+         */
+        public void update() {
+            setEnabled(isEditorInputModifiable());
+        }
+    }
+
+    /**
+     * Text operation action to select the next sub-word.
+     *
+     * @since 3.0
+     */
+    protected class SelectNextSubWordAction extends NextSubWordAction {
+
+        /**
+         * Creates a new select next sub-word action.
+         */
+        public SelectNextSubWordAction() {
+            super(ST.SELECT_WORD_NEXT);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(final int position) {
+            final ISourceViewer viewer= getSourceViewer();
+
+            final StyledText text= viewer.getTextWidget();
+            if (text != null && !text.isDisposed()) {
+
+                final Point selection= text.getSelection();
+                final int caret= text.getCaretOffset();
+                final int offset= modelOffset2WidgetOffset(viewer, position);
+
+                if (caret == selection.x)
+                    text.setSelectionRange(selection.y, offset - selection.y);
+                else
+                    text.setSelectionRange(selection.x, offset - selection.x);
+            }
+        }
+    }
+
+    /**
+     * Text navigation action to navigate to the previous sub-word.
+     *
+     * @since 3.0
+     */
+    protected abstract class PreviousSubWordAction extends TextNavigationAction {
+
+        protected JavaWordIterator fIterator= new JavaWordIterator();
+
+        /**
+         * Creates a new previous sub-word action.
+         *
+         * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
+         */
+        protected PreviousSubWordAction(final int code) {
+            super(getSourceViewer().getTextWidget(), code);
+        }
+
+        /*
+         * @see org.eclipse.jface.action.IAction#run()
+         */
+        @Override
+        public void run() {
+            final ISourceViewer viewer= getSourceViewer();
+            final IDocument document= viewer.getDocument();
+            try {
+                fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
+                int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
+                if (position == -1)
+                    return;
+
+                int previous= findPreviousPosition(position);
+                if (isBlockSelectionModeEnabled() && document.getLineOfOffset(previous) != document.getLineOfOffset(position)) {
+                    super.run(); // may navigate into virtual white space
+                } else if (previous != BreakIterator.DONE) {
+                    setCaretPosition(previous);
+                    getTextWidget().showSelection();
+                    fireSelectionChanged();
+                }
+            } catch (BadLocationException x) {
+                // ignore - getLineOfOffset failed
+            }
+
+        }
+
+        /**
+         * Finds the previous position before the given position.
+         *
+         * @param position the current position
+         * @return the previous position
+         */
+        protected int findPreviousPosition(int position) {
+            ISourceViewer viewer= getSourceViewer();
+            int widget= -1;
+            int previous= position;
+            while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize
+                previous= fIterator.preceding(previous);
+                if (previous != BreakIterator.DONE)
+                    widget= modelOffset2WidgetOffset(viewer, previous);
+            }
+
+            IDocument document= viewer.getDocument();
+            LinkedModeModel model= LinkedModeModel.getModel(document, position);
+            if (model != null && previous != BreakIterator.DONE) {
+                LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
+                if (linkedPosition != null) {
+                    int linkedPositionOffset= linkedPosition.getOffset();
+                    if (position != linkedPositionOffset && previous < linkedPositionOffset)
+                        previous= linkedPositionOffset;
+                } else {
+                    LinkedPosition previousLinkedPosition= model.findPosition(new LinkedPosition(document, previous, 0));
+                    if (previousLinkedPosition != null) {
+                        int previousLinkedPositionEnd= previousLinkedPosition.getOffset() + previousLinkedPosition.getLength();
+                        if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd)
+                            previous= previousLinkedPositionEnd;
+                    }
+                }
+            }
+
+            return previous;
+        }
+
+        /**
+         * Sets the caret position to the sub-word boundary given with <code>position</code>.
+         *
+         * @param position Position where the action should move the caret
+         */
+        protected abstract void setCaretPosition(int position);
+    }
+
+    /**
+     * Text navigation action to navigate to the previous sub-word.
+     *
+     * @since 3.0
+     */
+    protected class NavigatePreviousSubWordAction extends PreviousSubWordAction {
+
+        /**
+         * Creates a new navigate previous sub-word action.
+         */
+        public NavigatePreviousSubWordAction() {
+            super(ST.WORD_PREVIOUS);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(final int position) {
+            getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
+        }
+    }
+
+    /**
+     * Text operation action to delete the previous sub-word.
+     *
+     * @since 3.0
+     */
+    protected class DeletePreviousSubWordAction extends PreviousSubWordAction implements IUpdate {
+
+        /**
+         * Creates a new delete previous sub-word action.
+         */
+        public DeletePreviousSubWordAction() {
+            super(ST.DELETE_WORD_PREVIOUS);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(int position) {
+            if (!validateEditorInputState())
+                return;
+
+            final int length;
+            final ISourceViewer viewer= getSourceViewer();
+            StyledText text= viewer.getTextWidget();
+            Point widgetSelection= text.getSelection();
+            if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
+                final int caret= text.getCaretOffset();
+                final int offset= modelOffset2WidgetOffset(viewer, position);
+
+                if (caret == widgetSelection.x)
+                    text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
+                else
+                    text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
+                text.invokeAction(ST.DELETE_PREVIOUS);
+            } else {
+                Point selection= viewer.getSelectedRange();
+                if (selection.y != 0) {
+                    position= selection.x;
+                    length= selection.y;
+                } else {
+                    length= widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position;
+                }
+
+                try {
+                    viewer.getDocument().replace(position, length, ""); //$NON-NLS-1$
+                } catch (BadLocationException exception) {
+                    // Should not happen
+                }
+            }
+        }
+
+        /*
+         * @see org.eclipse.ui.texteditor.IUpdate#update()
+         */
+        public void update() {
+            setEnabled(isEditorInputModifiable());
+        }
+    }
+
+    /**
+     * Text operation action to select the previous sub-word.
+     *
+     * @since 3.0
+     */
+    protected class SelectPreviousSubWordAction extends PreviousSubWordAction {
+
+        /**
+         * Creates a new select previous sub-word action.
+         */
+        public SelectPreviousSubWordAction() {
+            super(ST.SELECT_WORD_PREVIOUS);
+        }
+
+        /*
+         * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+         */
+        @Override
+        protected void setCaretPosition(final int position) {
+            final ISourceViewer viewer= getSourceViewer();
+
+            final StyledText text= viewer.getTextWidget();
+            if (text != null && !text.isDisposed()) {
+
+                final Point selection= text.getSelection();
+                final int caret= text.getCaretOffset();
+                final int offset= modelOffset2WidgetOffset(viewer, position);
+
+                if (caret == selection.x)
+                    text.setSelectionRange(selection.y, offset - selection.y);
+                else
+                    text.setSelectionRange(selection.x, offset - selection.x);
+            }
+        }
+    }
+
+}