/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package liveplugin.pluginrunner; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; import com.intellij.util.ui.UIUtil; import liveplugin.IDEUtil; import liveplugin.Icons; import liveplugin.LivePluginAppComponent; import liveplugin.Settings; import liveplugin.toolwindow.PluginToolWindowManager; import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.*; import static com.intellij.util.containers.ContainerUtil.find; import static com.intellij.util.containers.ContainerUtil.map; import static java.util.Collections.emptyList; import static liveplugin.IDEUtil.SingleThreadBackgroundRunner; import static liveplugin.LivePluginAppComponent.*; import static liveplugin.pluginrunner.GroovyPluginRunner.MAIN_SCRIPT; import static liveplugin.pluginrunner.PluginRunner.IDE_STARTUP; public class RunPluginAction extends AnAction implements DumbAware { private static final SingleThreadBackgroundRunner backgroundRunner = new SingleThreadBackgroundRunner("LivePlugin thread"); private static final Function<Runnable,Void> RUN_ON_EDT = runnable -> { UIUtil.invokeAndWaitIfNeeded(runnable); return null; }; private static final String DISPOSABLE_KEY = "pluginDisposable"; private static final WeakHashMap<String, Map<String, Object>> bindingByPluginId = new WeakHashMap<>(); public RunPluginAction() { super("Run Plugin", "Run selected plugins", Icons.RUN_PLUGIN_ICON); } @Override public void actionPerformed(@NotNull AnActionEvent event) { runCurrentPlugin(event); } @Override public void update(@NotNull AnActionEvent event) { event.getPresentation().setEnabled(!findCurrentPluginIds(event).isEmpty()); } public static void runPlugins(final Collection<String> pluginIds, AnActionEvent event, final ErrorReporter errorReporter, final List<PluginRunner> pluginRunners) { checkThatGroovyIsOnClasspath(); final Project project = event.getProject(); final boolean isIdeStartup = event.getPlace().equals(IDE_STARTUP); if (!isIdeStartup) { Settings.countPluginsUsage(pluginIds); } Runnable runPlugins = () -> { for (final String pluginId : pluginIds) { try { final String pathToPluginFolder = LivePluginAppComponent.pluginIdToPathMap().get(pluginId); // TODO not thread-safe PluginRunner pluginRunner = find(pluginRunners, it -> pathToPluginFolder != null && it.canRunPlugin(pathToPluginFolder)); if (pluginRunner == null) { List<String> scriptNames = map(pluginRunners, it -> it.scriptName()); errorReporter.addNoScriptError(pluginId, scriptNames); } else { final Map<String, Object> oldBinding = bindingByPluginId.get(pluginId); if (oldBinding != null) { ApplicationManager.getApplication().invokeAndWait(() -> { try { Disposer.dispose((Disposable) oldBinding.get(DISPOSABLE_KEY)); } catch (Exception e) { errorReporter.addRunningError(pluginId, e); } }, ModalityState.NON_MODAL); } Map<String, Object> binding = createBinding(pathToPluginFolder, project, isIdeStartup); bindingByPluginId.put(pluginId, binding); pluginRunner.runPlugin(pathToPluginFolder, pluginId, binding, RUN_ON_EDT); } } catch (Exception e) { errorReporter.addLoadingError(pluginId, e); } finally { errorReporter.reportAllErrors((title, message) -> IDEUtil.displayError(title, message, project)); } } }; backgroundRunner.run(project, "Loading plugin", runPlugins); } public static List<PluginRunner> createPluginRunners(ErrorReporter errorReporter) { List<PluginRunner> result = new ArrayList<>(); result.add(new GroovyPluginRunner(MAIN_SCRIPT, errorReporter, environment())); if (kotlinCompilerIsOnClassPath()) result.add(new KotlinPluginRunner(errorReporter, environment())); if (scalaIsOnClassPath()) result.add(new ScalaPluginRunner(errorReporter, environment())); if (clojureIsOnClassPath()) result.add(new ClojurePluginRunner(errorReporter, environment())); return result; } private static Map<String, Object> createBinding(final String pathToPluginFolder, Project project, boolean isIdeStartup) { Disposable disposable = new Disposable() { @Override public void dispose() {} @Override public String toString() { return "LivePlugin: " + pathToPluginFolder; } }; Disposer.register(ApplicationManager.getApplication(), disposable); Map<String, Object> binding = new HashMap<>(); binding.put("project", project); binding.put("isIdeStartup", isIdeStartup); binding.put("pluginPath", pathToPluginFolder); binding.put(DISPOSABLE_KEY, disposable); return binding; } static Map<String, String> environment() { return new HashMap<>(System.getenv()); } static List<String> findCurrentPluginIds(AnActionEvent event) { List<String> pluginIds = pluginsSelectedInToolWindow(event); if (!pluginIds.isEmpty() && pluginToolWindowHasFocus(event)) { return pluginIds; } else { return pluginForCurrentlyOpenFile(event); } } private static boolean pluginToolWindowHasFocus(AnActionEvent event) { PluginToolWindowManager.PluginToolWindow pluginToolWindow = PluginToolWindowManager.getToolWindowFor(event.getProject()); return pluginToolWindow != null && pluginToolWindow.isActive(); } private static List<String> pluginsSelectedInToolWindow(AnActionEvent event) { // TODO get selected plugins through DataContext PluginToolWindowManager.PluginToolWindow pluginToolWindow = PluginToolWindowManager.getToolWindowFor(event.getProject()); if (pluginToolWindow == null) return emptyList(); return pluginToolWindow.selectedPluginIds(); } private static List<String> pluginForCurrentlyOpenFile(AnActionEvent event) { Project project = event.getProject(); if (project == null) return emptyList(); Editor selectedTextEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); if (selectedTextEditor == null) return emptyList(); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(selectedTextEditor.getDocument()); if (virtualFile == null) return emptyList(); final File file = new File(virtualFile.getPath()); Map.Entry<String, String> entry = find(LivePluginAppComponent.pluginIdToPathMap().entrySet(), entry1 -> { String pluginPath = entry1.getValue(); return FileUtil.isAncestor(new File(pluginPath), file, false); }); if (entry == null) return emptyList(); return Collections.singletonList(entry.getKey()); } private void runCurrentPlugin(AnActionEvent event) { IDEUtil.saveAllFiles(); List<String> pluginIds = findCurrentPluginIds(event); ErrorReporter errorReporter = new ErrorReporter(); runPlugins(pluginIds, event, errorReporter, createPluginRunners(errorReporter)); } }