From d3dea3fc01ea65e5708fc2c6e253fc437b2d3c71 Mon Sep 17 00:00:00 2001 From: Valentin Fondaratov Date: Fri, 20 Jun 2014 13:44:31 +0400 Subject: [PATCH] Duplex console; MultilineHandlingExecuteActionHandler --- .../execution/console/DuplexConsoleView.java | 250 ++++++++++++++++++ ...MultilineHandlingExecuteActionHandler.java | 158 +++++++++++ .../src/messages/ExecutionBundle.properties | 2 +- .../console/PyOpenDebugConsoleAction.java | 2 +- .../PythonDebugLanguageConsoleView.java | 234 ++-------------- 5 files changed, 429 insertions(+), 217 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/execution/console/DuplexConsoleView.java create mode 100644 platform/lang-impl/src/com/intellij/execution/console/MultilineHandlingExecuteActionHandler.java diff --git a/platform/lang-impl/src/com/intellij/execution/console/DuplexConsoleView.java b/platform/lang-impl/src/com/intellij/execution/console/DuplexConsoleView.java new file mode 100644 index 000000000000..c228f4076caf --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/console/DuplexConsoleView.java @@ -0,0 +1,250 @@ +package com.intellij.execution.console; + +import com.google.common.collect.Lists; +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.filters.Filter; +import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.execution.ui.ObservableConsoleView; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.Arrays; +import java.util.List; + +public class DuplexConsoleView extends JPanel implements ConsoleView, ObservableConsoleView { + private final static String PRIMARY_CONSOLE_PANEL = "PRIMARY_CONSOLE_PANEL"; + private final static String SECONDARY_CONSOLE_PANEL = "SECONDARY_CONSOLE_PANEL"; + + private final S myPrimaryConsoleView; + + private final T mySecondaryConsoleView; + + private boolean myPrimary; + + private ProcessHandler myProcessHandler; + + public DuplexConsoleView(S primaryConsoleView, T secondaryConsoleView) { + super(new CardLayout()); + myPrimaryConsoleView = primaryConsoleView; + mySecondaryConsoleView = secondaryConsoleView; + + add(myPrimaryConsoleView.getComponent(), PRIMARY_CONSOLE_PANEL); + add(mySecondaryConsoleView.getComponent(), SECONDARY_CONSOLE_PANEL); + + myPrimary = true; + enableConsole(false); + + Disposer.register(this, myPrimaryConsoleView); + Disposer.register(this, mySecondaryConsoleView); + } + + public static DuplexConsoleView create(S primary, T secondary) { + return new DuplexConsoleView(primary, secondary); + } + + public void enableConsole(boolean primary) { + if (primary == myPrimary) { + // nothing to do + return; + } + + CardLayout cl = (CardLayout)(getLayout()); + cl.show(this, primary ? PRIMARY_CONSOLE_PANEL : SECONDARY_CONSOLE_PANEL); + + getSubConsoleView(primary).getComponent().requestFocus(); + + myPrimary = primary; + } + + public boolean isPrimaryConsoleEnabled() { + return myPrimary; + } + + public S getPrimaryConsoleView() { + return myPrimaryConsoleView; + } + + public T getSecondaryConsoleView() { + return mySecondaryConsoleView; + } + + public ConsoleView getSubConsoleView(boolean primary) { + return primary ? getPrimaryConsoleView() : getSecondaryConsoleView(); + } + + @Override + public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) { + myPrimaryConsoleView.print(s, contentType); + mySecondaryConsoleView.print(s, contentType); + } + + @Override + public void clear() { + myPrimaryConsoleView.clear(); + mySecondaryConsoleView.clear(); + } + + @Override + public void scrollTo(int offset) { + myPrimaryConsoleView.scrollTo(offset); + mySecondaryConsoleView.scrollTo(offset); + } + + @Override + public void attachToProcess(ProcessHandler processHandler) { + myProcessHandler = processHandler; + + myPrimaryConsoleView.attachToProcess(processHandler); + mySecondaryConsoleView.attachToProcess(processHandler); + } + + @Override + public void setOutputPaused(boolean value) { + myPrimaryConsoleView.setOutputPaused(value); + mySecondaryConsoleView.setOutputPaused(value); + } + + @Override + public boolean isOutputPaused() { + return false; + } + + @Override + public boolean hasDeferredOutput() { + return myPrimaryConsoleView.hasDeferredOutput() && mySecondaryConsoleView.hasDeferredOutput(); + } + + @Override + public void performWhenNoDeferredOutput(Runnable runnable) { + } + + @Override + public void setHelpId(String helpId) { + myPrimaryConsoleView.setHelpId(helpId); + mySecondaryConsoleView.setHelpId(helpId); + } + + @Override + public void addMessageFilter(Filter filter) { + myPrimaryConsoleView.addMessageFilter(filter); + mySecondaryConsoleView.addMessageFilter(filter); + } + + @Override + public void printHyperlink(String hyperlinkText, HyperlinkInfo info) { + myPrimaryConsoleView.printHyperlink(hyperlinkText, info); + mySecondaryConsoleView.printHyperlink(hyperlinkText, info); + } + + @Override + public int getContentSize() { + return myPrimaryConsoleView.getContentSize(); + } + + @Override + public boolean canPause() { + return false; + } + + + @NotNull + @Override + public AnAction[] createConsoleActions() { + List actions = Lists.newArrayList(); + actions.addAll(Arrays.asList(myPrimaryConsoleView.createConsoleActions())); + //for (AnAction action : mySecondaryConsoleView.createConsoleActions()) { + // if (!actions.contains(action)) { + // actions.add(action); + // } + //} + + actions.add(new SwitchDuplexConsoleViewAction(this)); + + return ArrayUtil.toObjectArray(actions, AnAction.class); + } + + @Override + public void allowHeavyFilters() { + myPrimaryConsoleView.allowHeavyFilters(); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public JComponent getPreferredFocusableComponent() { + return this; + } + + @Override + public void dispose() { + // registered children in constructor + } + + @Override + public void addChangeListener(@NotNull ChangeListener listener, @NotNull Disposable parent) { + if (myPrimaryConsoleView instanceof ObservableConsoleView) { + ((ObservableConsoleView)myPrimaryConsoleView).addChangeListener(listener, parent); + } + if (mySecondaryConsoleView instanceof ObservableConsoleView) { + ((ObservableConsoleView)mySecondaryConsoleView).addChangeListener(listener, parent); + } + } + + private static class SwitchDuplexConsoleViewAction extends ToggleAction implements DumbAware { + private final DuplexConsoleView myConsole; + + public SwitchDuplexConsoleViewAction(final DuplexConsoleView console) { + super(ExecutionBundle.message("run.configuration.show.command.line.action.name"), null, + AllIcons.Debugger.ToolConsole); + myConsole = console; + } + + @Override + public boolean isSelected(final AnActionEvent event) { + return !myConsole.isPrimaryConsoleEnabled(); + } + + @Override + public void setSelected(final AnActionEvent event, final boolean flag) { + myConsole.enableConsole(!flag); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + update(event); + } + }); + } + + @Override + public void update(final AnActionEvent event) { + super.update(event); + final Presentation presentation = event.getPresentation(); + final boolean isRunning = myConsole.myProcessHandler != null && !myConsole.myProcessHandler.isProcessTerminated(); + if (isRunning) { + presentation.setEnabled(true); + } + else { + myConsole.enableConsole(true); + presentation.putClientProperty(SELECTED_PROPERTY, false); + presentation.setEnabled(false); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/console/MultilineHandlingExecuteActionHandler.java b/platform/lang-impl/src/com/intellij/execution/console/MultilineHandlingExecuteActionHandler.java new file mode 100644 index 000000000000..0b0a63fd164d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/console/MultilineHandlingExecuteActionHandler.java @@ -0,0 +1,158 @@ +/* + * 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.intellij.execution.console; + +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public abstract class MultilineHandlingExecuteActionHandler extends BaseConsoleExecuteActionHandler { + @NotNull + private BaseConsoleExecuteActionHandler myDelegateHandler; + @Nullable + private String myMultilineToken = null; + @NotNull + protected StringBuilder myInputBuffer; + + public MultilineHandlingExecuteActionHandler(@NotNull BaseConsoleExecuteActionHandler delegateHandler, boolean preserveMarkup) { + super(preserveMarkup); + myDelegateHandler = delegateHandler; + initInputBuffer(); + } + + @NotNull + protected abstract Collection getMultilineTokens(); + + protected abstract boolean startsAnotherCodeFragment(@NotNull String line); + + protected abstract void onMultilineEnter(); + + protected abstract void onMultilineExit(); + + protected abstract void onLineContinuation(); + + protected abstract boolean processLineIfShouldNotExecute(@NotNull String line); + + + @Nullable + protected String getOpeningMultilineToken(@NotNull String line) { + for (String token : getMultilineTokens()) { + if (isMultilineStarts(line, token)) { + return token; + } + } + return null; + } + + protected boolean closesThisMultilineToken(@NotNull String line, @NotNull String token) { + return isMultilineStarts(line, token); + } + + @Override + protected void execute(@NotNull String text, @NotNull LanguageConsoleView console) { + if (text.isEmpty()) { + processOneLine(text, console); + } + else { + if (StringUtil.countNewLines(text.trim()) > 0) { + executeMultiLine(text, console); + } + else { + processOneLine(text, console); + } + } + } + + protected final void executeMultiLine(@NotNull String text, @NotNull LanguageConsoleView console) { + myInputBuffer.append(text); + + myDelegateHandler.execute(myInputBuffer.toString(), console); + } + + protected final void processOneLine(@NotNull String line, @NotNull LanguageConsoleView console) { + line = StringUtil.trimTrailing(line); + if (StringUtil.isEmptyOrSpaces(line)) { + doProcessLine("\n", console); + } + else if (startsAnotherCodeFragment(line)) { + doProcessLine("\n", console); + doProcessLine(line, console); + } + else { + doProcessLine(line, console); + } + } + + protected final void doProcessLine(@NotNull final String line, @NotNull LanguageConsoleView console) { + if (!StringUtil.isEmptyOrSpaces(line)) { + myInputBuffer.append(line); + if (!line.endsWith("\n")) { + myInputBuffer.append("\n"); + } + } + else if (StringUtil.isEmptyOrSpaces(myInputBuffer.toString())) { + myInputBuffer.append(""); + } + + // multiline strings handling + if (myMultilineToken == null) { + myMultilineToken = getOpeningMultilineToken(line); + + if (myMultilineToken != null) { + // change language + onMultilineEnter(); + return; + } + } + else { + if (closesThisMultilineToken(line, myMultilineToken)) { + myMultilineToken = null; + // restore language + onMultilineExit(); + } + else { + if(line.equals("\n")) { + myInputBuffer.append("\n"); + } + return; + } + } + + // Process line continuation + if (line.endsWith("\\")) { + onLineContinuation(); + return; + } + + if (processLineIfShouldNotExecute(line)) { + return; + } + + myDelegateHandler.execute(myInputBuffer.toString(), console); + initInputBuffer(); + } + + private void initInputBuffer() { + myInputBuffer = new StringBuilder(); + } + + private static boolean isMultilineStarts(String line, String token) { + return StringUtil.getOccurrenceCount(line, token) % 2 == 1; + } + +} diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index e5d15e334da7..155e2b6c6d74 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -16,7 +16,7 @@ home.directory.not.specified.for.jdk.error.message=Home directory is not specifi run.configuration.pause.output.action.name=Pause Output main.class.is.not.specified.error.message=Main class is not specified close.tab.action.name=Close -run.configuration.show.command.line.action.name=Show Python Prompt +run.configuration.show.command.line.action.name=Show Console Prompt #--- create.run.configuration.action.name=Create Run Configuration diff --git a/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java b/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java index 45906d513f21..6b6edde16ae7 100644 --- a/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java +++ b/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java @@ -59,7 +59,7 @@ public class PyOpenDebugConsoleAction extends AnAction implements DumbAware { selectRunningProcess(e.getDataContext(), project, new Consumer() { @Override public void consume(PythonDebugLanguageConsoleView view) { - view.showDebugConsole(true); + view.enableConsole(false); IdeFocusManager.getInstance(project).requestFocus(view.getPydevConsoleView().getComponent(), true); } }); diff --git a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java index 495c26df74c8..4de1b094b499 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java @@ -15,64 +15,27 @@ */ package com.jetbrains.python.console; -import com.google.common.collect.Lists; -import com.intellij.execution.ExecutionBundle; -import com.intellij.execution.filters.Filter; -import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.console.DuplexConsoleView; +import com.intellij.execution.console.LanguageConsoleImpl; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.impl.ConsoleViewImpl; -import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.ui.ConsoleView; -import com.intellij.execution.ui.ConsoleViewContentType; -import com.intellij.execution.ui.ObservableConsoleView; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.actionSystem.ToggleAction; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.util.Disposer; -import icons.PythonIcons; +import com.intellij.openapi.wm.IdeFocusManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import java.awt.*; -import java.util.List; - /** * @author traff */ -public class PythonDebugLanguageConsoleView extends JPanel implements ConsoleView, ObservableConsoleView, PyCodeExecutor { - - private final static String TEXT_CONSOLE_PANEL = "TEXT_CONSOLE_PANEL"; - private final static String PYDEV_CONSOLE_PANEL = "PYDEV_CONSOLE_PANEL"; - - private final PythonConsoleView myPydevConsoleView; - - private final ConsoleView myTextConsole; - - public boolean myIsDebugConsole = false; - - private ProcessHandler myProcessHandler; +public class PythonDebugLanguageConsoleView extends DuplexConsoleView implements PyCodeExecutor { public PythonDebugLanguageConsoleView(final Project project, Sdk sdk, ConsoleView consoleView) { - super(new CardLayout()); - myPydevConsoleView = new PythonConsoleView(project, "Python Console", sdk); - myTextConsole = consoleView; + super(consoleView, new PythonConsoleView(project, "Python Console", sdk)); - add(myTextConsole.getComponent(), TEXT_CONSOLE_PANEL); - add(myPydevConsoleView.getComponent(), PYDEV_CONSOLE_PANEL); - - showDebugConsole(PyConsoleOptions.getInstance(project).isShowDebugConsoleByDefault()); - - Disposer.register(this, myPydevConsoleView); - Disposer.register(this, myTextConsole); + enableConsole(!PyConsoleOptions.getInstance(project).isShowDebugConsoleByDefault()); } public PythonDebugLanguageConsoleView(final Project project, Sdk sdk) { @@ -81,192 +44,33 @@ public class PythonDebugLanguageConsoleView extends JPanel implements ConsoleVie @Override public void executeCode(@NotNull String code, @Nullable Editor e) { - showDebugConsole(true); + enableConsole(false); getPydevConsoleView().executeCode(code, e); } - private void doShowConsole(String type) { - CardLayout cl = (CardLayout)(getLayout()); - cl.show(this, type); - } - - - public boolean isDebugConsole() { - return myIsDebugConsole; - } - - public void showDebugConsole(boolean flag) { - if (flag) { - doShowConsole(PYDEV_CONSOLE_PANEL); - myPydevConsoleView.requestFocus(); - } - else { - doShowConsole(TEXT_CONSOLE_PANEL); - } - myIsDebugConsole = flag; - } - public PythonConsoleView getPydevConsoleView() { - return myPydevConsoleView; + return getSecondaryConsoleView(); } public ConsoleViewImpl getTextConsole() { - if (myTextConsole instanceof ConsoleViewImpl) { - return (ConsoleViewImpl)myTextConsole; + ConsoleView consoleView = getPrimaryConsoleView(); + if (consoleView instanceof ConsoleViewImpl) { + return (ConsoleViewImpl)consoleView; } return null; } @Override - public void allowHeavyFilters() { - myTextConsole.allowHeavyFilters(); - } + public void enableConsole(boolean primary) { + super.enableConsole(primary); - @Override - public JComponent getComponent() { - return this; - } + if (!primary && !isPrimaryConsoleEnabled()) { + PythonConsoleView pydevConsoleView = getPydevConsoleView(); + LanguageConsoleImpl languageConsole = pydevConsoleView.getConsole(); - @Override - public JComponent getPreferredFocusableComponent() { - return this; - } - - @Override - public void dispose() { - } - - @Override - public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) { - myPydevConsoleView.print(s, contentType); - myTextConsole.print(s, contentType); - } - - @Override - public void clear() { - myPydevConsoleView.clear(); - myTextConsole.clear(); - } - - @Override - public void scrollTo(int offset) { - myPydevConsoleView.getLanguageConsole().getHistoryViewer().getCaretModel().moveToOffset(offset); - myPydevConsoleView.getLanguageConsole().getHistoryViewer().getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); - myTextConsole.scrollTo(offset); - } - - @Override - public void attachToProcess(ProcessHandler processHandler) { - myProcessHandler = processHandler; - myPydevConsoleView.attachToProcess(processHandler); - myTextConsole.attachToProcess(processHandler); - } - - @Override - public void setOutputPaused(boolean value) { - myPydevConsoleView.setOutputPaused(value); - myTextConsole.setOutputPaused(value); - } - - @Override - public boolean isOutputPaused() { - return false; - } - - @Override - public boolean hasDeferredOutput() { - return myPydevConsoleView.hasDeferredOutput() && myTextConsole.hasDeferredOutput(); - } - - @Override - public void performWhenNoDeferredOutput(Runnable runnable) { - - } - - @Override - public void setHelpId(String helpId) { - myPydevConsoleView.setHelpId(helpId); - myTextConsole.setHelpId(helpId); - } - - @Override - public void addMessageFilter(Filter filter) { - myPydevConsoleView.addMessageFilter(filter); - myTextConsole.addMessageFilter(filter); - } - - @Override - public void printHyperlink(String hyperlinkText, HyperlinkInfo info) { - myPydevConsoleView.printHyperlink(hyperlinkText, info); - myTextConsole.printHyperlink(hyperlinkText, info); - } - - @Override - public int getContentSize() { - return myTextConsole.getContentSize(); - } - - @Override - public boolean canPause() { - return false; - } - - @NotNull - @Override - public AnAction[] createConsoleActions() { - List actions = Lists.newArrayList(myTextConsole.createConsoleActions()); - actions.add(new ShowDebugConsoleAction(this)); - - return actions.toArray(new AnAction[actions.size()]); - } - - @Override - public void addChangeListener(@NotNull ChangeListener listener, @NotNull Disposable parent) { - myPydevConsoleView.addChangeListener(listener, parent); - if (myTextConsole instanceof ObservableConsoleView) { - ((ObservableConsoleView)myTextConsole).addChangeListener(listener, parent); - } - } - - private static class ShowDebugConsoleAction extends ToggleAction implements DumbAware { - private final PythonDebugLanguageConsoleView myConsole; - - - public ShowDebugConsoleAction(final PythonDebugLanguageConsoleView console) { - super(ExecutionBundle.message("run.configuration.show.command.line.action.name"), null, - PythonIcons.Python.Debug.CommandLine); - myConsole = console; - } - - @Override - public boolean isSelected(final AnActionEvent event) { - return myConsole.isDebugConsole(); - } - - @Override - public void setSelected(final AnActionEvent event, final boolean flag) { - myConsole.showDebugConsole(flag); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - update(event); - } - }); - } - - @Override - public void update(final AnActionEvent event) { - super.update(event); - final Presentation presentation = event.getPresentation(); - final boolean isRunning = myConsole.myProcessHandler != null && !myConsole.myProcessHandler.isProcessTerminated(); - if (isRunning) { - presentation.setEnabled(true); - } - else { - myConsole.showDebugConsole(false); - presentation.putClientProperty(SELECTED_PROPERTY, false); - presentation.setEnabled(false); - } + IdeFocusManager.findInstance().requestFocus(languageConsole.getConsoleEditor().getContentComponent(), true); + pydevConsoleView.updateUI(); + languageConsole.getHistoryViewer().getComponent().updateUI(); } } }