]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scl.ui / src / org / simantics / scl / ui / console / AbstractCommandConsole.java
1 package org.simantics.scl.ui.console;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Deque;
7 import java.util.concurrent.atomic.AtomicBoolean;
8
9 import org.eclipse.core.runtime.IProgressMonitor;
10 import org.eclipse.core.runtime.IStatus;
11 import org.eclipse.core.runtime.Status;
12 import org.eclipse.core.runtime.jobs.Job;
13 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
14 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
15 import org.eclipse.core.runtime.preferences.InstanceScope;
16 import org.eclipse.jface.preference.IPersistentPreferenceStore;
17 import org.eclipse.jface.preference.IPreferenceStore;
18 import org.eclipse.jface.resource.FontDescriptor;
19 import org.eclipse.jface.resource.FontRegistry;
20 import org.eclipse.jface.resource.JFaceResources;
21 import org.eclipse.jface.resource.LocalResourceManager;
22 import org.eclipse.jface.util.IPropertyChangeListener;
23 import org.eclipse.jface.util.PropertyChangeEvent;
24 import org.eclipse.jface.window.DefaultToolTip;
25 import org.eclipse.jface.window.ToolTip;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.custom.StyleRange;
28 import org.eclipse.swt.custom.StyledText;
29 import org.eclipse.swt.custom.StyledTextContent;
30 import org.eclipse.swt.events.VerifyEvent;
31 import org.eclipse.swt.events.VerifyListener;
32 import org.eclipse.swt.graphics.Color;
33 import org.eclipse.swt.graphics.Font;
34 import org.eclipse.swt.graphics.FontData;
35 import org.eclipse.swt.graphics.GC;
36 import org.eclipse.swt.graphics.Point;
37 import org.eclipse.swt.graphics.RGB;
38 import org.eclipse.swt.graphics.Rectangle;
39 import org.eclipse.swt.layout.FormAttachment;
40 import org.eclipse.swt.layout.FormData;
41 import org.eclipse.swt.layout.FormLayout;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.swt.widgets.Control;
44 import org.eclipse.swt.widgets.Display;
45 import org.eclipse.swt.widgets.Event;
46 import org.eclipse.swt.widgets.Listener;
47 import org.eclipse.swt.widgets.Sash;
48 import org.eclipse.ui.PlatformUI;
49 import org.eclipse.ui.preferences.ScopedPreferenceStore;
50 import org.simantics.scl.runtime.tuple.Tuple2;
51 import org.slf4j.Logger;
52
53 /**
54  * A console with input and output area that can be embedded
55  * into any editor or view.
56  * @author Hannu Niemistö
57  */
58 public abstract class AbstractCommandConsole extends Composite {
59
60     /**
61      * Use this option mask to hide and disable the console input field.
62      */
63     public static final int HIDE_INPUT = 1 << 0;
64
65     public static final String PLUGIN_ID = "org.simantics.scl.ui"; //$NON-NLS-1$
66
67     public static final int COMMAND_HISTORY_SIZE = 50;
68     
69     public static final int SASH_HEIGHT = 3;
70     
71     LocalResourceManager resourceManager;
72
73     protected final int options;
74
75         StyledText output;
76     Sash sash;
77     StyledText deco;
78     protected StyledText input;
79     
80     int userInputHeight=0;
81     int minInputHeight=0;
82     
83     protected Color greenColor;
84     protected Color redColor;
85
86     FontRegistry fontRegistry;
87     FontDescriptor textFontDescriptor;
88     Font textFont;
89
90     ArrayList<String> commandHistory = new ArrayList<String>();
91     int commandHistoryPos = 0;
92     
93     boolean outputModiLock = false;
94
95     boolean limitConsoleOutput;
96
97     /**
98      * The amount of buffered characters to adjust {@link #output} to when trimming
99      * the console buffer after its length has exceeded {@link #highWatermark}.
100      */
101     int lowWatermark;
102     /**
103      * The maximum amount of buffered characters allowed in {@link #output} before
104      * the buffer is pruned to under {@link #lowWatermark} characters.
105      */
106     int highWatermark;
107
108     /**
109      * The console preference scope listened to.
110      */
111     IEclipsePreferences preferences;
112
113     /**
114      * The console preference listener.
115      */
116     IPreferenceChangeListener preferenceListener = e -> {
117         String k = e.getKey();
118         if (Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT.equals(k)) {
119             limitConsoleOutput = preferences.getBoolean(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT, Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT_DEFAULT);
120         } else if (Preferences.CONSOLE_LOW_WATER_MARK.equals(k)) {
121             lowWatermark = preferences.getInt(Preferences.CONSOLE_LOW_WATER_MARK, Preferences.CONSOLE_LOW_WATER_MARK_DEFAULT_VALUE);
122         } else if (Preferences.CONSOLE_HIGH_WATER_MARK.equals(k)) {
123             highWatermark = preferences.getInt(Preferences.CONSOLE_HIGH_WATER_MARK, Preferences.CONSOLE_HIGH_WATER_MARK_DEFAULT_VALUE);
124         }
125     };
126
127     /*
128     Shell tip = null;
129     Label label = null;
130     */
131
132     public AbstractCommandConsole(Composite parent, int style, int options) {
133         super(parent, style);
134         this.options = options;
135         createControl();
136     }
137
138     @Override
139     public boolean setFocus() {
140         return input != null ? input.setFocus() : output.setFocus();
141     }
142
143     protected boolean canExecuteCommand() {
144         return true;
145     }
146
147     protected boolean hasOption(int mask) {
148         return (options & mask) != 0;
149     }
150
151     private void createControl() {
152         resourceManager = new LocalResourceManager(JFaceResources.getResources(), this);
153         greenColor = resourceManager.createColor(new RGB(0, 128, 0));
154         redColor = resourceManager.createColor(new RGB(172, 0, 0));
155
156         // Initialize current text font
157         fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
158         fontRegistry.addListener(fontRegistryListener);
159         FontDescriptor font = FontDescriptor.createFrom( fontRegistry.getFontData("org.simantics.scl.consolefont") ); //$NON-NLS-1$
160         setTextFont(font);
161
162         setLayout(new FormLayout());
163
164         // Sash
165         sash = new Sash(this, /*SWT.BORDER |*/ SWT.HORIZONTAL);
166         sash.addListener(SWT.Selection, new Listener () {
167             public void handleEvent(Event e) {
168                 Rectangle bounds = AbstractCommandConsole.this.getBounds();
169                 int max = bounds.y + bounds.height;
170
171                 userInputHeight = max-e.y;
172                 
173                 int actualInputHeight = Math.max(userInputHeight, minInputHeight);
174                 sash.setBounds(e.x, max-actualInputHeight, e.width, e.height);
175                 setInputHeight(actualInputHeight);
176             }
177         });
178
179         // Upper
180         output = new StyledText(this, SWT.MULTI /*| SWT.READ_ONLY*/ | SWT.V_SCROLL | SWT.H_SCROLL);
181         output.setFont(textFont);
182         output.setLayoutData( formData(0, sash, 0, 100) );
183         output.addVerifyListener(new VerifyListener() {
184             @Override
185             public void verifyText(VerifyEvent e) {
186                 if(outputModiLock)
187                     return;
188                 if (input != null) {
189                     input.append(e.text);
190                     input.setFocus();
191                     input.setCaretOffset(input.getText().length());
192                 }
193                 e.doit = false;
194             }
195         });
196
197         if (hasOption(HIDE_INPUT)) {
198             sash.setLayoutData( formData(new Tuple2(100, 0), null, 0, 100, 0) );
199             layout(true);
200         } else {
201             createInputArea();
202         }
203
204         readPreferences();
205
206         addListener(SWT.Dispose, event -> {
207             if (fontRegistry != null)
208                 fontRegistry.removeListener(fontRegistryListener);
209             if (preferences != null) 
210                 preferences.removePreferenceChangeListener(preferenceListener);
211             try {
212                 writePreferences();
213             } catch (IOException e) {
214                 getLogger().error("Failed to store command history in preferences", e);
215             }
216         });
217     }
218
219     protected void createInputArea() {
220         deco = new StyledText(this, SWT.MULTI | SWT.READ_ONLY);
221         deco.setFont(textFont);
222         deco.setEnabled(false);
223         GC gc = new GC(deco);
224         int inputLeftPos = gc.getFontMetrics().getAverageCharWidth()*2;
225         gc.dispose();
226         deco.setText(">"); //$NON-NLS-1$
227         deco.setLayoutData( formData(sash, 100, 0, new Tuple2(0, inputLeftPos)) );
228
229         // Input area
230         input = new StyledText(this, SWT.MULTI);
231         input.setFont(textFont);
232         input.setLayoutData( formData(sash, 100, new Tuple2(0, inputLeftPos), 100) );
233         adjustInputSize(""); //$NON-NLS-1$
234         input.addVerifyKeyListener(event -> {
235             switch(event.keyCode) {
236             case SWT.KEYPAD_CR:
237             case SWT.CR:
238                 if((event.stateMask & SWT.CTRL) == 0) {
239                     if(canExecuteCommand())
240                         execute();
241                     event.doit = false;
242                 }
243                 break;
244             case SWT.ARROW_UP:
245             case SWT.ARROW_DOWN: 
246                 if((event.stateMask & SWT.CTRL) != 0) {
247                     int targetHistoryPos = commandHistoryPos;
248                     if(event.keyCode == SWT.ARROW_UP) {
249                         if(commandHistoryPos <= 0)
250                             return;
251                         --targetHistoryPos;
252                     }
253                     else {
254                         if(commandHistoryPos >= commandHistory.size()-1)
255                             return;
256                         ++targetHistoryPos;
257                     }
258                     setInputText(commandHistory.get(targetHistoryPos));
259                     commandHistoryPos = targetHistoryPos;
260                     event.doit = false;
261                 }
262                 break;
263 //            case SWT.ESC:
264 //                setInputText("");
265 //                commandHistoryPos = commandHistory.size();
266 //                break;
267             }
268         });
269         input.addVerifyListener(e -> {
270             if(e.text.contains("\n")) { //$NON-NLS-1$
271                 int lineId = input.getLineAtOffset(e.start);
272                 int lineOffset = input.getOffsetAtLine(lineId);
273                 int indentAmount;
274                 for(indentAmount=0;
275                         lineOffset+indentAmount < input.getCharCount() && 
276                         input.getTextRange(lineOffset+indentAmount, 1).equals(" "); //$NON-NLS-1$
277                         ++indentAmount);
278                 StringBuilder indent = new StringBuilder();
279                 indent.append('\n');
280                 for(int i=0;i<indentAmount;++i)
281                     indent.append(' ');
282                 e.text = e.text.replace("\n", indent); //$NON-NLS-1$
283             }
284         });
285         input.addModifyListener(e -> {
286             adjustInputSize(input.getText());
287             commandHistoryPos = commandHistory.size();
288             //asyncValidate();
289         });
290         Listener hoverListener = new Listener() {
291             
292             DefaultToolTip toolTip = new DefaultToolTip(input, ToolTip.RECREATE, true);
293             
294             int min, max;
295             boolean toolTipVisible = false;
296             
297             @Override
298             public void handleEvent(Event e) {
299                 switch(e.type) {
300                 case SWT.MouseHover: {
301                     int offset = getOffsetInInput(e.x, e.y);
302                     if(offset == -1)
303                         return;
304                     
305                     min = Integer.MIN_VALUE;
306                     max = Integer.MAX_VALUE;
307                     StringBuilder description = new StringBuilder();
308                     boolean first = true;
309                     for(ErrorAnnotation annotation : errorAnnotations) {
310                         if(annotation.start <= offset && annotation.end > offset) {
311                             min = Math.max(min, annotation.start);
312                             max = Math.max(min, annotation.end);
313                             if(first)
314                                 first = false;
315                             else
316                                 description.append('\n');
317                             description.append(annotation.description);
318                         }
319                     }
320                     
321                     if(min != Integer.MIN_VALUE) {
322                         Rectangle bounds = input.getTextBounds(min, max-1);
323                         toolTip.setText(description.toString());
324                         toolTip.show(new Point(bounds.x, bounds.y+bounds.height));
325                         toolTipVisible = true;
326                     }
327                     return;
328                 }
329                 case SWT.MouseMove:
330                     if(toolTipVisible) {
331                         int offset = getOffsetInInput(e.x, e.y);
332                         if(offset < min || offset >= max) {
333                             toolTip.hide();
334                             toolTipVisible = false;
335                             return;
336                         }
337                     }
338                     return;
339                 case SWT.MouseExit:
340                     if(toolTipVisible) {
341                         toolTip.hide();
342                         toolTipVisible = false;
343                     }
344                     return;
345                 }
346             }
347         };
348         input.addListener(SWT.MouseHover, hoverListener);
349         input.addListener(SWT.MouseMove, hoverListener);
350         input.addListener(SWT.MouseExit, hoverListener);
351     }
352
353     private FormData formData(Object top, Object bottom, Object left, Object right) {
354         return formData(top, bottom, left, right, null);
355     }
356
357     private FormData formData(Object top, Object bottom, Object left, Object right, Integer height) {
358         FormData d = new FormData();
359         d.top = formAttachment(top);
360         d.bottom = formAttachment(bottom);
361         d.left = formAttachment(left);
362         d.right = formAttachment(right);
363         d.height = height != null ? (Integer) height : SWT.DEFAULT;
364         return d;
365     }
366
367     private FormAttachment formAttachment(Object o) {
368         if (o == null)
369             return null;
370         if (o instanceof Control)
371             return new FormAttachment((Control) o);
372         if (o instanceof Integer)
373             return new FormAttachment((Integer) o);
374         if (o instanceof Tuple2) {
375             Tuple2 t = (Tuple2) o;
376             return new FormAttachment((Integer) t.c0, (Integer) t.c1);
377         }
378         throw new IllegalArgumentException("argument not supported: " + o); //$NON-NLS-1$
379     }
380
381     private int getOffsetInInput(int x, int y) {
382         int offset;
383         try {
384             offset = input.getOffsetAtLocation(new Point(x, y));
385         } catch(IllegalArgumentException e) {
386             return -1;
387         }
388         if(offset == input.getText().length())
389             --offset;
390         else if(offset > 0) {
391             Rectangle rect = input.getTextBounds(offset, offset);
392             if(!rect.contains(x, y))
393                 --offset;
394         }
395         return offset;
396     }
397     
398     public void setInputText(String text) {
399         if (input == null)
400             return;
401         input.setText(text);
402         input.setCaretOffset(text.length());
403         adjustInputSize(text);
404     }
405     
406     String validatedText;
407     
408     Job validationJob = new Job("SCL input validation") { //$NON-NLS-1$
409
410         @Override
411         protected IStatus run(IProgressMonitor monitor) {
412             String text = validatedText;
413             asyncSetErrorAnnotations(text, validate(text));
414             return Status.OK_STATUS;
415         }
416         
417     };
418     
419     Job preValidationJob = new Job("SCL input validation") { //$NON-NLS-1$
420         @Override
421         protected IStatus run(IProgressMonitor monitor) {
422             if(!input.isDisposed()) {
423                 input.getDisplay().asyncExec(() -> {
424                     if(!input.isDisposed()) {
425                         validatedText = input.getText();
426                         validationJob.setPriority(Job.BUILD);
427                         validationJob.schedule();
428                     }
429                 });
430             }
431             
432             return Status.OK_STATUS;
433         }
434     };
435     
436     private void asyncValidate() {
437         if(!input.getText().equals(errorAnnotationsForCommand)) {
438             preValidationJob.cancel();
439             preValidationJob.setPriority(Job.BUILD);
440             preValidationJob.schedule(500); 
441         }
442     }
443     
444     private static int rowCount(String text) {
445         int rowCount = 1;
446         for(int i=0;i<text.length();++i)
447             if(text.charAt(i)=='\n')
448                 ++rowCount;
449         return rowCount;
450     }
451     
452     private void adjustInputSize(String text) {
453         int lineHeight = input.getLineHeight();
454         int height = rowCount(text)*lineHeight+SASH_HEIGHT;
455         if(height != minInputHeight) {
456             minInputHeight = height;
457             setInputHeight(Math.max(minInputHeight, userInputHeight));
458         }
459     }
460     
461     private void setInputHeight(int inputHeight) {
462         sash.setLayoutData( formData(new Tuple2(100, -inputHeight), null, 0, 100, SASH_HEIGHT) );
463         AbstractCommandConsole.this.layout(true);
464     }
465
466     private StringBuilder outputBuffer = new StringBuilder();
467     private ArrayList<StyleRange> styleRanges = new ArrayList<StyleRange>();
468     private AtomicBoolean outputScheduled = new AtomicBoolean(false);
469
470     public void appendOutput(final String text, final Color foreground, final Color background) {
471         boolean scheduleOutput = false;
472         synchronized (outputBuffer) {
473             styleRanges.add(new StyleRange(outputBuffer.length(), text.length(), foreground, background));
474             outputBuffer.append(text);
475             scheduleOutput = outputScheduled.compareAndSet(false, true);
476         }
477         if(scheduleOutput) {
478             final Display display = Display.getDefault();
479             if(display.isDisposed()) return;
480             display.asyncExec(() -> {
481                 if(output.isDisposed()) return;
482                 String outputText;
483                 StyleRange[] styleRangeArray;
484                 synchronized(outputBuffer) {
485                     outputScheduled.set(false);
486
487                     outputText = outputBuffer.toString();
488                     outputBuffer = new StringBuilder();
489
490                     styleRangeArray = styleRanges.toArray(new StyleRange[styleRanges.size()]);
491                     styleRanges.clear();
492                 }
493
494                 int addedLength = outputText.length();
495                 int currentLength = output.getCharCount();
496                 int insertPos = currentLength;
497                 int newLength = insertPos + addedLength;
498
499                 if (limitConsoleOutput && newLength > highWatermark) {
500                     // Test for corner case: buffer overflows and more text is incoming than fits low watermark
501                     if (addedLength > lowWatermark) {
502                         // Prune the new input text first if it is too large to fit in the buffer even on its own to be < lowWatermark
503                         int removedCharacters = addedLength - lowWatermark;
504
505                         outputText = outputText.substring(removedCharacters);
506                         addedLength = outputText.length();
507                         newLength = insertPos + addedLength;
508
509                         // Prune new incoming style ranges also
510                         int firstStyleRangeToCopy = 0;
511                         for (int i = 0; i < styleRangeArray.length; ++i, ++firstStyleRangeToCopy) {
512                             StyleRange sr = styleRangeArray[i];
513                             if ((sr.start + sr.length) > removedCharacters) {
514                                 if (sr.start < removedCharacters)
515                                     sr.start = removedCharacters;
516                                 break;
517                             }
518                         }
519                         styleRangeArray = Arrays.copyOfRange(styleRangeArray, firstStyleRangeToCopy, styleRangeArray.length);
520                         for (StyleRange sr : styleRangeArray)
521                             sr.start -= removedCharacters;
522                     }
523
524                     int minimallyRemoveFromBegin = Math.min(currentLength, newLength - lowWatermark);
525
526                     // Find the next line change to prune the text until then
527                     StyledTextContent content = output.getContent();
528                     int lineCount = content.getLineCount();
529                     int lastRemovedLine = content.getLineAtOffset(minimallyRemoveFromBegin);
530                     int removeUntilOffset = lastRemovedLine >= (lineCount-1)
531                             ? currentLength
532                             : content.getOffsetAtLine(lastRemovedLine + 1);
533
534                     insertPos -= removeUntilOffset;
535
536                     outputModiLock = true;
537                     output.replaceTextRange(0, removeUntilOffset, "");
538                     output.replaceTextRange(insertPos, 0, outputText);
539                     outputModiLock = false;
540                 } else {
541                     // Buffer does not need to be pruned, just append at end
542                     outputModiLock = true;
543                     output.replaceTextRange(insertPos, 0, outputText);
544                     outputModiLock = false;
545                 }
546
547                 for (StyleRange styleRange : styleRangeArray) {
548                     styleRange.start += insertPos;
549                     output.setStyleRange(styleRange);
550                 }
551
552                 output.setCaretOffset(output.getCharCount());
553                 output.showSelection();
554             });
555         }
556     }
557
558     private void execute() {
559         String command = input.getText().trim();
560         if(command.isEmpty())
561             return;
562         
563         // Add command to command history
564         if(commandHistory.isEmpty() || !commandHistory.get(commandHistory.size()-1).equals(command)) {
565             commandHistory.add(command);
566             if(commandHistory.size() > COMMAND_HISTORY_SIZE*2)
567                 commandHistory = new ArrayList<String>(
568                         commandHistory.subList(COMMAND_HISTORY_SIZE, COMMAND_HISTORY_SIZE*2));
569         }
570         commandHistoryPos = commandHistory.size();
571         
572         // Print it into output area
573         //appendOutput("> " + command.replace("\n", "\n  ") + "\n", greenColor, null);
574         input.setText(""); //$NON-NLS-1$
575         
576         // Execute
577         execute(command);
578     }
579     
580     public static final  ErrorAnnotation[] EMPTY_ANNOTATION_ARRAY = new ErrorAnnotation[0]; 
581     
582     String errorAnnotationsForCommand;
583     ErrorAnnotation[] errorAnnotations = EMPTY_ANNOTATION_ARRAY;
584     
585     private void syncSetErrorAnnotations(String forCommand, ErrorAnnotation[] annotations) {
586         errorAnnotationsForCommand = forCommand;
587         errorAnnotations = annotations;
588
589         {
590             StyleRange clearRange = new StyleRange(0, forCommand.length(), null, null);
591             input.setStyleRange(clearRange);
592         }
593         
594         for(int i=0;i<annotations.length;++i) {
595             ErrorAnnotation annotation = annotations[i];
596             StyleRange range = new StyleRange(
597                     annotation.start,
598                     annotation.end-annotation.start,
599                     null,
600                     null
601                     );
602             range.underline = true;
603             range.underlineColor = redColor;
604             range.underlineStyle = SWT.UNDERLINE_SQUIGGLE;
605             try {
606                 input.setStyleRange(range);
607             } catch(IllegalArgumentException e) {
608                 range.start = 0;
609                 range.length = 1;
610                 input.setStyleRange(range);
611                 getLogger().error("The following error message didn't have a proper location: {}", annotation.description, e); //$NON-NLS-1$
612             }
613         }
614     }
615     
616     private void asyncSetErrorAnnotations(final String forCommand, final ErrorAnnotation[] annotations) {
617         if(input.isDisposed())
618             return;
619         input.getDisplay().asyncExec(() -> {
620             if(input.isDisposed())
621                 return;
622             if(!input.getText().equals(forCommand))
623                 return;
624             syncSetErrorAnnotations(forCommand, annotations);
625         });
626     }
627
628     private boolean readPreferences() {
629         
630         IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID);
631
632         String commandHistoryPref = store.getString(Preferences.COMMAND_HISTORY);
633         Deque<String> recentImportPaths = Preferences.decodePaths(commandHistoryPref);
634         
635         commandHistory = new ArrayList<String>(recentImportPaths);
636         commandHistoryPos = commandHistory.size();
637
638         limitConsoleOutput = store.getBoolean(Preferences.CONSOLE_LIMIT_CONSOLE_OUTPUT);
639         lowWatermark = store.getInt(Preferences.CONSOLE_LOW_WATER_MARK);
640         highWatermark = store.getInt(Preferences.CONSOLE_HIGH_WATER_MARK);
641
642         preferences = InstanceScope.INSTANCE.getNode(PLUGIN_ID);
643         preferences.addPreferenceChangeListener(preferenceListener);
644
645         return true;
646     }
647
648     private void writePreferences() throws IOException {
649         
650         IPersistentPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID);
651
652         store.putValue(Preferences.COMMAND_HISTORY, Preferences.encodePaths(commandHistory));
653
654         if (store.needsSaving())
655             store.save();
656         
657     }
658     
659     public abstract void execute(String command);
660     public abstract ErrorAnnotation[] validate(String command);
661     
662     public void clear() {
663         outputModiLock = true;
664         output.setText(""); //$NON-NLS-1$
665         outputModiLock = false;
666     }
667
668     public StyledText getOutputWidget() {
669         return output;
670     }
671
672     IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() {
673         @Override
674         public void propertyChange(PropertyChangeEvent event) {
675             setTextFont( FontDescriptor.createFrom((FontData[]) event.getNewValue()) );
676         }
677     };
678
679     private void setTextFont(FontDescriptor font) {
680         FontDescriptor oldFontDesc = textFontDescriptor;
681         textFont = resourceManager.createFont(font);
682         textFontDescriptor = font;
683         applyTextFont(textFont);
684
685         // Only destroy old font after the new font has been set!
686         if (oldFontDesc != null)
687             resourceManager.destroyFont(oldFontDesc);
688     }
689
690     private void applyTextFont(Font font) {
691         if (output != null)
692             output.setFont(font);
693         if (deco != null)
694             deco.setFont(font);
695         if (input != null) {
696             input.setFont(font);
697             adjustInputSize(input.getText());
698         }
699     }
700
701     public abstract Logger getLogger();
702 }