package org.simantics.scl.ui.console; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuCreator; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.preferences.ScopedPreferenceStore; import org.simantics.scl.compiler.commands.CommandSessionImportEntry; import org.simantics.scl.compiler.commands.SCLConsoleListener; import org.simantics.scl.compiler.module.repository.UpdateListener; import org.simantics.scl.compiler.testing.TestRunnable; import org.simantics.scl.osgi.internal.TestUtils; import org.simantics.scl.ui.Activator; import org.simantics.scl.ui.imports.internal.ManageImportsDialog; import org.simantics.scl.ui.tests.SCLTestsDialog; public class SCLConsoleView extends ViewPart { public static final String PLUGIN_ID = "org.simantics.scl.ui"; public static final String IMPORTS = "imports"; public static final String REFRESH_AUTOMATICALLY = "refresh-automatically"; public static final String SEPARATOR = ";"; public static final String DISABLED_TAG = "[DISABLED]"; IPersistentPreferenceStore store; SCLConsole console; boolean refreshAutomatically = false; MenuItem refreshAutomaticallyItem; private ArrayList readImportPreferences() { String importsString = store.getString(IMPORTS); String[] splitted = importsString.split(SEPARATOR); ArrayList result = new ArrayList(splitted.length); for(String entryString : splitted) { if(entryString.isEmpty()) continue; boolean disabled = false; if(entryString.startsWith(DISABLED_TAG)) { disabled = true; entryString = entryString.substring(DISABLED_TAG.length()); } String[] parts = entryString.split("="); CommandSessionImportEntry entry; if(parts.length == 1) entry = new CommandSessionImportEntry(parts[0], "", true); else entry = new CommandSessionImportEntry(parts[1], parts[0], true); entry.disabled = disabled; result.add(entry); } return result; } private void writeImportPreferences(ArrayList entries) { StringBuilder b = new StringBuilder(); boolean first = true; for(CommandSessionImportEntry entry : entries) if(entry.persistent) { if(first) first = false; else b.append(SEPARATOR); if(entry.disabled) b.append(DISABLED_TAG); if(!entry.localName.isEmpty()) { b.append(entry.localName); b.append("="); } b.append(entry.moduleName); } IPersistentPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); store.setValue(IMPORTS, b.toString()); } private ArrayList getCurrentImports() { return console.getSession().getImportEntries(); } private void setCurrentImports(ArrayList entries) { console.getSession().setImportEntries(entries); } private void manageImports() { ManageImportsDialog dialog = new ManageImportsDialog( getSite().getShell(), getCurrentImports()); if(dialog.open() == Dialog.OK) { writeImportPreferences(dialog.getImports()); setCurrentImports(dialog.getImports()); } } private void sclTestDialog() { List tests = TestUtils.getTests(); SCLTestsDialog dialog = new SCLTestsDialog( getSite().getShell(), tests, true); if(dialog.open() == Dialog.OK) { for(Object result : dialog.getResult()) { TestRunnable test = (TestRunnable) result; try { // Bit of a haxx solution to get around a deadlock caused by simply // running the test with test.run() console.execute("import \"Commands/Tests\""); console.execute("runByName \"" + test.getName() + "\""); // test.run(); } catch (Exception e) { e.printStackTrace(); } } } } private UpdateListener dependencyListener = new UpdateListener() { @Override public void notifyAboutUpdate() { if(refreshAutomatically) console.getSession().updateRuntimeEnvironment(true); } }; private void setRefreshAutomatically(boolean refreshAutomatically, boolean refreshAlso) { this.refreshAutomatically = refreshAutomatically; if(refreshAutomaticallyItem != null) refreshAutomaticallyItem.setSelection(refreshAutomatically); store.setValue(REFRESH_AUTOMATICALLY, refreshAutomatically); if(refreshAutomatically) { console.getSession().setDependenciesListener(dependencyListener); if(refreshAlso) console.getSession().updateRuntimeEnvironment(true); } else { console.getSession().setDependenciesListener(null); dependencyListener.stopListening(); } } @Override public void createPartControl(Composite parent) { store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); this.console = new SCLConsole(parent, SWT.NONE); setRefreshAutomatically(store.getBoolean(REFRESH_AUTOMATICALLY), false); setCurrentImports(readImportPreferences()); addScriptDropSupport(console); IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager(); // Interrupt action final Action interruptAction = new Action("Interrupt current command", Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/stop.png")) { @Override public void run() { console.interruptCurrentCommands(); } }; interruptAction.setDisabledImageDescriptor( Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/stop_disabled.png")); interruptAction.setEnabled(false); toolBarManager.add(interruptAction); // Clear console action final Action clearAction = new Action("Clear console", Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/clear_console.png")) { @Override public void run() { setEnabled(false); console.clear(); } }; clearAction.setDisabledImageDescriptor( Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/clear_console_disabled.png")); clearAction.setEnabled(false); toolBarManager.add(clearAction); console.addListener(new SCLConsoleListener() { @Override public void startedExecution() { interruptAction.setEnabled(true); } @Override public void finishedExecution() { interruptAction.setEnabled(false); } @Override public void consoleIsNotEmptyAnymore() { clearAction.setEnabled(true); } }); // Refresh action toolBarManager.add(new Action("Refresh modules", IAction.AS_DROP_DOWN_MENU) { { setImageDescriptor(Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/arrow_refresh.png")); setMenuCreator(new IMenuCreator() { Menu menu; @Override public Menu getMenu(Menu parent) { throw new UnsupportedOperationException(); } @Override public Menu getMenu(Control parent) { if(menu == null) { menu = new Menu(parent); refreshAutomaticallyItem = new MenuItem(menu, SWT.CHECK); refreshAutomaticallyItem.setText("Refresh automatically"); refreshAutomaticallyItem.setSelection(refreshAutomatically); refreshAutomaticallyItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { setRefreshAutomatically(!refreshAutomatically, true); } }); } return menu; } @Override public void dispose() { if(menu != null) menu.dispose(); } }); } @Override public void run() { console.getSession().getModuleRepository().getSourceRepository().checkUpdates(); console.getSession().updateRuntimeEnvironment(true); console.appendOutput("refresh completed\n", console.greenColor, null); } }); toolBarManager.add(new Action("Manage imports", Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/configure_imports.png")) { @Override public void run() { manageImports(); } }); // Show action for running SCL tests if in development mode if (Platform.inDevelopmentMode()) { toolBarManager.add(new Action("Run tests", Activator.imageDescriptorFromPlugin("org.simantics.scl.ui", "icons/run_tests.png")) { @Override public void run() { sclTestDialog(); } }); } toolBarManager.update(true); } private class ScriptRunningDropTarget extends DropTargetAdapter { @Override public void dragEnter(DropTargetEvent event) { if (event.detail == DND.DROP_DEFAULT) { event.detail = DND.DROP_LINK; } } @Override public void dragOperationChanged(DropTargetEvent event) { if (event.detail == DND.DROP_DEFAULT) { event.detail = DND.DROP_LINK; } } public void drop(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { String[] files = ((String[]) event.data).clone(); // Sort files by name to allow deterministic multi-file drop Arrays.sort(files); for (String file : files) { Path p = Paths.get(file).toAbsolutePath(); if (isScriptFile(p)) { console.execute("runFromFile \"" + p.toString().replace('\\', '/') + "\""); } } } } private boolean isScriptFile(Path p) { return Files.isRegularFile(p) //&& p.toString().toLowerCase().endsWith(".scl") ; } } private void addScriptDropSupport(SCLConsole console) { DropTarget target = new DropTarget(console.getOutputWidget(), DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK | DND.DROP_DEFAULT); target.setTransfer(new Transfer[] { FileTransfer.getInstance() }); target.addDropListener(new ScriptRunningDropTarget()); } @Override public void setFocus() { console.setFocus(); } @Override public void dispose() { super.dispose(); console.dispose(); } }