IDEA-CR-63337: PY-40019 Make Console commands execution visible for users and properly report exceptions from Python side inside IDE

GitOrigin-RevId: 9bdcff1fc822dea47d6e23192920dec5c452cd04
This commit is contained in:
Elizaveta Shashkova
2020-06-02 16:11:36 +03:00
committed by intellij-monorepo-bot
parent 2647ec2735
commit b1936886a0
19 changed files with 1512 additions and 415 deletions

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class ArrayData implements org.apache.thrift.TBase<ArrayData, ArrayData._Fields>, java.io.Serializable, Cloneable, Comparable<ArrayData> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ArrayData");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class ArrayHeaders implements org.apache.thrift.TBase<ArrayHeaders, ArrayHeaders._Fields>, java.io.Serializable, Cloneable, Comparable<ArrayHeaders> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ArrayHeaders");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class ColHeader implements org.apache.thrift.TBase<ColHeader, ColHeader._Fields>, java.io.Serializable, Cloneable, Comparable<ColHeader> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ColHeader");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class CompletionOption implements org.apache.thrift.TBase<CompletionOption, CompletionOption._Fields>, java.io.Serializable, Cloneable, Comparable<CompletionOption> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("CompletionOption");

View File

@@ -10,7 +10,7 @@ package com.jetbrains.python.console.protocol;
/**
* Corresponds to `PyDebugValue`.
*/
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class DebugValue implements org.apache.thrift.TBase<DebugValue, DebugValue._Fields>, java.io.Serializable, Cloneable, Comparable<DebugValue> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DebugValue");

View File

@@ -11,7 +11,7 @@ package com.jetbrains.python.console.protocol;
* Indicates that the related array has more than two dimensions.
*
*/
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class ExceedingArrayDimensionsException extends org.apache.thrift.TException implements org.apache.thrift.TBase<ExceedingArrayDimensionsException, ExceedingArrayDimensionsException._Fields>, java.io.Serializable, Cloneable, Comparable<ExceedingArrayDimensionsException> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ExceedingArrayDimensionsException");

View File

@@ -11,7 +11,7 @@ package com.jetbrains.python.console.protocol;
* Corresponds to `ArrayChunk`.
*
*/
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class GetArrayResponse implements org.apache.thrift.TBase<GetArrayResponse, GetArrayResponse._Fields>, java.io.Serializable, Cloneable, Comparable<GetArrayResponse> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("GetArrayResponse");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class KeyboardInterruptException extends org.apache.thrift.TException implements org.apache.thrift.TBase<KeyboardInterruptException, KeyboardInterruptException._Fields>, java.io.Serializable, Cloneable, Comparable<KeyboardInterruptException> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("KeyboardInterruptException");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class PythonConsoleFrontendService {
public interface Iface {

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class PythonUnhandledException extends org.apache.thrift.TException implements org.apache.thrift.TBase<PythonUnhandledException, PythonUnhandledException._Fields>, java.io.Serializable, Cloneable, Comparable<PythonUnhandledException> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("PythonUnhandledException");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class RowHeader implements org.apache.thrift.TBase<RowHeader, RowHeader._Fields>, java.io.Serializable, Cloneable, Comparable<RowHeader> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("RowHeader");

View File

@@ -7,7 +7,7 @@
package com.jetbrains.python.console.protocol;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-03-16")
@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2020-06-03")
public class UnsupportedArrayTypeException extends org.apache.thrift.TException implements org.apache.thrift.TBase<UnsupportedArrayTypeException, UnsupportedArrayTypeException._Fields>, java.io.Serializable, Cloneable, Comparable<UnsupportedArrayTypeException> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("UnsupportedArrayTypeException");

View File

@@ -123,20 +123,28 @@ class BaseInterpreterInterface(BaseCodeExecutor):
return False
def execLine(self, line):
if not self.banner_shown:
line = self.build_banner() + line
self.banner_shown = True
return self.do_exec_code(line, True)
try:
if not self.banner_shown:
line = self.build_banner() + line
self.banner_shown = True
return self.do_exec_code(line, True)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def execMultipleLines(self, lines):
if not self.banner_shown:
lines = self.build_banner() + lines
self.banner_shown = True
if IS_JYTHON:
for line in lines.split('\n'):
self.do_exec_code(line, True)
else:
return self.do_exec_code(lines, False)
try:
if not self.banner_shown:
lines = self.build_banner() + lines
self.banner_shown = True
if IS_JYTHON:
for line in lines.split('\n'):
self.do_exec_code(line, True)
else:
return self.do_exec_code(lines, False)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def interrupt(self):
self.buffer = None # Also clear the buffer when it's interrupted.
@@ -212,33 +220,48 @@ class BaseInterpreterInterface(BaseCodeExecutor):
return True
def getFrame(self):
hidden_ns = self.get_ipython_hidden_vars_dict()
return pydevd_thrift.frame_vars_to_struct(self.get_namespace(), hidden_ns)
try:
hidden_ns = self.get_ipython_hidden_vars_dict()
return pydevd_thrift.frame_vars_to_struct(self.get_namespace(), hidden_ns)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def getVariable(self, attributes):
debug_values = []
val_dict = pydevd_vars.resolve_compound_var_object_fields(self.get_namespace(), attributes)
if val_dict is None:
val_dict = {}
try:
debug_values = []
val_dict = pydevd_vars.resolve_compound_var_object_fields(self.get_namespace(), attributes)
if val_dict is None:
val_dict = {}
keys = val_dict.keys()
for k in keys:
val = val_dict[k]
evaluate_full_value = pydevd_thrift.should_evaluate_full_value(val)
debug_values.append(pydevd_thrift.var_to_struct(val, k, evaluate_full_value=evaluate_full_value))
keys = val_dict.keys()
for k in keys:
val = val_dict[k]
evaluate_full_value = pydevd_thrift.should_evaluate_full_value(val)
debug_values.append(pydevd_thrift.var_to_struct(val, k, evaluate_full_value=evaluate_full_value))
return debug_values
return debug_values
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def getArray(self, attr, roffset, coffset, rows, cols, format):
name = attr.split("\t")[-1]
array = pydevd_vars.eval_in_context(name, self.get_namespace(), self.get_namespace())
return pydevd_thrift.table_like_struct_to_thrift_struct(array, name, roffset, coffset, rows, cols, format)
try:
name = attr.split("\t")[-1]
array = pydevd_vars.eval_in_context(name, self.get_namespace(), self.get_namespace())
return pydevd_thrift.table_like_struct_to_thrift_struct(array, name, roffset, coffset, rows, cols, format)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def evaluate(self, expression, do_trunc):
# returns `DebugValue` of evaluated expression
result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace())
return [pydevd_thrift.var_to_struct(result, expression, do_trim=do_trunc)]
try:
result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace())
return [pydevd_thrift.var_to_struct(result, expression, do_trim=do_trunc)]
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def do_get_completions(self, text, act_tok):
"""Retrieves completion options.
@@ -283,35 +306,43 @@ class BaseInterpreterInterface(BaseCodeExecutor):
(i.e.: obj\tattr1\tattr2NEXT_VALUE_SEPARATORobj2\attr1\tattr2)
:return:
"""
frame_variables = self.get_namespace()
var_objects = []
# vars = scope_attrs.split(NEXT_VALUE_SEPARATOR)
vars = scope_attrs
for var_attrs in vars:
if '\t' in var_attrs:
name, attrs = var_attrs.split('\t', 1)
try:
frame_variables = self.get_namespace()
var_objects = []
# vars = scope_attrs.split(NEXT_VALUE_SEPARATOR)
vars = scope_attrs
for var_attrs in vars:
if '\t' in var_attrs:
name, attrs = var_attrs.split('\t', 1)
else:
name = var_attrs
attrs = None
if name in frame_variables.keys():
var_object = pydevd_vars.resolve_var_object(frame_variables[name], attrs)
var_objects.append((var_object, name))
else:
var_object = pydevd_vars.eval_in_context(name, frame_variables, frame_variables)
var_objects.append((var_object, name))
else:
name = var_attrs
attrs = None
if name in frame_variables.keys():
var_object = pydevd_vars.resolve_var_object(frame_variables[name], attrs)
var_objects.append((var_object, name))
else:
var_object = pydevd_vars.eval_in_context(name, frame_variables, frame_variables)
var_objects.append((var_object, name))
from _pydev_bundle.pydev_console_commands import ThriftGetValueAsyncThreadConsole
t = ThriftGetValueAsyncThreadConsole(self.get_server(), seq, var_objects)
t.start()
from _pydev_bundle.pydev_console_commands import ThriftGetValueAsyncThreadConsole
t = ThriftGetValueAsyncThreadConsole(self.get_server(), seq, var_objects)
t.start()
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def changeVariable(self, attr, value):
def do_change_variable():
Exec('%s=%s' % (attr, value), self.get_namespace(), self.get_namespace())
try:
def do_change_variable():
Exec('%s=%s' % (attr, value), self.get_namespace(), self.get_namespace())
# Important: it has to be really enabled in the main thread, so, schedule
# it to run in the main thread.
self.exec_queue.put(do_change_variable)
# Important: it has to be really enabled in the main thread, so, schedule
# it to run in the main thread.
self.exec_queue.put(do_change_variable)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def _findFrame(self, thread_id, frame_id):
'''
@@ -333,65 +364,67 @@ class BaseInterpreterInterface(BaseCodeExecutor):
Used to show console with variables connection.
Mainly, monkey-patches things in the debugger structure so that the debugger protocol works.
'''
try:
if debugger_options is None:
debugger_options = {}
if debugger_options is None:
debugger_options = {}
for (env_name, value) in dict_iter_items(extra_envs):
existing_value = os.environ.get(env_name, None)
if existing_value:
os.environ[env_name] = "%s%c%s" % (existing_value, os.path.pathsep, value)
else:
os.environ[env_name] = value
if env_name == "PYTHONPATH":
sys.path.append(value)
def do_connect_to_debugger():
try:
# Try to import the packages needed to attach the debugger
import pydevd
from _pydev_imps._pydev_saved_modules import threading
except:
# This happens on Jython embedded in host eclipse
traceback.print_exc()
sys.stderr.write('pydevd is not available, cannot connect\n', )
from _pydevd_bundle.pydevd_constants import set_thread_id
from _pydev_bundle import pydev_localhost
set_thread_id(threading.currentThread(), "console_main")
self.orig_find_frame = pydevd_vars.find_frame
pydevd_vars.find_frame = self._findFrame
self.debugger = pydevd.PyDB()
try:
pydevd.apply_debugger_options(debugger_options)
if debugger_host is None or pydev_localhost.is_localhost(debugger_host):
host = pydev_localhost.get_localhost()
for (env_name, value) in dict_iter_items(extra_envs):
existing_value = os.environ.get(env_name, None)
if existing_value:
os.environ[env_name] = "%s%c%s" % (existing_value, os.path.pathsep, value)
else:
host = debugger_host
self.debugger.connect(host, debuggerPort)
self.debugger.prepare_to_run()
self.debugger.disable_tracing()
except:
traceback.print_exc()
sys.stderr.write('Failed to connect to target debugger.\n')
os.environ[env_name] = value
if env_name == "PYTHONPATH":
sys.path.append(value)
# Register to process commands when idle
self.debugrunning = False
try:
import pydevconsole
pydevconsole.set_debug_hook(self.debugger.process_internal_commands)
except:
traceback.print_exc()
sys.stderr.write('Version of Python does not support debuggable Interactive Console.\n')
def do_connect_to_debugger():
try:
# Try to import the packages needed to attach the debugger
import pydevd
from _pydev_imps._pydev_saved_modules import threading
# Important: it has to be really enabled in the main thread, so, schedule
# it to run in the main thread.
self.exec_queue.put(do_connect_to_debugger)
except:
# This happens on Jython embedded in host eclipse
traceback.print_exc()
sys.stderr.write('pydevd is not available, cannot connect\n', )
return ('connect complete',)
from _pydevd_bundle.pydevd_constants import set_thread_id
from _pydev_bundle import pydev_localhost
set_thread_id(threading.currentThread(), "console_main")
self.orig_find_frame = pydevd_vars.find_frame
pydevd_vars.find_frame = self._findFrame
self.debugger = pydevd.PyDB()
try:
pydevd.apply_debugger_options(debugger_options)
if debugger_host is None or pydev_localhost.is_localhost(debugger_host):
host = pydev_localhost.get_localhost()
else:
host = debugger_host
self.debugger.connect(host, debuggerPort)
self.debugger.prepare_to_run()
self.debugger.disable_tracing()
except:
traceback.print_exc()
sys.stderr.write('Failed to connect to target debugger.\n')
# Register to process commands when idle
self.debugrunning = False
try:
import pydevconsole
pydevconsole.set_debug_hook(self.debugger.process_internal_commands)
except:
traceback.print_exc()
sys.stderr.write('Version of Python does not support debuggable Interactive Console.\n')
# Important: it has to be really enabled in the main thread, so, schedule
# it to run in the main thread.
self.exec_queue.put(do_connect_to_debugger)
return ('connect complete',)
except:
traceback.print_exc()
raise PythonUnhandledException(traceback.format_exc())
def handshake(self):
if self.connect_status_queue is not None:

View File

@@ -132,13 +132,13 @@ service PythonConsoleBackendService {
* Returns `true` if Python console script needs more code to evaluate it.
* Returns `false` if the code is scheduled for evaluation.
*/
bool execLine(1: string line),
bool execLine(1: string line) throws (1: PythonUnhandledException unhandledException),
/**
* Returns `true` if Python console script needs more code to evaluate it.
* Returns `false` if the code is scheduled for evaluation.
*/
bool execMultipleLines(1: string lines),
bool execMultipleLines(1: string lines) throws (1: PythonUnhandledException unhandledException),
GetCompletionsResponse getCompletions(1: string text, 2: string actTok) throws (1: PythonUnhandledException unhandledException),
@@ -150,19 +150,20 @@ service PythonConsoleBackendService {
/**
* Return Frame
*/
GetFrameResponse getFrame(),
GetFrameResponse getFrame() throws (1: PythonUnhandledException unhandledException),
/**
* Parameter is a full path in a variables tree from the top-level parent to the debug value.
**/
DebugValues getVariable(1: string variable),
DebugValues getVariable(1: string variable) throws (1: PythonUnhandledException unhandledException),
/**
* Changes the variable value asynchronously.
*/
void changeVariable(1: string evaluationExpression, 2: string value),
void changeVariable(1: string evaluationExpression, 2: string value) throws (1: PythonUnhandledException unhandledException),
void connectToDebugger(1: i32 localPort, 2: string host, 3: map<string, bool> opts, 4: map<string, string> extraEnvs),
void connectToDebugger(1: i32 localPort, 2: string host, 3: map<string, bool> opts, 4: map<string, string> extraEnvs)
throws (1: PythonUnhandledException unhandledException),
void interrupt(),
@@ -176,15 +177,17 @@ service PythonConsoleBackendService {
*/
oneway void close(),
DebugValues evaluate(1: string expression, 2: bool doTrunc),
DebugValues evaluate(1: string expression, 2: bool doTrunc) throws (1: PythonUnhandledException unhandledException),
GetArrayResponse getArray(1: string vars, 2: i32 rowOffset, 3: i32 colOffset, 4: i32 rows, 5: i32 cols, 6: string format)
throws (1: UnsupportedArrayTypeException unsupported, 2: ExceedingArrayDimensionsException exceedingDimensions),
throws (1: UnsupportedArrayTypeException unsupported, 2: ExceedingArrayDimensionsException exceedingDimensions,
3: PythonUnhandledException unhandledException),
/**
* The result is returned asyncronously with `PythonConsoleFrontendService.returnFullValue`.
*/
void loadFullValue(1: LoadFullValueRequestSeq seq, 2: list<string> variables),
void loadFullValue(1: LoadFullValueRequestSeq seq, 2: list<string> variables) throws (1: PythonUnhandledException unhandledException),
}
exception KeyboardInterruptException {

View File

@@ -707,10 +707,17 @@ configurable.choose.path.to.the.package.requirements.file=Choose path to the pac
configurable.choose.working.directory=Please choose working directory:
configurable.select.working.directory=Select Working Directory
console.waiting.for.repl.response.with.timeout=Waiting for REPL response with {0}s timeout
console.waiting.for.repl.response=Waiting for REPL Response
console.repl.communication=REPL Communication
console.getting.description=Getting Description
console.waiting.execution.result=Waiting for Execution Result
console.getting.from.runtime=Getting {0} from Python Runtime
console.getting.completion=Completion
console.getting.documentation=Documentation
console.getting.frame.variables=Frame Variables
console.getting.variable.value=Variable Value
console.getting.array=Array
console.evaluating.expression.in.console=Evaluating Expression in Console
console.connecting.to.debugger=Connecting To Debugger
console.changing.variable=Changing Variable
console.interrupting.execution=Interrupting Execution
console.close.console.communication=Close Console Communication
console.executing.code.in.console=Executing Code in Console...
console.new.console.description=Creates new python console
@@ -720,6 +727,7 @@ console.cannot.connect.to.debugger=Can't connect to debugger
console.attach.debugger.description=Enables tracing of code executed in console
console.attach.debugger=Attach Debugger
console.restarting.console=Restarting Console
console.stopping.console=Stopping Console
console.command.executor=Python Console Command Executor
connecting.to.console.progress=Connecting to console...
connecting.to.console.title=Connecting to Console

View File

@@ -11,11 +11,11 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.concurrency.FutureResult;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.frame.XCompositeNode;
@@ -39,11 +39,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.jetbrains.python.console.PydevConsoleCommunicationUtil.*;
@@ -63,21 +62,16 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
/**
* Response that should be sent back to the shell.
*/
protected volatile InterpreterResponse nextResponse;
/**
* Helper to keep on busy loop.
*/
private final Object lock2 = new Object();
/**
* Keeps a flag indicating that we were able to communicate successfully with the shell at least once
* (if we haven't we may retry more than once the first time, as jython can take a while to initialize
* the communication)
*/
private volatile boolean firstCommWorked = false;
@Nullable protected volatile InterpreterResponse nextResponse;
private boolean myExecuting;
private PythonDebugConsoleCommunication myDebugCommunication;
private boolean myNeedsMore = false;
/**
* UI sends a lot of repeated requests for the same expression
* Store result of the previous request to avoid sending the same command several times
*/
@Nullable private Pair<String, String> myPrevNameToDescription = null;
private int myFullValueSeq = 0;
private final Map<Integer, List<PyFrameAccessor.PyAsyncValue<String>>> myCallbackHashMap = new ConcurrentHashMap<>();
@@ -140,7 +134,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
PyDebugValueExecutionService.getInstance(myProject).sessionStopped(this);
myCallbackHashMap.clear();
new Task.Backgroundable(myProject, PyBundle.message("console.close.console.communication"), true) {
new Task.Backgroundable(myProject, PyBundle.message("console.close.console.communication"), false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
@@ -185,7 +179,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
@NotNull
protected abstract Future<?> closeCommunication();
protected abstract boolean isCommunicationClosed();
public abstract boolean isCommunicationClosed();
/*
* Variables that control when we're expecting to give some input to the server or when we're
@@ -263,7 +257,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
* @param command
* @return a Pair with (null, more) or (error, false)
*/
protected Pair<String, Boolean> exec(final ConsoleCodeFragment command) {
protected Pair<String, Boolean> exec(final ConsoleCodeFragment command) throws PythonUnhandledException {
setExecuting(true);
boolean more;
@@ -275,7 +269,12 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
more = getPythonConsoleBackendClient().execMultipleLines(command.getText());
}
}
catch (PythonUnhandledException e) {
setExecuting(false);
throw e;
}
catch (TException e) {
setExecuting(false);
throw new RuntimeException(e);
}
@@ -286,6 +285,10 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
return Pair.create(null, more);
}
private static String createRuntimeMessage(String taskName) {
return PyBundle.message("console.getting.from.runtime", taskName);
}
/**
* @return completions from the client
*/
@@ -295,6 +298,8 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
if (waitingForInput || isExecuting()) {
return Collections.emptyList();
}
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
indicator.setText(createRuntimeMessage(PyBundle.message("console.getting.completion")));
return ApplicationUtil.runWithCheckCanceled(
() ->
{
@@ -312,7 +317,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
return Collections.emptyList();
}
},
ProgressManager.getInstance().getProgressIndicator());
indicator);
}
@NotNull
@@ -321,6 +326,48 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
return new PydevCompletionVariant(option.name, option.documentation, args, option.type.getValue());
}
private <T> void executeBackgroundTaskSuppressException(Callable<T> task,
String userVisibleMessage,
String errorLogMessage) {
try {
executeBackgroundTask(task, false, userVisibleMessage, errorLogMessage);
}
catch (Exception e) {
LOG.error(e);
}
}
@Nullable
private <T> T executeBackgroundTask(Callable<T> task, boolean waitForResult, String userVisibleMessage, String errorLogMessage)
throws PyDebuggerException {
final FutureResult<T> future = new FutureResult<>();
new Task.Backgroundable(myProject, userVisibleMessage, false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
T result = task.call();
future.set(result);
}
catch (PythonUnhandledException e) {
LOG.error(errorLogMessage + e.traceback);
future.set(null);
}
catch (Exception e) {
future.setException(e);
}
}
}.queue();
if (waitForResult) {
try {
return future.get();
}
catch (InterruptedException | ExecutionException e) {
throw new PyDebuggerException(errorLogMessage, e);
}
}
return null;
}
/**
* @return the description of the given attribute in the shell
*/
@@ -332,15 +379,27 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
if (waitingForInput) {
return "Unable to get description: waiting for input.";
}
ThrowableComputable<String, Exception> doGetDesc = () -> getPythonConsoleBackendClient().getDescription(text);
if (ApplicationManager.getApplication().isDispatchThread()) {
return ProgressManager.getInstance().runProcessWithProgressSynchronously(doGetDesc, PyBundle.message("console.getting.description"), true, myProject);
if (myPrevNameToDescription != null) {
if (myPrevNameToDescription.first.equals(text)) return myPrevNameToDescription.second;
}
else {
// note that the thread would still wait for the response after the timeout occurs
return ApplicationManager.getApplication().executeOnPooledThread(() -> doGetDesc.compute()).get(5, TimeUnit.SECONDS);
// add temporary value to avoid repeated requests for the same expression
myPrevNameToDescription = Pair.create(text, "");
}
if (ApplicationManager.getApplication().isDispatchThread()) {
throw new PyDebuggerException("Documentation in Python Console shouldn't be called from Dispatch Thread!");
}
return executeBackgroundTask(
() ->
{
final String resultDescription = getPythonConsoleBackendClient().getDescription(text);
myPrevNameToDescription = Pair.create(text, resultDescription);
return resultDescription;
},
true,
createRuntimeMessage(PyBundle.message("console.getting.documentation")),
"Error when Getting Description in Python Console: ");
}
/**
@@ -362,108 +421,34 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
}
else {
//create a thread that'll keep locked until an answer is received from the server.
new Task.Backgroundable(myProject, PyBundle.message("console.repl.communication"), true) {
new Task.Backgroundable(myProject, PyBundle.message("console.waiting.execution.result"), false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
boolean needInput = false;
try {
Pair<String, Boolean> executed = null;
//the 1st time we'll do a connection attempt, we can try to connect n times (until the 1st time the connection
//is accepted) -- that's mostly because the server may take a while to get started.
int commAttempts = 0;
while (true) {
if (indicator.isCanceled()) {
return;
}
executed = exec(command);
//executed.o1 is not null only if we had an error
String refusedConnPattern = "Failed to read servers response";
// Was "refused", but it didn't
// work on non English system
// (in Spanish localized systems
// it is "rechazada")
// This string always works,
// because it is hard-coded in
// the XML-RPC library)
if (executed.first != null && executed.first.contains(refusedConnPattern)) {
if (firstCommWorked) {
break;
}
else {
if (commAttempts < MAX_ATTEMPTS) {
commAttempts += 1;
Thread.sleep(250);
executed = Pair.create("", executed.second);
}
else {
break;
}
}
}
else {
break;
}
//unreachable code!! -- commented because eclipse will complain about it
//throw new RuntimeException("Can never get here!");
}
firstCommWorked = true;
boolean more = executed.second;
nextResponse = new InterpreterResponse(more, needInput);
if (indicator.isCanceled()) return;
Pair<String, Boolean> executed = exec(command);
nextResponse = new InterpreterResponse(executed.second, false);
}
catch (ProcessCanceledException e) {
//ignore
}
catch (PythonUnhandledException e) {
LOG.error("Error in execInterpreter():" + e.traceback);
nextResponse = new InterpreterResponse(false, false);
}
catch (Exception e) {
nextResponse = new InterpreterResponse(false, needInput);
nextResponse = new InterpreterResponse(false, false);
}
finally {
InterpreterResponse response = nextResponse;
if (response != null && response.more) {
myNeedsMore = true;
}
notifyCommandExecuted(true);
onResponseReceived.fun(response);
}
}
}.queue();
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, PyBundle.message("console.waiting.for.repl.response")) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
progressIndicator.setText(PyBundle.message("console.waiting.for.repl.response.with.timeout", (int)(TIMEOUT / 10e8)));
progressIndicator.setIndeterminate(false);
final long startTime = System.nanoTime();
while (nextResponse == null) {
if (progressIndicator.isCanceled()) {
LOG.debug("Canceled");
nextResponse = new InterpreterResponse(false, false);
}
final long time = System.nanoTime() - startTime;
progressIndicator.setFraction(((double)time) / TIMEOUT);
if (time > TIMEOUT) {
LOG.debug("Timeout exceeded");
nextResponse = new InterpreterResponse(false, false);
}
synchronized (lock2) {
try {
lock2.wait(20);
}
catch (InterruptedException e) {
LOG.error(e);
}
}
}
if (nextResponse.more) {
myNeedsMore = true;
notifyCommandExecuted(true);
}
onResponseReceived.fun(nextResponse);
}
});
}
}
@@ -475,12 +460,14 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
keyboardInterruption = true;
return;
}
try {
getPythonConsoleBackendClient().interrupt();
}
catch (CommunicationClosedException | PyConsoleProcessFinishedException | TException e) {
LOG.error(e);
}
executeBackgroundTaskSuppressException(
() ->
{
getPythonConsoleBackendClient().interrupt();
return null;
},
PyBundle.message("console.interrupting.execution"),
"");
}
@Override
@@ -496,13 +483,15 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
@Override
public PyDebugValue evaluate(String expression, boolean execute, boolean doTrunc) throws PyDebuggerException {
if (!isCommunicationClosed()) {
try {
List<DebugValue> debugValues = getPythonConsoleBackendClient().evaluate(expression, doTrunc);
return createPyDebugValue(debugValues.iterator().next(), this);
}
catch (Exception e) {
throw new PyDebuggerException("Evaluate in console failed", e);
}
return executeBackgroundTask(
() -> {
List<DebugValue> debugValues = getPythonConsoleBackendClient().evaluate(expression, doTrunc);
return createPyDebugValue(debugValues.iterator().next(), this);
},
true,
PyBundle.message("console.evaluating.expression.in.console"),
"Error in evaluate():"
);
}
return null;
}
@@ -511,13 +500,15 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
@Override
public XValueChildrenList loadFrame() throws PyDebuggerException {
if (!isCommunicationClosed()) {
try {
List<DebugValue> frame = getPythonConsoleBackendClient().getFrame();
return parseVars(frame, null, this);
}
catch (CommunicationClosedException | PyConsoleProcessFinishedException | TException e) {
throw new PyDebuggerException("Get frame from console failed", e);
}
return executeBackgroundTask(
() -> {
List<DebugValue> frame = getPythonConsoleBackendClient().getFrame();
return parseVars(frame, null, this);
},
true,
createRuntimeMessage(PyBundle.message("console.getting.frame.variables")),
"Error in loadFrame():"
);
}
return new XValueChildrenList();
}
@@ -562,18 +553,17 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
@Override
public XValueChildrenList loadVariable(PyDebugValue var) throws PyDebuggerException {
if (!isCommunicationClosed()) {
try {
final String name = var.getOffset() == 0 ? GetVariableCommand.composeName(var)
: var.getOffset() + "\t" + GetVariableCommand.composeName(var);
List<DebugValue> ret = getPythonConsoleBackendClient().getVariable(name);
return parseVars(ret, var, this);
}
catch (CommunicationClosedException | PyConsoleProcessFinishedException e) {
throw new PyDebuggerException(e.getLocalizedMessage(), e);
}
catch (TException e) {
throw new PyDebuggerException("Get variable from console failed", e);
}
return executeBackgroundTask(
() -> {
final String name = var.getOffset() == 0 ? GetVariableCommand.composeName(var)
: var.getOffset() + "\t" + GetVariableCommand.composeName(var);
List<DebugValue> ret = getPythonConsoleBackendClient().getVariable(name);
return parseVars(ret, var, this);
},
true,
createRuntimeMessage(PyBundle.message("console.getting.variable.value")),
"Error in loadVariable():"
);
}
return new XValueChildrenList();
}
@@ -595,16 +585,18 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
}
@Override
public void changeVariable(PyDebugValue variable, String value) throws PyDebuggerException {
public void changeVariable(PyDebugValue variable, String value) {
if (!isCommunicationClosed()) {
try {
// NOTE: The actual change is being scheduled in the exec_queue in main thread
// This method is async now
getPythonConsoleBackendClient().changeVariable(variable.getEvaluationExpression(), value);
}
catch (CommunicationClosedException | PyConsoleProcessFinishedException | TException e) {
throw new PyDebuggerException("Get change variable", e);
}
executeBackgroundTaskSuppressException(
() -> {
// NOTE: The actual change is being scheduled in the exec_queue in main thread
// This method is async now
getPythonConsoleBackendClient().changeVariable(variable.getEvaluationExpression(), value);
return null;
},
PyBundle.message("console.changing.variable"),
"Error in changeVariable():"
);
}
}
@@ -618,19 +610,15 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
public ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format)
throws PyDebuggerException {
if (!isCommunicationClosed()) {
try {
GetArrayResponse ret = getPythonConsoleBackendClient().getArray(var.getName(), rowOffset, colOffset, rows, cols, format);
return createArrayChunk(ret, this);
}
catch (UnsupportedArrayTypeException e) {
throw new IllegalArgumentException(var.getType() + " is not supported", e);
}
catch (ExceedingArrayDimensionsException e) {
throw new IllegalArgumentException(var.getName() + " has more than two dimensions", e);
}
catch (Exception e) {
throw new PyDebuggerException("Evaluate in console failed", e);
}
executeBackgroundTask(
() -> {
GetArrayResponse ret = getPythonConsoleBackendClient().getArray(var.getName(), rowOffset, colOffset, rows, cols, format);
return createArrayChunk(ret, this);
},
true,
createRuntimeMessage(PyBundle.message("console.getting.array")),
"Error in getArrayItems():"
);
}
return null;
}
@@ -660,16 +648,18 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
if (waitingForInput) {
throw new Exception("Can't connect debugger now, waiting for input");
}
try {
// though `connectToDebugger` returns "connect complete" string, let us just ignore it
getPythonConsoleBackendClient().connectToDebugger(localPort, debuggerHost, dbgOpts, extraEnvs);
}
catch (CommunicationClosedException | PyConsoleProcessFinishedException | TException e) {
throw new PyDebuggerException("pydevconsole failed to execute connectToDebugger", e);
}
executeBackgroundTask(
() -> {
// though `connectToDebugger` returns "connect complete" string, let us just ignore it
getPythonConsoleBackendClient().connectToDebugger(localPort, debuggerHost, dbgOpts, extraEnvs);
return null;
},
true,
PyBundle.message("console.connecting.to.debugger"),
"Error in connectToDebugger():"
);
}
@Override
public void notifyCommandExecuted(boolean more) {
super.notifyCommandExecuted(more);
@@ -733,9 +723,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica
public void returnFullValue(int requestSeq, List<DebugValue> response) {
final List<PyAsyncValue<String>> values = myCallbackHashMap.remove(requestSeq);
try {
List<PyDebugValue> debugValues = response.stream()
.map(value -> createPyDebugValue(value, PydevConsoleCommunication.this))
.collect(Collectors.toList());
List<PyDebugValue> debugValues = ContainerUtil.map(response, value -> createPyDebugValue(value, PydevConsoleCommunication.this));
for (int i = 0; i < debugValues.size(); ++i) {
PyDebugValue resultValue = debugValues.get(i);
values.get(i).getCallback().ok(resultValue.getValue());

View File

@@ -116,6 +116,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
public static final @NonNls String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" +
"sys.path.extend([" + WORKING_DIR_AND_PYTHON_PATHS + "])\n";
public static final @NonNls String STARTED_BY_RUNNER = "startedByRunner";
private static final Long WAIT_BEFORE_FORCED_CLOSE_MILLIS = 2000L;
private static final Logger LOG = Logger.getInstance(PydevConsoleRunnerImpl.class);
@SuppressWarnings("SpellCheckingInspection")
public static final @NonNls String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
@@ -674,7 +675,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
stopConsole();
stopAndRerunConsole(false, PyBundle.message("console.stopping.console"), null);
}
};
}
@@ -706,19 +707,6 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
}
}
private void stopConsole() {
if (myPydevConsoleCommunication != null) {
try {
closeCommunication();
// waiting for REPL communication before destroying process handler
Thread.sleep(300);
}
catch (Exception ignored) {
// Ignore
}
}
}
protected AnAction createSplitLineAction() {
class ConsoleSplitLineAction extends EditorAction {
@@ -824,7 +812,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
displayName = name;
}
}
myConsoleRunner.rerun(displayName);
myConsoleRunner.stopAndRerunConsole(true, PyBundle.message("console.restarting.console"), displayName);
}
}
@@ -838,21 +826,23 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
return content.getDisplayName();
}
private void rerun(String displayName) {
new Task.Backgroundable(myProject, PyBundle.message("console.restarting.console"), true) {
private void stopAndRerunConsole(Boolean rerun, @NotNull String message, @Nullable String displayName) {
new Task.Backgroundable(myProject, message, true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
if (myProcessHandler != null) {
UIUtil.invokeAndWaitIfNeeded((Runnable)() -> closeCommunication());
boolean processStopped = myProcessHandler.waitFor(5000L);
boolean processStopped = myProcessHandler.waitFor(WAIT_BEFORE_FORCED_CLOSE_MILLIS);
if (!processStopped && myProcessHandler.canKillProcess()) {
myProcessHandler.killProcess();
}
myProcessHandler.waitFor();
}
GuiUtils.invokeLaterIfNeeded(() -> myRerunAction.consume(displayName), ModalityState.defaultModalityState());
if (rerun) {
GuiUtils.invokeLaterIfNeeded(() -> myRerunAction.consume(displayName), ModalityState.defaultModalityState());
}
}
}.queue();
}

View File

@@ -149,7 +149,7 @@ public final class PyDataView implements DumbAware {
}
private static boolean isConnected(PydevConsoleCommunication accessor){
return accessor.handshake();
return !accessor.isCommunicationClosed();
}
public static PyDataView getInstance(@NotNull final Project project) {