// Copyright © 2010, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package net.orfjackal.sbt.plugin; import com.intellij.execution.console.LanguageConsoleImpl; import com.intellij.execution.filters.*; import com.intellij.execution.impl.ConsoleViewImpl; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.Key; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import javax.swing.*; import java.awt.*; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; public class SbtConsole { // org.jetbrains.idea.maven.embedder.MavenConsoleImpl private static final Logger logger = Logger.getInstance(SbtConsole.class.getName()); private static final Key<SbtConsole> CONSOLE_KEY = Key.create("SBT_CONSOLE_KEY"); public static final String CONSOLE_FILTER_REGEXP = "\\s" + RegexpFilter.FILE_PATH_MACROS + ":" + RegexpFilter.LINE_MACROS + ":\\s"; private final String title; private final Project project; private final ConsoleView consoleView; private final AtomicBoolean isOpen = new AtomicBoolean(false); private final SbtRunnerComponent runnerComponent; private boolean finished = false; public SbtConsole(String title, Project project, SbtRunnerComponent runnerComponent) { this.title = title; this.project = project; this.consoleView = createConsoleView(project); this.runnerComponent = runnerComponent; } private static ConsoleView createConsoleView(Project project) { // TODO can we figure out how to make this a LanguageConsole with IDEA 14.1+ // We need that for console history ConsoleView consoleView = createTextConsole(project); addFilters(project, consoleView); return consoleView; } private static ConsoleView createTextConsole(final Project project) { TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project); final SbtColorizerFilter logLevelFilter = new SbtColorizerFilter(); final ExceptionFilter exceptionFilter = new ExceptionFilter(GlobalSearchScope.allScope(project)); final RegexpFilter regexpFilter = new RegexpFilter(project, CONSOLE_FILTER_REGEXP); for (Filter filter : Arrays.asList(exceptionFilter, regexpFilter, logLevelFilter)) { builder.addFilter(filter); } return builder.getConsole(); } private static void addFilters(Project project, ConsoleView consoleView) { consoleView.addMessageFilter(new ExceptionFilter(GlobalSearchScope.allScope(project))); consoleView.addMessageFilter(new RegexpFilter(project, CONSOLE_FILTER_REGEXP)); consoleView.addMessageFilter(new SbtColorizerFilter()); } public boolean isFinished() { return finished; } public void finish() { finished = true; } public void attachToProcess(ProcessHandler processHandler, final SbtRunnerComponent runnerComponent) { consoleView.print(runnerComponent.getFormattedCommand() + "\n\n", ConsoleViewContentType.SYSTEM_OUTPUT); consoleView.attachToProcess(processHandler); processHandler.addProcessListener(new ProcessAdapter() { public void onTextAvailable(ProcessEvent event, Key outputType) { ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(MessageBundle.message("sbt.console.id")); /* When we retrieve a window from ToolWindowManager before SbtToolWindowFactory is called, * we get an undesirable Content */ for (Content each : window.getContentManager().getContents()) { if (each.getUserData(CONSOLE_KEY) == null) { window.getContentManager().removeContent(each, false); } } ensureAttachedToToolWindow(window, true); } }); } public void processTerminated(ProcessEvent event) { finish(); } }); } public final void ensureAttachedToToolWindow(ToolWindow window, boolean activate) { if (!isOpen.compareAndSet(false, true)) { return; } attachToToolWindow(window); if (activate) { if (!window.isActive()) { window.activate(null, false); } } } public void attachToToolWindow(ToolWindow window) { // org.jetbrains.idea.maven.embedder.MavenConsoleImpl#ensureAttachedToToolWindow SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true); JComponent consoleComponent = consoleView.getComponent(); toolWindowPanel.setContent(consoleComponent); StartSbtAction startSbtAction = new StartSbtAction(); toolWindowPanel.setToolbar(createToolbar(startSbtAction)); startSbtAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), consoleComponent); Content content = ContentFactory.SERVICE.getInstance().createContent(toolWindowPanel, title, true); content.putUserData(CONSOLE_KEY, SbtConsole.this); window.getContentManager().addContent(content); window.getContentManager().setSelectedContent(content); removeUnusedTabs(window, content); } private JComponent createToolbar(AnAction startSbtAction) { JPanel toolbarPanel = new JPanel(new GridLayout()); DefaultActionGroup group = new DefaultActionGroup(); AnAction killSbtAction = new KillSbtAction(); group.add(startSbtAction); group.add(killSbtAction); // Adds "Next/Prev hyperlink", "Use Soft Wraps", and "Scroll to End" AnAction[] actions = consoleView.createConsoleActions(); for (AnAction action : actions) { group.add(action); } toolbarPanel.add(ActionManager.getInstance().createActionToolbar("SbtConsoleToolbar", group, false).getComponent()); return toolbarPanel; } private void removeUnusedTabs(ToolWindow window, Content content) { for (Content each : window.getContentManager().getContents()) { if (each.isPinned()) { continue; } if (each == content) { continue; } SbtConsole console = each.getUserData(CONSOLE_KEY); if (console == null) { continue; } if (!title.equals(console.title)) { continue; } if (console.isFinished()) { window.getContentManager().removeContent(each, false); } } } public void scrollToEnd() { (((ConsoleViewImpl) consoleView)).scrollToEnd(); } private class StartSbtAction extends DumbAwareAction { public StartSbtAction() { super("Start SBT", "Start SBT", IconLoader.getIcon("/toolwindows/toolWindowRun.png")); } @Override public void actionPerformed(AnActionEvent event) { runnerComponent.startIfNotStartedSafe(false); } @Override public void update(AnActionEvent event) { event.getPresentation().setEnabled(!runnerComponent.isSbtAlive()); } } private class KillSbtAction extends DumbAwareAction { public KillSbtAction() { super("Kill SBT", "Forcibly kill the SBT process", IconLoader.getIcon("/debugger/killProcess.png")); } @Override public void actionPerformed(AnActionEvent event) { runnerComponent.destroyProcess(); } @Override public void update(AnActionEvent event) { event.getPresentation().setEnabled(runnerComponent.isSbtAlive()); } } }