]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLModuleEditor2.java
Add outline page for SCLModuleEditor2
[simantics/platform.git] / bundles / org.simantics.scl.ui / src / org / simantics / scl / ui / editor2 / SCLModuleEditor2.java
1 package org.simantics.scl.ui.editor2;
2
3 import java.text.CharacterIterator;
4
5 import org.eclipse.jface.action.IAction;
6 import org.eclipse.jface.resource.JFaceResources;
7 import org.eclipse.jface.resource.LocalResourceManager;
8 import org.eclipse.jface.resource.ResourceManager;
9 import org.eclipse.jface.text.BadLocationException;
10 import org.eclipse.jface.text.IDocument;
11 import org.eclipse.jface.text.link.LinkedModeModel;
12 import org.eclipse.jface.text.link.LinkedPosition;
13 import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
14 import org.eclipse.jface.text.source.ISourceViewer;
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.custom.ST;
17 import org.eclipse.swt.custom.StyledText;
18 import org.eclipse.swt.graphics.Point;
19 import org.eclipse.swt.widgets.Composite;
20 import org.eclipse.ui.IEditorInput;
21 import org.eclipse.ui.IEditorSite;
22 import org.eclipse.ui.PartInitException;
23 import org.eclipse.ui.contexts.IContextService;
24 import org.eclipse.ui.editors.text.TextEditor;
25 import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
26 import org.eclipse.ui.texteditor.IUpdate;
27 import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
28 import org.eclipse.ui.texteditor.TextNavigationAction;
29 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
30 import org.simantics.scl.ui.editor.SCLSourceViewerConfigurationNew;
31 import org.simantics.scl.ui.editor.completion.SCLTextEditorEnvironment;
32 import org.simantics.scl.ui.editor2.iterator.DocumentCharacterIterator;
33 import org.simantics.scl.ui.editor2.iterator.JavaWordIterator;
34 import org.simantics.scl.ui.outline.SCLModuleOutlinePage;
35
36 import com.ibm.icu.text.BreakIterator;
37
38 public class SCLModuleEditor2 extends TextEditor {
39     
40     private static final char[] CHARS = new char[] { '(', ')', '{', '}', '[', ']', '<', '>' };
41     
42     private static final String MATCHING_BRACKETS = "matchingBrackets"; //$NON-NLS-1$
43     private static final String MATCHING_BRACKETS_COLOR = "matchingBracketsColor"; //$NON-NLS-1$
44     private static final String HIGHLIGHT_BRACKET_AT_CARET_LOCATION = "highlightBracketAtCaretLocation"; //$NON-NLS-1$
45     private static final String ENCLOSING_BRACKETS = "enclosingBrackets"; //$NON-NLS-1$
46     
47     private boolean disposed = false;
48     protected ResourceManager resourceManager;
49     private DefaultCharacterPairMatcher matcher;
50
51         private SCLModuleOutlinePage outline;
52
53     public SCLModuleEditor2() {
54         super();
55         resourceManager = new LocalResourceManager(JFaceResources.getResources());
56         SCLSourceViewerConfigurationNew sourceViewerConfiguration = new SCLSourceViewerConfigurationNew(resourceManager);
57         setDocumentProvider(new SCLModuleEditor2DocumentProvider(sourceViewerConfiguration));
58         setSourceViewerConfiguration(sourceViewerConfiguration);
59         outline = new SCLModuleOutlinePage(this);
60     }
61     
62     @Override
63     public boolean isTabsToSpacesConversionEnabled() {
64         return true;
65     }
66     
67     @Override
68     public void init(IEditorSite site, IEditorInput input)
69             throws PartInitException {
70         super.init(site, input);
71         getPreferenceStore().setValue(MATCHING_BRACKETS, true);
72         getPreferenceStore().setValue(MATCHING_BRACKETS_COLOR, "192,192,192"); //$NON-NLS-1$
73         getPreferenceStore().setValue(HIGHLIGHT_BRACKET_AT_CARET_LOCATION, true);
74         getPreferenceStore().setValue(ENCLOSING_BRACKETS, true);
75     }
76
77     @Override
78     public void createPartControl(Composite parent) {
79         super.createPartControl(parent);
80         getEditorSite().getService(IContextService.class).activateContext("org.simantics.scl.ui.editor"); //$NON-NLS-1$
81         updatePartName();
82     }
83
84     @Override
85     protected void createNavigationActions() {
86         super.createNavigationActions();
87         
88         // Taken from org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.createNavigationActions()
89         final StyledText textWidget= getSourceViewer().getTextWidget();
90         
91         IAction action = new NavigatePreviousSubWordAction();
92         action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS);
93         setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action);
94         textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);
95
96         action = new NavigateNextSubWordAction();
97         action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT);
98         setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action);
99         textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);
100
101         action = new SelectPreviousSubWordAction();
102         action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS);
103         setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action);
104         textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);
105
106         action = new SelectNextSubWordAction();
107         action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT);
108         setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action);
109         textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);
110     }
111
112     protected void updatePartName() {
113         setPartName(getEditorInput().getName());
114     }
115
116     @Override
117     protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
118         matcher = new DefaultCharacterPairMatcher(CHARS);
119         support.setCharacterPairMatcher(matcher);
120         support.setMatchingCharacterPainterPreferenceKeys(MATCHING_BRACKETS, MATCHING_BRACKETS_COLOR, HIGHLIGHT_BRACKET_AT_CARET_LOCATION, ENCLOSING_BRACKETS);
121         super.configureSourceViewerDecorationSupport(support);
122     }
123     
124     @Override
125     public void dispose() {
126         disposed = true;
127         super.dispose();
128         resourceManager.dispose();
129         if (matcher != null) matcher.dispose();
130     }
131
132     public boolean isDisposed() {
133         return disposed;
134     }
135     
136     public SCLTextEditorEnvironment getSCLTextEditorEnvironment() {
137         return ((SCLSourceViewerConfigurationNew)getSourceViewerConfiguration())
138                 .getSclTextEditorEnvironment();
139     }
140
141     public IDocument getDocument() {
142         return getSourceViewer().getDocument();
143     }
144
145     @SuppressWarnings("unchecked")
146     @Override
147     public <T> T getAdapter(Class<T> adapter) {
148         if (IContentOutlinePage.class.equals(adapter)) {
149             return (T) outline;
150         }
151         return super.getAdapter(adapter);
152     }
153
154     /**
155      * Text navigation action to navigate to the next sub-word.
156      *
157      * @since 3.0
158      */
159     protected abstract class NextSubWordAction extends TextNavigationAction {
160
161         protected JavaWordIterator fIterator= new JavaWordIterator();
162
163         /**
164          * Creates a new next sub-word action.
165          *
166          * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
167          */
168         protected NextSubWordAction(int code) {
169             super(getSourceViewer().getTextWidget(), code);
170         }
171
172         /*
173          * @see org.eclipse.jface.action.IAction#run()
174          */
175         @Override
176         public void run() {
177             final ISourceViewer viewer= getSourceViewer();
178             final IDocument document= viewer.getDocument();
179             try {
180                 fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
181                 int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
182                 if (position == -1)
183                     return;
184
185                 int next= findNextPosition(position);
186                 if (isBlockSelectionModeEnabled() && document.getLineOfOffset(next) != document.getLineOfOffset(position)) {
187                     super.run(); // may navigate into virtual white space
188                 } else if (next != BreakIterator.DONE) {
189                     setCaretPosition(next);
190                     getTextWidget().showSelection();
191                     fireSelectionChanged();
192                 }
193             } catch (BadLocationException x) {
194                 // ignore
195             }
196         }
197
198         /**
199          * Finds the next position after the given position.
200          *
201          * @param position the current position
202          * @return the next position
203          */
204         protected int findNextPosition(int position) {
205             ISourceViewer viewer= getSourceViewer();
206             int widget= -1;
207             int next= position;
208             while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize
209                 next= fIterator.following(next);
210                 if (next != BreakIterator.DONE)
211                     widget= modelOffset2WidgetOffset(viewer, next);
212             }
213
214             IDocument document= viewer.getDocument();
215             LinkedModeModel model= LinkedModeModel.getModel(document, position);
216             if (model != null && next != BreakIterator.DONE) {
217                 LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
218                 if (linkedPosition != null) {
219                     int linkedPositionEnd= linkedPosition.getOffset() + linkedPosition.getLength();
220                     if (position != linkedPositionEnd && linkedPositionEnd < next)
221                         next= linkedPositionEnd;
222                 } else {
223                     LinkedPosition nextLinkedPosition= model.findPosition(new LinkedPosition(document, next, 0));
224                     if (nextLinkedPosition != null) {
225                         int nextLinkedPositionOffset= nextLinkedPosition.getOffset();
226                         if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next)
227                             next= nextLinkedPositionOffset;
228                     }
229                 }
230             }
231
232             return next;
233         }
234
235         /**
236          * Sets the caret position to the sub-word boundary given with <code>position</code>.
237          *
238          * @param position Position where the action should move the caret
239          */
240         protected abstract void setCaretPosition(int position);
241     }
242
243     /**
244      * Text navigation action to navigate to the next sub-word.
245      *
246      * @since 3.0
247      */
248     protected class NavigateNextSubWordAction extends NextSubWordAction {
249
250         /**
251          * Creates a new navigate next sub-word action.
252          */
253         public NavigateNextSubWordAction() {
254             super(ST.WORD_NEXT);
255         }
256
257         /*
258          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
259          */
260         @Override
261         protected void setCaretPosition(final int position) {
262             getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
263         }
264     }
265
266     /**
267      * Text operation action to delete the next sub-word.
268      *
269      * @since 3.0
270      */
271     protected class DeleteNextSubWordAction extends NextSubWordAction implements IUpdate {
272
273         /**
274          * Creates a new delete next sub-word action.
275          */
276         public DeleteNextSubWordAction() {
277             super(ST.DELETE_WORD_NEXT);
278         }
279
280         /*
281          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
282          */
283         @Override
284         protected void setCaretPosition(final int position) {
285             if (!validateEditorInputState())
286                 return;
287
288             final ISourceViewer viewer= getSourceViewer();
289             StyledText text= viewer.getTextWidget();
290             Point widgetSelection= text.getSelection();
291             if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
292                 final int caret= text.getCaretOffset();
293                 final int offset= modelOffset2WidgetOffset(viewer, position);
294
295                 if (caret == widgetSelection.x)
296                     text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
297                 else
298                     text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
299                 text.invokeAction(ST.DELETE_NEXT);
300             } else {
301                 Point selection= viewer.getSelectedRange();
302                 final int caret, length;
303                 if (selection.y != 0) {
304                     caret= selection.x;
305                     length= selection.y;
306                 } else {
307                     caret= widgetOffset2ModelOffset(viewer, text.getCaretOffset());
308                     length= position - caret;
309                 }
310
311                 try {
312                     viewer.getDocument().replace(caret, length, ""); //$NON-NLS-1$
313                 } catch (BadLocationException exception) {
314                     // Should not happen
315                 }
316             }
317         }
318
319         /*
320          * @see org.eclipse.ui.texteditor.IUpdate#update()
321          */
322         public void update() {
323             setEnabled(isEditorInputModifiable());
324         }
325     }
326
327     /**
328      * Text operation action to select the next sub-word.
329      *
330      * @since 3.0
331      */
332     protected class SelectNextSubWordAction extends NextSubWordAction {
333
334         /**
335          * Creates a new select next sub-word action.
336          */
337         public SelectNextSubWordAction() {
338             super(ST.SELECT_WORD_NEXT);
339         }
340
341         /*
342          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
343          */
344         @Override
345         protected void setCaretPosition(final int position) {
346             final ISourceViewer viewer= getSourceViewer();
347
348             final StyledText text= viewer.getTextWidget();
349             if (text != null && !text.isDisposed()) {
350
351                 final Point selection= text.getSelection();
352                 final int caret= text.getCaretOffset();
353                 final int offset= modelOffset2WidgetOffset(viewer, position);
354
355                 if (caret == selection.x)
356                     text.setSelectionRange(selection.y, offset - selection.y);
357                 else
358                     text.setSelectionRange(selection.x, offset - selection.x);
359             }
360         }
361     }
362
363     /**
364      * Text navigation action to navigate to the previous sub-word.
365      *
366      * @since 3.0
367      */
368     protected abstract class PreviousSubWordAction extends TextNavigationAction {
369
370         protected JavaWordIterator fIterator= new JavaWordIterator();
371
372         /**
373          * Creates a new previous sub-word action.
374          *
375          * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
376          */
377         protected PreviousSubWordAction(final int code) {
378             super(getSourceViewer().getTextWidget(), code);
379         }
380
381         /*
382          * @see org.eclipse.jface.action.IAction#run()
383          */
384         @Override
385         public void run() {
386             final ISourceViewer viewer= getSourceViewer();
387             final IDocument document= viewer.getDocument();
388             try {
389                 fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
390                 int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
391                 if (position == -1)
392                     return;
393
394                 int previous= findPreviousPosition(position);
395                 if (isBlockSelectionModeEnabled() && document.getLineOfOffset(previous) != document.getLineOfOffset(position)) {
396                     super.run(); // may navigate into virtual white space
397                 } else if (previous != BreakIterator.DONE) {
398                     setCaretPosition(previous);
399                     getTextWidget().showSelection();
400                     fireSelectionChanged();
401                 }
402             } catch (BadLocationException x) {
403                 // ignore - getLineOfOffset failed
404             }
405
406         }
407
408         /**
409          * Finds the previous position before the given position.
410          *
411          * @param position the current position
412          * @return the previous position
413          */
414         protected int findPreviousPosition(int position) {
415             ISourceViewer viewer= getSourceViewer();
416             int widget= -1;
417             int previous= position;
418             while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize
419                 previous= fIterator.preceding(previous);
420                 if (previous != BreakIterator.DONE)
421                     widget= modelOffset2WidgetOffset(viewer, previous);
422             }
423
424             IDocument document= viewer.getDocument();
425             LinkedModeModel model= LinkedModeModel.getModel(document, position);
426             if (model != null && previous != BreakIterator.DONE) {
427                 LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
428                 if (linkedPosition != null) {
429                     int linkedPositionOffset= linkedPosition.getOffset();
430                     if (position != linkedPositionOffset && previous < linkedPositionOffset)
431                         previous= linkedPositionOffset;
432                 } else {
433                     LinkedPosition previousLinkedPosition= model.findPosition(new LinkedPosition(document, previous, 0));
434                     if (previousLinkedPosition != null) {
435                         int previousLinkedPositionEnd= previousLinkedPosition.getOffset() + previousLinkedPosition.getLength();
436                         if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd)
437                             previous= previousLinkedPositionEnd;
438                     }
439                 }
440             }
441
442             return previous;
443         }
444
445         /**
446          * Sets the caret position to the sub-word boundary given with <code>position</code>.
447          *
448          * @param position Position where the action should move the caret
449          */
450         protected abstract void setCaretPosition(int position);
451     }
452
453     /**
454      * Text navigation action to navigate to the previous sub-word.
455      *
456      * @since 3.0
457      */
458     protected class NavigatePreviousSubWordAction extends PreviousSubWordAction {
459
460         /**
461          * Creates a new navigate previous sub-word action.
462          */
463         public NavigatePreviousSubWordAction() {
464             super(ST.WORD_PREVIOUS);
465         }
466
467         /*
468          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
469          */
470         @Override
471         protected void setCaretPosition(final int position) {
472             getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
473         }
474     }
475
476     /**
477      * Text operation action to delete the previous sub-word.
478      *
479      * @since 3.0
480      */
481     protected class DeletePreviousSubWordAction extends PreviousSubWordAction implements IUpdate {
482
483         /**
484          * Creates a new delete previous sub-word action.
485          */
486         public DeletePreviousSubWordAction() {
487             super(ST.DELETE_WORD_PREVIOUS);
488         }
489
490         /*
491          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
492          */
493         @Override
494         protected void setCaretPosition(int position) {
495             if (!validateEditorInputState())
496                 return;
497
498             final int length;
499             final ISourceViewer viewer= getSourceViewer();
500             StyledText text= viewer.getTextWidget();
501             Point widgetSelection= text.getSelection();
502             if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
503                 final int caret= text.getCaretOffset();
504                 final int offset= modelOffset2WidgetOffset(viewer, position);
505
506                 if (caret == widgetSelection.x)
507                     text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
508                 else
509                     text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
510                 text.invokeAction(ST.DELETE_PREVIOUS);
511             } else {
512                 Point selection= viewer.getSelectedRange();
513                 if (selection.y != 0) {
514                     position= selection.x;
515                     length= selection.y;
516                 } else {
517                     length= widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position;
518                 }
519
520                 try {
521                     viewer.getDocument().replace(position, length, ""); //$NON-NLS-1$
522                 } catch (BadLocationException exception) {
523                     // Should not happen
524                 }
525             }
526         }
527
528         /*
529          * @see org.eclipse.ui.texteditor.IUpdate#update()
530          */
531         public void update() {
532             setEnabled(isEditorInputModifiable());
533         }
534     }
535
536     /**
537      * Text operation action to select the previous sub-word.
538      *
539      * @since 3.0
540      */
541     protected class SelectPreviousSubWordAction extends PreviousSubWordAction {
542
543         /**
544          * Creates a new select previous sub-word action.
545          */
546         public SelectPreviousSubWordAction() {
547             super(ST.SELECT_WORD_PREVIOUS);
548         }
549
550         /*
551          * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
552          */
553         @Override
554         protected void setCaretPosition(final int position) {
555             final ISourceViewer viewer= getSourceViewer();
556
557             final StyledText text= viewer.getTextWidget();
558             if (text != null && !text.isDisposed()) {
559
560                 final Point selection= text.getSelection();
561                 final int caret= text.getCaretOffset();
562                 final int offset= modelOffset2WidgetOffset(viewer, position);
563
564                 if (caret == selection.x)
565                     text.setSelectionRange(selection.y, offset - selection.y);
566                 else
567                     text.setSelectionRange(selection.x, offset - selection.x);
568             }
569         }
570     }
571
572 }