mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-CR-57291: PY-37771 Port new IDEA debugger smart step into UI/UX to PyCharm
(cherry picked from commit 4fefae6a1d9fbc6df174d53222ceba9208691b65) GitOrigin-RevId: d6e65ace1378765be246fe09a86d2bff133855df
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1031d64091
commit
5029797a6c
@@ -7,7 +7,7 @@ from _pydev_bundle import pydev_log
|
||||
from _pydevd_bundle.pydevd_frame import PyDBFrame
|
||||
# ENDIF
|
||||
|
||||
version = 24
|
||||
version = 25
|
||||
|
||||
if not hasattr(sys, '_current_frames'):
|
||||
|
||||
@@ -70,7 +70,6 @@ class PyDBAdditionalThreadInfo(object):
|
||||
'pydev_step_stop',
|
||||
'pydev_step_cmd',
|
||||
'pydev_notify_kill',
|
||||
'pydev_smart_step_stop',
|
||||
'pydev_django_resolve_frame',
|
||||
'pydev_call_from_jinja2',
|
||||
'pydev_call_inside_jinja2',
|
||||
@@ -82,6 +81,7 @@ class PyDBAdditionalThreadInfo(object):
|
||||
'pydev_func_name',
|
||||
'suspended_at_unhandled',
|
||||
'trace_suspend_type',
|
||||
'pydev_smart_step_context'
|
||||
]
|
||||
# ENDIF
|
||||
|
||||
@@ -90,7 +90,6 @@ class PyDBAdditionalThreadInfo(object):
|
||||
self.pydev_step_stop = None
|
||||
self.pydev_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
|
||||
self.pydev_notify_kill = False
|
||||
self.pydev_smart_step_stop = None
|
||||
self.pydev_django_resolve_frame = False
|
||||
self.pydev_call_from_jinja2 = None
|
||||
self.pydev_call_inside_jinja2 = None
|
||||
@@ -102,6 +101,7 @@ class PyDBAdditionalThreadInfo(object):
|
||||
self.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
self.suspended_at_unhandled = False
|
||||
self.trace_suspend_type = 'trace' # 'trace' or 'frame_eval'
|
||||
self.pydev_smart_step_context = PydevSmartStepContext()
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
@@ -118,6 +118,34 @@ class PyDBAdditionalThreadInfo(object):
|
||||
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
|
||||
|
||||
|
||||
# IFDEF CYTHON
|
||||
# cdef class PydevSmartStepContext:
|
||||
# ELSE
|
||||
class PydevSmartStepContext:
|
||||
# ENDIF
|
||||
|
||||
# Note: the params in cython are declared in pydevd_cython.pxd.
|
||||
# IFDEF CYTHON
|
||||
# ELSE
|
||||
__slots__ = [
|
||||
'smart_step_stop',
|
||||
'call_order',
|
||||
'filename',
|
||||
'start_line',
|
||||
'end_line',
|
||||
]
|
||||
# ENDIF
|
||||
|
||||
def __init__(self):
|
||||
self.smart_step_stop = None
|
||||
self.call_order = -1
|
||||
self.filename = None
|
||||
self.start_line = -1
|
||||
self.end_line = -1
|
||||
|
||||
reset = __init__
|
||||
|
||||
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
_set_additional_thread_info_lock = threading.Lock()
|
||||
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
"""Bytecode analysing utils. Originally added for using in smart step into."""
|
||||
import dis
|
||||
import inspect
|
||||
from collections import namedtuple
|
||||
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY3K, IS_CPYTHON
|
||||
|
||||
__all__ = ["get_smart_step_into_candidates"]
|
||||
|
||||
_LOAD_OPNAMES = {
|
||||
'LOAD_BUILD_CLASS',
|
||||
'LOAD_CONST',
|
||||
'LOAD_NAME',
|
||||
'LOAD_ATTR',
|
||||
'LOAD_GLOBAL',
|
||||
'LOAD_FAST',
|
||||
'LOAD_CLOSURE',
|
||||
'LOAD_DEREF',
|
||||
}
|
||||
|
||||
_CALL_OPNAMES = {
|
||||
'CALL_FUNCTION',
|
||||
'CALL_FUNCTION_KW',
|
||||
}
|
||||
|
||||
if IS_PY3K:
|
||||
for opname in ('LOAD_CLASSDEREF', 'LOAD_METHOD'):
|
||||
_LOAD_OPNAMES.add(opname)
|
||||
for opname in ('CALL_FUNCTION_EX', 'CALL_METHOD'):
|
||||
_CALL_OPNAMES.add(opname)
|
||||
else:
|
||||
_LOAD_OPNAMES.add('LOAD_LOCALS')
|
||||
for opname in ('CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW'):
|
||||
_CALL_OPNAMES.add(opname)
|
||||
|
||||
_BINARY_OPS = set([opname for opname in dis.opname if opname.startswith('BINARY_')])
|
||||
|
||||
_BINARY_OP_MAP = {
|
||||
'BINARY_POWER': '__pow__',
|
||||
'BINARY_MULTIPLY': '__mul__',
|
||||
'BINARY_MATRIX_MULTIPLY': '__matmul__',
|
||||
'BINARY_FLOOR_DIVIDE': '__floordiv__',
|
||||
'BINARY_TRUE_DIVIDE': '__div__',
|
||||
'BINARY_MODULO': '__mod__',
|
||||
'BINARY_ADD': '__add__',
|
||||
'BINARY_SUBTRACT': '__sub__',
|
||||
'BINARY_LSHIFT': '__lshift__',
|
||||
'BINARY_RSHIFT': '__rshift__',
|
||||
'BINARY_AND': '__and__',
|
||||
'BINARY_OR': '__or__',
|
||||
'BINARY_XOR': '__xor__',
|
||||
'BINARY_SUBSCR': '__getitem__',
|
||||
}
|
||||
|
||||
if not IS_PY3K:
|
||||
_BINARY_OP_MAP['BINARY_DIVIDE'] = '__div__'
|
||||
|
||||
_UNARY_OPS = set([opname for opname in dis.opname if opname.startswith('UNARY_') and opname != 'UNARY_NOT'])
|
||||
|
||||
_UNARY_OP_MAP = {
|
||||
'UNARY_POSITIVE': '__pos__',
|
||||
'UNARY_NEGATIVE': '__neg__',
|
||||
'UNARY_INVERT': '__invert__',
|
||||
}
|
||||
|
||||
_MAKE_OPS = set([opname for opname in dis.opname if opname.startswith('MAKE_')])
|
||||
|
||||
_COMP_OP_MAP = {
|
||||
'<': '__lt__',
|
||||
'<=': '__le__',
|
||||
'==': '__eq__',
|
||||
'!=': '__ne__',
|
||||
'>': '__gt__',
|
||||
'>=': '__ge__',
|
||||
'in': '__contains__',
|
||||
'not in': '__contains__',
|
||||
}
|
||||
|
||||
|
||||
def _is_load_opname(opname):
|
||||
return opname in _LOAD_OPNAMES
|
||||
|
||||
|
||||
def _is_call_opname(opname):
|
||||
return opname in _CALL_OPNAMES
|
||||
|
||||
|
||||
def _is_binary_opname(opname):
|
||||
return opname in _BINARY_OPS
|
||||
|
||||
|
||||
def _is_unary_opname(opname):
|
||||
return opname in _UNARY_OPS
|
||||
|
||||
|
||||
def _is_make_opname(opname):
|
||||
return opname in _MAKE_OPS
|
||||
|
||||
|
||||
# Similar to :py:class:`dis._Instruction` but without fields we don't use. Also :py:class:`dis._Instruction`
|
||||
# is not available in Python 2.
|
||||
Instruction = namedtuple("Instruction", ["opname", "opcode", "arg", "argval", "lineno", "offset"])
|
||||
|
||||
if IS_PY3K:
|
||||
_unpack_opargs = dis._unpack_opargs
|
||||
long = int
|
||||
else:
|
||||
def _unpack_opargs(code):
|
||||
n = len(code)
|
||||
i = 0
|
||||
extended_arg = 0
|
||||
while i < n:
|
||||
c = code[i]
|
||||
op = ord(c)
|
||||
offset = i
|
||||
arg = None
|
||||
i += 1
|
||||
if op >= dis.HAVE_ARGUMENT:
|
||||
arg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
|
||||
extended_arg = 0
|
||||
i += 2
|
||||
if op == dis.EXTENDED_ARG:
|
||||
extended_arg = arg * long(65536)
|
||||
yield (offset, op, arg)
|
||||
|
||||
|
||||
def _code_to_name(inst):
|
||||
"""If thw instruction's ``argval`` is :py:class:`types.CodeType`, replace it with the name and return the updated instruction.
|
||||
|
||||
:type inst: :py:class:`Instruction`
|
||||
:rtype: :py:class:`Instruction`
|
||||
"""
|
||||
if inspect.iscode(inst.argval):
|
||||
return inst._replace(argval=inst.argval.co_name)
|
||||
return inst
|
||||
|
||||
|
||||
def get_smart_step_into_candidates(code):
|
||||
"""Iterate through the bytecode and return a list of instructions which can be smart step into candidates.
|
||||
|
||||
:param code: A code object where we searching for calls.
|
||||
:type code: :py:class:`types.CodeType`
|
||||
:return: list of :py:class:`~Instruction` that represents the objects that were called
|
||||
by one of the Python call instructions.
|
||||
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
|
||||
"""
|
||||
if not IS_CPYTHON:
|
||||
# For implementations other than CPython we fall back to simple step into.
|
||||
return []
|
||||
|
||||
linestarts = dict(dis.findlinestarts(code))
|
||||
varnames = code.co_varnames
|
||||
names = code.co_names
|
||||
constants = code.co_consts
|
||||
lineno = None
|
||||
stk = [] # only the instructions related to calls are pushed in the stack
|
||||
result = []
|
||||
|
||||
for offset, op, arg in _unpack_opargs(code.co_code):
|
||||
try:
|
||||
if linestarts is not None:
|
||||
lineno = linestarts.get(offset, None) or lineno
|
||||
opname = dis.opname[op]
|
||||
argval = None
|
||||
if arg is None:
|
||||
if _is_binary_opname(opname):
|
||||
stk.pop()
|
||||
result.append(Instruction(opname, op, arg, _BINARY_OP_MAP[opname], lineno, offset))
|
||||
elif _is_unary_opname(opname):
|
||||
result.append(Instruction(opname, op, arg, _UNARY_OP_MAP[opname], lineno, offset))
|
||||
if opname == 'COMPARE_OP':
|
||||
stk.pop()
|
||||
result.append(Instruction(opname, op, arg, _COMP_OP_MAP[dis.cmp_op[arg]], lineno, offset))
|
||||
if _is_load_opname(opname):
|
||||
if opname == 'LOAD_CONST':
|
||||
argval = constants[arg]
|
||||
elif opname == 'LOAD_NAME' or opname == 'LOAD_GLOBAL':
|
||||
argval = names[arg]
|
||||
elif opname == 'LOAD_ATTR':
|
||||
stk.pop()
|
||||
argval = names[arg]
|
||||
elif opname == 'LOAD_FAST':
|
||||
argval = varnames[arg]
|
||||
elif IS_PY3K and opname == 'LOAD_METHOD':
|
||||
stk.pop()
|
||||
argval = names[arg]
|
||||
stk.append(Instruction(opname, op, arg, argval, lineno, offset))
|
||||
elif _is_make_opname(opname):
|
||||
tos = stk.pop() # qualified name of the function or function code in Python 2
|
||||
argc = 0
|
||||
if IS_PY3K:
|
||||
stk.pop() # function code
|
||||
for flag in (0x01, 0x02, 0x04, 0x08):
|
||||
if arg & flag:
|
||||
argc += 1 # each flag means one extra element to pop
|
||||
else:
|
||||
argc = arg
|
||||
tos = _code_to_name(tos)
|
||||
while argc > 0:
|
||||
stk.pop()
|
||||
argc -= 1
|
||||
stk.append(tos)
|
||||
elif _is_call_opname(opname):
|
||||
argc = arg # the number of the function or method arguments
|
||||
if opname == 'CALL_FUNCTION_KW' or not IS_PY3K and opname == 'CALL_FUNCTION_VAR':
|
||||
stk.pop() # pop the mapping or iterable with arguments or parameters
|
||||
elif not IS_PY3K and opname == 'CALL_FUNCTION_VAR_KW':
|
||||
stk.pop() # pop the mapping with arguments
|
||||
stk.pop() # pop the iterable with parameters
|
||||
elif not IS_PY3K and opname == 'CALL_FUNCTION':
|
||||
argc = arg & 0xff # positional args
|
||||
argc += ((arg >> 8) * 2) # keyword args
|
||||
while argc > 0:
|
||||
stk.pop() # popping args from the stack
|
||||
argc -= 1
|
||||
tos = _code_to_name(stk[-1])
|
||||
if tos.opname == 'LOAD_BUILD_CLASS':
|
||||
# an internal `CALL_FUNCTION` for building a class
|
||||
continue
|
||||
result.append(tos._replace(offset=offset)) # the actual offset is not when a function was loaded but when it was called
|
||||
except:
|
||||
err_msg = "Bytecode parsing error at: offset(%d), opname(%s), arg(%d)" % (offset, dis.opname[op], arg)
|
||||
raise RuntimeError(err_msg)
|
||||
return result
|
||||
|
||||
|
||||
Variant = namedtuple('Variant', ['name', 'is_visited'])
|
||||
|
||||
|
||||
def calculate_smart_step_into_variants(frame, start_line, end_line):
|
||||
"""
|
||||
Calculate smart step into variants for the given line range.
|
||||
:param frame:
|
||||
:type frame: :py:class:`types.FrameType`
|
||||
:param start_line:
|
||||
:param end_line:
|
||||
:return: A list of call names from the first to the last.
|
||||
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
|
||||
"""
|
||||
variants = []
|
||||
is_context_reached = False
|
||||
code = frame.f_code
|
||||
lasti = frame.f_lasti
|
||||
for inst in get_smart_step_into_candidates(code):
|
||||
if inst.lineno and inst.lineno > end_line:
|
||||
break
|
||||
if not is_context_reached and inst.lineno is not None and inst.lineno >= start_line:
|
||||
is_context_reached = True
|
||||
if not is_context_reached:
|
||||
continue
|
||||
variants.append(Variant(inst.argval, inst.offset <= lasti))
|
||||
return variants
|
||||
|
||||
|
||||
def find_last_func_call_order(frame, start_line):
|
||||
"""Find the call order of the last function call between ``start_line`` and last executed instruction.
|
||||
|
||||
:param frame: A frame inside which we are looking the function call.
|
||||
:type frame: :py:class:`types.FrameType`
|
||||
:param start_line:
|
||||
:return: call order or -1 if we fail to find the call order for some
|
||||
reason.
|
||||
:rtype: int
|
||||
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
|
||||
"""
|
||||
code = frame.f_code
|
||||
lasti = frame.f_lasti
|
||||
cache = {}
|
||||
call_order = -1
|
||||
for inst in get_smart_step_into_candidates(code):
|
||||
if inst.offset > lasti:
|
||||
break
|
||||
if inst.lineno >= start_line:
|
||||
name = inst.argval
|
||||
call_order = cache.setdefault(name, -1)
|
||||
call_order += 1
|
||||
cache[name] = call_order
|
||||
return call_order
|
||||
|
||||
|
||||
def find_last_call_name(frame):
|
||||
"""Find the name of the last call made in the frame.
|
||||
|
||||
:param frame: A frame inside which we are looking the last call.
|
||||
:type frame: :py:class:`types.FrameType`
|
||||
:return: The name of a function or method that has been called last.
|
||||
:rtype: str
|
||||
:raise: :py:class:`RuntimeError` if failed to parse the bytecode.
|
||||
"""
|
||||
last_call_name = None
|
||||
code = frame.f_code
|
||||
lasti = frame.f_lasti
|
||||
for inst in get_smart_step_into_candidates(code):
|
||||
if inst.offset > lasti:
|
||||
break
|
||||
last_call_name = inst.argval
|
||||
|
||||
return last_call_name
|
||||
@@ -93,10 +93,12 @@ from _pydevd_bundle import pydevd_vars
|
||||
import pydevd_tracing
|
||||
from _pydevd_bundle import pydevd_xml
|
||||
from _pydevd_bundle import pydevd_vm_type
|
||||
from _pydevd_bundle import pydevd_bytecode_utils
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, norm_file_to_client, is_real_file
|
||||
import pydevd_file_utils
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import traceback
|
||||
from _pydevd_bundle.pydevd_utils import quote_smart as quote, compare_object_attrs_key, to_string, \
|
||||
get_non_pydevd_threads
|
||||
@@ -139,7 +141,7 @@ from _pydevd_bundle.pydevd_comm_constants import (
|
||||
CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, CMD_PROCESS_CREATED_MSG_RECEIVED, CMD_PYDEVD_JSON_CONFIG,
|
||||
CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, CMD_THREAD_RESUME_SINGLE_NOTIFICATION,
|
||||
CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS, CMD_SET_PROJECT_ROOTS, CMD_VERSION,
|
||||
CMD_RETURN, CMD_SET_PROTOCOL, CMD_ERROR,)
|
||||
CMD_RETURN, CMD_SET_PROTOCOL, CMD_ERROR, CMD_GET_SMART_STEP_INTO_VARIANTS,)
|
||||
MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive)
|
||||
#this number can be changed if there's need to do so
|
||||
|
||||
@@ -1175,6 +1177,36 @@ class InternalSetNextStatementThread(InternalThreadCommand):
|
||||
t.additional_info.pydev_message = str(self.seq)
|
||||
|
||||
|
||||
class InternalSmartStepInto(InternalThreadCommand):
|
||||
def __init__(self, thread_id, frame_id, cmd_id, func_name, line, call_order, start_line, end_line, seq=0):
|
||||
self.thread_id = thread_id
|
||||
self.cmd_id = cmd_id
|
||||
self.line = line
|
||||
self.start_line = start_line
|
||||
self.end_line = end_line
|
||||
self.seq = seq
|
||||
self.call_order = call_order
|
||||
|
||||
if IS_PY2:
|
||||
if isinstance(func_name, unicode):
|
||||
# On cython with python 2.X it requires an str, not unicode (but on python 3.3 it should be a str, not bytes).
|
||||
func_name = func_name.encode('utf-8')
|
||||
|
||||
self.func_name = func_name
|
||||
|
||||
def do_it(self, dbg):
|
||||
t = pydevd_find_thread_by_id(self.thread_id)
|
||||
if t:
|
||||
t.additional_info.pydev_step_cmd = self.cmd_id
|
||||
t.additional_info.pydev_next_line = int(self.line)
|
||||
t.additional_info.pydev_func_name = self.func_name
|
||||
t.additional_info.pydev_state = STATE_RUN
|
||||
t.additional_info.pydev_message = str(self.seq)
|
||||
t.additional_info.pydev_smart_step_context.call_order = int(self.call_order)
|
||||
t.additional_info.pydev_smart_step_context.start_line = int(self.start_line)
|
||||
t.additional_info.pydev_smart_step_context.end_line = int(self.end_line)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalGetVariable
|
||||
#=======================================================================================================================
|
||||
@@ -1303,6 +1335,43 @@ class InternalGetFrame(InternalThreadCommand):
|
||||
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving frame: %s from thread: %s" % (self.frame_id, self.thread_id))
|
||||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
class InternalGetSmartStepIntoVariants(InternalThreadCommand):
|
||||
def __init__(self, seq, thread_id, frame_id, start_line, end_line):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
self.start_line = int(start_line)
|
||||
self.end_line = int(end_line)
|
||||
|
||||
def do_it(self, dbg):
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
|
||||
variants = pydevd_bytecode_utils.calculate_smart_step_into_variants(frame, self.start_line, self.end_line)
|
||||
xml = "<xml>"
|
||||
|
||||
for name, is_visited in variants:
|
||||
xml += '<variant name="%s" isVisited="%s"></variant>' % (quote(name), str(is_visited).lower())
|
||||
|
||||
xml += "</xml>"
|
||||
cmd = NetCommand(CMD_GET_SMART_STEP_INTO_VARIANTS, self.sequence, xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error getting smart step into veriants for frame: %s from thread: %s"
|
||||
% (self.frame_id, self.thread_id))
|
||||
self._reset_smart_step_context()
|
||||
dbg.writer.add_command(cmd)
|
||||
|
||||
def _reset_smart_step_context(self):
|
||||
t = pydevd_find_thread_by_id(self.thread_id)
|
||||
if t:
|
||||
try:
|
||||
t.additional_info.pydev_smart_step_context.reset()
|
||||
except:
|
||||
pydevd_log(1, "Error while resetting smart step into context for thread %s" % self.thread_id)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# InternalGetNextStatementTargets
|
||||
#=======================================================================================================================
|
||||
|
||||
@@ -77,6 +77,8 @@ CMD_THREAD_RESUME_SINGLE_NOTIFICATION = 158
|
||||
|
||||
CMD_PROCESS_CREATED_MSG_RECEIVED = 159
|
||||
|
||||
CMD_GET_SMART_STEP_INTO_VARIANTS = 160
|
||||
|
||||
CMD_REDIRECT_OUTPUT = 200
|
||||
CMD_GET_NEXT_STATEMENT_TARGETS = 201
|
||||
CMD_SET_PROJECT_ROOTS = 202
|
||||
@@ -154,6 +156,8 @@ ID_TO_MEANING = {
|
||||
'158': 'CMD_THREAD_RESUME_SINGLE_NOTIFICATION',
|
||||
'159': 'CMD_PROCESS_CREATED_MSG_RECEIVED',
|
||||
|
||||
'160': 'CMD_GET_SMART_STEP_INTO_VARIANTS',
|
||||
|
||||
'200': 'CMD_REDIRECT_OUTPUT',
|
||||
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
|
||||
'202': 'CMD_SET_PROJECT_ROOTS',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
cdef public object pydev_step_stop; # Actually, it's a frame or None
|
||||
cdef public int pydev_step_cmd;
|
||||
cdef public bint pydev_notify_kill;
|
||||
cdef public object pydev_smart_step_stop; # Actually, it's a frame or None
|
||||
cdef public bint pydev_django_resolve_frame;
|
||||
cdef public object pydev_call_from_jinja2;
|
||||
cdef public object pydev_call_inside_jinja2;
|
||||
@@ -15,3 +14,13 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
cdef public str pydev_func_name;
|
||||
cdef public bint suspended_at_unhandled;
|
||||
cdef public str trace_suspend_type;
|
||||
cdef public PydevSmartStepContext pydev_smart_step_context;
|
||||
|
||||
|
||||
cdef class PydevSmartStepContext:
|
||||
cdef public object smart_step_stop; # Actually, it's a frame or None
|
||||
cdef public int call_order;
|
||||
cdef public str filename;
|
||||
cdef public int line;
|
||||
cdef public int start_line;
|
||||
cdef public int end_line;
|
||||
|
||||
@@ -13,7 +13,7 @@ pydev_log.debug("Using Cython speedups")
|
||||
# from _pydevd_bundle.pydevd_frame import PyDBFrame
|
||||
# ENDIF
|
||||
|
||||
version = 24
|
||||
version = 25
|
||||
|
||||
if not hasattr(sys, '_current_frames'):
|
||||
|
||||
@@ -76,7 +76,6 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
# 'pydev_step_stop',
|
||||
# 'pydev_step_cmd',
|
||||
# 'pydev_notify_kill',
|
||||
# 'pydev_smart_step_stop',
|
||||
# 'pydev_django_resolve_frame',
|
||||
# 'pydev_call_from_jinja2',
|
||||
# 'pydev_call_inside_jinja2',
|
||||
@@ -88,6 +87,7 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
# 'pydev_func_name',
|
||||
# 'suspended_at_unhandled',
|
||||
# 'trace_suspend_type',
|
||||
# 'pydev_smart_step_context'
|
||||
# ]
|
||||
# ENDIF
|
||||
|
||||
@@ -96,7 +96,6 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
self.pydev_step_stop = None
|
||||
self.pydev_step_cmd = -1 # Something as CMD_STEP_INTO, CMD_STEP_OVER, etc.
|
||||
self.pydev_notify_kill = False
|
||||
self.pydev_smart_step_stop = None
|
||||
self.pydev_django_resolve_frame = False
|
||||
self.pydev_call_from_jinja2 = None
|
||||
self.pydev_call_inside_jinja2 = None
|
||||
@@ -108,6 +107,7 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
self.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
self.suspended_at_unhandled = False
|
||||
self.trace_suspend_type = 'trace' # 'trace' or 'frame_eval'
|
||||
self.pydev_smart_step_context = PydevSmartStepContext()
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
@@ -124,6 +124,34 @@ cdef class PyDBAdditionalThreadInfo:
|
||||
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
|
||||
|
||||
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
cdef class PydevSmartStepContext:
|
||||
# ELSE
|
||||
# class PydevSmartStepContext:
|
||||
# ENDIF
|
||||
|
||||
# Note: the params in cython are declared in pydevd_cython.pxd.
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
# ELSE
|
||||
# __slots__ = [
|
||||
# 'smart_step_stop',
|
||||
# 'call_order',
|
||||
# 'filename',
|
||||
# 'start_line',
|
||||
# 'end_line',
|
||||
# ]
|
||||
# ENDIF
|
||||
|
||||
def __init__(self):
|
||||
self.smart_step_stop = None
|
||||
self.call_order = -1
|
||||
self.filename = None
|
||||
self.start_line = -1
|
||||
self.end_line = -1
|
||||
|
||||
reset = __init__
|
||||
|
||||
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
_set_additional_thread_info_lock = threading.Lock()
|
||||
|
||||
@@ -160,9 +188,10 @@ from _pydevd_bundle.pydevd_breakpoints import get_exception_breakpoint
|
||||
from _pydevd_bundle.pydevd_comm_constants import (CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \
|
||||
CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO_MY_CODE)
|
||||
from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, get_current_thread_id, STATE_RUN, dict_iter_values, IS_PY3K, \
|
||||
dict_keys, RETURN_VALUES_DICT, NO_FTRACE
|
||||
dict_keys, RETURN_VALUES_DICT, NO_FTRACE, IS_CPYTHON
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
|
||||
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import find_last_call_name, find_last_func_call_order
|
||||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, is_real_file
|
||||
|
||||
@@ -223,7 +252,7 @@ def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
finally:
|
||||
@@ -771,12 +800,19 @@ cdef class PyDBFrame:
|
||||
exist_result = False
|
||||
stop = False
|
||||
bp_type = None
|
||||
smart_stop_frame = info.pydev_smart_step_context.smart_step_stop
|
||||
context_start_line = info.pydev_smart_step_context.start_line
|
||||
context_end_line = info.pydev_smart_step_context.end_line
|
||||
is_within_context = context_start_line <= line <= context_end_line
|
||||
|
||||
if not is_return and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None and line in breakpoints_for_file:
|
||||
breakpoint = breakpoints_for_file[line]
|
||||
new_frame = frame
|
||||
stop = True
|
||||
if step_cmd == CMD_STEP_OVER and stop_frame is frame and (is_line or is_return):
|
||||
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
|
||||
elif step_cmd == CMD_SMART_STEP_INTO and (frame.f_back is smart_stop_frame and is_within_context):
|
||||
stop = False
|
||||
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
|
||||
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
|
||||
if result:
|
||||
@@ -831,11 +867,11 @@ cdef class PyDBFrame:
|
||||
|
||||
if stop:
|
||||
self.set_suspend(
|
||||
thread,
|
||||
CMD_SET_BREAK,
|
||||
thread,
|
||||
CMD_SET_BREAK,
|
||||
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
|
||||
)
|
||||
|
||||
|
||||
elif flag and plugin_manager is not None:
|
||||
result = plugin_manager.suspend(main_debugger, thread, frame, bp_type)
|
||||
if result:
|
||||
@@ -860,6 +896,7 @@ cdef class PyDBFrame:
|
||||
# step handling. We stop when we hit the right frame
|
||||
try:
|
||||
should_skip = 0
|
||||
|
||||
if pydevd_dont_trace.should_trace_hook is not None:
|
||||
if self.should_skip == -1:
|
||||
# I.e.: cache the result on self.should_skip (no need to evaluate the same frame multiple times).
|
||||
@@ -877,6 +914,44 @@ cdef class PyDBFrame:
|
||||
if should_skip:
|
||||
stop = False
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO:
|
||||
stop = False
|
||||
if smart_stop_frame is frame:
|
||||
if not is_within_context or not IS_CPYTHON:
|
||||
# We don't stop on jumps in multiline statements, which the Python interpreter does in some cases,
|
||||
# if we they happen in smart step into context.
|
||||
info.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
stop = True # act as if we did a step into
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
|
||||
if smart_stop_frame and smart_stop_frame is frame.f_back:
|
||||
if curr_func_name == info.pydev_func_name and not IS_CPYTHON:
|
||||
# for implementations other than CPython we don't perform any additional checks
|
||||
stop = True
|
||||
else:
|
||||
try:
|
||||
if curr_func_name != info.pydev_func_name and frame.f_back:
|
||||
# try to find function call name using bytecode analysis
|
||||
curr_func_name = find_last_call_name(frame.f_back)
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = find_last_func_call_order(frame.f_back, context_start_line) \
|
||||
== info.pydev_smart_step_context.call_order
|
||||
except:
|
||||
pydev_log.debug("Exception while handling smart step into in frame tracer, step into will be performed instead.")
|
||||
info.pydev_smart_step_context.reset()
|
||||
stop = True # act as if we did a step into
|
||||
|
||||
# we have to check this case for situations when a user has tried to step into a native function or method,
|
||||
# e.g. `len()`, `list.append()`, etc and this was the only call in a return statement
|
||||
if smart_stop_frame is frame and is_return:
|
||||
stop = True
|
||||
|
||||
elif step_cmd == CMD_STEP_INTO:
|
||||
stop = is_line or is_return
|
||||
if plugin_manager is not None:
|
||||
@@ -900,25 +975,8 @@ cdef class PyDBFrame:
|
||||
if result:
|
||||
stop, plugin_stop = result
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO:
|
||||
stop = False
|
||||
if info.pydev_smart_step_stop is frame:
|
||||
info.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
info.pydev_smart_step_stop = None
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = True
|
||||
|
||||
elif step_cmd == CMD_STEP_RETURN:
|
||||
stop = is_return and stop_frame is frame
|
||||
|
||||
else:
|
||||
stop = False
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -72,6 +72,7 @@ DONT_TRACE = {
|
||||
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,
|
||||
'pydevd_breakpointhook.py': PYDEV_FILE,
|
||||
'pydevd_breakpoints.py': PYDEV_FILE,
|
||||
'pydevd_bytecode_utils.py': PYDEV_FILE,
|
||||
'pydevd_collect_try_except_info.py': PYDEV_FILE,
|
||||
'pydevd_comm.py': PYDEV_FILE,
|
||||
'pydevd_comm_constants.py': PYDEV_FILE,
|
||||
|
||||
@@ -15,9 +15,10 @@ from _pydevd_bundle.pydevd_breakpoints import get_exception_breakpoint
|
||||
from _pydevd_bundle.pydevd_comm_constants import (CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \
|
||||
CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO_MY_CODE)
|
||||
from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, get_current_thread_id, STATE_RUN, dict_iter_values, IS_PY3K, \
|
||||
dict_keys, RETURN_VALUES_DICT, NO_FTRACE
|
||||
dict_keys, RETURN_VALUES_DICT, NO_FTRACE, IS_CPYTHON
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
|
||||
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
|
||||
from _pydevd_bundle.pydevd_bytecode_utils import find_last_call_name, find_last_func_call_order
|
||||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, is_real_file
|
||||
|
||||
@@ -78,7 +79,7 @@ def handle_breakpoint_condition(py_db, info, breakpoint, new_frame):
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
finally:
|
||||
@@ -626,12 +627,19 @@ class PyDBFrame:
|
||||
exist_result = False
|
||||
stop = False
|
||||
bp_type = None
|
||||
smart_stop_frame = info.pydev_smart_step_context.smart_step_stop
|
||||
context_start_line = info.pydev_smart_step_context.start_line
|
||||
context_end_line = info.pydev_smart_step_context.end_line
|
||||
is_within_context = context_start_line <= line <= context_end_line
|
||||
|
||||
if not is_return and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None and line in breakpoints_for_file:
|
||||
breakpoint = breakpoints_for_file[line]
|
||||
new_frame = frame
|
||||
stop = True
|
||||
if step_cmd == CMD_STEP_OVER and stop_frame is frame and (is_line or is_return):
|
||||
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
|
||||
elif step_cmd == CMD_SMART_STEP_INTO and (frame.f_back is smart_stop_frame and is_within_context):
|
||||
stop = False
|
||||
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
|
||||
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
|
||||
if result:
|
||||
@@ -686,11 +694,11 @@ class PyDBFrame:
|
||||
|
||||
if stop:
|
||||
self.set_suspend(
|
||||
thread,
|
||||
CMD_SET_BREAK,
|
||||
thread,
|
||||
CMD_SET_BREAK,
|
||||
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
|
||||
)
|
||||
|
||||
|
||||
elif flag and plugin_manager is not None:
|
||||
result = plugin_manager.suspend(main_debugger, thread, frame, bp_type)
|
||||
if result:
|
||||
@@ -715,6 +723,7 @@ class PyDBFrame:
|
||||
# step handling. We stop when we hit the right frame
|
||||
try:
|
||||
should_skip = 0
|
||||
|
||||
if pydevd_dont_trace.should_trace_hook is not None:
|
||||
if self.should_skip == -1:
|
||||
# I.e.: cache the result on self.should_skip (no need to evaluate the same frame multiple times).
|
||||
@@ -732,6 +741,44 @@ class PyDBFrame:
|
||||
if should_skip:
|
||||
stop = False
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO:
|
||||
stop = False
|
||||
if smart_stop_frame is frame:
|
||||
if not is_within_context or not IS_CPYTHON:
|
||||
# We don't stop on jumps in multiline statements, which the Python interpreter does in some cases,
|
||||
# if we they happen in smart step into context.
|
||||
info.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
stop = True # act as if we did a step into
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
|
||||
if smart_stop_frame and smart_stop_frame is frame.f_back:
|
||||
if curr_func_name == info.pydev_func_name and not IS_CPYTHON:
|
||||
# for implementations other than CPython we don't perform any additional checks
|
||||
stop = True
|
||||
else:
|
||||
try:
|
||||
if curr_func_name != info.pydev_func_name and frame.f_back:
|
||||
# try to find function call name using bytecode analysis
|
||||
curr_func_name = find_last_call_name(frame.f_back)
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = find_last_func_call_order(frame.f_back, context_start_line) \
|
||||
== info.pydev_smart_step_context.call_order
|
||||
except:
|
||||
pydev_log.debug("Exception while handling smart step into in frame tracer, step into will be performed instead.")
|
||||
info.pydev_smart_step_context.reset()
|
||||
stop = True # act as if we did a step into
|
||||
|
||||
# we have to check this case for situations when a user has tried to step into a native function or method,
|
||||
# e.g. `len()`, `list.append()`, etc and this was the only call in a return statement
|
||||
if smart_stop_frame is frame and is_return:
|
||||
stop = True
|
||||
|
||||
elif step_cmd == CMD_STEP_INTO:
|
||||
stop = is_line or is_return
|
||||
if plugin_manager is not None:
|
||||
@@ -755,25 +802,8 @@ class PyDBFrame:
|
||||
if result:
|
||||
stop, plugin_stop = result
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO:
|
||||
stop = False
|
||||
if info.pydev_smart_step_stop is frame:
|
||||
info.pydev_func_name = '.invalid.' # Must match the type in cython
|
||||
info.pydev_smart_step_stop = None
|
||||
|
||||
if is_line or is_exception_event:
|
||||
curr_func_name = frame.f_code.co_name
|
||||
|
||||
# global context is set with an empty name
|
||||
if curr_func_name in ('?', '<module>') or curr_func_name is None:
|
||||
curr_func_name = ''
|
||||
|
||||
if curr_func_name == info.pydev_func_name:
|
||||
stop = True
|
||||
|
||||
elif step_cmd == CMD_STEP_RETURN:
|
||||
stop = is_return and stop_frame is frame
|
||||
|
||||
else:
|
||||
stop = False
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY3K
|
||||
|
||||
class Frame(object):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -64,5 +62,3 @@ def cached_call(obj, func, *args):
|
||||
setattr(obj, cached_name, func(*args))
|
||||
|
||||
return getattr(obj, cached_name)
|
||||
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ from _pydevd_bundle.pydevd_comm import (CMD_RUN, CMD_VERSION, CMD_LIST_THREADS,
|
||||
CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \
|
||||
CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription, InternalLoadFullValue, \
|
||||
CMD_LOAD_FULL_VALUE, CMD_PROCESS_CREATED_MSG_RECEIVED, CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS,
|
||||
InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, \
|
||||
InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, CMD_GET_SMART_STEP_INTO_VARIANTS, \
|
||||
CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, NetCommand, \
|
||||
CMD_SET_PROTOCOL, CMD_PYDEVD_JSON_CONFIG, InternalGetThreadStack)
|
||||
CMD_SET_PROTOCOL, CMD_PYDEVD_JSON_CONFIG, InternalGetThreadStack, InternalSmartStepInto, InternalGetSmartStepIntoVariants,)
|
||||
from _pydevd_bundle.pydevd_constants import (get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \
|
||||
NEXT_VALUE_SEPARATOR, IS_WINDOWS, get_current_thread_id)
|
||||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
@@ -173,21 +173,26 @@ def process_net_command(py_db, cmd_id, seq, text):
|
||||
elif text.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't make tasklet step command: %s\n" % (text,))
|
||||
|
||||
|
||||
elif cmd_id == CMD_RUN_TO_LINE or cmd_id == CMD_SET_NEXT_STATEMENT or cmd_id == CMD_SMART_STEP_INTO:
|
||||
# we received some command to make a single step
|
||||
thread_id, line, func_name = text.split('\t', 2)
|
||||
elif cmd_id in (CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT, CMD_SMART_STEP_INTO):
|
||||
if cmd_id == CMD_SMART_STEP_INTO:
|
||||
# we received a smart step into command
|
||||
thread_id, frame_id, line, func_name, call_order, start_line, end_line = text.split('\t', 6)
|
||||
else:
|
||||
# we received some command to make a single step
|
||||
thread_id, line, func_name = text.split('\t', 2)
|
||||
if func_name == "None":
|
||||
# global context
|
||||
func_name = ''
|
||||
t = pydevd_find_thread_by_id(thread_id)
|
||||
if t:
|
||||
int_cmd = InternalSetNextStatementThread(thread_id, cmd_id, line, func_name, seq)
|
||||
if cmd_id == CMD_SMART_STEP_INTO:
|
||||
int_cmd = InternalSmartStepInto(thread_id, frame_id, cmd_id, func_name, line, call_order, start_line, end_line, seq)
|
||||
else:
|
||||
int_cmd = InternalSetNextStatementThread(thread_id, cmd_id, line, func_name, seq)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
elif thread_id.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))
|
||||
|
||||
|
||||
elif cmd_id == CMD_RELOAD_CODE:
|
||||
# we received some command to make a reload of a module
|
||||
module_name = text.strip()
|
||||
@@ -856,6 +861,11 @@ def process_net_command(py_db, cmd_id, seq, text):
|
||||
frame = None
|
||||
t = None
|
||||
|
||||
elif cmd_id == CMD_GET_SMART_STEP_INTO_VARIANTS:
|
||||
thread_id, frame_id, start_line, end_line = text.split('\t', 3)
|
||||
int_cmd = InternalGetSmartStepIntoVariants(seq, thread_id, frame_id, start_line, end_line)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
|
||||
else:
|
||||
#I have no idea what this is all about
|
||||
cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id))
|
||||
|
||||
@@ -833,6 +833,7 @@ static const char *__pyx_f[] = {
|
||||
|
||||
/*--- Type declarations ---*/
|
||||
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo;
|
||||
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext;
|
||||
struct __pyx_obj_18_pydevd_frame_eval_22pydevd_frame_evaluator_ThreadInfo;
|
||||
struct __pyx_obj_18_pydevd_frame_eval_22pydevd_frame_evaluator_FuncCodeInfo;
|
||||
|
||||
@@ -847,7 +848,6 @@ struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo {
|
||||
PyObject *pydev_step_stop;
|
||||
int pydev_step_cmd;
|
||||
int pydev_notify_kill;
|
||||
PyObject *pydev_smart_step_stop;
|
||||
int pydev_django_resolve_frame;
|
||||
PyObject *pydev_call_from_jinja2;
|
||||
PyObject *pydev_call_inside_jinja2;
|
||||
@@ -859,6 +859,25 @@ struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo {
|
||||
PyObject *pydev_func_name;
|
||||
int suspended_at_unhandled;
|
||||
PyObject *trace_suspend_type;
|
||||
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext *pydev_smart_step_context;
|
||||
};
|
||||
|
||||
|
||||
/* "_pydevd_bundle/pydevd_cython.pxd":20
|
||||
*
|
||||
*
|
||||
* cdef class PydevSmartStepContext: # <<<<<<<<<<<<<<
|
||||
* cdef public object smart_step_stop; # Actually, it's a frame or None
|
||||
* cdef public int call_order;
|
||||
*/
|
||||
struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext {
|
||||
PyObject_HEAD
|
||||
PyObject *smart_step_stop;
|
||||
int call_order;
|
||||
PyObject *filename;
|
||||
int line;
|
||||
int start_line;
|
||||
int end_line;
|
||||
};
|
||||
|
||||
|
||||
@@ -1395,6 +1414,7 @@ static int __Pyx_InitStrings(__Pyx_StringTabEntry *t);
|
||||
|
||||
/* Module declarations from '_pydevd_bundle.pydevd_cython' */
|
||||
static PyTypeObject *__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo = 0;
|
||||
static PyTypeObject *__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext = 0;
|
||||
|
||||
/* Module declarations from '_pydevd_frame_eval.pydevd_frame_evaluator' */
|
||||
static PyTypeObject *__pyx_ptype_18_pydevd_frame_eval_22pydevd_frame_evaluator_ThreadInfo = 0;
|
||||
@@ -9375,6 +9395,8 @@ static int __Pyx_modinit_type_import_code(void) {
|
||||
__Pyx_GOTREF(__pyx_t_1);
|
||||
__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo = __Pyx_ImportType(__pyx_t_1, "_pydevd_bundle.pydevd_cython", "PyDBAdditionalThreadInfo", sizeof(struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo), __Pyx_ImportType_CheckSize_Warn);
|
||||
if (!__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo) __PYX_ERR(2, 1, __pyx_L1_error)
|
||||
__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext = __Pyx_ImportType(__pyx_t_1, "_pydevd_bundle.pydevd_cython", "PydevSmartStepContext", sizeof(struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext), __Pyx_ImportType_CheckSize_Warn);
|
||||
if (!__pyx_ptype_14_pydevd_bundle_13pydevd_cython_PydevSmartStepContext) __PYX_ERR(2, 20, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
|
||||
__Pyx_RefNannyFinishContext();
|
||||
return 0;
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -62,6 +62,8 @@ public abstract class AbstractCommand<T> {
|
||||
public static final int SHOW_WARNING = 150;
|
||||
public static final int LOAD_FULL_VALUE = 151;
|
||||
|
||||
public static final int CMD_GET_SMART_STEP_INTO_VARIANTS = 160;
|
||||
|
||||
/**
|
||||
* The code of the message that means that IDE received
|
||||
* {@link #PROCESS_CREATED} message from the Python debugger script.
|
||||
|
||||
+8
-2
@@ -198,6 +198,12 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger {
|
||||
return debugger(threadId).loadFrame(threadId, frameId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
|
||||
throws PyDebuggerException {
|
||||
return debugger(threadId).getSmartStepIntoVariants(threadId, frameId, startContextLine, endContextLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException {
|
||||
return debugger(threadId).loadVariable(threadId, frameId, var);
|
||||
@@ -398,8 +404,8 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smartStepInto(String threadId, String functionName) {
|
||||
debugger(threadId).smartStepInto(threadId, functionName);
|
||||
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
|
||||
debugger(threadId).smartStepInto(threadId, frameId, functionName, callOrder, contextStartLine, contextEndLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.pydev;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.jetbrains.python.debugger.PyDebuggerException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetSmartStepIntoVariantsCommand extends AbstractFrameCommand {
|
||||
|
||||
private final int myContextStartLine;
|
||||
private final int myContextEndLine;
|
||||
|
||||
private List<Pair<String, Boolean>> myVariants;
|
||||
|
||||
protected GetSmartStepIntoVariantsCommand(RemoteDebugger debugger, String threadId, String frameId,
|
||||
int contextStartLine, int contextEndLine) {
|
||||
super(debugger, CMD_GET_SMART_STEP_INTO_VARIANTS, threadId, frameId);
|
||||
myContextStartLine = contextStartLine;
|
||||
myContextEndLine = contextEndLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildPayload(Payload payload) {
|
||||
super.buildPayload(payload);
|
||||
payload.add(myContextStartLine).add(myContextEndLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseExpected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException {
|
||||
super.processResponse(response);
|
||||
String payload = response.getPayload();
|
||||
myVariants = ProtocolParser.parseSmartStepIntoVariants(payload);
|
||||
}
|
||||
|
||||
public List<Pair<String, Boolean>> getVariants() {
|
||||
return myVariants;
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,12 @@ public class MultiProcessDebugger implements ProcessDebugger {
|
||||
return debugger(threadId).loadFrame(threadId, frameId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
|
||||
throws PyDebuggerException {
|
||||
return debugger(threadId).getSmartStepIntoVariants(threadId, frameId, startContextLine, endContextLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException {
|
||||
return debugger(threadId).loadVariable(threadId, frameId, var);
|
||||
@@ -361,8 +367,8 @@ public class MultiProcessDebugger implements ProcessDebugger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smartStepInto(String threadId, String functionName) {
|
||||
debugger(threadId).smartStepInto(threadId, functionName);
|
||||
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
|
||||
debugger(threadId).smartStepInto(threadId, frameId, functionName, callOrder, contextStartLine, contextEndLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,8 @@ public interface ProcessDebugger {
|
||||
|
||||
XValueChildrenList loadFrame(String threadId, String frameId) throws PyDebuggerException;
|
||||
|
||||
List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine) throws PyDebuggerException;
|
||||
|
||||
// todo: don't generate temp variables for qualified expressions - just split 'em
|
||||
XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException;
|
||||
|
||||
@@ -80,7 +82,7 @@ public interface ProcessDebugger {
|
||||
|
||||
void run() throws PyDebuggerException;
|
||||
|
||||
void smartStepInto(String threadId, String functionName);
|
||||
void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine);
|
||||
|
||||
void resumeOrStep(String threadId, ResumeOrStepCommand.Mode mode);
|
||||
|
||||
|
||||
@@ -315,6 +315,19 @@ public class ProtocolParser {
|
||||
return result.createArrayChunk();
|
||||
}
|
||||
|
||||
public static @NotNull List<Pair<String, Boolean>> parseSmartStepIntoVariants(String text) throws PyDebuggerException {
|
||||
XppReader reader = openReader(text, false);
|
||||
List<Pair<String, Boolean>> variants = Lists.newArrayList();
|
||||
while (reader.hasMoreChildren()) {
|
||||
reader.moveDown();
|
||||
String variantName = read(reader, "name", true);
|
||||
Boolean isVisited = read(reader, "isVisited", true).equals("true");
|
||||
variants.add(Pair.create(variantName, isVisited));
|
||||
reader.moveUp();
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
|
||||
private static void parseArrayHeaderData(XppReader reader, ArrayChunkBuilder result) throws PyDebuggerException {
|
||||
List<String> rowHeaders = Lists.newArrayList();
|
||||
List<ArrayChunk.ColHeader> colHeaders = Lists.newArrayList();
|
||||
|
||||
@@ -169,6 +169,14 @@ public class RemoteDebugger implements ProcessDebugger {
|
||||
return command.getVariables();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, Boolean>> getSmartStepIntoVariants(String threadId, String frameId, int startContextLine, int endContextLine)
|
||||
throws PyDebuggerException {
|
||||
GetSmartStepIntoVariantsCommand command = new GetSmartStepIntoVariantsCommand(this, threadId, frameId, startContextLine, endContextLine);
|
||||
command.execute();
|
||||
return command.getVariants();
|
||||
}
|
||||
|
||||
// todo: don't generate temp variables for qualified expressions - just split 'em
|
||||
@Override
|
||||
public XValueChildrenList loadVariable(final String threadId, final String frameId, final PyDebugValue var) throws PyDebuggerException {
|
||||
@@ -440,8 +448,9 @@ public class RemoteDebugger implements ProcessDebugger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smartStepInto(String threadId, String functionName) {
|
||||
final SmartStepIntoCommand command = new SmartStepIntoCommand(this, threadId, functionName);
|
||||
public void smartStepInto(String threadId, String frameId, String functionName, int callOrder, int contextStartLine, int contextEndLine) {
|
||||
final SmartStepIntoCommand command = new SmartStepIntoCommand(this, threadId, frameId, functionName, callOrder,
|
||||
contextStartLine, contextEndLine);
|
||||
execute(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,25 @@ package com.jetbrains.python.debugger.pydev;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SmartStepIntoCommand extends AbstractThreadCommand {
|
||||
public class SmartStepIntoCommand extends AbstractFrameCommand {
|
||||
private final String myFuncName;
|
||||
private final int myCallOrder;
|
||||
private final int myContextStartLine;
|
||||
private final int myContextEndLine;
|
||||
|
||||
public SmartStepIntoCommand(@NotNull final RemoteDebugger debugger, String threadId,
|
||||
String funcName) {
|
||||
super(debugger, SMART_STEP_INTO, threadId);
|
||||
|
||||
public SmartStepIntoCommand(@NotNull final RemoteDebugger debugger, String threadId, String frameId,
|
||||
String funcName, int callOrder, int contextStartLine, int contextEndLine) {
|
||||
super(debugger, SMART_STEP_INTO, threadId, frameId);
|
||||
myFuncName = funcName;
|
||||
myCallOrder = callOrder;
|
||||
myContextStartLine = contextStartLine;
|
||||
myContextEndLine = contextEndLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildPayload(Payload payload) {
|
||||
super.buildPayload(payload);
|
||||
payload.add("0").add(myFuncName);
|
||||
payload.add("0").add(myFuncName).add(myCallOrder).add(myContextStartLine).add(myContextEndLine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ import com.jetbrains.python.console.pydev.PydevCompletionVariant;
|
||||
import com.jetbrains.python.debugger.containerview.PyViewNumericContainerAction;
|
||||
import com.jetbrains.python.debugger.pydev.*;
|
||||
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
|
||||
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoContext;
|
||||
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoHandler;
|
||||
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoVariant;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
@@ -99,7 +102,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
private boolean myDownloadSources = false;
|
||||
|
||||
protected PyPositionConverter myPositionConverter;
|
||||
private final XSmartStepIntoHandler<?> mySmartStepIntoHandler;
|
||||
@NotNull private final XSmartStepIntoHandler<?> mySmartStepIntoHandler;
|
||||
private boolean myWaitingForConnection = false;
|
||||
private PyStackFrame myConsoleContextFrame = null;
|
||||
private PyReferrersLoader myReferrersProvider;
|
||||
@@ -109,6 +112,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
|
||||
private final Map<String, Map<String, PyDebugValueDescriptor>> myDescriptorsCache = Maps.newConcurrentMap();
|
||||
|
||||
|
||||
public PyDebugProcess(@NotNull XDebugSession session,
|
||||
@NotNull ServerSocket serverSocket,
|
||||
@NotNull ExecutionConsole executionConsole,
|
||||
@@ -144,7 +148,6 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
myBreakpointHandlers = breakpointHandlers.toArray(XBreakpointHandler.EMPTY_ARRAY);
|
||||
|
||||
myEditorsProvider = new PyDebuggerEditorsProvider();
|
||||
mySmartStepIntoHandler = new PySmartStepIntoHandler(this);
|
||||
myProcessHandler = processHandler;
|
||||
myExecutionConsole = executionConsole;
|
||||
if (myProcessHandler != null) {
|
||||
@@ -157,6 +160,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
myPositionConverter = new PyLocalPositionConverter();
|
||||
}
|
||||
|
||||
mySmartStepIntoHandler = new PySmartStepIntoHandler(this);
|
||||
|
||||
PyDebugValueExecutionService executionService = PyDebugValueExecutionService.getInstance(getProject());
|
||||
executionService.sessionStarted(this);
|
||||
session.addSessionListener(new XDebugSessionListener() {
|
||||
@@ -280,6 +285,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public XSmartStepIntoHandler<?> getSmartStepIntoHandler() {
|
||||
return mySmartStepIntoHandler;
|
||||
}
|
||||
@@ -501,7 +507,6 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setShowReturnValues(boolean showReturnValues) {
|
||||
myDebugger.setShowReturnValues(showReturnValues);
|
||||
}
|
||||
@@ -543,15 +548,32 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
passToCurrentThread(context, ResumeOrStepCommand.Mode.STEP_OUT);
|
||||
}
|
||||
|
||||
public void startSmartStepInto(String functionName) {
|
||||
dropFrameCaches();
|
||||
public void startSmartStepInto(@NotNull PySmartStepIntoVariant variant) {
|
||||
String threadId = variant.getContext().getFrame().getThreadId();
|
||||
String frameId = variant.getContext().getFrame().getFrameId();
|
||||
if (isConnected()) {
|
||||
dropFrameCaches();
|
||||
for (PyThreadInfo suspendedThread : mySuspendedThreads) {
|
||||
myDebugger.smartStepInto(suspendedThread.getId(), functionName);
|
||||
if (threadId.equals(suspendedThread.getId())) {
|
||||
PySmartStepIntoContext context = variant.getContext();
|
||||
myDebugger.smartStepInto(threadId, frameId, variant.getFunctionName(), variant.getCallOrder(),
|
||||
context.getStartLine(), context.getEndLine());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<String, Boolean>> getSmartStepIntoVariants(int startContextLine, int endContextLine) {
|
||||
try {
|
||||
PyStackFrame frame = currentFrame();
|
||||
return myDebugger.getSmartStepIntoVariants(frame.getThreadId(), frame.getFrameId(), startContextLine, endContextLine);
|
||||
}
|
||||
catch (PyDebuggerException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
myDebugger.close();
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XDebuggerUtil;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.PyCallExpression;
|
||||
import com.jetbrains.python.psi.PyElement;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class PySmartStepIntoHandler extends XSmartStepIntoHandler<PySmartStepIntoHandler.PySmartStepIntoVariant> {
|
||||
private final XDebugSession mySession;
|
||||
private final PyDebugProcess myProcess;
|
||||
|
||||
public PySmartStepIntoHandler(final PyDebugProcess process) {
|
||||
mySession = process.getSession();
|
||||
myProcess = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<PySmartStepIntoVariant> computeSmartStepVariants(@NotNull XSourcePosition position) {
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
|
||||
final List<PySmartStepIntoVariant> variants = Lists.newArrayList();
|
||||
final Set<PyCallExpression> visitedCalls = Sets.newHashSet();
|
||||
|
||||
final int line = position.getLine();
|
||||
XDebuggerUtil.getInstance().iterateLine(mySession.getProject(), document, line, psiElement -> {
|
||||
addVariants(document, line, psiElement, variants, visitedCalls);
|
||||
return true;
|
||||
});
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startStepInto(@NotNull PySmartStepIntoVariant smartStepIntoVariant) {
|
||||
myProcess.startSmartStepInto(smartStepIntoVariant.getFunctionName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPopupTitle(@NotNull XSourcePosition position) {
|
||||
return PyBundle.message("debug.popup.title.step.into.function");
|
||||
}
|
||||
|
||||
private static void addVariants(Document document, int line, @Nullable PsiElement element,
|
||||
List<PySmartStepIntoVariant> variants,
|
||||
Set<PyCallExpression> visited) {
|
||||
if (element == null) return;
|
||||
|
||||
final PyCallExpression expression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
|
||||
if (expression != null &&
|
||||
expression.getTextRange().getEndOffset() <= document.getLineEndOffset(line) &&
|
||||
visited.add(expression)) {
|
||||
addVariants(document, line, expression.getParent(), variants, visited);
|
||||
PyExpression ref = expression.getCallee();
|
||||
|
||||
variants.add(new PySmartStepIntoVariant(ref));
|
||||
}
|
||||
}
|
||||
|
||||
public static class PySmartStepIntoVariant extends XSmartStepIntoVariant {
|
||||
//private final String myFunctionName;
|
||||
|
||||
private final PyElement myElement;
|
||||
|
||||
public PySmartStepIntoVariant(PyElement element) {
|
||||
myElement = element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return myElement.getText() + "()";
|
||||
}
|
||||
|
||||
public String getFunctionName() {
|
||||
String name = myElement.getName();
|
||||
return name != null ? name : getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
@@ -22,6 +23,7 @@ import com.intellij.xdebugger.frame.XValue;
|
||||
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
|
||||
import icons.PythonIcons;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -37,14 +39,15 @@ public class PyStackFrame extends XStackFrame {
|
||||
|
||||
private static final Object STACK_FRAME_EQUALITY_OBJECT = new Object();
|
||||
public static final String DOUBLE_UNDERSCORE = "__";
|
||||
public static final String RETURN_VALUES_GROUP_NAME = "Return Values";
|
||||
public static final String SPECIAL_VARIABLES_GROUP_NAME = "Special Variables";
|
||||
public static final Set<String> HIDE_TYPES = ContainerUtil.set("function", "type", "classobj", "module");
|
||||
@NotNull @NonNls public static final String RETURN_VALUES_GROUP_NAME = "Return Values";
|
||||
@NotNull @NonNls public static final String SPECIAL_VARIABLES_GROUP_NAME = "Special Variables";
|
||||
@NotNull @NonNls public static final Set<String> HIDE_TYPES = ContainerUtil.set("function", "type", "classobj", "module");
|
||||
public static final int DUNDER_VALUES_IND = 0;
|
||||
public static final int SPECIAL_TYPES_IND = DUNDER_VALUES_IND + 1;
|
||||
public static final int IPYTHON_VALUES_IND = SPECIAL_TYPES_IND + 1;
|
||||
public static final int NUMBER_OF_GROUPS = IPYTHON_VALUES_IND + 1;
|
||||
|
||||
@NotNull @NonNls public static final Set<String> COMPREHENSION_NAMES = ImmutableSet.of("<genexpr>", "<listcomp>", "<dictcomp>",
|
||||
"<setcomp>");
|
||||
private final Project myProject;
|
||||
private final PyFrameAccessor myDebugProcess;
|
||||
private final PyStackFrameInfo myFrameInfo;
|
||||
@@ -217,6 +220,15 @@ public class PyStackFrame extends XStackFrame {
|
||||
return myPosition;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return myFrameInfo.getName();
|
||||
}
|
||||
|
||||
public boolean isComprehension() {
|
||||
return COMPREHENSION_NAMES.contains(getName());
|
||||
}
|
||||
|
||||
public void setChildrenDescriptors(@Nullable Map<String, PyDebugValueDescriptor> childrenDescriptors) {
|
||||
myChildrenDescriptors = childrenDescriptors;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettings> im
|
||||
private boolean myWatchReturnValues = false;
|
||||
private boolean mySimplifiedView = true;
|
||||
private volatile PyDebugValue.ValuesPolicy myValuesPolicy = PyDebugValue.ValuesPolicy.ASYNC;
|
||||
private boolean myAlwaysDoSmartStepIntoEnabled = true;
|
||||
|
||||
public PyDebuggerSettings() {
|
||||
super("python");
|
||||
@@ -78,6 +79,14 @@ public class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettings> im
|
||||
mySteppingFiltersEnabled = steppingFiltersEnabled;
|
||||
}
|
||||
|
||||
public void setAlwaysDoSmartStepIntoEnabled(boolean alwaysDoSmartStepIntoEnabled) {
|
||||
myAlwaysDoSmartStepIntoEnabled = alwaysDoSmartStepIntoEnabled;
|
||||
}
|
||||
|
||||
public boolean isAlwaysDoSmartStepInto() {
|
||||
return myAlwaysDoSmartStepIntoEnabled;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<PySteppingFilter> getSteppingFilters() {
|
||||
return mySteppingFilters;
|
||||
|
||||
+18
-8
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.debugger.settings.PyDebuggerSteppingConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
@@ -17,23 +17,33 @@
|
||||
<text resource-bundle="messages/PyBundle" key="form.debugger.stepping.do.not.step.into.scripts"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="1eb52">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<grid id="79cc7" binding="mySteppingPanel" custom-create="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<component id="dd487" class="com.intellij.ui.components.JBCheckBox" binding="myAlwaysDoSmartStepIntoCheckBox">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<horizontalAlignment value="2"/>
|
||||
<selected value="false"/>
|
||||
<text value="Always do smart step into"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="1eb52">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<component id="5fb35" class="com.intellij.ui.components.JBCheckBox" binding="myLibrariesFilterCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<horizontalAlignment value="2"/>
|
||||
|
||||
+4
@@ -40,6 +40,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
|
||||
private JPanel mySteppingPanel;
|
||||
private JBCheckBox myLibrariesFilterCheckBox;
|
||||
private JBCheckBox myStepFilterEnabledCheckBox;
|
||||
private JBCheckBox myAlwaysDoSmartStepIntoCheckBox;
|
||||
private TableModelEditor<PySteppingFilter> myPySteppingFilterEditor;
|
||||
|
||||
public PyDebuggerSteppingConfigurableUi() {
|
||||
@@ -62,6 +63,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
|
||||
public void reset(@NotNull PyDebuggerSettings settings) {
|
||||
myLibrariesFilterCheckBox.setSelected(settings.isLibrariesFilterEnabled());
|
||||
myStepFilterEnabledCheckBox.setSelected(settings.isSteppingFiltersEnabled());
|
||||
myAlwaysDoSmartStepIntoCheckBox.setSelected(settings.isAlwaysDoSmartStepInto());
|
||||
myPySteppingFilterEditor.reset(settings.getSteppingFilters());
|
||||
myPySteppingFilterEditor.enabled(myStepFilterEnabledCheckBox.isSelected());
|
||||
}
|
||||
@@ -70,6 +72,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
|
||||
public boolean isModified(@NotNull PyDebuggerSettings settings) {
|
||||
return myLibrariesFilterCheckBox.isSelected() != settings.isLibrariesFilterEnabled()
|
||||
|| myStepFilterEnabledCheckBox.isSelected() != settings.isSteppingFiltersEnabled()
|
||||
|| myAlwaysDoSmartStepIntoCheckBox.isSelected() != settings.isAlwaysDoSmartStepInto()
|
||||
|| myPySteppingFilterEditor.isModified();
|
||||
}
|
||||
|
||||
@@ -77,6 +80,7 @@ public class PyDebuggerSteppingConfigurableUi implements ConfigurableUi<PyDebugg
|
||||
public void apply(@NotNull PyDebuggerSettings settings) throws ConfigurationException {
|
||||
settings.setLibrariesFilterEnabled(myLibrariesFilterCheckBox.isSelected());
|
||||
settings.setSteppingFiltersEnabled(myStepFilterEnabledCheckBox.isSelected());
|
||||
settings.setAlwaysDoSmartStepIntoEnabled(myAlwaysDoSmartStepIntoCheckBox.isSelected());
|
||||
if (myPySteppingFilterEditor.isModified()) {
|
||||
settings.setSteppingFilters(myPySteppingFilterEditor.apply());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.jetbrains.python.debugger.PyStackFrame;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PySmartStepIntoContext {
|
||||
private final int myStartLine;
|
||||
private final int myEndLine;
|
||||
@NotNull private final PyStackFrame myFrame;
|
||||
|
||||
public PySmartStepIntoContext(int startLine, int endLine, @NotNull PyStackFrame frame) {
|
||||
myStartLine = startLine;
|
||||
myEndLine = endLine;
|
||||
myFrame = frame;
|
||||
}
|
||||
|
||||
public int getStartLine() {
|
||||
return myStartLine;
|
||||
}
|
||||
|
||||
public int getEndLine() {
|
||||
return myEndLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyStackFrame getFrame() {
|
||||
return myFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this context to the specified object. The result is {@code
|
||||
* true} if the argument is instance of {@code PySmartStepIntoContext},
|
||||
* the line the two contexts were created are the same and either the IDs
|
||||
* of the frames they were called are the same or this context frame is
|
||||
* the frame of a generator expression. The latest is important because we
|
||||
* don't want to make a difference between a generator expression frame and
|
||||
* the frame it was called from. Otherwise we would create a new context
|
||||
* on each generator iteration.
|
||||
*
|
||||
* @param o
|
||||
* The object to compare this {@code PySmartStepIntoContext} against
|
||||
*
|
||||
* @return {@code true} if the given object represents a {@code PySmartStepIntoContext}
|
||||
* equivalent to this context, {@code false} otherwise
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PySmartStepIntoContext)) return false;
|
||||
PySmartStepIntoContext context = (PySmartStepIntoContext)o;
|
||||
return myStartLine == context.myStartLine && myEndLine == context.myEndLine
|
||||
&& myFrame.getFrameId().equals(context.getFrame().getFrameId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myStartLine, myFrame);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.DocumentUtil;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.debugger.PyDebugProcess;
|
||||
import com.jetbrains.python.debugger.PyStackFrame;
|
||||
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.PyStatement;
|
||||
import com.jetbrains.python.psi.PyStatementPart;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
import org.jetbrains.concurrency.Promises;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class PySmartStepIntoHandler extends XSmartStepIntoHandler<PySmartStepIntoVariant> {
|
||||
@NotNull private final XDebugSession mySession;
|
||||
@NotNull private final PyDebugProcess myProcess;
|
||||
|
||||
public PySmartStepIntoHandler(@NotNull final PyDebugProcess process) {
|
||||
mySession = process.getSession();
|
||||
myProcess = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startStepInto(@NotNull PySmartStepIntoVariant smartStepIntoVariant) {
|
||||
myProcess.startSmartStepInto(smartStepIntoVariant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPopupTitle(@NotNull XSourcePosition position) {
|
||||
return PyBundle.message("debug.popup.title.step.into.function");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<PySmartStepIntoVariant> computeSmartStepVariants(@NotNull XSourcePosition position) {
|
||||
PyStackFrame currentFrame = (PyStackFrame)mySession.getCurrentStackFrame();
|
||||
if (currentFrame == null || currentFrame.isComprehension()) return Collections.emptyList();
|
||||
|
||||
PySmartStepIntoContext context = createSmartStepIntoContext(currentFrame);
|
||||
if (context == null) return Collections.emptyList();
|
||||
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
|
||||
if (document == null) return Collections.emptyList();
|
||||
|
||||
Future<List<Pair<String, Boolean>>> future = ApplicationManager.getApplication().executeOnPooledThread(
|
||||
() -> myProcess.getSmartStepIntoVariants(context.getStartLine(), context.getEndLine()));
|
||||
|
||||
List<Pair<String, Boolean>> variantsFromPython;
|
||||
try {
|
||||
variantsFromPython = future.get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (variantsFromPython.size() == 0) return Collections.emptyList();
|
||||
|
||||
return removePossiblyUnreachableVariants(document, position.getLine(), variantsFromPython, context);
|
||||
}
|
||||
|
||||
private @NotNull List<PySmartStepIntoVariant> removePossiblyUnreachableVariants(@NotNull Document document, int line,
|
||||
@NotNull List<Pair<String, Boolean>> variantsFromPython,
|
||||
@NotNull PySmartStepIntoContext context) {
|
||||
PsiElement statement = findLineTopStatement(document, line);
|
||||
if (statement == null) return Collections.emptyList();
|
||||
|
||||
List<PySmartStepIntoVariant> result = Lists.newArrayList();
|
||||
|
||||
// We are going to filter the variants that PyCharm cannot resolve to be sure we don't suggest stepping into a native function.
|
||||
statement.acceptChildren(new PySmartStepIntoVariantVisitor(result, variantsFromPython, context));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiElement findLineTopStatement(@NotNull Document document, int line) {
|
||||
int offset = DocumentUtil.getFirstNonSpaceCharOffset(document, line);
|
||||
PsiFile file = PsiDocumentManager.getInstance(mySession.getProject()).getPsiFile(document);
|
||||
if (file == null) return null;
|
||||
PsiElement element = file.findElementAt(offset);
|
||||
int lineEndOffset = document.getLineEndOffset(line);
|
||||
|
||||
while (element != null) {
|
||||
if ((element instanceof PyFunction) && (element.getStartOffsetInParent() < lineEndOffset)) {
|
||||
// A decorated function. We allowing stepping into decorators.
|
||||
return element;
|
||||
}
|
||||
if (element.getTextOffset() < lineEndOffset) {
|
||||
if (element instanceof PyStatement || element instanceof PyStatementPart)
|
||||
return element;
|
||||
}
|
||||
element = element.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Promise<List<PySmartStepIntoVariant>> computeStepIntoVariants(@NotNull XSourcePosition position) {
|
||||
if (PyDebuggerSettings.getInstance().isAlwaysDoSmartStepInto()) {
|
||||
return computeSmartStepVariantsAsync(position);
|
||||
}
|
||||
return Promises.rejectedPromise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and stores a smart step into context for the given frame.
|
||||
*/
|
||||
@Nullable
|
||||
public PySmartStepIntoContext createSmartStepIntoContext(@NotNull PyStackFrame frame) {
|
||||
XSourcePosition position = frame.getSourcePosition();
|
||||
if (position == null) return null;
|
||||
|
||||
Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
|
||||
if (document == null) return null;
|
||||
|
||||
PySmartStepIntoHandler handler = (PySmartStepIntoHandler)myProcess.getSmartStepIntoHandler();
|
||||
PsiElement statement = handler.findLineTopStatement(document, position.getLine());
|
||||
|
||||
if (statement != null) {
|
||||
TextRange range = statement.getTextRange();
|
||||
int startLine = document.getLineNumber(range.getStartOffset());
|
||||
int endLine = document.getLineNumber(range.getEndOffset());
|
||||
return new PySmartStepIntoContext(startLine + 1, endLine + 1, frame);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An abstract class for a thing we can smart step into.
|
||||
*
|
||||
* Should you need to support another Python expression as a smart step into target,
|
||||
* subclass this class and add the respective method override to {@code PySmartStepIntoVariantVisitor}.
|
||||
*
|
||||
*/
|
||||
public abstract class PySmartStepIntoVariant extends XSmartStepIntoVariant {
|
||||
@NotNull protected final PsiElement myElement;
|
||||
protected final int myCallOrder;
|
||||
@NotNull protected final PySmartStepIntoContext myContext;
|
||||
|
||||
protected PySmartStepIntoVariant(@NotNull PsiElement element, int callOrder, @NotNull PySmartStepIntoContext context) {
|
||||
myElement = element;
|
||||
myCallOrder = callOrder;
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
@Nullable public abstract String getFunctionName();
|
||||
|
||||
public int getCallOrder() {
|
||||
return myCallOrder;
|
||||
}
|
||||
|
||||
@NotNull public PySmartStepIntoContext getContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable public TextRange getHighlightRange() {
|
||||
return myElement.getTextRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PySmartStepIntoVariant)) return false;
|
||||
PySmartStepIntoVariant variant = (PySmartStepIntoVariant)o;
|
||||
return myElement.equals(variant.myElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myElement);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.jetbrains.python.psi.PyCallExpression;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PySmartStepIntoVariantCallExpression extends PySmartStepIntoVariant {
|
||||
@Nullable private final PyExpression myCallee;
|
||||
|
||||
public PySmartStepIntoVariantCallExpression(@NotNull PyCallExpression element, int callOrder, @NotNull PySmartStepIntoContext context) {
|
||||
super(element, callOrder, context);
|
||||
myCallee = element.getCallee();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getFunctionName() {
|
||||
return myCallee != null ? myCallee.getName() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TextRange getHighlightRange() {
|
||||
if (myCallee == null) return null;
|
||||
|
||||
String calleeName = myCallee.getName();
|
||||
if (calleeName == null) return null;
|
||||
|
||||
TextRange range = myCallee.getTextRange();
|
||||
|
||||
// For example, for the `foo().bar().baz()` call expression the callee will be `foo().bar().baz`.
|
||||
// The range must be adjusted in such cases to match only the last part which is `baz`.
|
||||
if (calleeName.length() < range.getLength()) {
|
||||
int diff = range.getLength() - calleeName.length();
|
||||
return new TextRange(range.getStartOffset() + diff, range.getEndOffset());
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getText() {
|
||||
return myCallee != null ? myCallee.getText() + "()" : null;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.jetbrains.python.debugger.PyStackFrame;
|
||||
import com.jetbrains.python.psi.PyComprehensionElement;
|
||||
import com.jetbrains.python.psi.PyDictCompExpression;
|
||||
import com.jetbrains.python.psi.PyGeneratorExpression;
|
||||
import com.jetbrains.python.psi.PyListCompExpression;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PySmartStepIntoVariantComprehension extends PySmartStepIntoVariant {
|
||||
|
||||
protected PySmartStepIntoVariantComprehension(@NotNull PyComprehensionElement element, int callOrder,
|
||||
@NotNull PySmartStepIntoContext context) {
|
||||
super(element, callOrder, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFunctionName() {
|
||||
return getText();
|
||||
}
|
||||
|
||||
@NonNls
|
||||
@Override
|
||||
@NotNull
|
||||
public String getText() {
|
||||
if (myElement instanceof PyGeneratorExpression)
|
||||
return "<genexpr>";
|
||||
else if (myElement instanceof PyListCompExpression)
|
||||
return "<listcomp>";
|
||||
else if (myElement instanceof PyDictCompExpression)
|
||||
return "<dictcomp>";
|
||||
else
|
||||
return "<setcomp>";
|
||||
}
|
||||
|
||||
public static boolean isComprehensionName(@NotNull String name) {
|
||||
return PyStackFrame.COMPREHENSION_NAMES.contains(name);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement;
|
||||
import com.jetbrains.python.psi.PyElementType;
|
||||
import com.jetbrains.python.psi.PyPrefixExpression;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class PySmartStepIntoVariantOperator extends PySmartStepIntoVariant {
|
||||
@NonNls private static final Map<String, String> UNARY_OPERATOR_MAPPING = ImmutableMap.of(
|
||||
"__sub__", "__neg__",
|
||||
"__add__", "__pos__",
|
||||
"__invert__", "__invert__"
|
||||
);
|
||||
|
||||
public PySmartStepIntoVariantOperator(@NotNull PsiElement element,
|
||||
int callOrder,
|
||||
@NotNull PySmartStepIntoContext context) {
|
||||
super(element, callOrder, context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getFunctionName() {
|
||||
if (myElement instanceof LeafPsiElement) {
|
||||
return ((PyElementType)((LeafPsiElement)myElement).getElementType()).getSpecialMethodName();
|
||||
}
|
||||
else if (myElement instanceof PyPrefixExpression) {
|
||||
return getUnaryOperatorSpecialMethodName(((PyPrefixExpression)myElement).getOperator());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
String text = myElement.getText();
|
||||
return myElement instanceof PyPrefixExpression ? text.substring(0, 1) : text;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public TextRange getHighlightRange() {
|
||||
TextRange textRange = myElement.getTextRange();
|
||||
return myElement instanceof PyPrefixExpression ?
|
||||
new TextRange(textRange.getStartOffset(), textRange.getStartOffset() + 1) : textRange;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getUnaryOperatorSpecialMethodName(@NotNull PyElementType operator) {
|
||||
return UNARY_OPERATOR_MAPPING.getOrDefault(operator.getSpecialMethodName(), null);
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger.smartstepinto;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.pyi.PyiFile;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PySmartStepIntoVariantVisitor extends PyRecursiveElementVisitor {
|
||||
@NotNull @NonNls private static final ImmutableSet<String> BUILTINS_MODULES = ImmutableSet.of("builtins.py", "__builtin__.py");
|
||||
|
||||
int myVariantIndex = -1;
|
||||
@NotNull private final List<PySmartStepIntoVariant> myCollector;
|
||||
@NotNull private final List<Pair<String, Boolean>> myVariantsFromPython;
|
||||
@NotNull private final PySmartStepIntoContext myContext;
|
||||
@NotNull private final Map<String, Integer> mySeenVariants = Maps.newHashMap();
|
||||
@NotNull private final Set<PsiElement> alreadyVisited = Sets.newHashSet();
|
||||
|
||||
public PySmartStepIntoVariantVisitor(@NotNull List<PySmartStepIntoVariant> collector,
|
||||
@NotNull List<Pair<String, Boolean>> variantsFromPython,
|
||||
@NotNull PySmartStepIntoContext context) {
|
||||
myCollector = collector;
|
||||
myVariantsFromPython = variantsFromPython;
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyCallExpression(@NotNull PyCallExpression node) {
|
||||
node.acceptChildren(this);
|
||||
|
||||
if (alreadyVisited.contains(node)) return;
|
||||
alreadyVisited.add(node);
|
||||
|
||||
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
|
||||
|
||||
PyExpression callee = node.getCallee();
|
||||
if (callee == null || callee.getName() == null) return;
|
||||
|
||||
if (!callee.getName().equals(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
|
||||
|
||||
myVariantIndex++;
|
||||
int callOrder = getCallOrder();
|
||||
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
|
||||
|
||||
PsiElement ref = callee.getReference() != null ? callee.getReference().resolve() : null;
|
||||
if (ref != null && isBuiltIn(ref)) return;
|
||||
|
||||
if (ref instanceof PyFunction && ((((PyFunction)ref).isAsync()) || ((PyFunction)ref).isGenerator())) return;
|
||||
|
||||
if (isAlreadySeen()) return;
|
||||
|
||||
myCollector.add(new PySmartStepIntoVariantCallExpression(node, callOrder, myContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyDecoratorList(@NotNull PyDecoratorList node) {
|
||||
for (PyDecorator decorator : node.getDecorators())
|
||||
visitPyCallExpression(decorator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyComprehensionElement(PyComprehensionElement node) {
|
||||
node.acceptChildren(this);
|
||||
|
||||
if (alreadyVisited.contains(node)) return;
|
||||
alreadyVisited.add(node);
|
||||
|
||||
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
|
||||
|
||||
if (!PySmartStepIntoVariantComprehension.isComprehensionName(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
|
||||
|
||||
myVariantIndex++;
|
||||
int callOrder = getCallOrder();
|
||||
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
|
||||
|
||||
if (isAlreadySeen()) return;
|
||||
|
||||
myCollector.add(new PySmartStepIntoVariantComprehension(node, callOrder, myContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyBinaryExpression(@NotNull PyBinaryExpression node) {
|
||||
// We have to visit both branches of a binary expression first to preserve the execution order as it is in a CPython interpreter,
|
||||
// E.g. in the `f() + g()` expression the execution order goes like `f()`, `g()`, `+`, but it's `f()`, `+`, `g()` in the PSI tree.
|
||||
node.getLeftExpression().accept(this);
|
||||
if (node.getRightExpression() != null) node.getRightExpression().accept(this);
|
||||
node.acceptChildren(this);
|
||||
|
||||
if (alreadyVisited.contains(node)) return;
|
||||
alreadyVisited.add(node);
|
||||
|
||||
if (myVariantIndex == myVariantsFromPython.size() - 1) return;
|
||||
|
||||
PyElementType operator = node.getOperator();
|
||||
|
||||
if (PyTokenTypes.OPERATIONS.contains(operator)) processOperator(operator, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyPrefixExpression(@NotNull PyPrefixExpression node) {
|
||||
PyExpression operand = node.getOperand();
|
||||
if (operand != null) operand.accept(this);
|
||||
|
||||
PyElementType operator = node.getOperator();
|
||||
|
||||
processOperator(operator, node);
|
||||
}
|
||||
|
||||
private void processOperator(PyElementType operator, PyReferenceOwner expression) {
|
||||
boolean isBinaryOperator = expression instanceof PyBinaryExpression;
|
||||
|
||||
String specialMethodName = isBinaryOperator ? operator.getSpecialMethodName() :
|
||||
PySmartStepIntoVariantOperator.getUnaryOperatorSpecialMethodName(operator);
|
||||
|
||||
if (specialMethodName == null || !specialMethodName.equals(myVariantsFromPython.get(myVariantIndex + 1).first)) return;
|
||||
myVariantIndex++;
|
||||
|
||||
int callOrder = getCallOrder();
|
||||
mySeenVariants.put(myVariantsFromPython.get(myVariantIndex).first, ++callOrder);
|
||||
|
||||
PsiElement resolved = expression.getReference(
|
||||
PyResolveContext.defaultContext().withTypeEvalContext(TypeEvalContext.userInitiated(
|
||||
expression.getProject(), expression.getContainingFile()))).resolve();
|
||||
|
||||
if (resolved == null || isBuiltIn(resolved) || isAlreadySeen()) return;
|
||||
|
||||
PsiElement psiOperator = isBinaryOperator ? ((PyBinaryExpression)expression).getPsiOperator() : expression;
|
||||
if (psiOperator != null) myCollector.add(new PySmartStepIntoVariantOperator(psiOperator, callOrder, myContext));
|
||||
}
|
||||
|
||||
private static boolean isBuiltIn(@NotNull PsiElement ref) {
|
||||
PsiElement navFile = ref.getNavigationElement().getContainingFile();
|
||||
return (navFile instanceof PyFile && BUILTINS_MODULES.contains(navFile.getContainingFile().getName())
|
||||
|| navFile instanceof PyiFile);
|
||||
}
|
||||
|
||||
private boolean isAlreadySeen() {
|
||||
return myVariantsFromPython.get(myVariantIndex).second;
|
||||
}
|
||||
|
||||
private int getCallOrder() {
|
||||
return mySeenVariants.getOrDefault(myVariantsFromPython.get(myVariantIndex).first, -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class A:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def get_x(self):
|
||||
return self.x
|
||||
@@ -0,0 +1,25 @@
|
||||
def foo():
|
||||
return bar() + baz() + bar() + barbaz(10) + barbaz(bar()) + add(bar(), baz())
|
||||
|
||||
|
||||
def bar():
|
||||
x = 42
|
||||
return x
|
||||
|
||||
|
||||
def baz():
|
||||
y = bar() + bar()
|
||||
return y
|
||||
|
||||
|
||||
def barbaz(i):
|
||||
return i
|
||||
|
||||
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
foo()
|
||||
foo()
|
||||
@@ -0,0 +1,3 @@
|
||||
import helper
|
||||
|
||||
a = helper.A(42).get_x()
|
||||
@@ -0,0 +1,5 @@
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
|
||||
z = identity(int("1") + identity((identity(identity(identity(42))))))
|
||||
@@ -0,0 +1,17 @@
|
||||
class Point(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __add__(self, other):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __sub__(self, other):
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
|
||||
|
||||
p1 = Point(1, 1)
|
||||
p2 = Point(2, 2)
|
||||
p3 = Point(3, 3)
|
||||
|
||||
p = p1 + p2 - p3 - Point(4, 4) + Point(5, 5)
|
||||
@@ -0,0 +1,48 @@
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self, a):
|
||||
self.a = a
|
||||
|
||||
def __add__(self, o):
|
||||
print("__add__")
|
||||
return A(self.a + o.a)
|
||||
|
||||
def __sub__(self, o):
|
||||
print("__sub__")
|
||||
return A(self.a - o.a)
|
||||
|
||||
def __mul__(self, o):
|
||||
print("__mul__")
|
||||
return A(self.a * o.a)
|
||||
|
||||
def __truediv__(self, o):
|
||||
print("__truediv__")
|
||||
return A(self.a / o.a)
|
||||
|
||||
__div__ = __truediv__ # Python 2 compatibility
|
||||
|
||||
def __floordiv__(self, o):
|
||||
print("__floordiv__")
|
||||
return A(self.a // o.a)
|
||||
|
||||
def __mod__(self, o):
|
||||
print("__mod__")
|
||||
return A(self.a)
|
||||
|
||||
def __pow__(self, o):
|
||||
print("__pow__")
|
||||
return A(self.a ** o.a)
|
||||
|
||||
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
|
||||
def foo():
|
||||
return (((((A(1) + A(2) + A(3) - A(3)) * A(1)) / A(identity(2))) // A(
|
||||
1)) % 2) ** A(3)
|
||||
|
||||
|
||||
foo()
|
||||
@@ -0,0 +1,7 @@
|
||||
class A(object):
|
||||
def f(self, x):
|
||||
return self
|
||||
|
||||
|
||||
a = A()
|
||||
a.f(1).f(2).f(3)
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self, a):
|
||||
self.a = a
|
||||
|
||||
def __gt__(self, other):
|
||||
if (self.a > other.a):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if (A(2) > A(3) > A(1)):
|
||||
print("ob1 is greater than ob2")
|
||||
else:
|
||||
print("ob2 is greater than ob1")
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def cond1():
|
||||
return True
|
||||
|
||||
|
||||
def cond2():
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
xs = [1, 2, 3]
|
||||
if 2 in xs:
|
||||
print("YES")
|
||||
else:
|
||||
print("NO")
|
||||
|
||||
if cond1() and cond2():
|
||||
print("YES")
|
||||
else:
|
||||
print("NO")
|
||||
@@ -0,0 +1,9 @@
|
||||
class A(object):
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def get_x(self):
|
||||
return self.x
|
||||
|
||||
|
||||
a = A(42).get_x()
|
||||
@@ -0,0 +1,13 @@
|
||||
def deco(func):
|
||||
def wrapper(x):
|
||||
x = x + 2
|
||||
return func(x)
|
||||
return wrapper
|
||||
|
||||
|
||||
@deco
|
||||
def f(x):
|
||||
return x
|
||||
|
||||
|
||||
y = f(1) + f(2) # breakpoint
|
||||
@@ -0,0 +1,27 @@
|
||||
def foo(i):
|
||||
return i
|
||||
|
||||
|
||||
def generate_power(exponent):
|
||||
def decorator(f):
|
||||
def inner(*args):
|
||||
result = f(*args)
|
||||
return exponent ** result
|
||||
|
||||
return inner
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@generate_power(foo(foo(3))) # breakpoint
|
||||
@generate_power(foo(foo(5)))
|
||||
def raise_three(n):
|
||||
return n
|
||||
|
||||
|
||||
@generate_power(2)
|
||||
def raise_two(n):
|
||||
return n
|
||||
|
||||
|
||||
raise_three(raise_two(2)) # breakpoint
|
||||
@@ -0,0 +1,12 @@
|
||||
class A(object):
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
|
||||
class B(A):
|
||||
def __init__(self, x, y):
|
||||
super(B, self).__init__(x)
|
||||
self.y = y
|
||||
|
||||
|
||||
b = B(1, 2)
|
||||
@@ -0,0 +1,13 @@
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def add1(x):
|
||||
return x + 1
|
||||
|
||||
|
||||
def add10(x):
|
||||
return x + 10
|
||||
|
||||
|
||||
for i in (add1(x) + add10(x) for x in range(3)):
|
||||
print(i)
|
||||
@@ -0,0 +1,20 @@
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def make_class(x):
|
||||
class C(A):
|
||||
p = x
|
||||
|
||||
return C
|
||||
|
||||
|
||||
def foo():
|
||||
return 100
|
||||
|
||||
|
||||
class D(make_class(foo())): # breakpoint
|
||||
def __init__(self):
|
||||
self.d = 3
|
||||
|
||||
def foo(self):
|
||||
return 1
|
||||
@@ -0,0 +1,11 @@
|
||||
def f(x):
|
||||
return x
|
||||
|
||||
|
||||
L = [1, 2, 3]
|
||||
|
||||
z = (
|
||||
f(0) + f(1) +
|
||||
L.pop() + f(2) + f(3) +
|
||||
f(4) + L.pop()
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def return_my_lucking_link():
|
||||
url = 'https://example.com/'
|
||||
return url
|
||||
|
||||
|
||||
def return_my_lucking_payload():
|
||||
payload = {'some': 'data'}
|
||||
return payload
|
||||
|
||||
|
||||
class A(object):
|
||||
def do_stuff(self, x, data=None):
|
||||
return "%s: %s" % (x, data)
|
||||
|
||||
|
||||
r = A().do_stuff( # breakpoint
|
||||
return_my_lucking_link(),
|
||||
data=json.dumps(return_my_lucking_payload())
|
||||
)
|
||||
|
||||
print(r) # breakpoint
|
||||
@@ -0,0 +1,11 @@
|
||||
def f(lst):
|
||||
lst.reverse()
|
||||
lst.append(42)
|
||||
return lst
|
||||
|
||||
|
||||
L = [1, 2, 3]
|
||||
counter = 2
|
||||
while counter > 0:
|
||||
len(f(f((f(L))))) # breakpoint
|
||||
counter -= 1
|
||||
@@ -0,0 +1,6 @@
|
||||
def f(s):
|
||||
s = s[::-1]
|
||||
return s.swapcase()
|
||||
|
||||
|
||||
result = f(f(f(f(f('abcdef'))))) # breakpoint
|
||||
@@ -0,0 +1,23 @@
|
||||
class A(object):
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def __neg__(self):
|
||||
return A(-self.x)
|
||||
|
||||
def __pos__(self):
|
||||
return A(abs(self.x))
|
||||
|
||||
def __invert__(self):
|
||||
return A(~self.x)
|
||||
|
||||
def __add__(self, other):
|
||||
return A(self.x + other.x)
|
||||
|
||||
|
||||
a1 = A(1)
|
||||
a2 = A(2)
|
||||
a3 = A(3)
|
||||
|
||||
|
||||
a5 = a1 + a2 + (-a3) + (+A(-4)) + (~a1) # breakpoint
|
||||
@@ -18,7 +18,7 @@ try:
|
||||
try:
|
||||
print(zoo(1).foo(2)) #we got ZeroDivision here
|
||||
finally:
|
||||
print(zoo(0).foo(2))
|
||||
print(zoo(0).foo(4))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.google.common.collect.Sets;
|
||||
import com.intellij.execution.ExecutionResult;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
@@ -27,6 +28,7 @@ import com.jetbrains.env.PyExecutionFixtureTestTask;
|
||||
import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
|
||||
import com.jetbrains.python.debugger.*;
|
||||
import com.jetbrains.python.debugger.pydev.PyDebugCallback;
|
||||
import com.jetbrains.python.debugger.smartstepinto.PySmartStepIntoVariant;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -120,13 +122,20 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask {
|
||||
debugProcess.startStepIntoMyCode(currentSession.getSuspendContext());
|
||||
}
|
||||
|
||||
protected void smartStepInto(String funcName) {
|
||||
protected void smartStepInto(String funcName, int callOrder) {
|
||||
XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
|
||||
|
||||
Assert.assertTrue(currentSession.isSuspended());
|
||||
Assert.assertEquals(0, myPausedSemaphore.availablePermits());
|
||||
|
||||
myDebugProcess.startSmartStepInto(funcName);
|
||||
ReadAction.run(() -> {
|
||||
List<?> smartStepIntoVariants = getSmartStepIntoVariants();
|
||||
for (Object o : smartStepIntoVariants) {
|
||||
PySmartStepIntoVariant variant = (PySmartStepIntoVariant) o;
|
||||
if (variant.getFunctionName().equals(funcName) && variant.getCallOrder() == callOrder)
|
||||
myDebugProcess.startSmartStepInto(variant);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Pair<Boolean, String> setNextStatement(int line) throws PyDebuggerException {
|
||||
@@ -528,6 +537,11 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask {
|
||||
return hasChildWithValue(children, Integer.toString(value));
|
||||
}
|
||||
|
||||
public List<?> getSmartStepIntoVariants() {
|
||||
XSourcePosition position = XDebuggerManager.getInstance(getProject()).getCurrentSession().getCurrentPosition();
|
||||
return myDebugProcess.getSmartStepIntoHandler().computeSmartStepVariants(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp(final String testName) throws Exception {
|
||||
if (myFixture == null) {
|
||||
|
||||
+1004
File diff suppressed because it is too large
Load Diff
@@ -142,116 +142,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOver() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("z").hasValue("2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepInto();
|
||||
waitForPause();
|
||||
eval("x").hasValue("1");
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("y").hasValue("3");
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("z").hasValue("1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepIntoMyCode() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_my_code.py") {
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 5);
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 7);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepIntoMyCode();
|
||||
waitForPause();
|
||||
eval("x").hasValue("2");
|
||||
resume();
|
||||
waitForPause();
|
||||
eval("x").hasValue("3");
|
||||
stepIntoMyCode();
|
||||
waitForPause();
|
||||
eval("stopped_in_user_file").hasValue("True");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmartStepInto() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 14);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
smartStepInto("foo");
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("y").hasValue("4");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmartStepInto2() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 18);
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 25);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
removeBreakpoint(getFilePath(getScriptName()), 18);
|
||||
smartStepInto("foo");
|
||||
waitForPause();
|
||||
eval("a.z").hasValue("1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInput() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_input.py") {
|
||||
@@ -636,109 +526,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStepOverConditionalBreakpoint() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_stepOverCondition.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getScriptName(), 1);
|
||||
toggleBreakpoint(getScriptName(), 2);
|
||||
XDebuggerTestUtil.setBreakpointCondition(getProject(), 2, "y == 3");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("y").hasValue("2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOverYieldFrom() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_step_over_yield.py") {
|
||||
@Override
|
||||
protected void init() {
|
||||
setMultiprocessDebug(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getScriptName(), 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
|
||||
waitForPause();
|
||||
|
||||
stepOver();
|
||||
|
||||
waitForPause();
|
||||
|
||||
eval("a").hasValue("42");
|
||||
|
||||
stepOver();
|
||||
|
||||
waitForPause();
|
||||
|
||||
eval("a").hasValue("42");
|
||||
|
||||
stepOver();
|
||||
|
||||
waitForPause();
|
||||
|
||||
eval("sum").hasValue("6");
|
||||
|
||||
resume();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> getTags() {
|
||||
return Sets.newHashSet("python34");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSteppingFilter() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_stepping_filter.py") {
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getScriptName(), 4);
|
||||
List<PySteppingFilter> filters = new ArrayList<>();
|
||||
filters.add(new PySteppingFilter(true, "*/test_m?_code.py"));
|
||||
final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
|
||||
debuggerSettings.setLibrariesFilterEnabled(true);
|
||||
debuggerSettings.setSteppingFiltersEnabled(true);
|
||||
debuggerSettings.setSteppingFilters(filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFinally() {
|
||||
final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
|
||||
debuggerSettings.setLibrariesFilterEnabled(false);
|
||||
debuggerSettings.setSteppingFiltersEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepInto();
|
||||
waitForPause();
|
||||
eval("stopped_in_user_file").hasValue("True");
|
||||
stepInto();
|
||||
waitForPause();
|
||||
eval("stopped_in_user_file").hasValue("True");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnValues() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_return_values.py") {
|
||||
@@ -904,38 +691,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResumeAfterStepping() {
|
||||
// This test case is important for frame evaluation debugging, because we reuse old tracing function for stepping and there were
|
||||
// some problems with switching between frame evaluation and tracing
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_resume_after_step.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getScriptName(), 2);
|
||||
toggleBreakpoint(getScriptName(), 5);
|
||||
toggleBreakpoint(getScriptName(), 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
eval("a").hasValue("1");
|
||||
stepOver();
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
eval("c").hasValue("3");
|
||||
resume();
|
||||
waitForPause();
|
||||
eval("d").hasValue("4");
|
||||
resume();
|
||||
waitForPause();
|
||||
eval("t").hasValue("1");
|
||||
resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddBreakWhileRunning() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_resume_after_step.py") {
|
||||
@@ -1778,37 +1533,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepIntoWithThreads() {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_step_into_with_threads.py") {
|
||||
@Override
|
||||
public void before() {
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 15);
|
||||
toggleBreakpoint(getFilePath(getScriptName()), 17);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
stepInto();
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
waitForOutput("foo");
|
||||
resume();
|
||||
waitForOutput("bar");
|
||||
waitForPause();
|
||||
stepInto();
|
||||
waitForPause();
|
||||
stepOver();
|
||||
waitForPause();
|
||||
waitForOutput("baz");
|
||||
resume();
|
||||
waitForTerminate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@StagingOn(os = TestEnv.WINDOWS)
|
||||
public void testNoDebuggerRelatedStacktraceOnDebuggerStop() {
|
||||
|
||||
Reference in New Issue
Block a user