blob: cbcf56334e983d25ac0e49f8a697a78b0ab58549 [file] [log] [blame]
/*
* 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;
}
}