@@ -102,6 +102,35 @@ def write_message(self, message: dict, incoming: bool):
102102 )
103103
104104
105+ # Debuggers communicate optional feature support.
106+ class DAPDebuggerCapabilities :
107+ def __init__ (self ):
108+ self .supportsConfigurationDoneRequest : bool = False
109+ self .supportsFunctionBreakpoints : bool = False
110+ self .supportsConditionalBreakpoints : bool = False
111+ self .supportsHitConditionalBreakpoints : bool = False
112+ self .supportsEvaluateForHovers : bool = False
113+ self .supportsSetVariable : bool = False
114+ self .supportsStepInTargetsRequest : bool = False
115+ self .supportsModulesRequest : bool = False
116+ self .supportsValueFormattingOptions : bool = False
117+ self .supportsLogPoints : bool = False
118+ self .supportsSetExpression : bool = False
119+ self .supportsDataBreakpoints : bool = False
120+ self .supportsReadMemoryRequest : bool = False
121+ self .supportsWriteMemoryRequest : bool = False
122+ self .supportsDisassembleRequest : bool = False
123+ self .supportsCancelRequest : bool = False
124+ self .supportsSteppingGranularity : bool = False
125+ self .supportsInstructionBreakpoints : bool = False
126+
127+ def update (self , logger : Logger , feature_dict : dict ):
128+ for k , v in feature_dict .items ():
129+ if hasattr (self , k ):
130+ setattr (self , k , v )
131+ else :
132+ logger .warning (f"DAP: Unknown support flag: { k } " )
133+
105134# As DAP does not give us a trivially query-able process, we are responsible for maintaining our own state information,
106135# including what breakpoints are currently set, and whether the debugger is running or stopped.
107136# This class holds all state that is set based on events sent by the debug adapter; most responses are forwarded through
@@ -144,6 +173,9 @@ def __init__(self):
144173 # Map of DAP breakpoint IDs to resolved instruction addresses.
145174 self .bp_addr_map : dict [int , str ] = {}
146175
176+ # DAP features supported by the debugger.
177+ self .capabilities = DAPDebuggerCapabilities ()
178+
147179 def set_response (self , req_id : int , response : dict ):
148180 if len (self .responses ) > req_id :
149181 self .responses [req_id ] = response
@@ -320,6 +352,9 @@ def _handle_message(
320352 and debugger_state .thread is None
321353 ):
322354 debugger_state .thread = event_details ["threadId" ]
355+ case "capabilities" :
356+ # Unchanged capabilites may not be included.
357+ debugger_state .capabilities .update (logger , event_details )
323358 # There are many events we do not care about, just skip processing them.
324359 case _:
325360 pass
@@ -343,6 +378,12 @@ def _handle_message(
343378 debugger_state .frame_map = [
344379 stackframe ["id" ] for stackframe in message ["body" ]["stackFrames" ]
345380 ]
381+ # The debugger communicates which optional DAP features are
382+ # supported in its initalize response.
383+ if message ["command" ] == "initialize" and message ["success" ] == True :
384+ body = message .get ("body" )
385+ if body :
386+ debugger_state .capabilities .update (logger , body )
346387
347388 def _colorize_dap_message (message : dict ) -> dict :
348389 colorized_message = copy .deepcopy (message )
0 commit comments