mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Add option for injecting custom GUI event loop
This commit is contained in:
parent
fba80681ad
commit
043b11fd12
5 changed files with 74 additions and 26 deletions
|
|
@ -125,6 +125,9 @@ class PyDevdAPI(object):
|
|||
'''
|
||||
pydevd_file_utils.set_ide_os(ide_os)
|
||||
|
||||
def set_gui_event_loop(self, py_db, gui_event_loop):
|
||||
py_db._gui_event_loop = gui_event_loop
|
||||
|
||||
def send_error_message(self, py_db, msg):
|
||||
sys.stderr.write('pydevd: %s\n' % (msg,))
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class DebugOptions(object):
|
|||
'flask_debug',
|
||||
'stop_on_entry',
|
||||
'max_exception_stack_frames',
|
||||
'gui_event_loop',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -30,6 +31,7 @@ class DebugOptions(object):
|
|||
self.flask_debug = False
|
||||
self.stop_on_entry = False
|
||||
self.max_exception_stack_frames = 0
|
||||
self.gui_event_loop = 'matplotlib'
|
||||
|
||||
def to_json(self):
|
||||
dct = {}
|
||||
|
|
@ -92,6 +94,8 @@ class DebugOptions(object):
|
|||
|
||||
self.max_exception_stack_frames = int_parser(args.get('maxExceptionStackFrames', 0))
|
||||
|
||||
if 'guiEventLoop' in args:
|
||||
self.gui_event_loop = str(args['guiEventLoop'])
|
||||
|
||||
def int_parser(s, default_value=0):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -460,6 +460,8 @@ class PyDevJsonCommandProcessor(object):
|
|||
if self._options.stop_on_entry and start_reason == 'launch':
|
||||
self.api.stop_on_entry()
|
||||
|
||||
self.api.set_gui_event_loop(py_db, self._options.gui_event_loop)
|
||||
|
||||
def _send_process_event(self, py_db, start_method):
|
||||
argv = getattr(sys, 'argv', [])
|
||||
if len(argv) > 0:
|
||||
|
|
|
|||
|
|
@ -614,9 +614,19 @@ class PyDB(object):
|
|||
self.thread_analyser = None
|
||||
self.asyncio_analyser = None
|
||||
|
||||
# The GUI event loop that's going to run.
|
||||
# Possible values:
|
||||
# matplotlib - Whatever GUI backend matplotlib is using.
|
||||
# 'wx'/'qt'/'none'/... - GUI toolkits that have bulitin support. See pydevd_ipython/inputhook.py:24.
|
||||
# Other - A custom function that'll be imported and run.
|
||||
self._gui_event_loop = 'matplotlib'
|
||||
self._installed_gui_support = False
|
||||
self.gui_in_use = False
|
||||
|
||||
# GUI event loop support in debugger
|
||||
self.activate_gui_function = None
|
||||
|
||||
# matplotlib support in debugger and debug console
|
||||
self._installed_mpl_support = False
|
||||
self.mpl_in_use = False
|
||||
self.mpl_hooks_in_debug_console = False
|
||||
self.mpl_modules_for_patching = {}
|
||||
|
||||
|
|
@ -1522,14 +1532,15 @@ class PyDB(object):
|
|||
for module in dict_keys(self.mpl_modules_for_patching):
|
||||
import_hook_manager.add_module_name(module, self.mpl_modules_for_patching.pop(module))
|
||||
|
||||
def init_matplotlib_support(self):
|
||||
if self._installed_mpl_support:
|
||||
def init_gui_support(self):
|
||||
if self._installed_gui_support:
|
||||
return
|
||||
self._installed_mpl_support = True
|
||||
# prepare debugger for integration with matplotlib GUI event loop
|
||||
self._installed_gui_support = True
|
||||
# prepare debugger for integration with GUI event loop
|
||||
from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot, do_enable_gui
|
||||
from pydev_ipython.inputhook import enable_gui
|
||||
|
||||
# enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console,
|
||||
# enalbe_gui and enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console,
|
||||
# in the debug console we have no interpreter instance with exec_queue, but we run this code in the main
|
||||
# thread and can call it directly.
|
||||
class _MatplotlibHelper:
|
||||
|
|
@ -1545,11 +1556,14 @@ class PyDB(object):
|
|||
from pydev_ipython.inputhook import set_return_control_callback
|
||||
set_return_control_callback(return_control)
|
||||
|
||||
self.mpl_modules_for_patching = {"matplotlib": lambda: activate_matplotlib(do_enable_gui),
|
||||
"matplotlib.pyplot": activate_pyplot,
|
||||
"pylab": activate_pylab }
|
||||
if self._gui_event_loop == 'matplotlib':
|
||||
self.mpl_modules_for_patching = {"matplotlib": lambda: activate_matplotlib(do_enable_gui),
|
||||
"matplotlib.pyplot": activate_pyplot,
|
||||
"pylab": activate_pylab }
|
||||
else:
|
||||
self.activate_gui_function = enable_gui
|
||||
|
||||
def _activate_mpl_if_needed(self):
|
||||
def _activate_gui_if_needed(self):
|
||||
if len(self.mpl_modules_for_patching) > 0:
|
||||
if is_current_thread_main_thread(): # Note that we call only in the main thread.
|
||||
for module in dict_keys(self.mpl_modules_for_patching):
|
||||
|
|
@ -1557,9 +1571,31 @@ class PyDB(object):
|
|||
activate_function = self.mpl_modules_for_patching.pop(module, None)
|
||||
if activate_function is not None:
|
||||
activate_function()
|
||||
self.mpl_in_use = True
|
||||
self.gui_in_use = True
|
||||
|
||||
def _call_mpl_hook(self):
|
||||
if self.activate_gui_function:
|
||||
if is_current_thread_main_thread(): # Only call enable_gui in the main thread.
|
||||
try:
|
||||
# First try to activate builtin GUI event loops.
|
||||
self.activate_gui_function(self._gui_event_loop)
|
||||
self.activate_gui_function = None
|
||||
self.gui_in_use = True
|
||||
except ValueError:
|
||||
# The user requested a custom GUI event loop, try to import it.
|
||||
from importlib import import_module
|
||||
from pydev_ipython.inputhook import set_inputhook
|
||||
try:
|
||||
module_name, inputhook_name = self._gui_event_loop.rsplit('.', 1)
|
||||
module = import_module(module_name)
|
||||
inputhook_function = getattr(module, inputhook_name)
|
||||
set_inputhook(inputhook_function)
|
||||
self.gui_in_use = True
|
||||
except Exception as e:
|
||||
pydev_log.debug("Cannot activate custom GUI event loop {}: {}".format(self._gui_event_loop, e))
|
||||
finally:
|
||||
self.activate_gui_function = None
|
||||
|
||||
def _call_input_hook(self):
|
||||
try:
|
||||
from pydev_ipython.inputhook import get_inputhook
|
||||
inputhook = get_inputhook()
|
||||
|
|
@ -1704,7 +1740,7 @@ class PyDB(object):
|
|||
# add import hooks for matplotlib patches if only debug console was started
|
||||
try:
|
||||
self.init_matplotlib_in_debug_console()
|
||||
self.mpl_in_use = True
|
||||
self.gui_in_use = True
|
||||
except:
|
||||
pydev_log.debug("Matplotlib support in debug console failed", traceback.format_exc())
|
||||
self.mpl_hooks_in_debug_console = True
|
||||
|
|
@ -2006,12 +2042,13 @@ class PyDB(object):
|
|||
keep_suspended = False
|
||||
|
||||
with self._main_lock: # Use lock to check if suspended state changed
|
||||
activate_matplotlib = info.pydev_state == STATE_SUSPEND and not self.pydb_disposed
|
||||
activate_gui = info.pydev_state == STATE_SUSPEND and not self.pydb_disposed
|
||||
|
||||
in_main_thread = is_current_thread_main_thread()
|
||||
if activate_matplotlib and in_main_thread:
|
||||
if activate_gui and in_main_thread:
|
||||
# before every stop check if matplotlib modules were imported inside script code
|
||||
self._activate_mpl_if_needed()
|
||||
# or some GUI event loop needs to be activated
|
||||
self._activate_gui_if_needed()
|
||||
|
||||
while True:
|
||||
with self._main_lock: # Use lock to check if suspended state changed
|
||||
|
|
@ -2019,9 +2056,9 @@ class PyDB(object):
|
|||
# Note: we can't exit here if terminate was requested while a breakpoint was hit.
|
||||
break
|
||||
|
||||
if in_main_thread and self.mpl_in_use:
|
||||
# call input hooks if only matplotlib is in use
|
||||
self._call_mpl_hook()
|
||||
if in_main_thread and self.gui_in_use:
|
||||
# call input hooks if only GUI is in use
|
||||
self._call_input_hook()
|
||||
|
||||
self.process_internal_commands()
|
||||
time.sleep(0.01)
|
||||
|
|
@ -2396,7 +2433,7 @@ class PyDB(object):
|
|||
|
||||
try:
|
||||
if INTERACTIVE_MODE_AVAILABLE:
|
||||
self.init_matplotlib_support()
|
||||
self.init_gui_support()
|
||||
except:
|
||||
pydev_log.exception("Matplotlib support in debugger failed")
|
||||
|
||||
|
|
@ -2441,7 +2478,7 @@ class PyDB(object):
|
|||
return globals
|
||||
|
||||
def wait_for_commands(self, globals):
|
||||
self._activate_mpl_if_needed()
|
||||
self._activate_gui_if_needed()
|
||||
|
||||
thread = threading.current_thread()
|
||||
from _pydevd_bundle import pydevd_frame_utils
|
||||
|
|
@ -2455,9 +2492,9 @@ class PyDB(object):
|
|||
self.writer.add_command(cmd)
|
||||
|
||||
while True:
|
||||
if self.mpl_in_use:
|
||||
# call input hooks if only matplotlib is in use
|
||||
self._call_mpl_hook()
|
||||
if self.gui_in_use:
|
||||
# call input hooks if only GUI is in use
|
||||
self._call_input_hook()
|
||||
self.process_internal_commands()
|
||||
time.sleep(0.01)
|
||||
|
||||
|
|
@ -2849,7 +2886,7 @@ def _locked_settrace(
|
|||
|
||||
try:
|
||||
if INTERACTIVE_MODE_AVAILABLE:
|
||||
py_db.init_matplotlib_support()
|
||||
py_db.init_gui_support()
|
||||
except:
|
||||
pydev_log.exception("Matplotlib support in debugger failed")
|
||||
|
||||
|
|
|
|||
|
|
@ -5210,6 +5210,7 @@ def test_debug_options(case_setup, val):
|
|||
flask=val,
|
||||
stopOnEntry=val,
|
||||
maxExceptionStackFrames=4 if val else 5,
|
||||
guiEventLoop='qt5' if val else 'matplotlib',
|
||||
)
|
||||
json_facade.write_launch(**args)
|
||||
|
||||
|
|
@ -5232,6 +5233,7 @@ def test_debug_options(case_setup, val):
|
|||
'breakOnSystemExitZero': 'break_system_exit_zero',
|
||||
'stopOnEntry': 'stop_on_entry',
|
||||
'maxExceptionStackFrames': 'max_exception_stack_frames',
|
||||
'guiEventLoop': 'gui_event_loop',
|
||||
}
|
||||
|
||||
assert json.loads(output.body.output) == dict((translation[key], val) for key, val in args.items())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue