| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.plugins.groovy.mvc; |
| |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.filters.TextConsoleBuilderFactory; |
| import com.intellij.execution.impl.ConsoleViewImpl; |
| import com.intellij.execution.process.*; |
| import com.intellij.execution.ui.ConsoleViewContentType; |
| import com.intellij.execution.ui.RunnerLayoutUi; |
| import com.intellij.execution.ui.layout.PlaceInGrid; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.ActionPlaces; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.DefaultActionGroup; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.wm.ToolWindow; |
| import com.intellij.openapi.wm.ToolWindowAnchor; |
| import com.intellij.openapi.wm.ToolWindowManager; |
| import com.intellij.ui.content.Content; |
| import com.intellij.ui.content.ContentFactory; |
| import com.intellij.ui.content.ContentManager; |
| import com.intellij.util.DisposeAwareRunnable; |
| import com.intellij.util.containers.ContainerUtil; |
| import icons.JetgroovyIcons; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.io.OutputStreamWriter; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| |
| public class MvcConsole implements Disposable { |
| |
| private static final Key<Boolean> UPDATING_BY_CONSOLE_PROCESS = Key.create("UPDATING_BY_CONSOLE_PROCESS"); |
| |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.mvc.MvcConsole"); |
| private final ConsoleViewImpl myConsole; |
| private final Project myProject; |
| private final ToolWindow myToolWindow; |
| private final JPanel myPanel = new JPanel(new BorderLayout()); |
| private final Queue<MyProcessInConsole> myProcessQueue = new LinkedList<MyProcessInConsole>(); |
| |
| @NonNls private static final String CONSOLE_ID = "Groovy MVC Console"; |
| |
| @NonNls public static final String TOOL_WINDOW_ID = "Console"; |
| |
| private final MyKillProcessAction myKillAction = new MyKillProcessAction(); |
| private boolean myExecuting = false; |
| private final Content myContent; |
| |
| public MvcConsole(Project project, TextConsoleBuilderFactory consoleBuilderFactory) { |
| myProject = project; |
| myConsole = (ConsoleViewImpl)consoleBuilderFactory.createBuilder(myProject).getConsole(); |
| Disposer.register(this, myConsole); |
| |
| myToolWindow = ToolWindowManager.getInstance(myProject).registerToolWindow(TOOL_WINDOW_ID, false, ToolWindowAnchor.BOTTOM, this, true); |
| myToolWindow.setIcon(JetgroovyIcons.Groovy.Groovy_13x13); |
| |
| myContent = setUpToolWindow(); |
| } |
| |
| public static MvcConsole getInstance(@NotNull Project project) { |
| return ServiceManager.getService(project, MvcConsole.class); |
| } |
| |
| public static boolean isUpdatingVfsByConsoleProcess(@NotNull Module module) { |
| Boolean flag = module.getUserData(UPDATING_BY_CONSOLE_PROCESS); |
| return flag != null && flag; |
| } |
| |
| private Content setUpToolWindow() { |
| //Create runner UI layout |
| final RunnerLayoutUi.Factory factory = RunnerLayoutUi.Factory.getInstance(myProject); |
| final RunnerLayoutUi layoutUi = factory.create("", "", "session", myProject); |
| |
| // Adding actions |
| DefaultActionGroup group = new DefaultActionGroup(); |
| group.add(myKillAction); |
| group.addSeparator(); |
| |
| layoutUi.getOptions().setLeftToolbar(group, ActionPlaces.UNKNOWN); |
| |
| final Content console = layoutUi.createContent(CONSOLE_ID, myConsole.getComponent(), "", null, null); |
| layoutUi.addContent(console, 0, PlaceInGrid.right, false); |
| |
| final JComponent uiComponent = layoutUi.getComponent(); |
| myPanel.add(uiComponent, BorderLayout.CENTER); |
| |
| final ContentManager manager = myToolWindow.getContentManager(); |
| final ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); |
| final Content content = contentFactory.createContent(uiComponent, null, true); |
| manager.addContent(content); |
| return content; |
| } |
| |
| public void show(@Nullable final Runnable runnable, boolean focus) { |
| Runnable r = null; |
| if (runnable != null) { |
| r = DisposeAwareRunnable.create(runnable, myProject); |
| } |
| |
| myToolWindow.activate(r, focus); |
| } |
| |
| private static class MyProcessInConsole implements ConsoleProcessDescriptor { |
| final Module module; |
| final GeneralCommandLine commandLine; |
| @Nullable final Runnable onDone; |
| final boolean closeOnDone; |
| final boolean showConsole; |
| final String[] input; |
| private final List<ProcessListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| |
| private OSProcessHandler myHandler; |
| |
| public MyProcessInConsole(final Module module, |
| final GeneralCommandLine commandLine, |
| final Runnable onDone, |
| final boolean showConsole, |
| final boolean closeOnDone, |
| final String[] input) { |
| this.module = module; |
| this.commandLine = commandLine; |
| this.onDone = onDone; |
| this.closeOnDone = closeOnDone; |
| this.input = input; |
| this.showConsole = showConsole; |
| } |
| |
| @Override |
| public ConsoleProcessDescriptor addProcessListener(@NotNull ProcessListener listener) { |
| if (myHandler != null) { |
| myHandler.addProcessListener(listener); |
| } |
| else { |
| myListeners.add(listener); |
| } |
| return this; |
| } |
| |
| @Override |
| public ConsoleProcessDescriptor waitWith(ProgressIndicator progressIndicator) { |
| if (myHandler != null) { |
| doWait(progressIndicator); |
| } |
| return this; |
| } |
| |
| private void doWait(ProgressIndicator progressIndicator) { |
| while (!myHandler.waitFor(500)) { |
| if (progressIndicator.isCanceled()) { |
| myHandler.destroyProcess(); |
| break; |
| } |
| } |
| } |
| |
| public void setHandler(OSProcessHandler handler) { |
| myHandler = handler; |
| for (final ProcessListener listener : myListeners) { |
| handler.addProcessListener(listener); |
| } |
| } |
| } |
| |
| public static ConsoleProcessDescriptor executeProcess(final Module module, |
| final GeneralCommandLine commandLine, |
| @Nullable final Runnable onDone, |
| final boolean closeOnDone, |
| final String... input) { |
| return getInstance(module.getProject()).executeProcess(module, commandLine, onDone, true, closeOnDone, input); |
| } |
| |
| public ConsoleProcessDescriptor executeProcess(final Module module, |
| final GeneralCommandLine commandLine, |
| @Nullable final Runnable onDone, |
| boolean showConsole, |
| final boolean closeOnDone, |
| final String... input) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| assert module.getProject() == myProject; |
| |
| final MyProcessInConsole process = new MyProcessInConsole(module, commandLine, onDone, showConsole, closeOnDone, input); |
| if (isExecuting()) { |
| myProcessQueue.add(process); |
| } |
| else { |
| executeProcessImpl(process, true); |
| } |
| return process; |
| } |
| |
| public boolean isExecuting() { |
| return myExecuting; |
| } |
| |
| private void executeProcessImpl(final MyProcessInConsole pic, boolean toFocus) { |
| final Module module = pic.module; |
| final GeneralCommandLine commandLine = pic.commandLine; |
| final String[] input = pic.input; |
| final boolean closeOnDone = pic.closeOnDone; |
| final Runnable onDone = pic.onDone; |
| |
| assert module.getProject() == myProject; |
| |
| myExecuting = true; |
| |
| // Module creation was cancelled |
| if (module.isDisposed()) return; |
| |
| final ModalityState modalityState = ModalityState.current(); |
| final boolean modalContext = modalityState != ModalityState.NON_MODAL; |
| |
| if (!modalContext && pic.showConsole) { |
| show(null, toFocus); |
| } |
| |
| FileDocumentManager.getInstance().saveAllDocuments(); |
| myConsole.print(commandLine.getCommandLineString(), ConsoleViewContentType.SYSTEM_OUTPUT); |
| final OSProcessHandler handler; |
| try { |
| Process process = commandLine.createProcess(); |
| handler = new OSProcessHandler(process); |
| |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| OutputStreamWriter writer = new OutputStreamWriter(process.getOutputStream()); |
| for (String s : input) { |
| writer.write(s); |
| } |
| writer.flush(); |
| |
| final Ref<Boolean> gotError = new Ref<Boolean>(false); |
| handler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key key) { |
| if (key == ProcessOutputTypes.STDERR) gotError.set(true); |
| LOG.debug("got text: " + event.getText()); |
| } |
| |
| @Override |
| public void processTerminated(ProcessEvent event) { |
| final int exitCode = event.getExitCode(); |
| if (exitCode == 0 && !gotError.get().booleanValue()) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (myProject.isDisposed() || !closeOnDone) return; |
| myToolWindow.hide(null); |
| } |
| }, modalityState); |
| } |
| } |
| }); |
| } |
| catch (final Exception e) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| Messages.showErrorDialog(e.getMessage(), "Cannot Start Process"); |
| |
| try { |
| if (onDone != null && !module.isDisposed()) onDone.run(); |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| } |
| } |
| }, modalityState); |
| return; |
| } |
| |
| pic.setHandler(handler); |
| myKillAction.setHandler(handler); |
| |
| final MvcFramework framework = MvcFramework.getInstance(module); |
| myToolWindow.setIcon(framework == null ? JetgroovyIcons.Groovy.Groovy_13x13 : framework.getToolWindowIcon()); |
| |
| myContent.setDisplayName((framework == null ? "" : framework.getDisplayName() + ":") + "Executing..."); |
| myConsole.scrollToEnd(); |
| myConsole.attachToProcess(handler); |
| ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { |
| @Override |
| public void run() { |
| handler.startNotify(); |
| handler.waitFor(); |
| |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (myProject.isDisposed()) return; |
| |
| module.putUserData(UPDATING_BY_CONSOLE_PROCESS, true); |
| LocalFileSystem.getInstance().refresh(false); |
| module.putUserData(UPDATING_BY_CONSOLE_PROCESS, null); |
| |
| try { |
| if (onDone != null && !module.isDisposed()) onDone.run(); |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| } |
| myConsole.print("\n", ConsoleViewContentType.NORMAL_OUTPUT); |
| myKillAction.setHandler(null); |
| myContent.setDisplayName(""); |
| |
| myExecuting = false; |
| |
| final MyProcessInConsole pic = myProcessQueue.poll(); |
| if (pic != null) { |
| executeProcessImpl(pic, false); |
| } |
| } |
| }, modalityState); |
| } |
| }); |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| private class MyKillProcessAction extends AnAction { |
| private OSProcessHandler myHandler = null; |
| |
| public MyKillProcessAction() { |
| super("Kill process", "Kill process", AllIcons.Debugger.KillProcess); |
| } |
| |
| public void setHandler(@Nullable OSProcessHandler handler) { |
| myHandler = handler; |
| } |
| |
| @Override |
| public void update(final AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setEnabled(isEnabled()); |
| } |
| |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| if (myHandler != null) { |
| final Process process = myHandler.getProcess(); |
| process.destroy(); |
| myConsole.print("Process terminated", ConsoleViewContentType.ERROR_OUTPUT); |
| } |
| } |
| |
| public boolean isEnabled() { |
| return myHandler != null; |
| } |
| } |
| |
| public ConsoleViewImpl getConsole() { |
| return myConsole; |
| } |
| } |