7#include <Poco/JSON/Array.h>
8#include <Poco/JSON/Object.h>
9#include <Poco/JSON/Parser.h>
10#include <Poco/JSON/Stringifier.h>
11#include <Poco/Exception.h>
12#include <Poco/Net/NetException.h>
13#include <Poco/Net/ServerSocket.h>
14#include <Poco/Net/SocketAddress.h>
15#include <Poco/Net/StreamSocket.h>
16#include <Poco/Timespan.h>
26#ifndef WIN32_LEAN_AND_MEAN
27#define WIN32_LEAN_AND_MEAN
37std::string
stringify(
const Poco::Dynamic::Var&
v) {
38 std::ostringstream oss;
39 Poco::JSON::Stringifier::stringify(
v, oss);
43std::string jsonEscape(
const std::string&
s) {
45 out.reserve(
s.size() + 8);
78DebugAdapter::DebugAdapter() =
default;
80DebugAdapter::~DebugAdapter() {
stop(); }
83 for (
char&
c :
root) {
84 if (
c ==
'\\')
c =
'/';
86 while (!
root.empty() && (
root.back() ==
'/' ||
root.back() ==
'\\'))
root.pop_back();
87 sourceRoot_ = std::move(
root);
88 discoverEngineScriptAliases();
93 if (
name.empty() || content.empty())
return;
94 std::lock_guard<std::recursive_mutex> lock(ioMu_);
95 sourceContents_[
name] = std::move(content);
98Poco::JSON::Object::Ptr DebugAdapter::makeSourceObject(
const std::string&
source)
const {
99 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
101 const auto it = sourceContents_.find(norm);
102 if (it != sourceContents_.end()) {
105 const int ref = 900000 +
static_cast<int>(std::hash<std::string>{}(norm) % 100000);
106 const_cast<DebugAdapter*
>(
this)->sourceRefContents_[ref] = it->second;
108 src->set(
"sourceReference", ref);
109 src->set(
"content", it->second);
112 const std::string resolved = resolveSourcePath(norm);
113 const auto slash = resolved.find_last_of(
'/');
114 src->set(
"name", slash == std::string::npos ? resolved : resolved.substr(slash + 1));
115 src->set(
"path", resolved);
119void DebugAdapter::discoverEngineScriptAliases() {
120 namespace fs = std::filesystem;
123 if (!sourceRoot_.empty())
124 cur = fs::path(sourceRoot_);
126 cur = fs::current_path(ec);
128 for (
int i = 0; i < 8; ++i) {
129 const fs::path cand = cur /
"src" /
"scripts" /
"load.nut";
130 if (fs::exists(cand, ec) && !ec) {
131 rememberSourcePath(cand.string());
135 sourceAliases_[
"buffer"] = sourceAliases_[
"load.nut"];
138 if (!cur.has_parent_path())
break;
139 const fs::path
parent = cur.parent_path();
145void DebugAdapter::rememberSourcePath(
const std::string&
path) {
147 if (norm.empty())
return;
148 sourceAliases_[norm] = norm;
150 if (!base.empty()) sourceAliases_[base] = norm;
153 std::string rel = norm;
154 if (rel.rfind(sourceRoot_, 0) == 0) {
155 rel = rel.substr(sourceRoot_.size());
156 while (!rel.empty() && rel[0] ==
'/') rel.erase(rel.begin());
157 if (!rel.empty()) sourceAliases_[rel] = norm;
162std::string DebugAdapter::resolveSourcePath(std::string
source)
const {
168 auto it = sourceAliases_.find(
source);
169 if (it != sourceAliases_.end())
return it->second;
172 it = sourceAliases_.find(base);
173 if (it != sourceAliases_.end())
return it->second;
179 if (sourceRoot_.empty())
return source;
181 const auto joined = std::filesystem::path(sourceRoot_) /
source;
183 auto canon = std::filesystem::weakly_canonical(joined, ec);
184 std::string out = ec ? joined.string() : canon.string();
185 for (
char&
c : out) {
186 if (
c ==
'\\')
c =
'/';
190 return sourceRoot_ +
"/" +
source;
194int DebugAdapter::varRefForFrame(
int frameId) {
195 if (frameId <= 0)
return varRefLocals_;
196 const int ref = varRefFrameBase_ + frameId;
197 varScopes_[ref] = VarScope{0, frameId, {}};
201int DebugAdapter::allocVarRef(
int kind,
int frame, std::vector<std::string>
path) {
202 const int ref = nextVarRef_++;
203 varScopes_[ref] = VarScope{
kind, frame, std::move(
path)};
207void DebugAdapter::handleBreakpointEvent(
int id,
const std::string&
source,
int line,
210 source, {
static_cast<uint32_t
>(std::max(
line, 1)), 1});
211 Poco::JSON::Object::Ptr bp =
new Poco::JSON::Object();
213 bp->set(
"verified", verified);
215 const std::string resolved = resolveSourcePath(
source);
216 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
218 src->set(
"path", resolved);
219 bp->set(
"source", src);
220 bp->set(
"line",
static_cast<int>(original.line));
222 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
223 body->set(
"reason",
"changed");
224 body->set(
"breakpoint", bp);
225 sendMessage(makeEvent(
"breakpoint",
stringify(Poco::Dynamic::Var(
body))));
231 [
this](
int id,
const std::string&
source,
int line,
bool verified) {
232 handleBreakpointEvent(
id,
source,
line, verified);
235 Poco::Net::SocketAddress addr(
"127.0.0.1",
port);
236 server_ = std::make_unique<Poco::Net::ServerSocket>(addr);
237 server_->setBlocking(
false);
238 const int bound =
static_cast<int>(server_->address().port());
240 listening_.store(
true);
245 discoverEngineScriptAliases();
250 listening_.store(
false);
257 std::lock_guard<std::recursive_mutex> lock(ioMu_);
274 listening_.store(
false);
275 hasClient_.store(
false);
278 stopOnEntry_ =
false;
279 sourceAliases_.clear();
281 lastException_.clear();
284void DebugAdapter::acceptNonBlocking() {
285 if (!server_ || client_)
return;
287 Poco::Net::SocketAddress clientAddr;
288 Poco::Net::StreamSocket ss = server_->acceptConnection(clientAddr);
290 ss.setBlocking(
true);
291 ss.setReceiveTimeout(Poco::Timespan(0, 1000));
292 client_ = std::make_unique<Poco::Net::StreamSocket>(ss);
293 hasClient_.store(
true);
295 }
catch (
const Poco::TimeoutException&) {
296 }
catch (
const Poco::Net::NetException&) {
302bool DebugAdapter::sendMessage(
const std::string& json) {
303 std::lock_guard<std::recursive_mutex> lock(ioMu_);
304 if (!client_)
return false;
305 const std::string frame =
306 "Content-Length: " + std::to_string(json.size()) +
"\r\n\r\n" + json;
308 const int sent = client_->sendBytes(frame.data(),
static_cast<int>(frame.size()));
309 return sent ==
static_cast<int>(frame.size());
312 hasClient_.store(
false);
317std::string DebugAdapter::makeResponse(
int ,
int requestSeq,
const std::string& command,
318 bool ok,
const std::string& bodyJson,
320 const int outSeq = seq_.fetch_add(1);
321 std::ostringstream oss;
322 oss <<
"{\"seq\":" << outSeq <<
",\"type\":\"response\",\"request_seq\":" << requestSeq
323 <<
",\"success\":" << (
ok ?
"true" :
"false") <<
",\"command\":\"" << jsonEscape(command)
325 if (!
ok && !
message.empty()) oss <<
",\"message\":\"" << jsonEscape(
message) <<
"\"";
326 if (!bodyJson.empty()) oss <<
",\"body\":" << bodyJson;
331std::string DebugAdapter::makeEvent(
const std::string& event,
const std::string& bodyJson) {
332 const int outSeq = seq_.fetch_add(1);
333 std::ostringstream oss;
334 oss <<
"{\"seq\":" << outSeq <<
",\"type\":\"event\",\"event\":\"" << jsonEscape(event) <<
"\"";
335 if (!bodyJson.empty()) oss <<
",\"body\":" << bodyJson;
359 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
360 body->set(
"reason", reasonString(reason));
361 body->set(
"threadId", 1);
362 body->set(
"allThreadsStopped",
true);
365 if (!lastException_.empty()) {
366 body->set(
"description", lastException_);
367 body->set(
"text", lastException_);
375 loc.
source, {static_cast<uint32_t>(loc.line), 1});
377 body->set(
"source", makeSourceObject(loc.
source));
378 body->set(
"line",
static_cast<int>(original.line));
379 body->set(
"column",
static_cast<int>(original.column));
381 sendMessage(makeEvent(
"stopped", stringify(Poco::Dynamic::Var(
body))));
385 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
386 body->set(
"threadId", 1);
387 body->set(
"allThreadsContinued",
true);
388 sendMessage(makeEvent(
"continued", stringify(Poco::Dynamic::Var(
body))));
392 sendMessage(makeEvent(
"terminated",
"{}"));
395void DebugAdapter::readAndDispatch() {
396 if (!client_)
return;
399 const int n = client_->receiveBytes(buf,
sizeof(buf));
403 hasClient_.store(
false);
407 recvBuf_.append(buf,
static_cast<size_t>(
n));
408 }
catch (
const Poco::TimeoutException&) {
410 }
catch (
const Poco::Net::NetException&) {
415 hasClient_.store(
false);
420 const auto headerEnd = recvBuf_.find(
"\r\n\r\n");
421 if (headerEnd == std::string::npos)
break;
422 const std::string
header = recvBuf_.substr(0, headerEnd);
423 int contentLength = -1;
425 const std::string
key =
"Content-Length:";
427 if (
pos != std::string::npos) {
430 contentLength = std::atoi(
header.c_str() +
pos);
433 if (contentLength < 0) {
434 recvBuf_.erase(0, headerEnd + 4);
437 const size_t total = headerEnd + 4 +
static_cast<size_t>(contentLength);
438 if (recvBuf_.size() < total)
break;
439 const std::string json = recvBuf_.substr(headerEnd + 4,
static_cast<size_t>(contentLength));
440 recvBuf_.erase(0, total);
445void DebugAdapter::handleRequest(
const std::string& json) {
447 Poco::JSON::Parser parser;
448 auto root = parser.parse(json).extract<Poco::JSON::Object::Ptr>();
450 const std::string
type =
root->optValue<std::string>(
"type",
"");
451 if (
type !=
"request")
return;
452 const std::string command =
root->optValue<std::string>(
"command",
"");
453 const int reqSeq =
root->optValue<
int>(
"seq", 0);
454 Poco::JSON::Object::Ptr args =
455 root->has(
"arguments") ?
root->getObject(
"arguments") :
nullptr;
459 if (command ==
"initialize") {
461 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
462 body->set(
"supportsConfigurationDoneRequest",
true);
463 body->set(
"supportsEvaluateForHovers",
true);
464 body->set(
"supportsTerminateRequest",
true);
465 body->set(
"supportsSingleThreadExecutionRequests",
true);
466 body->set(
"supportsSetVariable",
false);
467 body->set(
"supportsStepInTargetsRequest",
false);
468 body->set(
"supportsStepBack",
false);
469 body->set(
"supportTerminateDebuggee",
true);
470 body->set(
"supportsCancelRequest",
false);
471 body->set(
"supportsExceptionInfoRequest",
true);
472 body->set(
"supportsConditionalBreakpoints",
true);
476 Poco::JSON::Object::Ptr
filter =
new Poco::JSON::Object();
477 filter->set(
"filter",
"script_error");
478 filter->set(
"label",
"Script Errors");
479 filter->set(
"default",
false);
480 filter->set(
"supportsCondition",
false);
483 "Break when a script error is raised (caught or uncaught)");
484 Poco::JSON::Array::Ptr filters =
new Poco::JSON::Array();
486 body->set(
"exceptionBreakpointFilters", filters);
488 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
489 sendMessage(makeEvent(
"initialized",
"{}"));
492 if (command ==
"launch" || command ==
"attach") {
495 std::string cwd = args->optValue<std::string>(
"cwd",
"");
496 if (cwd.empty()) cwd = args->optValue<std::string>(
"program",
"");
498 stopOnEntry_ = args->optValue<
bool>(
"stopOnEntry",
false);
500 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
503 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
504 body->set(
"name",
"eve");
506 body->set(
"systemProcessId",
static_cast<int>(::GetCurrentProcessId()));
508 body->set(
"systemProcessId",
static_cast<int>(::getpid()));
510 body->set(
"isLocalProcess",
true);
511 body->set(
"startMethod", command ==
"launch" ?
"launch" :
"attach");
512 sendMessage(makeEvent(
"process",
stringify(Poco::Dynamic::Var(
body))));
516 if (command ==
"configurationDone") {
518 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
525 if (command ==
"setBreakpoints") {
527 if (args && args->has(
"source")) {
528 auto src = args->getObject(
"source");
530 sourcePath = src->optValue<std::string>(
"path",
"");
536 Poco::JSON::Array::Ptr outBps =
new Poco::JSON::Array();
537 if (args && args->has(
"breakpoints")) {
538 auto arr = args->getArray(
"breakpoints");
540 for (
size_t i = 0; i < arr->size(); ++i) {
541 auto bp = arr->getObject(
static_cast<unsigned int>(i));
543 const int line = bp->optValue<
int>(
"line", 0);
545 bp->optValue<std::string>(
"condition",
"");
548 const int id = dbg.setBreakpoint(
sourcePath,
static_cast<int>(generated.line),
true,
condition);
549 Poco::JSON::Object::Ptr ob =
new Poco::JSON::Object();
553 ob->set(
"verified",
false);
554 ob->set(
"line",
line);
555 if (
id > 0) ob->set(
"message",
"waiting for line to execute");
557 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
558 const std::string resolved = resolveSourcePath(
sourcePath);
560 src->set(
"path", resolved);
561 ob->set(
"source", src);
567 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
568 body->set(
"breakpoints", outBps);
569 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
572 if (command ==
"threads") {
573 Poco::JSON::Object::Ptr th =
new Poco::JSON::Object();
575 th->set(
"name",
"main");
576 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
578 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
579 body->set(
"threads", arr);
580 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
583 if (command ==
"stackTrace") {
584 auto frames = dbg.stackTrace(64);
586 if (frames.empty() && !dbg.pauseLocation().empty()) {
589 f.loc = dbg.pauseLocation();
590 f.name =
f.loc.function.empty() ?
"frame" :
f.loc.function;
593 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
594 for (
const auto&
f : frames) {
596 f.loc.source, {static_cast<uint32_t>(std::max(f.loc.line, 1)), 1});
597 Poco::JSON::Object::Ptr fo =
new Poco::JSON::Object();
599 fo->set(
"name",
f.name);
600 fo->set(
"line",
static_cast<int>(original.line));
601 fo->set(
"column",
static_cast<int>(original.column));
602 fo->set(
"source", makeSourceObject(
f.loc.source));
605 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
606 body->set(
"stackFrames", arr);
607 body->set(
"totalFrames",
static_cast<int>(frames.size()));
608 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
611 if (command ==
"scopes") {
612 const int frameId = args ? args->optValue<
int>(
"frameId", 0) : 0;
613 const int localsRef = varRefForFrame(frameId);
614 Poco::JSON::Object::Ptr locals =
new Poco::JSON::Object();
615 locals->set(
"name",
"Locals");
616 locals->set(
"variablesReference", localsRef);
617 locals->set(
"expensive",
false);
618 Poco::JSON::Object::Ptr watches =
new Poco::JSON::Object();
619 watches->set(
"name",
"Watches");
620 watches->set(
"variablesReference", varRefWatches_);
621 watches->set(
"expensive",
false);
622 Poco::JSON::Object::Ptr globals =
new Poco::JSON::Object();
623 globals->set(
"name",
"Globals");
624 globals->set(
"variablesReference", varRefGlobals_);
625 globals->set(
"expensive",
false);
626 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
630 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
631 body->set(
"scopes", arr);
632 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
635 if (command ==
"variables") {
636 const int ref = args ? args->optValue<
int>(
"variablesReference", 0) : 0;
637 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
638 const auto addChild = [&](
const VariableInfo&
v,
const VarScope& base) {
639 Poco::JSON::Object::Ptr o =
new Poco::JSON::Object();
640 o->set(
"name",
v.name);
641 o->set(
"value",
v.value);
642 o->set(
"type",
v.type);
645 child.path.push_back(
v.name);
646 o->set(
"variablesReference", allocVarRef(
child.kind,
child.frame,
child.path));
651 o->set(
"__vscodeVariableMenuContext",
"object");
653 o->set(
"variablesReference", 0);
658 if (ref == varRefLocals_) {
661 for (
const auto&
v : dbg.locals(0)) addChild(
v, base);
662 }
else if (ref == varRefWatches_) {
663 dbg.refreshWatches();
664 for (
const auto&
w : dbg.watches()) {
665 Poco::JSON::Object::Ptr o =
new Poco::JSON::Object();
666 o->set(
"name",
w.expression);
667 o->set(
"value",
w.value);
668 o->set(
"type",
w.ok ?
"watch" :
"error");
669 o->set(
"variablesReference", 0);
672 }
else if (ref == varRefGlobals_) {
675 for (
const auto&
v : dbg.globals()) addChild(
v, base);
677 const auto it = varScopes_.find(ref);
678 if (it != varScopes_.end()) {
679 const auto children = dbg.containerChildren(
680 static_cast<VarKind>(it->second.kind), it->second.frame, it->second.path);
684 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
685 body->set(
"variables", arr);
686 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
689 if (command ==
"continue") {
692 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
693 body->set(
"allThreadsContinued",
true);
694 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
697 if (command ==
"next") {
701 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
702 body->set(
"threadId", 1);
703 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
706 if (command ==
"stepIn") {
710 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
711 body->set(
"threadId", 1);
712 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
715 if (command ==
"stepOut") {
719 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
720 body->set(
"threadId", 1);
721 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
724 if (command ==
"stepFrame") {
728 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
729 body->set(
"threadId", 1);
730 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
733 if (command ==
"pause") {
738 if (!dbg.isPaused()) {
740 Poco::JSON::Object::Ptr stoppedBody =
new Poco::JSON::Object();
741 stoppedBody->set(
"reason",
"pause");
742 stoppedBody->set(
"threadId", 1);
743 stoppedBody->set(
"allThreadsStopped",
true);
744 sendMessage(makeEvent(
"stopped",
stringify(Poco::Dynamic::Var(stoppedBody))));
746 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
749 if (command ==
"errorSlice") {
751 std::string report = dt.lastReport();
752 SliceResult slice = dt.lastSlice();
753 if (report.empty()) {
755 args ? args->optValue<std::string>(
"message",
"slice") :
std::
string(
"slice");
756 report = dt.formatError(
message);
757 slice = dt.analyzeError(
message);
759 Poco::JSON::Array::Ptr locations =
new Poco::JSON::Array();
760 for (
const SourceLoc& loc : slice.locations) {
761 if (loc.line <= 0 && loc.source.empty())
continue;
762 Poco::JSON::Object::Ptr item =
new Poco::JSON::Object();
763 item->set(
"path", resolveSourcePath(loc.source));
764 item->set(
"name", loc.source);
765 item->set(
"line", loc.line);
766 item->set(
"function", loc.function);
767 locations->add(item);
769 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
770 body->set(
"report", report);
771 body->set(
"error", dt.lastError());
772 body->set(
"locations", locations);
773 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
776 if (command ==
"evaluate") {
777 const std::string expr = args ? args->optValue<std::string>(
"expression",
"") :
"";
778 const int frameId = args ? args->optValue<
int>(
"frameId", 0) : 0;
779 const std::string
context = args ? args->optValue<std::string>(
"context",
"") :
"";
780 auto info = dbg.evaluate(expr, frameId);
783 if (
context ==
"watch" &&
info.type !=
"error") dbg.addWatch(expr);
784 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
787 body->set(
"variablesReference", 0);
788 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
791 if (command ==
"source") {
792 const int ref = args ? args->optValue<
int>(
"sourceReference", 0) : 0;
793 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
794 const auto it = sourceRefContents_.find(ref);
795 if (it != sourceRefContents_.end()) {
796 body->set(
"content", it->second);
797 body->set(
"mimeType",
"text/plain");
799 body->set(
"content",
"");
801 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
804 if (command ==
"setExceptionBreakpoints") {
808 Poco::JSON::Array::Ptr
applied =
new Poco::JSON::Array();
809 if (args && args->has(
"filters")) {
810 auto filters = args->getArray(
"filters");
812 for (
size_t i = 0; i < filters->size(); ++i) {
813 const std::string
f =
814 filters->get(
static_cast<unsigned int>(i)).convert<std::string>();
815 if (
f ==
"all" ||
f ==
"uncaught" ||
f ==
"script_error" ||
816 f ==
"runtime_error") {
823 dbg.setBreakOnError(on);
824 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
825 body->set(
"filters", applied);
826 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
829 if (command ==
"exceptionInfo") {
830 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
831 body->set(
"exceptionId",
"script_error");
832 body->set(
"description", lastException_.empty() ?
"script error" : lastException_);
833 body->set(
"breakMode", dbg.breakOnError() ?
"always" :
"never");
834 Poco::JSON::Object::Ptr details =
new Poco::JSON::Object();
835 details->set(
"message", lastException_);
836 body->set(
"details", details);
837 sendMessage(makeResponse(0, reqSeq, command,
true,
stringify(Poco::Dynamic::Var(
body))));
840 if (command ==
"disconnect" || command ==
"terminate") {
842 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
848 sendMessage(makeResponse(0, reqSeq, command,
false,
"{}",
"unsupported: " + command));
849 }
catch (
const std::exception& e) {
851 sendMessage(makeResponse(0, 0,
"error",
false,
"{}", e.what()));
853 sendMessage(makeResponse(0, 0,
"error",
false,
"{}",
"unknown DAP handler error"));
858 if (!listening_.load())
return;
859 std::lock_guard<std::recursive_mutex> lock(ioMu_);
865 if (!listening_.load())
return false;
866 if (timeoutMs < 0) timeoutMs = 0;
867 using clock = std::chrono::steady_clock;
868 const auto deadline = clock::now() + std::chrono::milliseconds(timeoutMs);
870 const auto clientDeadline = clock::now() + std::chrono::milliseconds(
871 std::min(timeoutMs, 2500));
872 while (clock::now() < clientDeadline) {
874 if (configured_)
return true;
875 if (hasClient_.load())
break;
876 std::this_thread::sleep_for(std::chrono::milliseconds(10));
878 if (!hasClient_.load()) {
884 if (configured_)
return true;
885 std::this_thread::sleep_for(std::chrono::milliseconds(10));
const UnitySourceAsset & source
const VegetationPresetContext & context
Minimal Debug Adapter Protocol (DAP) server for VS Code.
void setSourceRoot(std::string root)
Game directory used to resolve relative script paths in stack frames.
void poll()
Accept clients + process one request batch (non-blocking).
int listen(uint16_t port)
Bind TCP listen port (0 = ephemeral). Returns bound port or 0 on failure.
bool waitUntilConfigured(int timeoutMs=15000)
Block briefly until a DAP client finishes initialize/launch/setBreakpoints/ configurationDone so scri...
void registerSource(std::string name, std::string content)
Register virtual source content for compiled buffers (compilestring).
void notifyStopped(PauseReason reason, const SourceLoc &loc, const std::string &description={})
Emit stopped event to the connected client (if any).
static DebugAdapter & instance()
static std::string sourceBasename(const std::string &source)
Basename of a normalized path (empty-safe).
void setBreakpointEventFn(BreakpointEventFn fn)
static std::string normalizeSource(std::string source)
Normalize source paths for breakpoint matching (basename fallback).
static Debugger & instance()
static bool sourcesMatch(const std::string &a, const std::string &b)
True when two source paths refer to the same script file. Matches exact path, or one path as a direct...
static ScriptSourcePosition toOriginalPosition(std::string_view source, ScriptSourcePosition generated)
Maps a VM/generated location through any active compiler metadata.
static ScriptSourcePosition toGeneratedPosition(std::string_view source, ScriptSourcePosition original)
Maps an IDE/original location to the VM location used for breakpoints.
VarKind
Where a variable tree node is rooted (used by containerChildren).
@ Snapshot
Script state was restored while the debugger was already paused.
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
Result< std::string > stringify(const eve::Value &value)
Serialize the canonical owning Value as deterministic compact JSON.
std::string header(int localX, int localY)
Header.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Source location in a Squirrel (or synthetic) script.