From d05c29bef0d35fb33814727e67e3475690a5c737 Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Mon, 4 Aug 2014 04:55:24 +0200 Subject: [PATCH] Added Python Console tool window. --- .../AbstractConsoleRunnerWithHistory.java | 70 ++++++---- python/src/META-INF/python-core.xml | 12 ++ .../actions/ExecuteInConsoleAction.java | 3 +- .../python/console/PydevConsoleRunner.java | 106 ++++++++++++--- .../console/PythonConsoleToolWindow.java | 124 ++++++++++++++++++ .../PythonConsoleToolWindowFactory.java | 33 +++++ .../console/RunPythonConsoleAction.java | 8 +- 7 files changed, 309 insertions(+), 47 deletions(-) create mode 100644 python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java create mode 100644 python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java diff --git a/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java b/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java index df7f7e1b2e1c..6fe792a2b426 100644 --- a/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java +++ b/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java @@ -15,6 +15,9 @@ */ package com.intellij.execution.runners; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionHelper; import com.intellij.execution.ExecutionManager; @@ -129,6 +132,7 @@ public abstract class AbstractConsoleRunnerWithHistory consoles = ExecutionHelper.collectConsolesByDisplayName(myProject, new NotNullFunction() { - @NotNull - @Override - public Boolean fun(String dom) { - return dom.contains(consoleTitle); - } - }); + List activeConsoleNames = getActiveConsoleNames(consoleTitle); int max = 0; - for (RunContentDescriptor dsc : consoles) { - ProcessHandler handler = dsc.getProcessHandler(); - if (handler != null && !handler.isProcessTerminated()) { - if (max == 0) { - max = 1; - } - try { - int num = Integer.parseInt(dsc.getDisplayName().substring(consoleTitle.length() + 1, dsc.getDisplayName().length() - 1)); - if (num > max) { - max = num; - } - } - catch (Exception ignored) { - //skip + for (String name : activeConsoleNames) { + if (max == 0) { + max = 1; + } + try { + int num = Integer.parseInt(name.substring(consoleTitle.length() + 1, name.length() - 1)); + if (num > max) { + max = num; } } + catch (Exception ignored) { + //skip + } } if (max >= 1) { return consoleTitle + "(" + (max + 1) + ")"; @@ -178,9 +173,9 @@ public abstract class AbstractConsoleRunnerWithHistory getActiveConsoleNames(final String consoleTitle) { + List consoles = ExecutionHelper.collectConsolesByDisplayName(myProject, new NotNullFunction() { + @NotNull + @Override + public Boolean fun(String dom) { + return dom.contains(consoleTitle); + } + }); + + return FluentIterable.from(consoles).filter(new Predicate() { + @Override + public boolean apply(RunContentDescriptor input) { + ProcessHandler handler = input.getProcessHandler(); + return handler != null && !handler.isProcessTerminated(); + } + }).transform(new Function() { + @Override + public String apply(RunContentDescriptor input) { + return input.getDisplayName(); + } + }).toList(); + } } diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 88280cc2cf10..a27eb44ae3af 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -528,6 +528,12 @@ + + + + + @@ -615,6 +621,12 @@ + + + com.jetbrains.python.console.PythonConsoleToolWindow + + + diff --git a/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java b/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java index 58b35fe5f591..cb90d498e663 100644 --- a/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java +++ b/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java @@ -17,7 +17,6 @@ package com.jetbrains.python.actions; import com.intellij.execution.ExecutionHelper; import com.intellij.execution.console.LanguageConsoleView; -import com.intellij.execution.console.LanguageConsoleViewImpl; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.openapi.actionSystem.*; @@ -215,7 +214,7 @@ public class ExecuteInConsoleAction extends AnAction { private static void startConsole(final Project project, final Consumer consumer, Module context) { - PydevConsoleRunner runner = RunPythonConsoleAction.runPythonConsole(project, context); + PydevConsoleRunner runner = RunPythonConsoleAction.runPythonConsole(project, context, null); runner.addConsoleListener(new PydevConsoleRunner.ConsoleListener() { @Override public void handleConsoleInitialized(LanguageConsoleView consoleView) { diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java index 3d77ac4f62b1..fb89784b1911 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java @@ -16,6 +16,10 @@ package com.jetbrains.python.console; import com.google.common.base.CharMatcher; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionHelper; @@ -59,11 +63,14 @@ import com.intellij.openapi.util.io.StreamUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.remote.RemoteSshProcess; import com.intellij.testFramework.LightVirtualFile; +import com.intellij.ui.content.Content; import com.intellij.util.ArrayUtil; import com.intellij.util.IJSwingUtilities; import com.intellij.util.PathMappingSettings; @@ -132,6 +139,7 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory environmentVariables, + @Nullable final ToolWindow toolWindow, final String... statements2execute) { final PydevConsoleRunner consoleRunner = create(project, sdk, consoleType, workingDirectory, environmentVariables); + consoleRunner.setToolWindow(toolWindow); consoleRunner.setStatementsToExecute(statements2execute); consoleRunner.run(); return consoleRunner; @@ -481,6 +491,12 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory getActiveConsoleNames(final String consoleTitle) { + return FluentIterable.from( + Lists.newArrayList(PythonConsoleToolWindow.getInstance(getProject()).getToolWindow().getContentManager().getContents())).transform( + new Function() { + @Override + public String apply(Content input) { + return input.getDisplayName(); + } + }).filter(new Predicate() { + @Override + public boolean apply(String input) { + return input.contains(consoleTitle); + } + }).toList(); + } } diff --git a/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java b/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java new file mode 100644 index 000000000000..b5dc5ac155b7 --- /dev/null +++ b/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java @@ -0,0 +1,124 @@ +package com.jetbrains.python.console; + +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.openapi.wm.ex.ToolWindowManagerEx; +import com.intellij.openapi.wm.ex.ToolWindowManagerListener; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +/** + * @author traff + */ +public class PythonConsoleToolWindow { + + private final Project myProject; + + private boolean myInitialized = false; + + public PythonConsoleToolWindow(Project project) { + myProject = project; + } + + public static PythonConsoleToolWindow getInstance(@NotNull Project project) { + return project.getComponent(PythonConsoleToolWindow.class); + } + + + public void init(final @NotNull ToolWindow toolWindow, final @NotNull RunContentDescriptor contentDescriptor) { + addContent(toolWindow, contentDescriptor); + + if (!myInitialized) { + doInit(toolWindow); + } + } + + private void doInit(final ToolWindow toolWindow) { + myInitialized = true; + + toolWindow.setToHideOnEmptyContent(true); + + ((ToolWindowManagerEx)ToolWindowManager.getInstance(myProject)).addToolWindowManagerListener(new ToolWindowManagerListener() { + @Override + public void toolWindowRegistered(@NotNull String id) { + } + + @Override + public void stateChanged() { + ToolWindow window = getToolWindow(); + if (window != null) { + boolean visible = window.isVisible(); + if (visible && toolWindow.getContentManager().getContentCount() == 0) { + RunPythonConsoleAction.runPythonConsole(myProject, null, toolWindow); + } + } + } + }); + } + + private static void addContent(ToolWindow toolWindow, RunContentDescriptor contentDescriptor) { + Content content = toolWindow.getContentManager().findContent(contentDescriptor.getDisplayName()); + if (content == null) { + content = createContent(contentDescriptor); + toolWindow.getContentManager().addContent(content); + } + else { + SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true); + resetContent(contentDescriptor, panel, content); + } + + toolWindow.getContentManager().setSelectedContent(content); + } + + public ToolWindow getToolWindow() { + return ToolWindowManager.getInstance(myProject).getToolWindow(PythonConsoleToolWindowFactory.ID); + } + + private static Content createContent(final @NotNull RunContentDescriptor contentDescriptor) { + SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true); + + final Content content = ContentFactory.SERVICE.getInstance().createContent(panel, contentDescriptor.getDisplayName(), false); + content.setCloseable(true); + + resetContent(contentDescriptor, panel, content); + + return content; + } + + private static void resetContent(RunContentDescriptor contentDescriptor, SimpleToolWindowPanel panel, Content content) { + panel.setContent(contentDescriptor.getComponent()); + //panel.addFocusListener(createFocusListener(toolWindow)); + + + content.setPreferredFocusableComponent(contentDescriptor.getComponent()); + } + + private static FocusListener createFocusListener(final ToolWindow toolWindow) { + return new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + JComponent component = getComponentToFocus(toolWindow); + if (component != null) { + component.requestFocusInWindow(); + } + } + + @Override + public void focusLost(FocusEvent e) { + + } + }; + } + + private static JComponent getComponentToFocus(ToolWindow window) { + return window.getContentManager().getComponent(); + } +} diff --git a/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java b/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java new file mode 100644 index 000000000000..f042a539bc22 --- /dev/null +++ b/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java @@ -0,0 +1,33 @@ +/* + * 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 com.jetbrains.python.console; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; + +/** + * @author traff + */ +public class PythonConsoleToolWindowFactory implements ToolWindowFactory, DumbAware { + public static final String ID = "Python Console"; + + @Override + public void createToolWindowContent(Project project, ToolWindow toolWindow) { + RunPythonConsoleAction.runPythonConsole(project, null, toolWindow); + } +} diff --git a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java index 02a153f7a016..566adea43c87 100644 --- a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java +++ b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java @@ -32,6 +32,7 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; import com.intellij.util.PathMappingSettings; import com.jetbrains.python.buildout.BuildoutFacet; import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; @@ -42,6 +43,7 @@ import com.jetbrains.python.sdk.PythonEnvUtil; import com.jetbrains.python.sdk.PythonSdkType; import icons.PythonIcons; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; @@ -77,11 +79,11 @@ public class RunPythonConsoleAction extends AnAction implements DumbAware { public void actionPerformed(final AnActionEvent e) { final Project project = e.getData(CommonDataKeys.PROJECT); - runPythonConsole(project, e.getData(LangDataKeys.MODULE)); + runPythonConsole(project, e.getData(LangDataKeys.MODULE), null); } @NotNull - public static PydevConsoleRunner runPythonConsole(Project project, Module contextModule) { + public static PydevConsoleRunner runPythonConsole(Project project, Module contextModule, @Nullable ToolWindow toolWindow) { assert project != null : "Project is null"; Pair sdkAndModule = findPythonSdkAndModule(project, contextModule); @@ -152,7 +154,7 @@ public class RunPythonConsoleAction extends AnAction implements DumbAware { envs.put(PythonEnvUtil.IPYTHONENABLE, ipythonEnabled); return PydevConsoleRunner - .createAndRun(project, sdk, PyConsoleType.PYTHON, workingDir, envs, setupFragment); + .createAndRun(project, sdk, PyConsoleType.PYTHON, workingDir, envs, toolWindow, setupFragment); } public static PathMappingSettings getMappings(Project project, Sdk sdk) {