becca3d05c6905e16df30715e374db96e9fe5275
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / runners / AbstractConsoleRunnerWithHistory.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.runners;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.codeInsight.lookup.Lookup;
20 import com.intellij.codeInsight.lookup.LookupManager;
21 import com.intellij.execution.*;
22 import com.intellij.execution.console.LanguageConsoleImpl;
23 import com.intellij.execution.console.LanguageConsoleViewImpl;
24 import com.intellij.execution.executors.DefaultRunExecutor;
25 import com.intellij.execution.process.*;
26 import com.intellij.execution.ui.RunContentDescriptor;
27 import com.intellij.execution.ui.actions.CloseAction;
28 import com.intellij.ide.CommonActionsManager;
29 import com.intellij.openapi.actionSystem.*;
30 import com.intellij.openapi.application.Application;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.application.Result;
33 import com.intellij.openapi.command.WriteCommandAction;
34 import com.intellij.openapi.editor.CaretModel;
35 import com.intellij.openapi.editor.Document;
36 import com.intellij.openapi.editor.Editor;
37 import com.intellij.openapi.editor.ex.EditorEx;
38 import com.intellij.openapi.project.DumbAwareAction;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.util.Computable;
41 import com.intellij.openapi.util.IconLoader;
42 import com.intellij.openapi.util.text.StringUtil;
43 import com.intellij.openapi.wm.IdeFocusManager;
44 import com.intellij.openapi.wm.ToolWindow;
45 import com.intellij.openapi.wm.ToolWindowManager;
46 import com.intellij.util.NotNullFunction;
47 import com.intellij.util.PairProcessor;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import java.awt.*;
53 import java.util.List;
54
55 /**
56  * @author oleg
57  *         This class provides basic functionality for running consoles.
58  *         It launches external process and handles line input with history
59  */
60 public abstract class AbstractConsoleRunnerWithHistory {
61   private final Project myProject;
62   private final String myConsoleTitle;
63
64   private ProcessHandler myProcessHandler;
65   private final CommandLineArgumentsProvider myProvider;
66   private final String myWorkingDir;
67
68   private LanguageConsoleViewImpl myConsoleView;
69
70   private AnAction myRunAction;
71
72   private ConsoleExecuteActionHandler myConsoleExecuteActionHandler;
73
74   public AbstractConsoleRunnerWithHistory(@NotNull final Project project,
75                                           @NotNull final String consoleTitle,
76                                           @NotNull final CommandLineArgumentsProvider provider,
77                                           @Nullable final String workingDir) {
78     myProject = project;
79     myConsoleTitle = consoleTitle;
80     myProvider = provider;
81     myWorkingDir = workingDir;
82   }
83
84   /**
85    * Launch process, setup history, actions etc.
86    *
87    * @throws ExecutionException
88    */
89   public void initAndRun() throws ExecutionException {
90     // Create Server process
91     final Process process = createProcess(myProvider);
92
93     Application application = ApplicationManager.getApplication();
94
95     if (application.isDispatchThread()) {
96       initConsoleUI(process);
97     }
98     else {
99       application.invokeLater(new Runnable() {
100         @Override
101         public void run() {
102           initConsoleUI(process);
103         }
104       });
105     }
106   }
107
108
109   private void initConsoleUI(Process process) {
110     // Init console view
111     myConsoleView = createConsoleView();
112
113     myProcessHandler = createProcessHandler(process, myProvider.getCommandLineString());
114
115     myConsoleExecuteActionHandler = createConsoleExecuteActionHandler();
116
117     ProcessTerminatedListener.attach(myProcessHandler);
118
119     myProcessHandler.addProcessListener(new ProcessAdapter() {
120       @Override
121       public void processTerminated(ProcessEvent event) {
122         finishConsole();
123       }
124     });
125
126 // Attach to process
127     myConsoleView.attachToProcess(myProcessHandler);
128
129 // Runner creating
130     final Executor defaultExecutor = ExecutorRegistry.getInstance().getExecutorById(DefaultRunExecutor.EXECUTOR_ID);
131     final DefaultActionGroup toolbarActions = new DefaultActionGroup();
132     final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false);
133
134 // Runner creating
135     final JPanel panel = new JPanel(new BorderLayout());
136     panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
137     panel.add(myConsoleView.getComponent(), BorderLayout.CENTER);
138
139     final RunContentDescriptor contentDescriptor =
140       new RunContentDescriptor(myConsoleView, myProcessHandler, panel, constructConsoleTitle(myConsoleTitle));
141
142 // tool bar actions
143     final AnAction[] actions = fillToolBarActions(toolbarActions, defaultExecutor, contentDescriptor);
144     registerActionShortcuts(actions, getLanguageConsole().getConsoleEditor().getComponent());
145     registerActionShortcuts(actions, panel);
146     panel.updateUI();
147     showConsole(defaultExecutor, contentDescriptor);
148
149 // Run
150     myProcessHandler.startNotify();
151   }
152
153   private String constructConsoleTitle(final @NotNull String consoleTitle) {
154     if (shouldAddNumberToTitle()) {
155       List<RunContentDescriptor> consoles = ExecutionHelper.collectConsolesByDisplayName(myProject, new NotNullFunction<String, Boolean>() {
156         @NotNull
157         @Override
158         public Boolean fun(String dom) {
159           return dom.contains(consoleTitle);
160         }
161       });
162       int max = consoles.size() > 0 ? 1 : 0;
163       for (RunContentDescriptor dsc : consoles) {
164         try {
165           int num = Integer.parseInt(dsc.getDisplayName().substring(consoleTitle.length()+1, dsc.getDisplayName().length()-1));
166           if (num > max) {
167             max = num;
168           }
169         }
170         catch (Exception e) {
171           //skip
172         }
173       }
174       if (max >= 1) {
175         return consoleTitle + "(" + (max + 1) + ")";
176       }
177     }
178
179     return consoleTitle;
180   }
181
182   protected boolean shouldAddNumberToTitle() {
183     return false;
184   }
185
186   protected void showConsole(Executor defaultExecutor, RunContentDescriptor myDescriptor) {
187     // Show in run toolwindow
188     ExecutionManager.getInstance(myProject).getContentManager().showRunContent(defaultExecutor, myDescriptor);
189
190 // Request focus
191     final ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(defaultExecutor.getId());
192     window.activate(new Runnable() {
193       public void run() {
194         IdeFocusManager.getInstance(myProject).requestFocus(getLanguageConsole().getCurrentEditor().getContentComponent(), true);
195       }
196     });
197   }
198
199   protected void finishConsole() {
200     myRunAction.getTemplatePresentation().setEnabled(false);
201     myConsoleView.getConsole().setPrompt("");
202     myConsoleView.getConsole().getConsoleEditor().setRendererMode(true);
203     ApplicationManager.getApplication().invokeLater(new Runnable() {
204       public void run() {
205         myConsoleView.getConsole().getConsoleEditor().getComponent().updateUI();
206       }
207     });
208   }
209
210   protected abstract LanguageConsoleViewImpl createConsoleView();
211
212   @Nullable
213   protected abstract Process createProcess(CommandLineArgumentsProvider provider) throws ExecutionException;
214
215   protected abstract OSProcessHandler createProcessHandler(final Process process, final String commandLine);
216
217   public static void registerActionShortcuts(final AnAction[] actions, final JComponent component) {
218     for (AnAction action : actions) {
219       if (action.getShortcutSet() != null) {
220         action.registerCustomShortcutSet(action.getShortcutSet(), component);
221       }
222     }
223   }
224
225   protected AnAction[] fillToolBarActions(final DefaultActionGroup toolbarActions,
226                                           final Executor defaultExecutor,
227                                           final RunContentDescriptor contentDescriptor) {
228
229     List<AnAction> actionList = Lists.newArrayList();
230
231 //stop
232     final AnAction stopAction = createStopAction();
233     actionList.add(stopAction);
234
235 //close
236     final AnAction closeAction = createCloseAction(defaultExecutor, contentDescriptor);
237     actionList.add(closeAction);
238
239 // run and history actions
240
241     ConsoleExecutionActions executionActions = createExecuteAction();
242     actionList.addAll(executionActions.getActionsAsList());
243
244 // Help
245     actionList.add(CommonActionsManager.getInstance().createHelpAction("interactive_console"));
246
247     AnAction[] actions = actionList.toArray(new AnAction[actionList.size()]);
248
249     toolbarActions.addAll(actions);
250
251     return actions;
252   }
253
254   protected ConsoleExecutionActions createExecuteAction() {
255     ConsoleExecutionActions executionActions =
256       createConsoleExecActions(getLanguageConsole(), myProcessHandler, myConsoleExecuteActionHandler);
257     myRunAction = executionActions.getRunAction();
258     return executionActions;
259   }
260
261   protected AnAction createCloseAction(final Executor defaultExecutor, final RunContentDescriptor myDescriptor) {
262     return new CloseAction(defaultExecutor, myDescriptor, myProject);
263   }
264
265   protected AnAction createStopAction() {
266     return ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
267   }
268
269   public LanguageConsoleImpl getLanguageConsole() {
270     return myConsoleView.getConsole();
271   }
272
273   public static ConsoleExecutionActions createConsoleExecActions(final LanguageConsoleImpl languageConsole,
274                                                                  final ProcessHandler processHandler,
275                                                                  final ConsoleExecuteActionHandler consoleExecuteActionHandler) {
276     final AnAction runAction = new ConsoleExecuteAction(languageConsole,
277                                                         processHandler, consoleExecuteActionHandler);
278
279     final PairProcessor<AnActionEvent, String> historyProcessor = new PairProcessor<AnActionEvent, String>() {
280       public boolean process(final AnActionEvent e, final String s) {
281         new WriteCommandAction(languageConsole.getProject(), languageConsole.getFile()) {
282           protected void run(final Result result) throws Throwable {
283             languageConsole.getEditorDocument().setText(s == null ? "" : s);
284           }
285         }.execute();
286         return true;
287       }
288     };
289
290     final EditorEx consoleEditor = languageConsole.getConsoleEditor();
291     final AnAction upAction = ConsoleHistoryModel.createConsoleHistoryUpAction(createCanMoveUpComputable(consoleEditor),
292                                                                                consoleExecuteActionHandler.getConsoleHistoryModel(),
293                                                                                historyProcessor);
294     final AnAction downAction = ConsoleHistoryModel.createConsoleHistoryDownAction(createCanMoveDownComputable(consoleEditor),
295                                                                                    consoleExecuteActionHandler.getConsoleHistoryModel(),
296                                                                                    historyProcessor);
297
298     return new ConsoleExecutionActions(runAction, downAction, upAction);
299   }
300
301   public static Computable<Boolean> createCanMoveDownComputable(final Editor consoleEditor) {
302     return new Computable<Boolean>() {
303       @Override
304       public Boolean compute() {
305         final Document document = consoleEditor.getDocument();
306         final CaretModel caretModel = consoleEditor.getCaretModel();
307
308         // Check if we have active lookup or if we can move in editor
309         return LookupManager.getActiveLookup(consoleEditor) != null ||
310                document.getLineNumber(caretModel.getOffset()) < document.getLineCount() - 1 &&
311                !StringUtil.isEmptyOrSpaces(document.getText().substring(caretModel.getOffset()));
312       }
313     };
314   }
315
316   public static Computable<Boolean> createCanMoveUpComputable(final Editor consoleEditor) {
317     return new Computable<Boolean>() {
318       @Override
319       public Boolean compute() {
320         final Document document = consoleEditor.getDocument();
321         final CaretModel caretModel = consoleEditor.getCaretModel();
322         // Check if we have active lookup or if we can move in editor
323         return LookupManager.getActiveLookup(consoleEditor) != null || document.getLineNumber(caretModel.getOffset()) > 0;
324       }
325     };
326   }
327
328   @NotNull
329   protected abstract ConsoleExecuteActionHandler createConsoleExecuteActionHandler();
330
331   public static class ConsoleExecutionActions {
332     private final AnAction myRunAction;
333     private final AnAction myNextAction;
334     private final AnAction myPrevAction;
335
336
337     public ConsoleExecutionActions(AnAction runAction, AnAction nextAction, AnAction prevAction) {
338       myRunAction = runAction;
339       myNextAction = nextAction;
340       myPrevAction = prevAction;
341     }
342
343     public AnAction[] getActions() {
344       return getActionsAsList().toArray(new AnAction[getActionsAsList().size()]);
345     }
346
347
348     public List<AnAction> getActionsAsList() {
349       return Lists.newArrayList(myRunAction, myNextAction, myPrevAction);
350     }
351
352     public AnAction getRunAction() {
353       return myRunAction;
354     }
355   }
356
357
358   public static class ConsoleExecuteAction extends DumbAwareAction {
359     public static final String ACTIONS_EXECUTE_ICON = "/actions/execute.png";
360     public static final String CONSOLE_EXECUTE = "Console.Execute";
361
362     private final LanguageConsoleImpl myLanguageConsole;
363     private final ProcessHandler myProcessHandler;
364
365     private final ConsoleExecuteActionHandler myConsoleExecuteActionHandler;
366
367
368     public ConsoleExecuteAction(LanguageConsoleImpl languageConsole,
369                                 ProcessHandler processHandler,
370                                 ConsoleExecuteActionHandler consoleExecuteActionHandler) {
371       super(null, null, IconLoader.getIcon(ACTIONS_EXECUTE_ICON));
372       myLanguageConsole = languageConsole;
373       myProcessHandler = processHandler;
374       myConsoleExecuteActionHandler = consoleExecuteActionHandler;
375       EmptyAction.setupAction(this, CONSOLE_EXECUTE, null);
376     }
377
378     public void actionPerformed(final AnActionEvent e) {
379       myConsoleExecuteActionHandler.runExecuteAction(myLanguageConsole);
380     }
381
382     public void update(final AnActionEvent e) {
383       final EditorEx editor = myLanguageConsole.getConsoleEditor();
384       final Lookup lookup = LookupManager.getActiveLookup(editor);
385       e.getPresentation().setEnabled(!myProcessHandler.isProcessTerminated() &&
386                                      (lookup == null || !lookup.isCompletion()));
387     }
388   }
389
390   public LanguageConsoleViewImpl getConsoleView() {
391     return myConsoleView;
392   }
393
394   public Project getProject() {
395     return myProject;
396   }
397
398   public String getConsoleTitle() {
399     return myConsoleTitle;
400   }
401
402   public String getWorkingDir() {
403     return myWorkingDir;
404   }
405
406   public ProcessHandler getProcessHandler() {
407     return myProcessHandler;
408   }
409
410   public ConsoleExecuteActionHandler getConsoleExecuteActionHandler() {
411     return myConsoleExecuteActionHandler;
412   }
413 }