@@ -100,6 +100,36 @@ def write_message(self, message: dict, incoming: bool):
100100 )
101101
102102
103+ # Debuggers communicate optional feature support.
104+ class DAPDebuggerCapabilities :
105+ def __init__ (self ):
106+ self .supportsConfigurationDoneRequest : bool = False
107+ self .supportsFunctionBreakpoints : bool = False
108+ self .supportsConditionalBreakpoints : bool = False
109+ self .supportsHitConditionalBreakpoints : bool = False
110+ self .supportsEvaluateForHovers : bool = False
111+ self .supportsSetVariable : bool = False
112+ self .supportsStepInTargetsRequest : bool = False
113+ self .supportsModulesRequest : bool = False
114+ self .supportsValueFormattingOptions : bool = False
115+ self .supportsLogPoints : bool = False
116+ self .supportsSetExpression : bool = False
117+ self .supportsDataBreakpoints : bool = False
118+ self .supportsReadMemoryRequest : bool = False
119+ self .supportsWriteMemoryRequest : bool = False
120+ self .supportsDisassembleRequest : bool = False
121+ self .supportsCancelRequest : bool = False
122+ self .supportsSteppingGranularity : bool = False
123+ self .supportsInstructionBreakpoints : bool = False
124+
125+ def update (self , logger : Logger , feature_dict : dict ):
126+ for k , v in feature_dict .items ():
127+ if hasattr (self , k ):
128+ setattr (self , k , v )
129+ else :
130+ logger .warning (f"DAP: Unknown support flag: { k } " )
131+
132+
103133# As DAP does not give us a trivially query-able process, we are responsible for maintaining our own state information,
104134# including what breakpoints are currently set, and whether the debugger is running or stopped.
105135# This class holds all state that is set based on events sent by the debug adapter; most responses are forwarded through
@@ -142,6 +172,9 @@ def __init__(self):
142172 # Map of DAP breakpoint IDs to resolved instruction addresses.
143173 self .bp_addr_map = {}
144174
175+ # DAP features supported by the debugger.
176+ self .capabilities = DAPDebuggerCapabilities ()
177+
145178 def set_response (self , req_id : int , response : dict ):
146179 if len (self .responses ) > req_id :
147180 self .responses [req_id ] = response
@@ -315,6 +348,9 @@ def _handle_message(
315348 and debugger_state .thread is None
316349 ):
317350 debugger_state .thread = event_details ["threadId" ]
351+ elif event_type == "capabilities" :
352+ # Unchanged capabilites may not be included.
353+ debugger_state .capabilities .update (logger , event_details )
318354 # There are many events we do not care about, just skip processing them.
319355 else :
320356 pass
@@ -338,6 +374,12 @@ def _handle_message(
338374 debugger_state .frame_map = [
339375 stackframe ["id" ] for stackframe in message ["body" ]["stackFrames" ]
340376 ]
377+ # The debugger communicates which optional DAP features are
378+ # supported in its initalize response.
379+ if message ["command" ] == "initialize" and message ["success" ] == True :
380+ body = message .get ("body" )
381+ if body :
382+ debugger_state .capabilities .update (logger , body )
341383
342384 def _colorize_dap_message (message : dict ) -> dict :
343385 colorized_message = copy .deepcopy (message )
0 commit comments