5#include <Poco/JSON/Array.h>
6#include <Poco/JSON/Object.h>
7#include <Poco/JSON/Parser.h>
8#include <Poco/JSON/Stringifier.h>
9#include <Poco/Exception.h>
10#include <Poco/Net/NetException.h>
11#include <Poco/Net/ServerSocket.h>
12#include <Poco/Net/SocketAddress.h>
13#include <Poco/Net/StreamSocket.h>
14#include <Poco/Timespan.h>
24#ifndef WIN32_LEAN_AND_MEAN
25#define WIN32_LEAN_AND_MEAN
35std::string stringify(
const Poco::Dynamic::Var&
v) {
36 std::ostringstream oss;
37 Poco::JSON::Stringifier::stringify(
v, oss);
41std::string jsonEscape(
const std::string&
s) {
43 out.reserve(
s.size() + 8);
76DebugAdapter::DebugAdapter() =
default;
78DebugAdapter::~DebugAdapter() {
stop(); }
81 for (
char&
c : root) {
82 if (
c ==
'\\')
c =
'/';
84 while (!root.empty() && (root.back() ==
'/' || root.back() ==
'\\')) root.pop_back();
85 sourceRoot_ = std::move(root);
86 discoverEngineScriptAliases();
91 if (
name.empty() || content.empty())
return;
92 std::lock_guard<std::recursive_mutex> lock(ioMu_);
93 sourceContents_[
name] = std::move(content);
96Poco::JSON::Object::Ptr DebugAdapter::makeSourceObject(
const std::string& source)
const {
97 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
99 const auto it = sourceContents_.find(norm);
100 if (it != sourceContents_.end()) {
103 const int ref = 900000 +
static_cast<int>(std::hash<std::string>{}(norm) % 100000);
104 const_cast<DebugAdapter*
>(
this)->sourceRefContents_[
ref] = it->second;
106 src->set(
"sourceReference",
ref);
107 src->set(
"content", it->second);
110 const std::string resolved = resolveSourcePath(norm);
111 const auto slash = resolved.find_last_of(
'/');
112 src->set(
"name", slash == std::string::npos ? resolved : resolved.substr(slash + 1));
113 src->set(
"path", resolved);
117void DebugAdapter::discoverEngineScriptAliases() {
118 namespace fs = std::filesystem;
121 if (!sourceRoot_.empty())
122 cur = fs::path(sourceRoot_);
124 cur = fs::current_path(ec);
126 for (
int i = 0; i < 8; ++i) {
127 const fs::path cand = cur /
"src" /
"scripts" /
"load.nut";
128 if (fs::exists(cand, ec) && !ec) {
129 rememberSourcePath(cand.string());
133 sourceAliases_[
"buffer"] = sourceAliases_[
"load.nut"];
136 if (!cur.has_parent_path())
break;
137 const fs::path
parent = cur.parent_path();
143void DebugAdapter::rememberSourcePath(
const std::string& path) {
145 if (norm.empty())
return;
146 sourceAliases_[norm] = norm;
148 if (!base.empty()) sourceAliases_[base] = norm;
151 std::string rel = norm;
152 if (rel.rfind(sourceRoot_, 0) == 0) {
153 rel = rel.substr(sourceRoot_.size());
154 while (!rel.empty() && rel[0] ==
'/') rel.erase(rel.begin());
155 if (!rel.empty()) sourceAliases_[rel] = norm;
160std::string DebugAdapter::resolveSourcePath(std::string source)
const {
162 if (source.empty())
return source;
166 auto it = sourceAliases_.find(source);
167 if (it != sourceAliases_.end())
return it->second;
170 it = sourceAliases_.find(base);
171 if (it != sourceAliases_.end())
return it->second;
176 if (source[0] ==
'/' || (source.size() >= 2 && source[1] ==
':'))
return source;
177 if (sourceRoot_.empty())
return source;
179 const auto joined = std::filesystem::path(sourceRoot_) / source;
181 auto canon = std::filesystem::weakly_canonical(joined, ec);
182 std::string out = ec ? joined.string() : canon.string();
183 for (
char&
c : out) {
184 if (
c ==
'\\')
c =
'/';
188 return sourceRoot_ +
"/" + source;
192int DebugAdapter::varRefForFrame(
int frameId) {
193 if (frameId <= 0)
return varRefLocals_;
194 const int ref = varRefFrameBase_ + frameId;
195 varScopes_[ref] = VarScope{0, frameId, {}};
199int DebugAdapter::allocVarRef(
int kind,
int frame, std::vector<std::string> path) {
200 const int ref = nextVarRef_++;
201 varScopes_[ref] = VarScope{
kind, frame, std::move(path)};
205void DebugAdapter::handleBreakpointEvent(
int id,
const std::string& source,
int line,
207 Poco::JSON::Object::Ptr bp =
new Poco::JSON::Object();
209 bp->set(
"verified", verified);
210 if (!source.empty() &&
line > 0) {
211 const std::string resolved = resolveSourcePath(source);
212 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
214 src->set(
"path", resolved);
215 bp->set(
"source", src);
216 bp->set(
"line",
line);
218 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
219 body->set(
"reason",
"changed");
220 body->set(
"breakpoint", bp);
221 sendMessage(makeEvent(
"breakpoint", stringify(Poco::Dynamic::Var(
body))));
227 [
this](
int id,
const std::string& source,
int line,
bool verified) {
228 handleBreakpointEvent(
id, source,
line, verified);
231 Poco::Net::SocketAddress addr(
"127.0.0.1",
port);
232 server_ = std::make_unique<Poco::Net::ServerSocket>(addr);
233 server_->setBlocking(
false);
234 const int bound =
static_cast<int>(server_->address().port());
236 listening_.store(
true);
241 discoverEngineScriptAliases();
246 listening_.store(
false);
253 std::lock_guard<std::recursive_mutex> lock(ioMu_);
270 listening_.store(
false);
271 hasClient_.store(
false);
274 stopOnEntry_ =
false;
275 sourceAliases_.clear();
277 lastException_.clear();
280void DebugAdapter::acceptNonBlocking() {
281 if (!server_ || client_)
return;
283 Poco::Net::SocketAddress clientAddr;
284 Poco::Net::StreamSocket ss = server_->acceptConnection(clientAddr);
286 ss.setBlocking(
true);
287 ss.setReceiveTimeout(Poco::Timespan(0, 1000));
288 client_ = std::make_unique<Poco::Net::StreamSocket>(ss);
289 hasClient_.store(
true);
291 }
catch (
const Poco::TimeoutException&) {
292 }
catch (
const Poco::Net::NetException&) {
298bool DebugAdapter::sendMessage(
const std::string& json) {
299 std::lock_guard<std::recursive_mutex> lock(ioMu_);
300 if (!client_)
return false;
301 const std::string frame =
302 "Content-Length: " + std::to_string(json.size()) +
"\r\n\r\n" + json;
304 const int sent = client_->sendBytes(frame.data(),
static_cast<int>(frame.size()));
305 return sent ==
static_cast<int>(frame.size());
308 hasClient_.store(
false);
313std::string DebugAdapter::makeResponse(
int ,
int requestSeq,
const std::string& command,
314 bool ok,
const std::string& bodyJson,
315 const std::string& message) {
316 const int outSeq = seq_.fetch_add(1);
317 std::ostringstream oss;
318 oss <<
"{\"seq\":" << outSeq <<
",\"type\":\"response\",\"request_seq\":" << requestSeq
319 <<
",\"success\":" << (
ok ?
"true" :
"false") <<
",\"command\":\"" << jsonEscape(command)
321 if (!
ok && !message.empty()) oss <<
",\"message\":\"" << jsonEscape(message) <<
"\"";
322 if (!bodyJson.empty()) oss <<
",\"body\":" << bodyJson;
327std::string DebugAdapter::makeEvent(
const std::string& event,
const std::string& bodyJson) {
328 const int outSeq = seq_.fetch_add(1);
329 std::ostringstream oss;
330 oss <<
"{\"seq\":" << outSeq <<
",\"type\":\"event\",\"event\":\"" << jsonEscape(event) <<
"\"";
331 if (!bodyJson.empty()) oss <<
",\"body\":" << bodyJson;
336std::string DebugAdapter::reasonString(
PauseReason r) {
352 const std::string& description) {
353 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
354 body->set(
"reason", reasonString(reason));
355 body->set(
"threadId", 1);
356 body->set(
"allThreadsStopped",
true);
358 lastException_ = description.empty() ? lastException_ : description;
359 if (!lastException_.empty()) {
360 body->set(
"description", lastException_);
361 body->set(
"text", lastException_);
366 body->set(
"source", makeSourceObject(loc.
source));
368 body->set(
"column", 1);
370 sendMessage(makeEvent(
"stopped", stringify(Poco::Dynamic::Var(
body))));
374 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
375 body->set(
"threadId", 1);
376 body->set(
"allThreadsContinued",
true);
377 sendMessage(makeEvent(
"continued", stringify(Poco::Dynamic::Var(
body))));
381 sendMessage(makeEvent(
"terminated",
"{}"));
384void DebugAdapter::readAndDispatch() {
385 if (!client_)
return;
388 const int n = client_->receiveBytes(buf,
sizeof(buf));
392 hasClient_.store(
false);
396 recvBuf_.append(buf,
static_cast<size_t>(
n));
397 }
catch (
const Poco::TimeoutException&) {
399 }
catch (
const Poco::Net::NetException&) {
404 hasClient_.store(
false);
409 const auto headerEnd = recvBuf_.find(
"\r\n\r\n");
410 if (headerEnd == std::string::npos)
break;
411 const std::string header = recvBuf_.substr(0, headerEnd);
412 int contentLength = -1;
414 const std::string key =
"Content-Length:";
415 auto pos = header.find(key);
416 if (
pos != std::string::npos) {
418 while (
pos < header.size() && (header[
pos] ==
' ' || header[
pos] ==
'\t')) ++
pos;
419 contentLength = std::atoi(header.c_str() +
pos);
422 if (contentLength < 0) {
423 recvBuf_.erase(0, headerEnd + 4);
426 const size_t total = headerEnd + 4 +
static_cast<size_t>(contentLength);
427 if (recvBuf_.size() < total)
break;
428 const std::string json = recvBuf_.substr(headerEnd + 4,
static_cast<size_t>(contentLength));
429 recvBuf_.erase(0, total);
434void DebugAdapter::handleRequest(
const std::string& json) {
436 Poco::JSON::Parser parser;
437 auto root = parser.parse(json).extract<Poco::JSON::Object::Ptr>();
439 const std::string
type = root->optValue<std::string>(
"type",
"");
440 if (
type !=
"request")
return;
441 const std::string command = root->optValue<std::string>(
"command",
"");
442 const int reqSeq = root->optValue<
int>(
"seq", 0);
443 Poco::JSON::Object::Ptr args =
444 root->has(
"arguments") ? root->getObject(
"arguments") :
nullptr;
448 if (command ==
"initialize") {
450 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
451 body->set(
"supportsConfigurationDoneRequest",
true);
452 body->set(
"supportsEvaluateForHovers",
true);
453 body->set(
"supportsTerminateRequest",
true);
454 body->set(
"supportsSingleThreadExecutionRequests",
true);
455 body->set(
"supportsSetVariable",
false);
456 body->set(
"supportsStepInTargetsRequest",
false);
457 body->set(
"supportsStepBack",
false);
458 body->set(
"supportTerminateDebuggee",
true);
459 body->set(
"supportsCancelRequest",
false);
460 body->set(
"supportsExceptionInfoRequest",
true);
461 body->set(
"supportsConditionalBreakpoints",
true);
465 Poco::JSON::Object::Ptr
filter =
new Poco::JSON::Object();
466 filter->set(
"filter",
"script_error");
467 filter->set(
"label",
"Script Errors");
468 filter->set(
"default",
false);
469 filter->set(
"supportsCondition",
false);
472 "Break when a script error is raised (caught or uncaught)");
473 Poco::JSON::Array::Ptr filters =
new Poco::JSON::Array();
475 body->set(
"exceptionBreakpointFilters", filters);
477 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
478 sendMessage(makeEvent(
"initialized",
"{}"));
481 if (command ==
"launch" || command ==
"attach") {
484 std::string root = args->optValue<std::string>(
"cwd",
"");
485 if (root.empty()) root = args->optValue<std::string>(
"program",
"");
487 stopOnEntry_ = args->optValue<
bool>(
"stopOnEntry",
false);
489 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
492 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
493 body->set(
"name",
"eve");
495 body->set(
"systemProcessId",
static_cast<int>(::GetCurrentProcessId()));
497 body->set(
"systemProcessId",
static_cast<int>(::getpid()));
499 body->set(
"isLocalProcess",
true);
500 body->set(
"startMethod", command ==
"launch" ?
"launch" :
"attach");
501 sendMessage(makeEvent(
"process", stringify(Poco::Dynamic::Var(
body))));
505 if (command ==
"configurationDone") {
507 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
514 if (command ==
"setBreakpoints") {
515 std::string sourcePath;
516 if (args && args->has(
"source")) {
517 auto src = args->getObject(
"source");
519 sourcePath = src->optValue<std::string>(
"path",
"");
520 if (sourcePath.empty()) sourcePath = src->optValue<std::string>(
"name",
"");
523 if (!sourcePath.empty()) rememberSourcePath(sourcePath);
524 dbg.clearBreakpoints(sourcePath);
525 Poco::JSON::Array::Ptr outBps =
new Poco::JSON::Array();
526 if (args && args->has(
"breakpoints")) {
527 auto arr = args->getArray(
"breakpoints");
529 for (
size_t i = 0; i < arr->size(); ++i) {
530 auto bp = arr->getObject(i);
532 const int line = bp->optValue<
int>(
"line", 0);
533 const std::string condition =
534 bp->optValue<std::string>(
"condition",
"");
535 const int id = dbg.setBreakpoint(sourcePath,
line,
true, condition);
536 Poco::JSON::Object::Ptr ob =
new Poco::JSON::Object();
540 ob->set(
"verified",
false);
541 ob->set(
"line",
line);
542 if (
id > 0) ob->set(
"message",
"waiting for line to execute");
543 if (
id > 0 && !sourcePath.empty()) {
544 Poco::JSON::Object::Ptr src =
new Poco::JSON::Object();
545 const std::string resolved = resolveSourcePath(sourcePath);
547 src->set(
"path", resolved);
548 ob->set(
"source", src);
554 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
555 body->set(
"breakpoints", outBps);
556 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
559 if (command ==
"threads") {
560 Poco::JSON::Object::Ptr th =
new Poco::JSON::Object();
562 th->set(
"name",
"main");
563 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
565 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
566 body->set(
"threads", arr);
567 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
570 if (command ==
"stackTrace") {
571 auto frames = dbg.stackTrace(64);
573 if (frames.empty() && !dbg.pauseLocation().empty()) {
576 f.loc = dbg.pauseLocation();
577 f.name =
f.loc.function.empty() ?
"frame" :
f.loc.function;
580 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
581 for (
const auto&
f : frames) {
582 Poco::JSON::Object::Ptr fo =
new Poco::JSON::Object();
584 fo->set(
"name",
f.name);
585 fo->set(
"line",
f.loc.line);
586 fo->set(
"column", 1);
587 fo->set(
"source", makeSourceObject(
f.loc.source));
590 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
591 body->set(
"stackFrames", arr);
592 body->set(
"totalFrames",
static_cast<int>(frames.size()));
593 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
596 if (command ==
"scopes") {
597 const int frameId = args ? args->optValue<
int>(
"frameId", 0) : 0;
598 const int localsRef = varRefForFrame(frameId);
599 Poco::JSON::Object::Ptr locals =
new Poco::JSON::Object();
600 locals->set(
"name",
"Locals");
601 locals->set(
"variablesReference", localsRef);
602 locals->set(
"expensive",
false);
603 Poco::JSON::Object::Ptr watches =
new Poco::JSON::Object();
604 watches->set(
"name",
"Watches");
605 watches->set(
"variablesReference", varRefWatches_);
606 watches->set(
"expensive",
false);
607 Poco::JSON::Object::Ptr globals =
new Poco::JSON::Object();
608 globals->set(
"name",
"Globals");
609 globals->set(
"variablesReference", varRefGlobals_);
610 globals->set(
"expensive",
false);
611 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
615 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
616 body->set(
"scopes", arr);
617 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
620 if (command ==
"variables") {
621 const int ref = args ? args->optValue<
int>(
"variablesReference", 0) : 0;
622 Poco::JSON::Array::Ptr arr =
new Poco::JSON::Array();
623 const auto addChild = [&](
const VariableInfo&
v,
const VarScope& base) {
624 Poco::JSON::Object::Ptr o =
new Poco::JSON::Object();
625 o->set(
"name",
v.name);
626 o->set(
"value",
v.value);
627 o->set(
"type",
v.type);
630 child.path.push_back(
v.name);
631 o->set(
"variablesReference", allocVarRef(
child.kind,
child.frame,
child.path));
636 o->set(
"__vscodeVariableMenuContext",
"object");
638 o->set(
"variablesReference", 0);
643 if (ref == varRefLocals_) {
646 for (
const auto&
v : dbg.locals(0)) addChild(
v, base);
647 }
else if (ref == varRefWatches_) {
648 dbg.refreshWatches();
649 for (
const auto&
w : dbg.watches()) {
650 Poco::JSON::Object::Ptr o =
new Poco::JSON::Object();
651 o->set(
"name",
w.expression);
652 o->set(
"value",
w.value);
653 o->set(
"type",
w.ok ?
"watch" :
"error");
654 o->set(
"variablesReference", 0);
657 }
else if (ref == varRefGlobals_) {
660 for (
const auto&
v : dbg.globals()) addChild(
v, base);
662 const auto it = varScopes_.find(ref);
663 if (it != varScopes_.end()) {
664 const auto children = dbg.containerChildren(
665 static_cast<VarKind>(it->second.kind), it->second.frame, it->second.path);
666 for (
const auto&
v :
children) addChild(
v, it->second);
669 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
670 body->set(
"variables", arr);
671 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
674 if (command ==
"continue") {
677 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
678 body->set(
"allThreadsContinued",
true);
679 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
682 if (command ==
"next") {
686 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
687 body->set(
"threadId", 1);
688 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
691 if (command ==
"stepIn") {
695 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
696 body->set(
"threadId", 1);
697 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
700 if (command ==
"stepOut") {
704 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
705 body->set(
"threadId", 1);
706 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
709 if (command ==
"stepFrame") {
713 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
714 body->set(
"threadId", 1);
715 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
718 if (command ==
"pause") {
723 if (!dbg.isPaused()) {
725 Poco::JSON::Object::Ptr stoppedBody =
new Poco::JSON::Object();
726 stoppedBody->set(
"reason",
"pause");
727 stoppedBody->set(
"threadId", 1);
728 stoppedBody->set(
"allThreadsStopped",
true);
729 sendMessage(makeEvent(
"stopped", stringify(Poco::Dynamic::Var(stoppedBody))));
731 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
734 if (command ==
"evaluate") {
735 const std::string expr = args ? args->optValue<std::string>(
"expression",
"") :
"";
736 const int frameId = args ? args->optValue<
int>(
"frameId", 0) : 0;
737 const std::string context = args ? args->optValue<std::string>(
"context",
"") :
"";
738 auto info = dbg.evaluate(expr, frameId);
741 if (context ==
"watch" && info.type !=
"error") dbg.addWatch(expr);
742 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
743 body->set(
"result", info.value);
744 body->set(
"type", info.type);
745 body->set(
"variablesReference", 0);
746 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
749 if (command ==
"source") {
750 const int ref = args ? args->optValue<
int>(
"sourceReference", 0) : 0;
751 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
752 const auto it = sourceRefContents_.find(ref);
753 if (it != sourceRefContents_.end()) {
754 body->set(
"content", it->second);
755 body->set(
"mimeType",
"text/plain");
757 body->set(
"content",
"");
759 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
762 if (command ==
"setExceptionBreakpoints") {
766 Poco::JSON::Array::Ptr applied =
new Poco::JSON::Array();
767 if (args && args->has(
"filters")) {
768 auto filters = args->getArray(
"filters");
770 for (
size_t i = 0; i < filters->size(); ++i) {
771 const std::string
f = filters->get(i).convert<std::string>();
772 if (
f ==
"all" ||
f ==
"uncaught" ||
f ==
"script_error" ||
773 f ==
"runtime_error") {
780 dbg.setBreakOnError(on);
781 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
782 body->set(
"filters", applied);
783 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
786 if (command ==
"exceptionInfo") {
787 Poco::JSON::Object::Ptr
body =
new Poco::JSON::Object();
788 body->set(
"exceptionId",
"script_error");
789 body->set(
"description", lastException_.empty() ?
"script error" : lastException_);
790 body->set(
"breakMode", dbg.breakOnError() ?
"always" :
"never");
791 Poco::JSON::Object::Ptr details =
new Poco::JSON::Object();
792 details->set(
"message", lastException_);
793 body->set(
"details", details);
794 sendMessage(makeResponse(0, reqSeq, command,
true, stringify(Poco::Dynamic::Var(
body))));
797 if (command ==
"disconnect" || command ==
"terminate") {
799 sendMessage(makeResponse(0, reqSeq, command,
true,
"{}"));
805 sendMessage(makeResponse(0, reqSeq, command,
false,
"{}",
"unsupported: " + command));
806 }
catch (
const std::exception& e) {
808 sendMessage(makeResponse(0, 0,
"error",
false,
"{}", e.what()));
810 sendMessage(makeResponse(0, 0,
"error",
false,
"{}",
"unknown DAP handler error"));
815 if (!listening_.load())
return;
816 std::lock_guard<std::recursive_mutex> lock(ioMu_);
822 if (!listening_.load())
return false;
823 if (timeoutMs < 0) timeoutMs = 0;
824 using clock = std::chrono::steady_clock;
825 const auto deadline = clock::now() + std::chrono::milliseconds(timeoutMs);
827 const auto clientDeadline = clock::now() + std::chrono::milliseconds(
828 std::min(timeoutMs, 2500));
829 while (clock::now() < clientDeadline) {
831 if (configured_)
return true;
832 if (hasClient_.load())
break;
833 std::this_thread::sleep_for(std::chrono::milliseconds(10));
835 if (!hasClient_.load()) {
839 while (clock::now() < deadline) {
841 if (configured_)
return true;
842 std::this_thread::sleep_for(std::chrono::milliseconds(10));
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, basename,...
ref is a smart pointer that automatically calls ref() and unref() on the object. Type T must be a sub...
VarKind
Where a variable tree node is rooted (used by containerChildren/resolvePath).
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.