Next version of python repl console: internal web server launch and stop

This commit is contained in:
Oleg Shpynov
2010-04-12 16:15:05 +04:00
parent a4367787dc
commit ac4313a8da
6 changed files with 214 additions and 306 deletions
-225
View File
@@ -1,225 +0,0 @@
try:
from code import InteractiveConsole
except ImportError:
from pydevconsole_code_for_ironpython import InteractiveConsole
import os
import sys
try:
False
True
except NameError: # version < 2.3 -- didn't have the True/False builtins
import __builtin__
setattr(__builtin__, 'True', 1) #Python 3.0 does not accept __builtin__.True = 1 in its syntax
setattr(__builtin__, 'False', 0)
try:
try:
import xmlrpclib
except ImportError:
import xmlrpc.client as xmlrpclib
except ImportError:
import _pydev_xmlrpclib as xmlrpclib
#=======================================================================================================================
# StdIn
#=======================================================================================================================
class StdIn:
'''
Object to be added to stdin (to emulate it as non-blocking while the next line arrives)
'''
def __init__(self, interpreter, host, client_port):
self.interpreter = interpreter
self.client_port = client_port
self.host = host
def readline(self, *args, **kwargs): #@UnusedVariable
#Ok, callback into the client to see get the new input
server = xmlrpclib.Server('http://%s:%s' % (self.host, self.client_port))
return server.RequestInput()
def isatty(self):
return False #not really a file
def write(self, *args, **kwargs):
pass #not available StdIn (but it can be expected to be in the stream interface)
def flush(self, *args, **kwargs):
pass #not available StdIn (but it can be expected to be in the stream interface)
#in the interactive interpreter, a read and a readline are the same.
read = readline
#=======================================================================================================================
# InterpreterInterface
#=======================================================================================================================
class InterpreterInterface:
'''
The methods in this class should be registered in the xml-rpc server.
'''
def __init__(self, host, client_port):
self.client_port = client_port
self.host = host
self.namespace = {}
self.interpreter = InteractiveConsole(self.namespace)
def addExec(self, line):
#f_opened = open('c:/temp/a.txt', 'a')
#f_opened.write(line+'\n')
original_in = sys.stdin
try:
help = None
if 'pydoc' in sys.modules:
pydoc = sys.modules['pydoc'] #Don't import it if it still is not there.
if hasattr(pydoc, 'help'):
#You never know how will the API be changed, so, let's code defensively here
help = pydoc.help
if not hasattr(help, 'input'):
help = None
except:
#Just ignore any error here
pass
sys.stdin = StdIn(self, self.host, self.client_port)
if help is not None:
#This will enable the help() function to work.
help.input = sys.stdin
try:
more = self.interpreter.push(line)
finally:
if help is not None:
help.input = original_in
sys.stdin = original_in
#it's always false at this point
need_input = False
return more, need_input
def getCompletions(self, text):
from _completer import Completer
completer = Completer(self.namespace, None)
return completer.complete(text)
def getDescription(self, text):
obj = None
if '.' not in text:
try:
obj = self.namespace[text]
except KeyError:
return ''
else:
try:
splitted = text.split('.')
obj = self.namespace[splitted[0]]
for t in splitted[1:]:
obj = getattr(obj, t)
except:
return ''
if obj is not None:
try:
if sys.platform.startswith("java"):
#Jython
doc = obj.__doc__
if doc is not None:
return doc
import jyimportsTipper
is_method, infos = jyimportsTipper.ismethod(obj)
ret = ''
if is_method:
for info in infos:
ret += info.getAsDoc()
return ret
else:
#Python and Iron Python
import inspect #@UnresolvedImport
doc = inspect.getdoc(obj)
if doc is not None:
return doc
except:
pass
try:
#if no attempt succeeded, try to return repr()...
return repr(obj)
except:
try:
#otherwise the class
return str(obj.__class__)
except:
#if all fails, go to an empty string
return ''
def close(self):
sys.exit(0)
#=======================================================================================================================
# _DoExit
#=======================================================================================================================
def _DoExit(*args):
'''
We have to override the exit because calling sys.exit will only actually exit the main thread,
and as we're in a Xml-rpc server, that won't work.
'''
try:
import java.lang.System
java.lang.System.exit(1)
except ImportError:
if len(args) == 1:
os._exit(args[0])
else:
os._exit(0)
#=======================================================================================================================
# StartServer
#=======================================================================================================================
def StartServer(host, port, client_port):
#replace exit (see comments on method)
#note that this does not work in jython!!! (sys method can't be replaced).
sys.exit = _DoExit
try:
try:
from SimpleXMLRPCServer import SimpleXMLRPCServer
except ImportError:
from xmlrpc.server import SimpleXMLRPCServer
except ImportError:
from _pydev_SimpleXMLRPCServer import SimpleXMLRPCServer
interpreter = InterpreterInterface(host, client_port)
server = SimpleXMLRPCServer((host, port), logRequests=False)
server.register_function(interpreter.addExec)
server.register_function(interpreter.getCompletions)
server.register_function(interpreter.getDescription)
server.register_function(interpreter.close)
server.serve_forever()
#=======================================================================================================================
# main
#=======================================================================================================================
if __name__ == '__main__':
port, client_port = sys.argv[1:3]
StartServer('localhost', int(port), int(client_port))
@@ -0,0 +1,69 @@
package com.jetbrains.python.console;
import com.intellij.execution.console.LanguageConsoleImpl;
import com.intellij.execution.process.ColoredProcessHandler;
import com.intellij.execution.process.ConsoleHighlighter;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.Key;
import com.jetbrains.python.PyHighlighter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author oleg
*/
public class PyConsoleHighlightingUtil {
public static final String ORDINARY_PROMPT = ">>> ";
public static final String INDENT_PROMPT = "... ";
static final String HELP_PROMPT = "help> ";
public static final Key STRING_KEY = new Key("PYTHON_STRING");
public static final Key NUMBER_KEY = new Key("PYTHON_NUMBER");
static final String NUMBERS = "((0[xX][0-9a-fA-F]+)|(\\d+(_\\d+)*(\\.\\d+)?))";
static final String STRINGS = "((\"[^\"\n]*\")|((?<!\\w)'[^'\n]*'))";
static final Pattern CODE_ELEMENT_PATTERN = Pattern.compile(NUMBERS + "|" + STRINGS);
static final ConsoleViewContentType STRING_ATTRIBUTES = new ConsoleViewContentType(STRING_KEY.toString(),
ColoredProcessHandler.getByKey(PyHighlighter.PY_STRING));
static final ConsoleViewContentType NUMBER_ATTRIBUTES = new ConsoleViewContentType(NUMBER_KEY.toString(),
ColoredProcessHandler.getByKey(PyHighlighter.PY_NUMBER));
public static void processOutput(final LanguageConsoleImpl console, String string, final Key attributes) {
final ConsoleViewContentType type =
attributes == ProcessOutputTypes.STDERR ? ConsoleViewContentType.ERROR_OUTPUT : ConsoleViewContentType.NORMAL_OUTPUT;
// Highlight output by pattern
Matcher matcher;
while ((matcher = CODE_ELEMENT_PATTERN.matcher(string)).find()) {
printToConsole(console, string.substring(0, matcher.start()), type);
// Number group
if (matcher.group(1) != null) {
printToConsole(console, matcher.group(1), NUMBER_ATTRIBUTES);
}
// String group
else if (matcher.group(6) != null) {
printToConsole(console, matcher.group(6), STRING_ATTRIBUTES);
}
else {
printToConsole(console, matcher.group(), type);
}
string = string.substring(matcher.end());
}
printToConsole(console, string, type);
}
static void printToConsole(final LanguageConsoleImpl console, final String string, final ConsoleViewContentType type) {
final TextAttributes attributes = TextAttributes.merge(type.getAttributes(), ConsoleHighlighter.OUT.getDefaultAttributes());
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
console.printToHistory(string, attributes);
}
}, ModalityState.stateForComponent(console.getComponent()));
}
}
@@ -1,21 +1,11 @@
package com.jetbrains.python.console;
import com.intellij.execution.console.LanguageConsoleImpl;
import com.intellij.execution.process.ColoredProcessHandler;
import com.intellij.execution.process.ConsoleHighlighter;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.python.PyHighlighter;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author oleg
@@ -33,59 +23,22 @@ public class PyConsoleProcessHandler extends OSProcessHandler {
myCharset = charset;
}
public static final String ORDINARY_PROMPT = ">>> ";
private static final String INDENT_PROMPT = "... ";
private static final String HELP_PROMPT = "help> ";
private final String[] PROMPTS = new String[]{ORDINARY_PROMPT, INDENT_PROMPT, HELP_PROMPT};
public static final Key STRING_KEY = new Key("PYTHON_STRING");
public static final Key NUMBER_KEY = new Key("PYTHON_NUMBER");
private static final String NUMBERS = "((0[xX][0-9a-fA-F]+)|(\\d+(_\\d+)*(\\.\\d+)?))";
private static final String STRINGS = "((\"[^\"\n]*\")|((?<!\\w)'[^'\n]*'))";
private static final Pattern CODE_ELEMENT_PATTERN = Pattern.compile(NUMBERS + "|" + STRINGS);
private final String[] PROMPTS = new String[]{
PyConsoleHighlightingUtil.ORDINARY_PROMPT,
PyConsoleHighlightingUtil.INDENT_PROMPT,
PyConsoleHighlightingUtil.HELP_PROMPT
};
@Override
public Charset getCharset() {
return myCharset != null ? myCharset : super.getCharset();
}
private static final ConsoleViewContentType STRING_ATTRIBUTES = new ConsoleViewContentType(STRING_KEY.toString(),
ColoredProcessHandler.getByKey(PyHighlighter.PY_STRING));
private static final ConsoleViewContentType NUMBER_ATTRIBUTES = new ConsoleViewContentType(NUMBER_KEY.toString(),
ColoredProcessHandler.getByKey(PyHighlighter.PY_NUMBER));
@Override
public void notifyTextAvailable(final String text, final Key attributes) {
String string = processPrompts(myLanguageConsole, StringUtil.convertLineSeparators(text));
processOutput(myLanguageConsole, string, attributes);
}
public static void processOutput(LanguageConsoleImpl console, String string, final Key attributes) {
final ConsoleViewContentType type =
attributes == ProcessOutputTypes.STDERR ? ConsoleViewContentType.ERROR_OUTPUT : ConsoleViewContentType.NORMAL_OUTPUT;
// Highlight output by pattern
Matcher matcher;
while ((matcher = CODE_ELEMENT_PATTERN.matcher(string)).find()) {
printToConsole(console, string.substring(0, matcher.start()), type);
// Number group
if (matcher.group(1) != null) {
printToConsole(console, matcher.group(1), NUMBER_ATTRIBUTES);
}
// String group
else if (matcher.group(6) != null) {
printToConsole(console, matcher.group(6), STRING_ATTRIBUTES);
}
else {
printToConsole(console, matcher.group(), type);
}
string = string.substring(matcher.end());
}
printToConsole(console, string, type);
PyConsoleHighlightingUtil.processOutput(myLanguageConsole, string, attributes);
}
private String processPrompts(final LanguageConsoleImpl languageConsole, String string) {
@@ -93,14 +46,14 @@ public class PyConsoleProcessHandler extends OSProcessHandler {
for (String prompt : PROMPTS) {
if (string.startsWith(prompt)) {
// Process multi prompts here
if (prompt != HELP_PROMPT){
if (prompt != PyConsoleHighlightingUtil.HELP_PROMPT){
final StringBuilder builder = new StringBuilder();
builder.append(prompt).append(prompt);
while (string.startsWith(builder.toString())){
builder.append(prompt);
}
final String multiPrompt = builder.toString().substring(prompt.length());
if (prompt == INDENT_PROMPT){
if (prompt == PyConsoleHighlightingUtil.INDENT_PROMPT){
prompt = multiPrompt;
}
string = string.substring(multiPrompt.length());
@@ -119,13 +72,4 @@ public class PyConsoleProcessHandler extends OSProcessHandler {
}
return string;
}
private static void printToConsole(final LanguageConsoleImpl console, final String string, final ConsoleViewContentType type) {
final TextAttributes attributes = TextAttributes.merge(type.getAttributes(), ConsoleHighlighter.OUT.getDefaultAttributes());
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
console.printToHistory(string, attributes);
}
}, ModalityState.stateForComponent(console.getComponent()));
}
}
@@ -130,7 +130,7 @@ public class PyConsoleRunner {
});
// Setup default prompt
myConsoleView.getConsole().setPrompt(PyConsoleProcessHandler.ORDINARY_PROMPT.trim());
myConsoleView.getConsole().setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT.trim());
// Attach to process
myConsoleView.attachToProcess(myProcessHandler);
@@ -177,9 +177,9 @@ public class PyConsoleRunner {
return Runner.createProcess(myWorkingDir, true, myProvider.getAdditionalEnvs(), myProvider.getArguments());
}
protected PyConsoleProcessHandler createProcessHandler(final Process process) {
private PyConsoleProcessHandler createProcessHandler(final Process process) {
final Charset outputEncoding = EncodingManager.getInstance().getDefaultCharset();
return new PyConsoleProcessHandler(process, myConsoleView.getConsole(), getProviderCommandLine(), outputEncoding);
return new PyConsoleProcessHandler(process, myConsoleView.getConsole(), getProviderCommandLine(myProvider), outputEncoding);
}
private void registerActionShortcuts(final AnAction[] actions, final JComponent component) {
@@ -194,11 +194,11 @@ public class PyConsoleRunner {
final Executor defaultExecutor,
final RunContentDescriptor myDescriptor) {
//stop
final AnAction stopAction = ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
final AnAction stopAction = createStopAction();
toolbarActions.add(stopAction);
//close
final CloseAction closeAction = new CloseAction(defaultExecutor, myDescriptor, myProject);
final AnAction closeAction = createCloseAction(defaultExecutor, myDescriptor);
toolbarActions.add(closeAction);
// run action
@@ -241,6 +241,14 @@ public class PyConsoleRunner {
return new AnAction[]{stopAction, closeAction, myRunAction, historyNextAction, historyPrevAction};
}
protected AnAction createCloseAction(final Executor defaultExecutor, final RunContentDescriptor myDescriptor) {
return new CloseAction(defaultExecutor, myDescriptor, myProject);
}
protected AnAction createStopAction() {
return ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
}
protected void sendInput(final String input) {
final Charset charset = myProcessHandler.getCharset();
final OutputStream outputStream = myProcessHandler.getProcessInput();
@@ -259,6 +267,7 @@ public class PyConsoleRunner {
}
private void runExecuteActionInner(final boolean erase) {
// Process input and add to history
final Document document = getLanguageConsole().getCurrentEditor().getDocument();
final String documentText = document.getText();
final TextRange range = new TextRange(0, document.getTextLength());
@@ -271,14 +280,15 @@ public class PyConsoleRunner {
if (!StringUtil.isEmptyOrSpaces(line)){
myHistory.addToHistory(line);
}
// Send to interpreter / server
final String text2send = line.length() == 0 ? "\n\n" : line + "\n";
sendInput(text2send);
myConsoleView.inputSent(text2send);
}
protected String getProviderCommandLine() {
private static String getProviderCommandLine(final CommandLineArgumentsProvider provider) {
final StringBuilder builder = new StringBuilder();
for (String s : myProvider.getArguments()) {
for (String s : provider.getArguments()) {
if (builder.length() > 0){
builder.append(' ');
}
@@ -1,14 +1,17 @@
package com.jetbrains.python.console;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
import com.intellij.execution.console.LanguageConsoleImpl;
import com.intellij.execution.process.*;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.ui.actions.CloseAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.util.net.NetUtils;
import com.jetbrains.django.run.ExecutionHelper;
import com.jetbrains.django.run.Runner;
@@ -20,8 +23,8 @@ import com.jetbrains.python.console.pydev.PydevConsoleCommunication;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -49,7 +52,7 @@ public class PydevConsoleRunner extends PyConsoleRunner {
final String consoleTitle = "TRUE console";
final int[] ports;
try {
// File "pydev/pydevconsole.py", line 223, in <module>
// File "pydev/console/pydevconsole.py", line 223, in <module>
// port, client_port = sys.argv[1:3]
ports = NetUtils.findAvailableSocketPorts(2);
}
@@ -57,7 +60,8 @@ public class PydevConsoleRunner extends PyConsoleRunner {
ExecutionHelper.showErrors(project, Arrays.<Exception>asList(e), consoleTitle, null);
return;
}
final ArrayList<String> args = new ArrayList<String>(Arrays.asList(sdk.getHomePath(), "-u", PythonHelpersLocator.getHelperPath("pydev/pydevconsole.py")));
final ArrayList<String> args = new ArrayList<String>(
Arrays.asList(sdk.getHomePath(), "-u", PythonHelpersLocator.getHelperPath("pydev/console/pydevconsole.py")));
for (int port : ports) {
args.add(String.valueOf(port));
}
@@ -99,21 +103,101 @@ public class PydevConsoleRunner extends PyConsoleRunner {
@Override
public void initAndRun() throws ExecutionException {
super.initAndRun();
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// Ignore
}
sendInput("import sys; print('Python %s on %s' % (sys.version, sys.platform))\n");
}
@Override
protected AnAction createCloseAction(final Executor defaultExecutor, final RunContentDescriptor myDescriptor) {
final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, myDescriptor);
final AnAction closeAction = new AnAction() {
@Override
public void update(AnActionEvent e) {
generalCloseAction.update(e);
}
@Override
public void actionPerformed(AnActionEvent e) {
if (myPydevConsoleCommunication != null) {
try {
myPydevConsoleCommunication.close();
}
catch (Exception e1) {
// Ignore
}
generalCloseAction.actionPerformed(e);
}
}
};
closeAction.copyFrom(generalCloseAction);
return closeAction;
}
@Override
protected AnAction createStopAction() {
final AnAction generalStopAction = super.createStopAction();
final AnAction stopAction = new AnAction() {
@Override
public void update(AnActionEvent e) {
generalStopAction.update(e);
}
@Override
public void actionPerformed(AnActionEvent e) {
if (myPydevConsoleCommunication != null) {
try {
myPydevConsoleCommunication.close();
}
catch (Exception e1) {
// Ignore
}
generalStopAction.actionPerformed(e);
}
}
};
stopAction.copyFrom(generalStopAction);
return stopAction;
}
@Override
protected void sendInput(final String input) {
if (myPydevConsoleCommunication != null){
//if ("comp\n".equals(input)){
// String[] completions = new String[]{};
// try {
// completions = myPydevConsoleCommunication.getCompletions("", 0);
// }
// catch (Exception e) {
// PyConsoleProcessHandler.processOutput(myConsoleView.getConsole(), e.toString()+'\n', ProcessOutputTypes.STDERR);
// }
// for (String completion : completions) {
// PyConsoleProcessHandler.processOutput(myConsoleView.getConsole(), completion + "\n", ProcessOutputTypes.STDOUT);
// }
// return;
//}
myPydevConsoleCommunication.execInterpreter(input, new ICallback<Object, InterpreterResponse>() {
public Object call(final InterpreterResponse interpreterResponse) {
final LanguageConsoleImpl console = myConsoleView.getConsole();
// TODO[oleg] More sophisticated callback required!
// At least this one works!
if (!StringUtil.isEmpty(interpreterResponse.err)){
PyConsoleProcessHandler.processOutput(console, interpreterResponse.err, ProcessOutputTypes.STDERR);
// Handle prompt
if (interpreterResponse.more){
if (!PyConsoleHighlightingUtil.INDENT_PROMPT.equals(console.getPrompt())){
console.setPrompt(PyConsoleHighlightingUtil.INDENT_PROMPT);
}
} else {
PyConsoleProcessHandler.processOutput(console, interpreterResponse.out, ProcessOutputTypes.STDOUT);
if (!PyConsoleHighlightingUtil.ORDINARY_PROMPT.equals(console.getPrompt())){
console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT);
}
}
// Handle output
if (!StringUtil.isEmpty(interpreterResponse.err)){
PyConsoleHighlightingUtil.processOutput(console, interpreterResponse.err, ProcessOutputTypes.STDERR);
} else {
PyConsoleHighlightingUtil.processOutput(console, interpreterResponse.out, ProcessOutputTypes.STDOUT);
}
return null;
}
@@ -0,0 +1,26 @@
package com.jetbrains.python.console;
import com.jetbrains.python.console.pydev.PydevConsoleCommunication;
import org.apache.xmlrpc.WebServer;
import org.apache.xmlrpc.XmlRpcHandlerMapping;
import org.apache.xmlrpc.XmlRpcServer;
/**
* Web server to handle all the input requests by PydevConsoleCommunication
* @author oleg
*/
public class PydevWebServer extends WebServer {
public PydevWebServer(int port, final PydevConsoleCommunication communication) {
super(port, null, new XmlRpcServer(){
final XmlRpcHandlerMapping xmlRpcHandlerMapping = new XmlRpcHandlerMapping() {
public Object getHandler(String handlerName) throws Exception {
return communication;
}
};
@Override
public XmlRpcHandlerMapping getHandlerMapping() {
return xmlRpcHandlerMapping;
}
});
}
}