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