|
"""Debugger implementation for the IPython kernel.""" |
|
import os |
|
import re |
|
import sys |
|
import typing as t |
|
from pathlib import Path |
|
|
|
import zmq |
|
from IPython.core.getipython import get_ipython |
|
from IPython.core.inputtransformer2 import leading_empty_lines |
|
from tornado.locks import Event |
|
from tornado.queues import Queue |
|
from zmq.utils import jsonapi |
|
|
|
try: |
|
from jupyter_client.jsonutil import json_default |
|
except ImportError: |
|
from jupyter_client.jsonutil import date_default as json_default |
|
|
|
from .compiler import get_file_name, get_tmp_directory, get_tmp_hash_seed |
|
|
|
try: |
|
|
|
from debugpy.server import api |
|
|
|
from _pydevd_bundle import pydevd_frame_utils |
|
from _pydevd_bundle.pydevd_suspended_frames import ( |
|
SuspendedFramesManager, |
|
_FramesTracker, |
|
) |
|
|
|
_is_debugpy_available = True |
|
except ImportError: |
|
_is_debugpy_available = False |
|
except Exception as e: |
|
|
|
|
|
if e.__class__.__name__ == "DebuggerInitializationError": |
|
_is_debugpy_available = False |
|
else: |
|
raise e |
|
|
|
|
|
|
|
ROUTING_ID = getattr(zmq, "ROUTING_ID", None) or zmq.IDENTITY |
|
|
|
|
|
class _FakeCode: |
|
"""Fake code class.""" |
|
|
|
def __init__(self, co_filename, co_name): |
|
"""Init.""" |
|
self.co_filename = co_filename |
|
self.co_name = co_name |
|
|
|
|
|
class _FakeFrame: |
|
"""Fake frame class.""" |
|
|
|
def __init__(self, f_code, f_globals, f_locals): |
|
"""Init.""" |
|
self.f_code = f_code |
|
self.f_globals = f_globals |
|
self.f_locals = f_locals |
|
self.f_back = None |
|
|
|
|
|
class _DummyPyDB: |
|
"""Fake PyDb class.""" |
|
|
|
def __init__(self): |
|
"""Init.""" |
|
from _pydevd_bundle.pydevd_api import PyDevdAPI |
|
|
|
self.variable_presentation = PyDevdAPI.VariablePresentation() |
|
|
|
|
|
class VariableExplorer: |
|
"""A variable explorer.""" |
|
|
|
def __init__(self): |
|
"""Initialize the explorer.""" |
|
self.suspended_frame_manager = SuspendedFramesManager() |
|
self.py_db = _DummyPyDB() |
|
self.tracker = _FramesTracker(self.suspended_frame_manager, self.py_db) |
|
self.frame = None |
|
|
|
def track(self): |
|
"""Start tracking.""" |
|
var = get_ipython().user_ns |
|
self.frame = _FakeFrame(_FakeCode("<module>", get_file_name("sys._getframe()")), var, var) |
|
self.tracker.track("thread1", pydevd_frame_utils.create_frames_list_from_frame(self.frame)) |
|
|
|
def untrack_all(self): |
|
"""Stop tracking.""" |
|
self.tracker.untrack_all() |
|
|
|
def get_children_variables(self, variable_ref=None): |
|
"""Get the child variables for a variable reference.""" |
|
var_ref = variable_ref |
|
if not var_ref: |
|
var_ref = id(self.frame) |
|
variables = self.suspended_frame_manager.get_variable(var_ref) |
|
return [x.get_var_data() for x in variables.get_children_variables()] |
|
|
|
|
|
class DebugpyMessageQueue: |
|
"""A debugpy message queue.""" |
|
|
|
HEADER = "Content-Length: " |
|
HEADER_LENGTH = 16 |
|
SEPARATOR = "\r\n\r\n" |
|
SEPARATOR_LENGTH = 4 |
|
|
|
def __init__(self, event_callback, log): |
|
"""Init the queue.""" |
|
self.tcp_buffer = "" |
|
self._reset_tcp_pos() |
|
self.event_callback = event_callback |
|
self.message_queue: Queue[t.Any] = Queue() |
|
self.log = log |
|
|
|
def _reset_tcp_pos(self): |
|
self.header_pos = -1 |
|
self.separator_pos = -1 |
|
self.message_size = 0 |
|
self.message_pos = -1 |
|
|
|
def _put_message(self, raw_msg): |
|
self.log.debug("QUEUE - _put_message:") |
|
msg = t.cast(t.Dict[str, t.Any], jsonapi.loads(raw_msg)) |
|
if msg["type"] == "event": |
|
self.log.debug("QUEUE - received event:") |
|
self.log.debug(msg) |
|
self.event_callback(msg) |
|
else: |
|
self.log.debug("QUEUE - put message:") |
|
self.log.debug(msg) |
|
self.message_queue.put_nowait(msg) |
|
|
|
def put_tcp_frame(self, frame): |
|
"""Put a tcp frame in the queue.""" |
|
self.tcp_buffer += frame |
|
|
|
self.log.debug("QUEUE - received frame") |
|
while True: |
|
|
|
if self.header_pos == -1: |
|
self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER) |
|
if self.header_pos == -1: |
|
return |
|
|
|
self.log.debug("QUEUE - found header at pos %i", self.header_pos) |
|
|
|
|
|
if self.separator_pos == -1: |
|
hint = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH |
|
self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) |
|
if self.separator_pos == -1: |
|
return |
|
|
|
self.log.debug("QUEUE - found separator at pos %i", self.separator_pos) |
|
|
|
if self.message_pos == -1: |
|
size_pos = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH |
|
self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH |
|
self.message_size = int(self.tcp_buffer[size_pos : self.separator_pos]) |
|
|
|
self.log.debug("QUEUE - found message at pos %i", self.message_pos) |
|
self.log.debug("QUEUE - message size is %i", self.message_size) |
|
|
|
if len(self.tcp_buffer) - self.message_pos < self.message_size: |
|
return |
|
|
|
self._put_message( |
|
self.tcp_buffer[self.message_pos : self.message_pos + self.message_size] |
|
) |
|
if len(self.tcp_buffer) - self.message_pos == self.message_size: |
|
self.log.debug("QUEUE - resetting tcp_buffer") |
|
self.tcp_buffer = "" |
|
self._reset_tcp_pos() |
|
return |
|
|
|
self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size :] |
|
self.log.debug("QUEUE - slicing tcp_buffer: %s", self.tcp_buffer) |
|
self._reset_tcp_pos() |
|
|
|
async def get_message(self): |
|
"""Get a message from the queue.""" |
|
return await self.message_queue.get() |
|
|
|
|
|
class DebugpyClient: |
|
"""A client for debugpy.""" |
|
|
|
def __init__(self, log, debugpy_stream, event_callback): |
|
"""Initialize the client.""" |
|
self.log = log |
|
self.debugpy_stream = debugpy_stream |
|
self.event_callback = event_callback |
|
self.message_queue = DebugpyMessageQueue(self._forward_event, self.log) |
|
self.debugpy_host = "127.0.0.1" |
|
self.debugpy_port = -1 |
|
self.routing_id = None |
|
self.wait_for_attach = True |
|
self.init_event = Event() |
|
self.init_event_seq = -1 |
|
|
|
def _get_endpoint(self): |
|
host, port = self.get_host_port() |
|
return "tcp://" + host + ":" + str(port) |
|
|
|
def _forward_event(self, msg): |
|
if msg["event"] == "initialized": |
|
self.init_event.set() |
|
self.init_event_seq = msg["seq"] |
|
self.event_callback(msg) |
|
|
|
def _send_request(self, msg): |
|
if self.routing_id is None: |
|
self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) |
|
content = jsonapi.dumps( |
|
msg, |
|
default=json_default, |
|
ensure_ascii=False, |
|
allow_nan=False, |
|
) |
|
content_length = str(len(content)) |
|
buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode( |
|
"ascii" |
|
) |
|
buf += content |
|
self.log.debug("DEBUGPYCLIENT:") |
|
self.log.debug(self.routing_id) |
|
self.log.debug(buf) |
|
self.debugpy_stream.send_multipart((self.routing_id, buf)) |
|
|
|
async def _wait_for_response(self): |
|
|
|
|
|
|
|
return await self.message_queue.get_message() |
|
|
|
async def _handle_init_sequence(self): |
|
|
|
await self.init_event.wait() |
|
|
|
|
|
configurationDone = { |
|
"type": "request", |
|
"seq": int(self.init_event_seq) + 1, |
|
"command": "configurationDone", |
|
} |
|
self._send_request(configurationDone) |
|
|
|
|
|
await self._wait_for_response() |
|
|
|
|
|
return await self._wait_for_response() |
|
|
|
def get_host_port(self): |
|
"""Get the host debugpy port.""" |
|
if self.debugpy_port == -1: |
|
socket = self.debugpy_stream.socket |
|
socket.bind_to_random_port("tcp://" + self.debugpy_host) |
|
self.endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode("utf-8") |
|
socket.unbind(self.endpoint) |
|
index = self.endpoint.rfind(":") |
|
self.debugpy_port = self.endpoint[index + 1 :] |
|
return self.debugpy_host, self.debugpy_port |
|
|
|
def connect_tcp_socket(self): |
|
"""Connect to the tcp socket.""" |
|
self.debugpy_stream.socket.connect(self._get_endpoint()) |
|
self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) |
|
|
|
def disconnect_tcp_socket(self): |
|
"""Disconnect from the tcp socket.""" |
|
self.debugpy_stream.socket.disconnect(self._get_endpoint()) |
|
self.routing_id = None |
|
self.init_event = Event() |
|
self.init_event_seq = -1 |
|
self.wait_for_attach = True |
|
|
|
def receive_dap_frame(self, frame): |
|
"""Receive a dap frame.""" |
|
self.message_queue.put_tcp_frame(frame) |
|
|
|
async def send_dap_request(self, msg): |
|
"""Send a dap request.""" |
|
self._send_request(msg) |
|
if self.wait_for_attach and msg["command"] == "attach": |
|
rep = await self._handle_init_sequence() |
|
self.wait_for_attach = False |
|
return rep |
|
|
|
rep = await self._wait_for_response() |
|
self.log.debug("DEBUGPYCLIENT - returning:") |
|
self.log.debug(rep) |
|
return rep |
|
|
|
|
|
class Debugger: |
|
"""The debugger class.""" |
|
|
|
|
|
started_debug_msg_types = [ |
|
"dumpCell", |
|
"setBreakpoints", |
|
"source", |
|
"stackTrace", |
|
"variables", |
|
"attach", |
|
"configurationDone", |
|
] |
|
|
|
|
|
static_debug_msg_types = [ |
|
"debugInfo", |
|
"inspectVariables", |
|
"richInspectVariables", |
|
"modules", |
|
"copyToGlobals", |
|
] |
|
|
|
def __init__( |
|
self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True |
|
): |
|
"""Initialize the debugger.""" |
|
self.log = log |
|
self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) |
|
self.shell_socket = shell_socket |
|
self.session = session |
|
self.is_started = False |
|
self.event_callback = event_callback |
|
self.just_my_code = just_my_code |
|
self.stopped_queue: Queue[t.Any] = Queue() |
|
|
|
self.started_debug_handlers = {} |
|
for msg_type in Debugger.started_debug_msg_types: |
|
self.started_debug_handlers[msg_type] = getattr(self, msg_type) |
|
|
|
self.static_debug_handlers = {} |
|
for msg_type in Debugger.static_debug_msg_types: |
|
self.static_debug_handlers[msg_type] = getattr(self, msg_type) |
|
|
|
self.breakpoint_list = {} |
|
self.stopped_threads = set() |
|
|
|
self.debugpy_initialized = False |
|
self._removed_cleanup = {} |
|
|
|
self.debugpy_host = "127.0.0.1" |
|
self.debugpy_port = 0 |
|
self.endpoint = None |
|
|
|
self.variable_explorer = VariableExplorer() |
|
|
|
def _handle_event(self, msg): |
|
if msg["event"] == "stopped": |
|
if msg["body"]["allThreadsStopped"]: |
|
self.stopped_queue.put_nowait(msg) |
|
|
|
return |
|
self.stopped_threads.add(msg["body"]["threadId"]) |
|
self.event_callback(msg) |
|
elif msg["event"] == "continued": |
|
if msg["body"]["allThreadsContinued"]: |
|
self.stopped_threads = set() |
|
else: |
|
self.stopped_threads.remove(msg["body"]["threadId"]) |
|
self.event_callback(msg) |
|
else: |
|
self.event_callback(msg) |
|
|
|
async def _forward_message(self, msg): |
|
return await self.debugpy_client.send_dap_request(msg) |
|
|
|
def _build_variables_response(self, request, variables): |
|
var_list = [var for var in variables if self.accept_variable(var["name"])] |
|
return { |
|
"seq": request["seq"], |
|
"type": "response", |
|
"request_seq": request["seq"], |
|
"success": True, |
|
"command": request["command"], |
|
"body": {"variables": var_list}, |
|
} |
|
|
|
def _accept_stopped_thread(self, thread_name): |
|
|
|
|
|
forbid_list = ["IPythonHistorySavingThread", "Thread-2", "Thread-3", "Thread-4"] |
|
return thread_name not in forbid_list |
|
|
|
async def handle_stopped_event(self): |
|
"""Handle a stopped event.""" |
|
|
|
|
|
event = await self.stopped_queue.get() |
|
req = {"seq": event["seq"] + 1, "type": "request", "command": "threads"} |
|
rep = await self._forward_message(req) |
|
for thread in rep["body"]["threads"]: |
|
if self._accept_stopped_thread(thread["name"]): |
|
self.stopped_threads.add(thread["id"]) |
|
self.event_callback(event) |
|
|
|
@property |
|
def tcp_client(self): |
|
return self.debugpy_client |
|
|
|
def start(self): |
|
"""Start the debugger.""" |
|
if not self.debugpy_initialized: |
|
tmp_dir = get_tmp_directory() |
|
if not Path(tmp_dir).exists(): |
|
Path(tmp_dir).mkdir(parents=True) |
|
host, port = self.debugpy_client.get_host_port() |
|
code = "import debugpy;" |
|
code += 'debugpy.listen(("' + host + '",' + port + "))" |
|
content = {"code": code, "silent": True} |
|
self.session.send( |
|
self.shell_socket, |
|
"execute_request", |
|
content, |
|
None, |
|
(self.shell_socket.getsockopt(ROUTING_ID)), |
|
) |
|
|
|
ident, msg = self.session.recv(self.shell_socket, mode=0) |
|
self.debugpy_initialized = msg["content"]["status"] == "ok" |
|
|
|
|
|
cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms |
|
if leading_empty_lines in cleanup_transforms: |
|
index = cleanup_transforms.index(leading_empty_lines) |
|
self._removed_cleanup[index] = cleanup_transforms.pop(index) |
|
|
|
self.debugpy_client.connect_tcp_socket() |
|
return self.debugpy_initialized |
|
|
|
def stop(self): |
|
"""Stop the debugger.""" |
|
self.debugpy_client.disconnect_tcp_socket() |
|
|
|
|
|
cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms |
|
for index in sorted(self._removed_cleanup): |
|
func = self._removed_cleanup.pop(index) |
|
cleanup_transforms.insert(index, func) |
|
|
|
async def dumpCell(self, message): |
|
"""Handle a dump cell message.""" |
|
code = message["arguments"]["code"] |
|
file_name = get_file_name(code) |
|
|
|
with open(file_name, "w", encoding="utf-8") as f: |
|
f.write(code) |
|
|
|
return { |
|
"type": "response", |
|
"request_seq": message["seq"], |
|
"success": True, |
|
"command": message["command"], |
|
"body": {"sourcePath": file_name}, |
|
} |
|
|
|
async def setBreakpoints(self, message): |
|
"""Handle a set breakpoints message.""" |
|
source = message["arguments"]["source"]["path"] |
|
self.breakpoint_list[source] = message["arguments"]["breakpoints"] |
|
message_response = await self._forward_message(message) |
|
|
|
|
|
if message_response.get("success"): |
|
self.breakpoint_list[source] = [ |
|
{"line": breakpoint["line"]} |
|
for breakpoint in message_response["body"]["breakpoints"] |
|
] |
|
return message_response |
|
|
|
async def source(self, message): |
|
"""Handle a source message.""" |
|
reply = {"type": "response", "request_seq": message["seq"], "command": message["command"]} |
|
source_path = message["arguments"]["source"]["path"] |
|
if Path(source_path).is_file(): |
|
with open(source_path, encoding="utf-8") as f: |
|
reply["success"] = True |
|
reply["body"] = {"content": f.read()} |
|
else: |
|
reply["success"] = False |
|
reply["message"] = "source unavailable" |
|
reply["body"] = {} |
|
|
|
return reply |
|
|
|
async def stackTrace(self, message): |
|
"""Handle a stack trace message.""" |
|
reply = await self._forward_message(message) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try: |
|
sf_list = reply["body"]["stackFrames"] |
|
module_idx = len(sf_list) - next( |
|
i for i, v in enumerate(reversed(sf_list), 1) if v["name"] == "<module>" and i != 1 |
|
) |
|
reply["body"]["stackFrames"] = reply["body"]["stackFrames"][: module_idx + 1] |
|
except StopIteration: |
|
pass |
|
return reply |
|
|
|
def accept_variable(self, variable_name): |
|
"""Accept a variable by name.""" |
|
forbid_list = [ |
|
"__name__", |
|
"__doc__", |
|
"__package__", |
|
"__loader__", |
|
"__spec__", |
|
"__annotations__", |
|
"__builtins__", |
|
"__builtin__", |
|
"__display__", |
|
"get_ipython", |
|
"debugpy", |
|
"exit", |
|
"quit", |
|
"In", |
|
"Out", |
|
"_oh", |
|
"_dh", |
|
"_", |
|
"__", |
|
"___", |
|
] |
|
cond = variable_name not in forbid_list |
|
cond = cond and not bool(re.search(r"^_\d", variable_name)) |
|
cond = cond and variable_name[0:2] != "_i" |
|
return cond |
|
|
|
async def variables(self, message): |
|
"""Handle a variables message.""" |
|
reply = {} |
|
if not self.stopped_threads: |
|
variables = self.variable_explorer.get_children_variables( |
|
message["arguments"]["variablesReference"] |
|
) |
|
return self._build_variables_response(message, variables) |
|
|
|
reply = await self._forward_message(message) |
|
|
|
reply["body"]["variables"] = [ |
|
var for var in reply["body"]["variables"] if self.accept_variable(var["name"]) |
|
] |
|
return reply |
|
|
|
async def attach(self, message): |
|
"""Handle an attach message.""" |
|
host, port = self.debugpy_client.get_host_port() |
|
message["arguments"]["connect"] = {"host": host, "port": port} |
|
message["arguments"]["logToFile"] = True |
|
|
|
|
|
|
|
|
|
if not self.just_my_code: |
|
message["arguments"]["debugOptions"] = ["DebugStdLib"] |
|
return await self._forward_message(message) |
|
|
|
async def configurationDone(self, message): |
|
"""Handle a configuration done message.""" |
|
return { |
|
"seq": message["seq"], |
|
"type": "response", |
|
"request_seq": message["seq"], |
|
"success": True, |
|
"command": message["command"], |
|
} |
|
|
|
async def debugInfo(self, message): |
|
"""Handle a debug info message.""" |
|
breakpoint_list = [] |
|
for key, value in self.breakpoint_list.items(): |
|
breakpoint_list.append({"source": key, "breakpoints": value}) |
|
return { |
|
"type": "response", |
|
"request_seq": message["seq"], |
|
"success": True, |
|
"command": message["command"], |
|
"body": { |
|
"isStarted": self.is_started, |
|
"hashMethod": "Murmur2", |
|
"hashSeed": get_tmp_hash_seed(), |
|
"tmpFilePrefix": get_tmp_directory() + os.sep, |
|
"tmpFileSuffix": ".py", |
|
"breakpoints": breakpoint_list, |
|
"stoppedThreads": list(self.stopped_threads), |
|
"richRendering": True, |
|
"exceptionPaths": ["Python Exceptions"], |
|
"copyToGlobals": True, |
|
}, |
|
} |
|
|
|
async def inspectVariables(self, message): |
|
"""Handle an inspect variables message.""" |
|
self.variable_explorer.untrack_all() |
|
|
|
|
|
|
|
self.variable_explorer = VariableExplorer() |
|
self.variable_explorer.track() |
|
variables = self.variable_explorer.get_children_variables() |
|
return self._build_variables_response(message, variables) |
|
|
|
async def richInspectVariables(self, message): |
|
"""Handle a rich inspect variables message.""" |
|
reply = { |
|
"type": "response", |
|
"sequence_seq": message["seq"], |
|
"success": False, |
|
"command": message["command"], |
|
} |
|
|
|
var_name = message["arguments"]["variableName"] |
|
valid_name = str.isidentifier(var_name) |
|
if not valid_name: |
|
reply["body"] = {"data": {}, "metadata": {}} |
|
if var_name == "special variables" or var_name == "function variables": |
|
reply["success"] = True |
|
return reply |
|
|
|
repr_data = {} |
|
repr_metadata = {} |
|
if not self.stopped_threads: |
|
|
|
|
|
result = get_ipython().user_expressions({var_name: var_name})[var_name] |
|
if result.get("status", "error") == "ok": |
|
repr_data = result.get("data", {}) |
|
repr_metadata = result.get("metadata", {}) |
|
else: |
|
|
|
|
|
code = f"get_ipython().display_formatter.format({var_name})" |
|
frame_id = message["arguments"]["frameId"] |
|
seq = message["seq"] |
|
reply = await self._forward_message( |
|
{ |
|
"type": "request", |
|
"command": "evaluate", |
|
"seq": seq + 1, |
|
"arguments": {"expression": code, "frameId": frame_id, "context": "clipboard"}, |
|
} |
|
) |
|
if reply["success"]: |
|
repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) |
|
|
|
body = { |
|
"data": repr_data, |
|
"metadata": {k: v for k, v in repr_metadata.items() if k in repr_data}, |
|
} |
|
|
|
reply["body"] = body |
|
reply["success"] = True |
|
return reply |
|
|
|
async def copyToGlobals(self, message): |
|
dst_var_name = message["arguments"]["dstVariableName"] |
|
src_var_name = message["arguments"]["srcVariableName"] |
|
src_frame_id = message["arguments"]["srcFrameId"] |
|
|
|
expression = f"globals()['{dst_var_name}']" |
|
seq = message["seq"] |
|
return await self._forward_message( |
|
{ |
|
"type": "request", |
|
"command": "setExpression", |
|
"seq": seq + 1, |
|
"arguments": { |
|
"expression": expression, |
|
"value": src_var_name, |
|
"frameId": src_frame_id, |
|
}, |
|
} |
|
) |
|
|
|
async def modules(self, message): |
|
"""Handle a modules message.""" |
|
modules = list(sys.modules.values()) |
|
startModule = message.get("startModule", 0) |
|
moduleCount = message.get("moduleCount", len(modules)) |
|
mods = [] |
|
for i in range(startModule, moduleCount): |
|
module = modules[i] |
|
filename = getattr(getattr(module, "__spec__", None), "origin", None) |
|
if filename and filename.endswith(".py"): |
|
mods.append({"id": i, "name": module.__name__, "path": filename}) |
|
|
|
return {"body": {"modules": mods, "totalModules": len(modules)}} |
|
|
|
async def process_request(self, message): |
|
"""Process a request.""" |
|
reply = {} |
|
|
|
if message["command"] == "initialize": |
|
if self.is_started: |
|
self.log.info("The debugger has already started") |
|
else: |
|
self.is_started = self.start() |
|
if self.is_started: |
|
self.log.info("The debugger has started") |
|
else: |
|
reply = { |
|
"command": "initialize", |
|
"request_seq": message["seq"], |
|
"seq": 3, |
|
"success": False, |
|
"type": "response", |
|
} |
|
|
|
handler = self.static_debug_handlers.get(message["command"], None) |
|
if handler is not None: |
|
reply = await handler(message) |
|
elif self.is_started: |
|
handler = self.started_debug_handlers.get(message["command"], None) |
|
if handler is not None: |
|
reply = await handler(message) |
|
else: |
|
reply = await self._forward_message(message) |
|
|
|
if message["command"] == "disconnect": |
|
self.stop() |
|
self.breakpoint_list = {} |
|
self.stopped_threads = set() |
|
self.is_started = False |
|
self.log.info("The debugger has stopped") |
|
|
|
return reply |
|
|