From 3223c82609d5870477a5f92fe20c9b7392e48e80 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 22 Apr 2026 01:00:34 +0200 Subject: [PATCH] Add JetBrainsDebugTool --- .../jetbrains/jetbrains_plugin_client.py | 26 ++++++ .../config/internal_modes/jetbrains.yml | 1 + src/serena/tools/jetbrains_tools.py | 79 +++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index d65925ea..61a6a563 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -633,6 +633,32 @@ class JetBrainsPluginClient(ToStringMixin): self._postprocess_symbol_collection_response(symbol_collection) return symbol_collection + def debug_eval(self, session_key: str, expression: str) -> dict[str, Any]: + """ + Evaluates a Groovy expression in the persistent debug REPL. + + :param session_key: the session key identifying the REPL instance + :param expression: the Groovy expression to evaluate + :return: the response containing sessionKey and result + """ + request_data = { + "sessionKey": session_key, + "expression": expression, + } + return self._make_request("POST", "/debugEval", request_data) + + def debug_reset(self, session_key: str) -> dict[str, Any]: + """ + Resets the debug REPL for the given session key, clearing all state. + + :param session_key: the session key identifying the REPL instance to reset + :return: the status response + """ + request_data = { + "sessionKey": session_key, + } + return self._make_request("POST", "/debugReset", request_data) + def close(self) -> None: self._session.close() diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index a08b189f..7f76271f 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -23,3 +23,4 @@ included_optional_tools: - jet_brains_type_hierarchy - jet_brains_rename - jet_brains_safe_delete + - jet_brains_debug diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 00d0adbd..dc17009f 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -565,3 +565,82 @@ class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): rename_in_text_occurrences=rename_in_text_occurrences, ) return self._to_json(result) + + +class JetBrainsDebugTool(Tool, ToolMarkerBeta): + """ + Evaluates Groovy/Java expressions in a persistent debug REPL connected to the JetBrains IDE. + Provides full debugger control: run configs, breakpoints, stepping, inspection, and evaluation. + """ + + def apply( + self, + expression: str, + session_key: str = "default", + ) -> str: + """ + Evaluate a Groovy/Java expression in the persistent debug REPL. State (variables, objects) + persists across calls within the same session_key. + + Pre-bound variables: ``debug`` (DebuggerFacade), ``project`` (IntelliJ Project). + + **Run configurations**:: + + debug.listRunConfigs() + debug.startDebug("MyTestRun") # returns a DebugSession + debug.runConfig("MyApp") # non-debug run, fire-and-forget + + **Session discovery**:: + + debug.sessions() + debug.currentSession() + debug.session(id) + + **Breakpoints** (project-global, set before or during debugging):: + + debug.addBreakpoint(file, line) + debug.addBreakpoint(file, line, condition) + debug.addLogpoint(file, line, logExpression) + debug.listBreakpoints() + debug.removeBreakpointAt(file, line) + + **Execution control** (on a DebugSession, e.g. ``session``):: + + session.stepOver() / .stepInto() / .stepOut() + session.resume() / .pause() / .stop() + session.runToLine(file, line) + + **Wait for pause** (blocks until the session pauses or times out):: + + session.waitForPause(timeoutSeconds) + session.stepOverAndWait(timeoutSeconds) # step + wait in one call + session.resumeAndWait(timeoutSeconds) + + **Inspection**:: + + session.status() # location + source + variables + stack in one call + session.variables() # all locals/args + session.variable(name) # single variable + session.frames() / .frame() # stack trace / current frame + session.sourceContext(lines) # source around current line + session.threads() + + **Evaluation & modification**:: + + session.eval("expr") # evaluate in current frame + session.setVar("x", "42") # set variable via assignment + + **Example**:: + + session = debug.startDebug("MyTest") + debug.addBreakpoint("src/main/java/Foo.java", 42) + session.waitForPause(30) + session.status() + + :param expression: a Groovy/Java expression to evaluate in the REPL + :param session_key: identifies the REPL instance. State persists across calls with the same key. + :return: the string representation of the expression's result + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response = client.debug_eval(session_key=session_key, expression=expression) + return response.get("result", str(response))