Wire REPL routing into DapDebugSession

Route `evaluate` requests by context:
- `repl` context → DSL parser → command dispatch (help/run)
- All other contexts (watch, hover, etc.) → expression evaluation

If REPL input doesn't match any DSL command, it falls through to
expression evaluation so the Debug Console also works for ad-hoc
`github.repository`-style queries.

Changes:
- HandleEvaluateAsync replaces the sync HandleEvaluate
- HandleReplInputAsync parses input through DapReplParser.TryParse
- DispatchReplCommandAsync dispatches HelpCommand and RunCommand
- DapReplExecutor is created alongside the DAP server reference
- Remove vestigial `await Task.CompletedTask` from HandleMessageAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Francesco Renzi
2026-03-12 11:03:10 +00:00
committed by GitHub
parent 735dd69833
commit 165fb90296

View File

@@ -66,6 +66,9 @@ namespace GitHub.Runner.Worker.Dap
// Scope/variable inspection provider — reusable by future DAP features
private DapVariableProvider _variableProvider;
// REPL command executor for run() commands
private DapReplExecutor _replExecutor;
public bool IsActive =>
_state == DapSessionState.Ready ||
_state == DapSessionState.Paused ||
@@ -83,6 +86,7 @@ namespace GitHub.Runner.Worker.Dap
public void SetDapServer(IDapServer server)
{
_server = server;
_replExecutor = new DapReplExecutor(HostContext, server);
Trace.Info("DAP server reference set");
}
@@ -114,23 +118,30 @@ namespace GitHub.Runner.Worker.Dap
Trace.Info($"Handling DAP request: {request.Command}");
var response = request.Command switch
Response response;
if (request.Command == "evaluate")
{
"initialize" => HandleInitialize(request),
"attach" => HandleAttach(request),
"configurationDone" => HandleConfigurationDone(request),
"disconnect" => HandleDisconnect(request),
"threads" => HandleThreads(request),
"stackTrace" => HandleStackTrace(request),
"scopes" => HandleScopes(request),
"variables" => HandleVariables(request),
"evaluate" => HandleEvaluate(request),
"continue" => HandleContinue(request),
"next" => HandleNext(request),
"setBreakpoints" => HandleSetBreakpoints(request),
"setExceptionBreakpoints" => HandleSetExceptionBreakpoints(request),
_ => CreateResponse(request, false, $"Unsupported command: {request.Command}", body: null)
};
response = await HandleEvaluateAsync(request, cancellationToken);
}
else
{
response = request.Command switch
{
"initialize" => HandleInitialize(request),
"attach" => HandleAttach(request),
"configurationDone" => HandleConfigurationDone(request),
"disconnect" => HandleDisconnect(request),
"threads" => HandleThreads(request),
"stackTrace" => HandleStackTrace(request),
"scopes" => HandleScopes(request),
"variables" => HandleVariables(request),
"continue" => HandleContinue(request),
"next" => HandleNext(request),
"setBreakpoints" => HandleSetBreakpoints(request),
"setExceptionBreakpoints" => HandleSetExceptionBreakpoints(request),
_ => CreateResponse(request, false, $"Unsupported command: {request.Command}", body: null)
};
}
response.RequestSeq = request.Seq;
response.Command = request.Command;
@@ -148,8 +159,6 @@ namespace GitHub.Runner.Worker.Dap
_server?.SendResponse(errorResponse);
}
}
await Task.CompletedTask;
}
#endregion
@@ -367,18 +376,94 @@ namespace GitHub.Runner.Worker.Dap
});
}
private Response HandleEvaluate(Request request)
private async Task<Response> HandleEvaluateAsync(Request request, CancellationToken cancellationToken)
{
var args = request.Arguments?.ToObject<EvaluateArguments>();
var expression = args?.Expression ?? string.Empty;
var frameId = args?.FrameId ?? CurrentFrameId;
var evalContext = args?.Context ?? "hover";
Trace.Info($"Evaluate request: '{expression}' (frame: {frameId}, context: {args?.Context ?? "unknown"})");
Trace.Info($"Evaluate request: '{expression}' (frame: {frameId}, context: {evalContext})");
// REPL context → route through the DSL dispatcher
if (string.Equals(evalContext, "repl", StringComparison.OrdinalIgnoreCase))
{
var result = await HandleReplInputAsync(expression, frameId, cancellationToken);
return CreateResponse(request, true, body: result);
}
// Watch/hover/variables/clipboard → expression evaluation only
var context = GetExecutionContextForFrame(frameId);
var result = _variableProvider.EvaluateExpression(expression, context);
var evalResult = _variableProvider.EvaluateExpression(expression, context);
return CreateResponse(request, true, body: evalResult);
}
return CreateResponse(request, true, body: result);
/// <summary>
/// Routes REPL input through the DSL parser. If the input matches a
/// known command it is dispatched; otherwise it falls through to
/// expression evaluation.
/// </summary>
private async Task<EvaluateResponseBody> HandleReplInputAsync(
string input,
int frameId,
CancellationToken cancellationToken)
{
// Try to parse as a DSL command
var command = DapReplParser.TryParse(input, out var parseError);
if (parseError != null)
{
return new EvaluateResponseBody
{
Result = parseError,
Type = "error",
VariablesReference = 0
};
}
if (command != null)
{
return await DispatchReplCommandAsync(command, frameId, cancellationToken);
}
// Not a DSL command → evaluate as a GitHub Actions expression
// (this lets the REPL console also work for ad-hoc expression queries)
var context = GetExecutionContextForFrame(frameId);
return _variableProvider.EvaluateExpression(input, context);
}
private async Task<EvaluateResponseBody> DispatchReplCommandAsync(
DapReplCommand command,
int frameId,
CancellationToken cancellationToken)
{
switch (command)
{
case HelpCommand help:
var helpText = string.IsNullOrEmpty(help.Topic)
? DapReplParser.GetGeneralHelp()
: help.Topic.Equals("run", StringComparison.OrdinalIgnoreCase)
? DapReplParser.GetRunHelp()
: $"Unknown help topic: {help.Topic}. Try: help or help(\"run\")";
return new EvaluateResponseBody
{
Result = helpText,
Type = "string",
VariablesReference = 0
};
case RunCommand run:
var context = GetExecutionContextForFrame(frameId);
return await _replExecutor.ExecuteRunCommandAsync(run, context, cancellationToken);
default:
return new EvaluateResponseBody
{
Result = $"Unknown command type: {command.GetType().Name}",
Type = "error",
VariablesReference = 0
};
}
}
private Response HandleContinue(Request request)