Fixed SCLConsoleView.setCurrentImports to be performed in a Job
[simantics/platform.git] / bundles / org.simantics.scl.ui / src / org / simantics / scl / ui / console / SCLConsoleView.java
1 package org.simantics.scl.ui.console;
2
3 import java.nio.file.Files;
4 import java.nio.file.Path;
5 import java.nio.file.Paths;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.List;
9 import java.util.concurrent.atomic.AtomicReference;
10
11 import org.eclipse.core.runtime.IProgressMonitor;
12 import org.eclipse.core.runtime.IStatus;
13 import org.eclipse.core.runtime.Platform;
14 import org.eclipse.core.runtime.Status;
15 import org.eclipse.core.runtime.jobs.Job;
16 import org.eclipse.core.runtime.preferences.InstanceScope;
17 import org.eclipse.jface.action.Action;
18 import org.eclipse.jface.action.IAction;
19 import org.eclipse.jface.action.IMenuCreator;
20 import org.eclipse.jface.action.IToolBarManager;
21 import org.eclipse.jface.dialogs.Dialog;
22 import org.eclipse.jface.preference.IPersistentPreferenceStore;
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.dnd.DND;
25 import org.eclipse.swt.dnd.DropTarget;
26 import org.eclipse.swt.dnd.DropTargetAdapter;
27 import org.eclipse.swt.dnd.DropTargetEvent;
28 import org.eclipse.swt.dnd.FileTransfer;
29 import org.eclipse.swt.dnd.Transfer;
30 import org.eclipse.swt.events.SelectionAdapter;
31 import org.eclipse.swt.events.SelectionEvent;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.swt.widgets.Control;
34 import org.eclipse.swt.widgets.Menu;
35 import org.eclipse.swt.widgets.MenuItem;
36 import org.eclipse.ui.PlatformUI;
37 import org.eclipse.ui.part.ViewPart;
38 import org.eclipse.ui.preferences.ScopedPreferenceStore;
39 import org.simantics.scl.compiler.commands.CommandSession;
40 import org.simantics.scl.compiler.commands.CommandSessionImportEntry;
41 import org.simantics.scl.compiler.commands.SCLConsoleListener;
42 import org.simantics.scl.compiler.module.repository.UpdateListener;
43 import org.simantics.scl.compiler.testing.TestRunnable;
44 import org.simantics.scl.osgi.internal.TestUtils;
45 import org.simantics.scl.runtime.reporting.SCLReportingHandler;
46 import org.simantics.scl.ui.Activator;
47 import org.simantics.scl.ui.imports.internal.ManageImportsDialog;
48 import org.simantics.scl.ui.tests.SCLTestsDialog;
49
50 public class SCLConsoleView extends ViewPart {
51
52     public static final String PLUGIN_ID = "org.simantics.scl.ui"; //$NON-NLS-1$
53     public static final String IMPORTS = "imports"; //$NON-NLS-1$
54     public static final String REFRESH_AUTOMATICALLY = "refresh-automatically"; //$NON-NLS-1$
55     public static final String SEPARATOR = ";"; //$NON-NLS-1$
56     public static final String DISABLED_TAG = "[DISABLED]"; //$NON-NLS-1$
57     
58     IPersistentPreferenceStore store;
59     SCLConsole console;
60     boolean refreshAutomatically = false;
61     MenuItem refreshAutomaticallyItem;
62     
63     private ArrayList<CommandSessionImportEntry> readImportPreferences() {
64         String importsString = store.getString(IMPORTS);
65         
66         String[] splitted = importsString.split(SEPARATOR);
67         ArrayList<CommandSessionImportEntry> result = new ArrayList<CommandSessionImportEntry>(splitted.length);
68         for(String entryString : splitted) {
69             if(entryString.isEmpty())
70                 continue;
71             boolean disabled = false;
72             if(entryString.startsWith(DISABLED_TAG)) {
73                 disabled = true;
74                 entryString = entryString.substring(DISABLED_TAG.length());
75             }
76             String[] parts = entryString.split("="); //$NON-NLS-1$
77             CommandSessionImportEntry entry;
78             if(parts.length == 1)
79                 entry = new CommandSessionImportEntry(parts[0], "", true); //$NON-NLS-1$
80             else
81                 entry = new CommandSessionImportEntry(parts[1], parts[0], true);
82             entry.disabled = disabled;
83             result.add(entry);
84         }
85         return result;
86     }
87     
88     private void writeImportPreferences(ArrayList<CommandSessionImportEntry> entries) {
89         StringBuilder b = new StringBuilder();
90         
91         boolean first = true;
92         for(CommandSessionImportEntry entry : entries)
93             if(entry.persistent) {
94                 if(first)
95                     first = false;
96                 else
97                     b.append(SEPARATOR);
98                 if(entry.disabled)
99                     b.append(DISABLED_TAG);
100                 if(!entry.localName.isEmpty()) {
101                     b.append(entry.localName);
102                     b.append("="); //$NON-NLS-1$
103                 }
104                 b.append(entry.moduleName);
105             }
106         
107         IPersistentPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID);
108         store.setValue(IMPORTS, b.toString());
109     }
110     
111     private ArrayList<CommandSessionImportEntry> getCurrentImports() {
112         return console.getSession().getImportEntries();
113     }
114     
115     private void setCurrentImports(ArrayList<CommandSessionImportEntry> entries) {
116         console.getSession().setImportEntries(entries);
117     }
118     
119     AtomicReference<ArrayList<CommandSessionImportEntry>> assignedImports = new AtomicReference<>();
120     
121     private class SetImportsJob extends Job {
122
123         public SetImportsJob() {
124             super(Messages.SCLConsoleView_SetImports);
125             setUser(true);
126         }
127
128         @Override
129         protected IStatus run(IProgressMonitor monitor) {
130             try {
131                 ArrayList<CommandSessionImportEntry> entries = assignedImports.getAndSet(null);
132                 if (entries != null)
133                     setCurrentImports(entries);
134                 return Status.OK_STATUS;
135             } finally {
136                 monitor.done();
137             }
138         }
139
140         @Override
141         public boolean shouldSchedule() {
142             return PlatformUI.isWorkbenchRunning();
143         }
144
145         @Override
146         public boolean shouldRun() {
147             return PlatformUI.isWorkbenchRunning();
148         }
149
150     }
151     
152     SetImportsJob setImportsJob = new SetImportsJob();
153     
154     private void scheduleSetCurrentImports(ArrayList<CommandSessionImportEntry> entries) {
155         boolean scheduled = assignedImports.getAndSet(entries) != null;
156         if (!scheduled)
157             setImportsJob.schedule();
158     }
159     
160     private void manageImports() {
161         ManageImportsDialog dialog = new ManageImportsDialog(
162                 getSite().getShell(),
163                 getCurrentImports());
164         if(dialog.open() == Dialog.OK) {
165             writeImportPreferences(dialog.getImports());
166             scheduleSetCurrentImports(dialog.getImports());
167         }
168     }
169     
170     private void sclTestDialog() {
171         List<TestRunnable> tests = TestUtils.getTests();
172         SCLTestsDialog dialog = new SCLTestsDialog(
173                 getSite().getShell(),
174                 tests, true);
175         if(dialog.open() == Dialog.OK) {
176             for(Object result : dialog.getResult()) {
177                 TestRunnable test = (TestRunnable) result;
178                 try {
179                     // Bit of a haxx solution to get around a deadlock caused by simply
180                     // running the test with test.run()
181                     console.execute("import \"Commands/Tests\""); //$NON-NLS-1$
182                     console.execute("runByName \"" + test.getName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
183 //                    test.run();
184                 } catch (Exception e) {
185                     e.printStackTrace();
186                 }
187             }
188         }
189     }
190     
191     private UpdateListener dependencyListener = new UpdateListener() {
192         @Override
193         public void notifyAboutUpdate() {
194             if(refreshAutomatically)
195                 console.getSession().updateRuntimeEnvironment(true);
196         }
197     };
198
199     private void setRefreshAutomatically(boolean refreshAutomatically, boolean refreshAlso) {
200         this.refreshAutomatically = refreshAutomatically;
201         if(refreshAutomaticallyItem != null)
202             refreshAutomaticallyItem.setSelection(refreshAutomatically);
203         
204         store.setValue(REFRESH_AUTOMATICALLY, refreshAutomatically);
205         
206         if(refreshAutomatically) {
207             console.getSession().setDependenciesListener(dependencyListener);
208             if(refreshAlso)
209                 console.getSession().updateRuntimeEnvironment(true);
210         }
211         else {
212             console.getSession().setDependenciesListener(null);
213             dependencyListener.stopListening();
214         }
215     }
216     
217     @Override
218     public void createPartControl(Composite parent) {
219         store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID);
220         store.setDefault(REFRESH_AUTOMATICALLY, true);
221         
222         this.console = new SCLConsole(parent, SWT.NONE);
223
224         IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager();
225         
226         // Interrupt action
227         Action interruptAction = ConsoleActions.createInterruptAction(console);
228         toolBarManager.add(interruptAction);
229         
230         // Clear console action
231         Action clearAction = ConsoleActions.createClearAction(console);
232         toolBarManager.add(clearAction);
233         
234         console.addListener(new SCLConsoleListener() {
235             @Override
236             public void startedExecution() {
237                 interruptAction.setEnabled(true);
238             }
239             @Override
240             public void finishedExecution() {
241                 interruptAction.setEnabled(false);
242             }
243             @Override
244             public void consoleIsNotEmptyAnymore() {
245                 clearAction.setEnabled(true);
246             }
247         });
248         
249         // Refresh action
250         toolBarManager.add(new Action(Messages.SCLConsoleView_RefreshModules, IAction.AS_DROP_DOWN_MENU) {
251             {
252                 setImageDescriptor(Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/arrow_refresh.png")); //$NON-NLS-1$ //$NON-NLS-2$
253                 setMenuCreator(new IMenuCreator() {
254                     Menu menu;
255                     @Override
256                     public Menu getMenu(Menu parent) {
257                         throw new UnsupportedOperationException();
258                     }
259                     
260                     @Override
261                     public Menu getMenu(Control parent) {
262                         if(menu == null) {
263                             menu = new Menu(parent);
264                             refreshAutomaticallyItem = new MenuItem(menu, SWT.CHECK);
265                             refreshAutomaticallyItem.setText(Messages.SCLConsoleView_RefreshAutomatically);
266                             refreshAutomaticallyItem.setSelection(refreshAutomatically);
267                             refreshAutomaticallyItem.addSelectionListener(new SelectionAdapter() {
268                                 @Override
269                                 public void widgetSelected(SelectionEvent e) {
270                                     setRefreshAutomatically(!refreshAutomatically, true);
271                                 }
272                             });
273                         }
274                         return menu;
275                     }
276                     
277                     @Override
278                     public void dispose() {
279                         if(menu != null)
280                             menu.dispose();
281                     }
282                 });
283             }
284             @Override
285             public void run() {
286                 console.getSession().getModuleRepository().getSourceRepository().checkUpdates();
287                 console.getSession().updateRuntimeEnvironment(true);
288                 console.appendOutput(Messages.SCLConsoleView_RefreshCompleted, console.greenColor, null);
289             }
290         });
291         toolBarManager.add(new Action(Messages.SCLConsoleView_ManageImports,
292                 Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/configure_imports.png")) { //$NON-NLS-1$ //$NON-NLS-2$
293             @Override
294             public void run() {
295                 manageImports();
296             }
297         });
298
299         // Show action for running SCL tests if in development mode
300         if (Platform.inDevelopmentMode()) {
301             toolBarManager.add(new Action(Messages.SCLConsoleView_RunTests,
302                     Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/run_tests.png")) { //$NON-NLS-1$ //$NON-NLS-2$
303                 @Override
304                 public void run() {
305                     sclTestDialog();
306                 }
307             });
308         }
309         
310         toolBarManager.update(true);
311
312         setRefreshAutomatically(store.getBoolean(REFRESH_AUTOMATICALLY), false);
313         addScriptDropSupport(console);
314
315         // Do this after the actions and SCLConsoleListener are
316         // registered because it can cause output to the console.
317         scheduleSetCurrentImports(readImportPreferences());
318     }
319
320     private class ScriptRunningDropTarget extends DropTargetAdapter {
321         @Override
322         public void dragEnter(DropTargetEvent event) {
323             if (event.detail == DND.DROP_DEFAULT) {
324                 event.detail = DND.DROP_LINK;
325             }
326         }
327
328         @Override
329         public void dragOperationChanged(DropTargetEvent event) {
330             if (event.detail == DND.DROP_DEFAULT) {
331                 event.detail = DND.DROP_LINK;
332             }
333         }
334
335         public void drop(DropTargetEvent event) {
336             if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
337                 String[] files = ((String[]) event.data).clone();
338                 // Sort files by name to allow deterministic multi-file drop
339                 Arrays.sort(files);
340                 for (String file : files) {
341                     Path p = Paths.get(file).toAbsolutePath();
342                     if (isScriptFile(p)) {
343                         console.execute("runFromFile \"" + p.toString().replace('\\', '/') + "\""); //$NON-NLS-1$ //$NON-NLS-2$
344                     }
345                 }
346             }
347         }
348
349         private boolean isScriptFile(Path p) {
350             return Files.isRegularFile(p)
351                     //&& p.toString().toLowerCase().endsWith(".scl")
352                     ;
353         }
354     }
355
356     private void addScriptDropSupport(SCLConsole console) {
357         DropTarget target = new DropTarget(console.getOutputWidget(), DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK | DND.DROP_DEFAULT);
358         target.setTransfer(new Transfer[] { FileTransfer.getInstance() });
359         target.addDropListener(new ScriptRunningDropTarget());
360     }
361
362     @Override
363     public void setFocus() {
364         console.setFocus();
365     }
366     
367     @Override
368     public void dispose() {
369         super.dispose();
370         console.dispose();
371     }
372
373     @SuppressWarnings("unchecked")
374     @Override
375     public <T> T getAdapter(Class<T> adapter) {
376         if (adapter == CommandSession.class)
377             return (T) console.getSession();
378         if (adapter == SCLReportingHandler.class)
379             return (T) console.getHandler();
380         return super.getAdapter(adapter);
381     }
382
383 }