Option to run python process in a terminal emulation mode (PY-22487)
[idea/community.git] / platform / platform-impl / src / com / intellij / terminal / JBTerminalPanel.java
1 /*
2  * Copyright 2000-2016 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
17 /* -*-mode:java; c-basic-offset:2; -*- */
18
19
20 package com.intellij.terminal;
21
22 import com.google.common.collect.Lists;
23 import com.intellij.ide.GeneralSettings;
24 import com.intellij.ide.IdeEventQueue;
25 import com.intellij.ide.ui.UISettings;
26 import com.intellij.openapi.Disposable;
27 import com.intellij.openapi.actionSystem.*;
28 import com.intellij.openapi.application.TransactionGuard;
29 import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry;
30 import com.intellij.openapi.editor.impl.FontInfo;
31 import com.intellij.openapi.fileEditor.FileDocumentManager;
32 import com.intellij.openapi.ide.CopyPasteManager;
33 import com.intellij.openapi.project.DumbAwareAction;
34 import com.intellij.openapi.vfs.LocalFileSystem;
35 import com.intellij.util.JBHiDPIScaledImage;
36 import com.intellij.util.RetinaImage;
37 import com.intellij.util.ui.UIUtil;
38 import com.jediterm.terminal.TextStyle;
39 import com.jediterm.terminal.model.StyleState;
40 import com.jediterm.terminal.model.TerminalTextBuffer;
41 import com.jediterm.terminal.ui.TerminalPanel;
42 import org.intellij.lang.annotations.JdkConstants;
43 import org.jetbrains.annotations.NotNull;
44
45 import javax.swing.*;
46 import java.awt.*;
47 import java.awt.datatransfer.StringSelection;
48 import java.awt.event.FocusEvent;
49 import java.awt.event.FocusListener;
50 import java.awt.event.KeyEvent;
51 import java.awt.image.BufferedImage;
52 import java.awt.image.ImageObserver;
53 import java.util.List;
54
55 public class JBTerminalPanel extends TerminalPanel implements FocusListener, TerminalSettingsListener, Disposable,
56                                                               IdeEventQueue.EventDispatcher {
57   private static final String[] ACTIONS_TO_SKIP = new String[]{
58     "ActivateTerminalToolWindow",
59     "ActivateMessagesToolWindow",
60     "ActivateProjectToolWindow",
61     "ActivateFavoritesToolWindow",
62     "ActivateFindToolWindow",
63     "ActivateRunToolWindow",
64     "ActivateDebugToolWindow",
65     "ActivateTODOToolWindow",
66     "ActivateStructureToolWindow",
67     "ActivateHierarchyToolWindow",
68     "ActivateVersionControlToolWindow",
69     "HideAllWindows",
70
71     "ShowBookmarks",
72     "GotoBookmark0",
73     "GotoBookmark1",
74     "GotoBookmark2",
75     "GotoBookmark3",
76     "GotoBookmark4",
77     "GotoBookmark5",
78     "GotoBookmark6",
79     "GotoBookmark7",
80     "GotoBookmark8",
81     "GotoBookmark9",
82     
83     "GotoAction",
84     "GotoFile",
85     "GotoClass",
86     "GotoSymbol",
87     
88     "ShowSettings",
89     "RecentFiles",
90     "Switcher"
91   };
92
93   private final JBTerminalSystemSettingsProviderBase mySettingsProvider;
94
95   private List<AnAction> myActionsToSkip;
96
97   public JBTerminalPanel(@NotNull JBTerminalSystemSettingsProviderBase settingsProvider,
98                          @NotNull TerminalTextBuffer backBuffer,
99                          @NotNull StyleState styleState) {
100     super(settingsProvider, backBuffer, styleState);
101
102     mySettingsProvider = settingsProvider;
103
104     registerKeymapActions(this);
105
106     addFocusListener(this);
107
108     mySettingsProvider.addListener(this);
109   }
110
111   private static void registerKeymapActions(final TerminalPanel terminalPanel) {
112
113     ActionManager actionManager = ActionManager.getInstance();
114     for (String actionId : ACTIONS_TO_SKIP) {
115       final AnAction action = actionManager.getAction(actionId);
116       if (action != null) {
117         AnAction a = new DumbAwareAction() {
118           @Override
119           public void actionPerformed(AnActionEvent e) {
120             if (e.getInputEvent() instanceof KeyEvent) {
121               AnActionEvent event =
122                 new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(), new Presentation(), e.getActionManager(),
123                                   e.getModifiers());
124               action.update(event);
125               if (event.getPresentation().isEnabled()) {
126                 action.actionPerformed(event);
127               }
128               else {
129                 terminalPanel.handleKeyEvent((KeyEvent)event.getInputEvent());
130               }
131
132               event.getInputEvent().consume();
133             }
134           }
135         };
136         for (Shortcut sc : action.getShortcutSet().getShortcuts()) {
137           if (sc.isKeyboard() && sc instanceof KeyboardShortcut) {
138             KeyboardShortcut ksc = (KeyboardShortcut)sc;
139             a.registerCustomShortcutSet(ksc.getFirstKeyStroke().getKeyCode(), ksc.getFirstKeyStroke().getModifiers(), terminalPanel);
140           }
141         }
142       }
143     }
144   }
145
146   @Override
147   public boolean dispatch(AWTEvent e) {
148     if (e instanceof KeyEvent && !skipKeyEvent((KeyEvent)e)) {
149       dispatchEvent(e);
150       return true;
151     }
152     return false;
153   }
154
155   private boolean skipKeyEvent(KeyEvent e) {
156     if (myActionsToSkip == null) {
157       return false;
158     }
159     int kc = e.getKeyCode();
160     return kc == KeyEvent.VK_ESCAPE || skipAction(e, myActionsToSkip);
161   }
162
163   private static boolean skipAction(KeyEvent e, List<AnAction> actionsToSkip) {
164     if (actionsToSkip != null) {
165       final KeyboardShortcut eventShortcut = new KeyboardShortcut(KeyStroke.getKeyStrokeForEvent(e), null);
166       for (AnAction action : actionsToSkip) {
167         for (Shortcut sc : action.getShortcutSet().getShortcuts()) {
168           if (sc.isKeyboard() && sc.startsWith(eventShortcut)) {
169             return true;
170           }
171         }
172       }
173     }
174     return false;
175   }
176
177
178   @Override
179   protected void setupAntialiasing(Graphics graphics) {
180     UIUtil.setupComposite((Graphics2D)graphics);
181     UISettings.setupAntialiasing(graphics);
182   }
183
184   @Override
185   protected void setCopyContents(StringSelection selection) {
186     CopyPasteManager.getInstance().setContents(selection);
187   }
188
189   @Override
190   protected void drawImage(Graphics2D gfx, BufferedImage image, int x, int y, ImageObserver observer) {
191     UIUtil.drawImage(gfx, image, x, y, observer);
192   }
193
194   @Override
195   protected void drawImage(Graphics2D g, BufferedImage image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2) {
196     drawImage(g, image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null);
197   }
198
199   public static void drawImage(Graphics g,
200                                Image image,
201                                int dx1,
202                                int dy1,
203                                int dx2,
204                                int dy2,
205                                int sx1,
206                                int sy1,
207                                int sx2,
208                                int sy2,
209                                ImageObserver observer) {
210     if (image instanceof JBHiDPIScaledImage) {
211       final Graphics2D newG = (Graphics2D)g.create(0, 0, image.getWidth(observer), image.getHeight(observer));
212       newG.scale(0.5, 0.5);
213       Image img = ((JBHiDPIScaledImage)image).getDelegate();
214       if (img == null) {
215         img = image;
216       }
217       newG.drawImage(img, 2 * dx1, 2 * dy1, 2 * dx2, 2 * dy2, sx1 * 2, sy1 * 2, sx2 * 2, sy2 * 2, observer);
218       newG.scale(1, 1);
219       newG.dispose();
220     }
221     else if (RetinaImage.isAppleHiDPIScaledImage(image)) {
222       g.drawImage(image, dx1, dy1, dx2, dy2, sx1 * 2, sy1 * 2, sx2 * 2, sy2 * 2, observer);
223     }
224     else {
225       g.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
226     }
227   }
228
229   @Override
230   protected boolean isRetina() {
231     return UIUtil.isRetina();
232   }
233
234   @Override
235   protected BufferedImage createBufferedImage(int width, int height) {
236     return UIUtil.createImage(width, height, BufferedImage.TYPE_INT_ARGB);
237   }
238
239
240   @Override
241   public void focusGained(FocusEvent event) {
242     installKeyDispatcher();
243
244     if (GeneralSettings.getInstance().isSaveOnFrameDeactivation()) {
245       TransactionGuard.submitTransaction(this, () -> FileDocumentManager.getInstance().saveAllDocuments());
246     }
247   }
248
249   private void installKeyDispatcher() {
250     if (mySettingsProvider.overrideIdeShortcuts()) {
251       myActionsToSkip = setupActionsToSkip();
252       IdeEventQueue.getInstance().addDispatcher(this, this);
253     }
254     else {
255       myActionsToSkip = null;
256     }
257   }
258
259   private static List<AnAction> setupActionsToSkip() {
260     List<AnAction> res = Lists.newArrayList();
261     ActionManager actionManager = ActionManager.getInstance();
262     for (String actionId : ACTIONS_TO_SKIP) {
263       AnAction action = actionManager.getAction(actionId);
264       if (action != null) {
265         res.add(action);
266       }
267     }
268     return res;
269   }
270
271   @Override
272   public void focusLost(FocusEvent event) {
273     if (myActionsToSkip != null) {
274       myActionsToSkip = null;
275       IdeEventQueue.getInstance().removeDispatcher(this);
276     }
277
278     refreshAfterExecution();
279   }
280
281   @Override
282   protected Font getFontToDisplay(char c, TextStyle style) {
283     FontInfo fontInfo = fontForChar(c, style.hasOption(TextStyle.Option.BOLD) ? Font.BOLD : Font.PLAIN);
284     return fontInfo.getFont();
285   }
286
287   public FontInfo fontForChar(final char c, @JdkConstants.FontStyle int style) {
288     return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, mySettingsProvider.getColorScheme().getConsoleFontPreferences());
289   }
290
291   @Override
292   public void fontChanged() {
293     reinitFontAndResize();
294   }
295
296   @Override
297   public void dispose() {
298     super.dispose();
299     mySettingsProvider.removeListener(this);
300   }
301
302   public static void refreshAfterExecution() {
303     if (GeneralSettings.getInstance().isSyncOnFrameActivation()) {
304       //we need to refresh local file system after a command has been executed in the terminal
305       LocalFileSystem.getInstance().refresh(true);
306     }
307   }
308 }
309