Warning, file /utilities/kate/addons/gdbplugin/dapbackend.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include <KMessageBox>
0007 #include <QDir>
0008 #include <QFileInfo>
0009 #include <QJsonDocument>
0010 #include <QRegularExpression>
0011 #include <pthread.h>
0012 
0013 #include <KLocalizedString>
0014 
0015 #include "dapbackend.h"
0016 #include "json_placeholders.h"
0017 
0018 QString newLine(const QString &text)
0019 {
0020     return QStringLiteral("\n") + text;
0021 }
0022 
0023 QString printEvent(const QString &text)
0024 {
0025     return QStringLiteral("\n--> %1").arg(text);
0026 }
0027 
0028 DapBackend::DapBackend(QObject *parent)
0029     : BackendInterface(parent)
0030     , m_client(nullptr)
0031     , m_state(State::None)
0032     , m_requests(0)
0033 {
0034 }
0035 
0036 void DapBackend::unsetClient()
0037 {
0038     if (m_client) {
0039         disconnect(m_client->bus());
0040         disconnect(m_client);
0041         m_client->detach();
0042         m_client->deleteLater();
0043         m_client = nullptr;
0044     }
0045     resetState(State::None);
0046     shutdownUntil();
0047     m_currentScope = std::nullopt;
0048 }
0049 
0050 void DapBackend::resetState(State state)
0051 {
0052     m_requests = 0;
0053     m_runToCursor = std::nullopt;
0054     if (state != Running) {
0055         m_currentThread = std::nullopt;
0056     }
0057     m_watchedThread = std::nullopt;
0058     m_currentFrame = std::nullopt;
0059     m_commandQueue.clear();
0060     m_restart = false;
0061     m_frames.clear();
0062     m_task = Idle;
0063     setState(state);
0064 }
0065 
0066 void DapBackend::setState(State state)
0067 {
0068     if (state == m_state)
0069         return;
0070 
0071     m_state = state;
0072     Q_EMIT readyForInput(debuggerRunning());
0073 
0074     switch (m_state) {
0075     case State::Terminated:
0076         Q_EMIT programEnded();
0077         if (continueShutdown()) {
0078             tryDisconnect();
0079         }
0080         break;
0081     case State::Disconnected:
0082         if (continueShutdown()) {
0083             cmdShutdown();
0084         }
0085         break;
0086     case State::PostMortem:
0087         shutdownUntil();
0088         if (m_restart) {
0089             m_restart = false;
0090             start();
0091         }
0092         break;
0093     case State::None:
0094         shutdownUntil();
0095         break;
0096     default:
0097         break;
0098     }
0099 }
0100 
0101 void DapBackend::setTaskState(Task state)
0102 {
0103     if (state == m_task)
0104         return;
0105     m_task = state;
0106     Q_EMIT readyForInput(debuggerRunning() && (m_task != Busy));
0107     if ((m_task == Idle) && !m_commandQueue.isEmpty()) {
0108         issueCommand(m_commandQueue.takeFirst());
0109     }
0110 }
0111 
0112 void DapBackend::pushRequest()
0113 {
0114     ++m_requests;
0115     setTaskState(Busy);
0116 }
0117 
0118 void DapBackend::popRequest()
0119 {
0120     if (m_requests > 0) {
0121         --m_requests;
0122     }
0123     setTaskState(m_requests > 0 ? Busy : Idle);
0124 }
0125 
0126 dap::settings::ClientSettings &DapBackend::target2dap(const DAPTargetConf &target)
0127 {
0128     // resolve dynamic port
0129     auto settings = dap::settings::resolveClientPort(target.dapSettings->settings);
0130     if (!settings) {
0131         settings = target.dapSettings->settings;
0132     }
0133     // resolve user variables
0134     auto varMap = dap::settings::findReferences(*settings);
0135     for (auto it = target.variables.constBegin(); it != target.variables.constEnd(); ++it) {
0136         varMap[it.key()] = it.value().toJsonValue();
0137         if (it.key() == QStringLiteral("file")) {
0138             m_file = it.value().toString();
0139         } else if (it.key() == QStringLiteral("workdir")) {
0140             m_workDir = it.value().toString();
0141         }
0142     }
0143 
0144     const auto out = json::resolve(*settings, varMap);
0145 
0146     Q_EMIT outputText(QString::fromLocal8Bit(QJsonDocument(out).toJson()) + QStringLiteral("\n"));
0147 
0148     m_settings = dap::settings::ClientSettings(out);
0149     return *m_settings;
0150 }
0151 
0152 void DapBackend::start()
0153 {
0154     if ((m_state != None) && (m_state != PostMortem)) {
0155         KMessageBox::error(nullptr, i18n("A debugging session is on course. Please, use re-run or stop the current session."));
0156         return;
0157     }
0158     unsetClient();
0159 
0160     m_client = new dap::Client(*m_settings, this);
0161 
0162     Q_EMIT debuggerCapabilitiesChanged();
0163 
0164     // connect
0165     connect(m_client->bus(), &dap::Bus::error, this, &DapBackend::onError);
0166 
0167     connect(m_client, &dap::Client::finished, this, &DapBackend::onServerFinished);
0168     connect(m_client, &dap::Client::failed, [this] {
0169         onError(i18n("DAP backend failed"));
0170     });
0171 
0172     connect(m_client, &dap::Client::serverDisconnected, this, &DapBackend::onServerDisconnected);
0173     connect(m_client, &dap::Client::debuggeeExited, this, &DapBackend::onProgramEnded);
0174     connect(m_client, &dap::Client::debuggeeTerminated, this, &DapBackend::onTerminated);
0175     connect(m_client, &dap::Client::debuggeeStopped, this, &DapBackend::onStopped);
0176     connect(m_client, &dap::Client::capabilitiesReceived, this, &DapBackend::onCapabilitiesReceived);
0177     connect(m_client, &dap::Client::debuggeeRunning, this, &DapBackend::onRunning);
0178     connect(m_client, &dap::Client::debuggeeContinued, this, &DapBackend::onContinuedEvent);
0179     connect(m_client, &dap::Client::debuggingProcess, this, &DapBackend::onDebuggingProcess);
0180 
0181     connect(m_client, &dap::Client::threads, this, &DapBackend::onThreads);
0182     connect(m_client, &dap::Client::stackTrace, this, &DapBackend::onStackTrace);
0183     connect(m_client, &dap::Client::initialized, this, &DapBackend::onInitialized);
0184     connect(m_client, &dap::Client::errorResponse, this, &DapBackend::onErrorResponse);
0185     connect(m_client, &dap::Client::outputProduced, this, &DapBackend::onOutputProduced);
0186 
0187     connect(m_client, &dap::Client::threadChanged, this, &DapBackend::onThreadEvent);
0188     connect(m_client, &dap::Client::moduleChanged, this, &DapBackend::onModuleEvent);
0189     connect(m_client, &dap::Client::scopes, this, &DapBackend::onScopes);
0190     connect(m_client, &dap::Client::variables, this, &DapBackend::onVariables);
0191     connect(m_client, &dap::Client::modules, this, &DapBackend::onModules);
0192     connect(m_client, &dap::Client::sourceBreakpoints, this, &DapBackend::onSourceBreakpoints);
0193     connect(m_client, &dap::Client::breakpointChanged, this, &DapBackend::onBreakpointEvent);
0194     connect(m_client, &dap::Client::expressionEvaluated, this, &DapBackend::onExpressionEvaluated);
0195     connect(m_client, &dap::Client::gotoTargets, this, &DapBackend::onGotoTargets);
0196 
0197     m_client->bus()->start(m_settings->busSettings);
0198 }
0199 
0200 void DapBackend::runDebugger(const DAPTargetConf &conf)
0201 {
0202     m_targetName = conf.targetName;
0203 
0204     target2dap(conf);
0205 
0206     start();
0207 }
0208 
0209 void DapBackend::onTerminated()
0210 {
0211     Q_EMIT outputText(printEvent(i18n("program terminated")));
0212     if (m_state < Terminated) {
0213         setState(Terminated);
0214     }
0215 }
0216 
0217 bool DapBackend::tryDisconnect()
0218 {
0219     if (!isConnectedState()) {
0220         return false;
0221     }
0222 
0223     Q_EMIT outputError(newLine(i18n("requesting disconnection")));
0224     if (m_client) {
0225         m_client->requestDisconnect();
0226     } else {
0227         setState(Disconnected);
0228     }
0229     return true;
0230 }
0231 
0232 void DapBackend::cmdShutdown()
0233 {
0234     if (m_state == None)
0235         return;
0236 
0237     Q_EMIT outputError(newLine(i18n("requesting shutdown")));
0238     if (m_client) {
0239         m_client->bus()->close();
0240     } else {
0241         setState(None);
0242     }
0243 }
0244 
0245 bool DapBackend::tryTerminate()
0246 {
0247     if (!isRunningState())
0248         return false;
0249 
0250     if (!m_client->supportsTerminate()) {
0251         setState(Terminated);
0252         return false;
0253     }
0254 
0255     m_client->requestTerminate();
0256     return true;
0257 }
0258 
0259 void DapBackend::onError(const QString &message)
0260 {
0261     Q_EMIT outputError(newLine(i18n("DAP backend: %1", message)));
0262     setState(PostMortem);
0263 }
0264 
0265 void DapBackend::onStopped(const dap::StoppedEvent &info)
0266 {
0267     setState(Stopped);
0268     m_currentThread = m_watchedThread = info.threadId;
0269 
0270     QStringList text = {i18n("stopped (%1).", info.reason)};
0271     if (info.description) {
0272         text << QStringLiteral(" (%1)").arg(info.description.value());
0273     }
0274 
0275     if (info.threadId) {
0276         text << QStringLiteral(" ");
0277         if (info.allThreadsStopped && info.allThreadsStopped.value()) {
0278             text << i18n("Active thread: %1 (all threads stopped).", info.threadId.value());
0279         } else {
0280             text << i18n("Active thread: %1.", info.threadId.value());
0281         }
0282     }
0283 
0284     if (info.hitBreakpointsIds) {
0285         text << QStringLiteral(" ") << i18n("Breakpoint(s) reached:");
0286         for (const int b : info.hitBreakpointsIds.value()) {
0287             text << QStringLiteral(" [%1] ").arg(b);
0288         }
0289     }
0290 
0291     Q_EMIT outputText(printEvent(text.join(QString())));
0292 
0293     // request stack trace
0294     if (m_currentThread) {
0295         pushRequest();
0296         m_client->requestStackTrace(*m_currentThread);
0297     }
0298 
0299     // request threads
0300     pushRequest();
0301     m_client->requestThreads();
0302 }
0303 
0304 void DapBackend::onContinuedEvent(const dap::ContinuedEvent &info)
0305 {
0306     resetState();
0307     Q_EMIT outputText(printEvent(i18n("(continued) thread %1", QString::number(info.threadId))));
0308     if (info.allThreadsContinued) {
0309         Q_EMIT outputText(QStringLiteral(" (%1)").arg(i18n("all threads continued")));
0310     }
0311 }
0312 
0313 void DapBackend::onRunning()
0314 {
0315     setState(State::Running);
0316     Q_EMIT outputText(printEvent(i18n("(running)")));
0317     // if there is not thread, request in case pause is called
0318     if (!m_currentThread) {
0319         pushRequest();
0320         m_client->requestThreads();
0321     }
0322 }
0323 
0324 void DapBackend::onThreads(const QList<dap::Thread> &threads)
0325 {
0326     if (!m_currentThread) {
0327         if (!threads.isEmpty()) {
0328             m_currentThread = threads[0].id;
0329         }
0330     } else {
0331         Q_EMIT threadInfo(dap::Thread(-1), false);
0332         for (const auto &thread : threads) {
0333             Q_EMIT threadInfo(thread, thread.id == m_currentThread.value_or(-1));
0334         }
0335     }
0336     popRequest();
0337 }
0338 
0339 void DapBackend::informStackFrame()
0340 {
0341     if (!m_queryLocals)
0342         return;
0343 
0344     int level = 0;
0345 
0346     for (const auto &frame : m_frames) {
0347         // emit stackFrameInfo
0348         // name at source:line
0349         QString info = frame.name;
0350         if (frame.source) {
0351             info = QStringLiteral("%1 at %2:%3").arg(info).arg(frame.source->path).arg(frame.line);
0352         }
0353 
0354         Q_EMIT stackFrameInfo(level, info);
0355 
0356         ++level;
0357     }
0358     Q_EMIT stackFrameInfo(-1, QString());
0359 }
0360 
0361 void DapBackend::onStackTrace(const int /* threadId */, const dap::StackTraceInfo &info)
0362 {
0363     m_currentFrame = std::nullopt;
0364     m_frames = info.stackFrames;
0365     informStackFrame();
0366 
0367     if (!m_frames.isEmpty()) {
0368         changeStackFrame(0);
0369     }
0370     popRequest();
0371 }
0372 
0373 void DapBackend::clearBreakpoints()
0374 {
0375     for (const auto &[url, breakpoints] : m_breakpoints) {
0376         const auto path = QUrl::fromLocalFile(url);
0377         for (const auto &bp : breakpoints) {
0378             if (bp && bp->line) {
0379                 Q_EMIT breakPointCleared(path, bp->line.value() - 1);
0380             }
0381         }
0382     }
0383     Q_EMIT clearBreakpointMarks();
0384 }
0385 
0386 void DapBackend::onServerDisconnected()
0387 {
0388     if (!isConnectedState()) {
0389         return;
0390     }
0391 
0392     clearBreakpoints();
0393 
0394     if (!m_restart) {
0395         m_breakpoints.clear();
0396         m_wantedBreakpoints.clear();
0397     }
0398 
0399     setState(Disconnected);
0400 }
0401 
0402 void DapBackend::onServerFinished()
0403 {
0404     Q_EMIT outputError(newLine(i18n("*** connection with server closed ***")));
0405 
0406     setState(PostMortem);
0407 }
0408 
0409 void DapBackend::onProgramEnded(int exitCode)
0410 {
0411     Q_EMIT outputText(printEvent(i18n("program exited with code %1", exitCode)));
0412 }
0413 
0414 void DapBackend::onInitialized()
0415 {
0416     Q_EMIT clearBreakpointMarks();
0417     if (!m_wantedBreakpoints.empty()) {
0418         for (const auto &[url, breakpoints] : m_wantedBreakpoints) {
0419             m_breakpoints[url].clear();
0420             pushRequest();
0421             m_client->requestSetBreakpoints(url, breakpoints, true);
0422         }
0423     }
0424     shutdownUntil(PostMortem);
0425     Q_EMIT outputText(newLine(i18n("*** waiting for user actions ***")));
0426 }
0427 
0428 void DapBackend::onErrorResponse(const QString &summary, const std::optional<dap::Message> &message)
0429 {
0430     Q_EMIT outputError(newLine(i18n("error on response: %1", summary)));
0431     if (message) {
0432         Q_EMIT outputError(QStringLiteral(" {code %1: %2}").arg(message->id).arg(message->format));
0433     }
0434 }
0435 
0436 void DapBackend::onOutputProduced(const dap::Output &output)
0437 {
0438     if (output.output.isEmpty())
0439         return;
0440 
0441     if (output.isSpecialOutput() && !output.output.isEmpty()) {
0442         QString channel;
0443         switch (output.category) {
0444         case dap::Output::Category::Important:
0445             channel = i18n("important");
0446             break;
0447         case dap::Output::Category::Telemetry:
0448             channel = i18n("telemetry");
0449             break;
0450         default:
0451             break;
0452         }
0453         if (channel.isEmpty()) {
0454             Q_EMIT(outputError(newLine(output.output)));
0455         } else {
0456             Q_EMIT(outputError(QStringLiteral("\n(%1) %2").arg(channel).arg(output.output)));
0457         }
0458     } else {
0459         Q_EMIT debuggeeOutput(output);
0460     }
0461 }
0462 
0463 void DapBackend::onDebuggingProcess(const dap::ProcessInfo &info)
0464 {
0465     QString out;
0466     if (info.systemProcessId) {
0467         out = i18n("debugging process [%1] %2", QString::number(info.systemProcessId.value()), info.name);
0468     } else {
0469         out = i18n("debugging process %1", info.name);
0470     }
0471     if (info.startMethod) {
0472         out += QStringLiteral(" (%1)").arg(i18n("Start method: %1", info.startMethod.value()));
0473     }
0474     Q_EMIT outputText(printEvent(out));
0475 }
0476 
0477 void DapBackend::onThreadEvent(const dap::ThreadEvent &info)
0478 {
0479     Q_EMIT outputText(printEvent(QStringLiteral("(%1) %2").arg(info.reason).arg(i18n("thread %1", QString::number(info.threadId)))));
0480 }
0481 
0482 QString printModule(const dap::Module &module)
0483 {
0484     QString out = QStringLiteral("module %2: %1").arg(module.name);
0485     if (module.id_int) {
0486         out = out.arg(module.id_int.value());
0487     } else if (module.id_str) {
0488         out = out.arg(module.id_str.value());
0489     }
0490     if (module.isOptimized && module.isOptimized.value()) {
0491         out += QStringLiteral(" [optimized]");
0492     }
0493     if (module.path) {
0494         out += QStringLiteral(": %1").arg(module.path.value());
0495     }
0496     return out;
0497 }
0498 
0499 QString printBreakpoint(const QString &sourceId, const dap::SourceBreakpoint &def, const std::optional<dap::Breakpoint> &bp, const int bId)
0500 {
0501     QString txtId = QStringLiteral("%1.").arg(bId);
0502     if (!bp) {
0503         txtId += QStringLiteral(" ");
0504     } else {
0505         if (bp->verified) {
0506             txtId += bp->id ? QString::number(bp->id.value()) : QStringLiteral("?");
0507         } else {
0508             txtId += QStringLiteral("!");
0509         }
0510     }
0511 
0512     QStringList out = {QStringLiteral("[%1] %2: %3").arg(txtId).arg(sourceId).arg(def.line)};
0513     if (def.column) {
0514         out << QStringLiteral(", %1").arg(def.column.value());
0515     }
0516     if (bp) {
0517         if (bp->line) {
0518             out << QStringLiteral("->%1").arg(bp->line.value());
0519             if (bp->endLine) {
0520                 out << QStringLiteral("-%1").arg(bp->endLine.value());
0521             }
0522             if (bp->column) {
0523                 out << QStringLiteral(",%1").arg(bp->column.value());
0524                 if (bp->endColumn) {
0525                     out << QStringLiteral("-%1").arg(bp->endColumn.value());
0526                 }
0527             }
0528         }
0529     }
0530     if (def.condition) {
0531         out << QStringLiteral(" when {%1}").arg(def.condition.value());
0532     }
0533     if (def.hitCondition) {
0534         out << QStringLiteral(" hitcount {%1}").arg(def.hitCondition.value());
0535     }
0536     if (bp && bp->message) {
0537         out << QStringLiteral(" (%1)").arg(bp->message.value());
0538     }
0539 
0540     return out.join(QString());
0541 }
0542 
0543 void DapBackend::onModuleEvent(const dap::ModuleEvent &info)
0544 {
0545     Q_EMIT outputText(printEvent(QStringLiteral("(%1) %2").arg(info.reason).arg(printModule(info.module))));
0546 }
0547 
0548 void DapBackend::changeScope(int scopeId)
0549 {
0550     if (!m_client)
0551         return;
0552 
0553     if (m_currentScope && (*m_currentScope == scopeId))
0554         return;
0555 
0556     m_currentScope = scopeId;
0557 
0558     if (m_queryLocals) {
0559         pushRequest();
0560         m_client->requestVariables(scopeId);
0561     }
0562 }
0563 
0564 void DapBackend::onScopes(const int /*frameId*/, const QList<dap::Scope> &scopes)
0565 {
0566     std::optional<int> activeScope = std::nullopt;
0567 
0568     for (const auto &scope : scopes) {
0569         if (!m_currentScope || (*m_currentScope == scope.variablesReference)) {
0570             activeScope = scope.variablesReference;
0571             break;
0572         }
0573     }
0574 
0575     if (activeScope) {
0576         m_currentScope = activeScope;
0577     } else if (!scopes.isEmpty()) {
0578         m_currentScope = scopes[0].variablesReference;
0579     } else {
0580         m_currentScope = std::nullopt;
0581     }
0582 
0583     if (m_queryLocals) {
0584         // preload variables
0585         pushRequest();
0586         m_client->requestVariables(*m_currentScope);
0587         Q_EMIT scopesInfo(scopes, m_currentScope);
0588     }
0589     popRequest();
0590 }
0591 
0592 void DapBackend::onVariables(const int variablesReference, const QList<dap::Variable> &variables)
0593 {
0594     if (!m_queryLocals) {
0595         popRequest();
0596         return;
0597     }
0598 
0599     const bool rootLevel = m_currentScope && (*m_currentScope == variablesReference);
0600     if (rootLevel) {
0601         Q_EMIT variableScopeOpened();
0602     }
0603 
0604     for (const auto &variable : variables) {
0605         Q_EMIT variableInfo(rootLevel ? 0 : variablesReference, variable);
0606 
0607         if (rootLevel && (variable.variablesReference > 0)) {
0608             // TODO don't retrieve expensive variables
0609             // retrieve inner info
0610             pushRequest();
0611             m_client->requestVariables(variable.variablesReference);
0612         }
0613     }
0614 
0615     if (m_requests == 0) {
0616         Q_EMIT variableScopeClosed();
0617     }
0618 
0619     popRequest();
0620 }
0621 
0622 void DapBackend::onModules(const dap::ModulesInfo &modules)
0623 {
0624     for (const auto &mod : modules.modules) {
0625         Q_EMIT outputText(newLine(printModule(mod)));
0626     }
0627     popRequest();
0628 }
0629 
0630 void DapBackend::informBreakpointAdded(const QString &path, const dap::Breakpoint &bpoint)
0631 {
0632     if (bpoint.line) {
0633         Q_EMIT outputText(QStringLiteral("\n%1 %2:%3\n").arg(i18n("breakpoint set")).arg(path).arg(bpoint.line.value()));
0634         // zero based line expected
0635         Q_EMIT breakPointSet(QUrl::fromLocalFile(path), bpoint.line.value() - 1);
0636     }
0637 }
0638 
0639 void DapBackend::informBreakpointRemoved(const QString &path, int line)
0640 {
0641     Q_EMIT outputText(QStringLiteral("\n%1 %2:%3\n").arg(i18n("breakpoint cleared")).arg(path).arg(line));
0642     // zero based line expected
0643     Q_EMIT breakPointCleared(QUrl::fromLocalFile(path), line - 1);
0644 }
0645 
0646 void DapBackend::onSourceBreakpoints(const QString &path, int reference, const std::optional<QList<dap::Breakpoint>> &breakpoints)
0647 {
0648     if (!breakpoints) {
0649         popRequest();
0650         return;
0651     }
0652 
0653     const auto id = dap::Source::getUnifiedId(path, reference);
0654     if (id.isEmpty()) {
0655         popRequest();
0656         return;
0657     }
0658 
0659     if (m_breakpoints.find(id) == m_breakpoints.end()) {
0660         m_breakpoints[id] = QList<std::optional<dap::Breakpoint>>();
0661         m_breakpoints[id].reserve(breakpoints->size());
0662     }
0663 
0664     // if runToCursor is pending, a bpoint with hit condition has been added
0665     const bool withRunToCursor = m_runToCursor && (m_runToCursor->path == path);
0666     bool mustContinue = false;
0667     const auto &wanted = m_wantedBreakpoints[path];
0668 
0669     auto &table = m_breakpoints[id];
0670     int pointIdx = 0;
0671     const int last = table.size();
0672     for (const auto &point : *breakpoints) {
0673         if (pointIdx >= last) {
0674             // bpoint added
0675             table << point;
0676             informBreakpointAdded(id, point);
0677         } else if (!table[pointIdx]) {
0678             // bpoint added
0679             table[pointIdx] = point;
0680             informBreakpointAdded(id, point);
0681         }
0682         if (withRunToCursor) {
0683             if (wanted[pointIdx].line == m_runToCursor->line) {
0684                 mustContinue = point.line.has_value();
0685                 m_runToCursor = std::nullopt;
0686             }
0687         }
0688         ++pointIdx;
0689     }
0690 
0691     popRequest();
0692 
0693     if (mustContinue) {
0694         slotContinue();
0695     }
0696 }
0697 
0698 void DapBackend::onBreakpointEvent(const dap::BreakpointEvent &info)
0699 {
0700     QStringList parts = {i18n("(%1) breakpoint", info.reason)};
0701     if (info.breakpoint.source) {
0702         parts << QStringLiteral(" ") << info.breakpoint.source->unifiedId();
0703     }
0704     if (info.breakpoint.line) {
0705         parts << QStringLiteral(":%1").arg(info.breakpoint.line.value());
0706     }
0707 
0708     Q_EMIT outputText(printEvent(parts.join(QString())));
0709 }
0710 
0711 void DapBackend::onExpressionEvaluated(const QString &expression, const std::optional<dap::EvaluateInfo> &info)
0712 {
0713     QString result;
0714     if (info) {
0715         result = info->result;
0716     } else {
0717         result = i18n("<not evaluated>");
0718     }
0719 
0720     Q_EMIT outputText(QStringLiteral("\n(%1) = %2").arg(expression).arg(result));
0721 
0722     popRequest();
0723 }
0724 
0725 void DapBackend::onGotoTargets(const dap::Source &source, const int, const QList<dap::GotoTarget> &targets)
0726 {
0727     if (!targets.isEmpty() && m_currentThread) {
0728         Q_EMIT outputError(newLine(QStringLiteral("jump target %1:%2 (%3)").arg(source.unifiedId()).arg(targets[0].line).arg(targets[0].label)));
0729         m_client->requestGoto(*m_currentThread, targets[0].id);
0730     }
0731     popRequest();
0732 }
0733 
0734 void DapBackend::onCapabilitiesReceived(const dap::Capabilities &capabilities)
0735 {
0736     // can set breakpoints now
0737     setState(State::Initializing);
0738 
0739     QStringList text = {QStringLiteral("\n%1:\n").arg(i18n("server capabilities"))};
0740     const QString tpl = QStringLiteral("* %1 \n");
0741     const auto format = [](const QString &field, bool value) {
0742         return QStringLiteral("* %1: %2\n").arg(field).arg(value ? i18n("supported") : i18n("unsupported"));
0743     };
0744 
0745     text << format(i18n("conditional breakpoints"), capabilities.supportsConditionalBreakpoints)
0746          << format(i18n("function breakpoints"), capabilities.supportsFunctionBreakpoints)
0747          << format(i18n("hit conditional breakpoints"), capabilities.supportsHitConditionalBreakpoints)
0748          << format(i18n("log points"), capabilities.supportsLogPoints) << format(i18n("modules request"), capabilities.supportsModulesRequest)
0749          << format(i18n("goto targets request"), capabilities.supportsGotoTargetsRequest)
0750          << format(i18n("terminate request"), capabilities.supportsTerminateRequest)
0751          << format(i18n("terminate debuggee"), capabilities.supportTerminateDebuggee);
0752 
0753     Q_EMIT outputText(text.join(QString()));
0754 }
0755 
0756 bool DapBackend::supportsMovePC() const
0757 {
0758     return isRunningState() && m_client && m_client->adapterCapabilities().supportsGotoTargetsRequest;
0759 }
0760 
0761 bool DapBackend::supportsRunToCursor() const
0762 {
0763     return isAttachedState() && m_client && m_client->adapterCapabilities().supportsHitConditionalBreakpoints;
0764 }
0765 
0766 bool DapBackend::isConnectedState() const
0767 {
0768     return m_client && (m_state != None) && (m_state != Disconnected) && (m_state != PostMortem);
0769 }
0770 
0771 bool DapBackend::isAttachedState() const
0772 {
0773     return isConnectedState() && (m_state != Terminated);
0774 }
0775 
0776 bool DapBackend::isRunningState() const
0777 {
0778     return (m_state == Running) || (m_state == Stopped);
0779 }
0780 
0781 bool DapBackend::canSetBreakpoints() const
0782 {
0783     return isAttachedState();
0784 }
0785 
0786 bool DapBackend::canMove() const
0787 {
0788     return isRunningState();
0789 }
0790 
0791 bool DapBackend::canContinue() const
0792 {
0793     return (m_state == Initializing) || (m_state == Stopped);
0794 }
0795 
0796 bool DapBackend::debuggerRunning() const
0797 {
0798     return m_client && (m_state != None);
0799 }
0800 
0801 bool DapBackend::debuggerBusy() const
0802 {
0803     return debuggerRunning() && (m_task == Busy);
0804 }
0805 
0806 std::optional<int> DapBackend::findBreakpoint(const QString &path, int line) const
0807 {
0808     if (m_breakpoints.find(path) == m_breakpoints.end())
0809         return std::nullopt;
0810 
0811     const auto &bpoints = m_breakpoints.at(path);
0812     int index = 0;
0813     for (const auto &bp : bpoints) {
0814         if (bp && bp->line && (line == bp->line)) {
0815             return index;
0816         }
0817         ++index;
0818     }
0819     return std::nullopt;
0820 }
0821 
0822 std::optional<int> DapBackend::findBreakpointIntent(const QString &path, int line) const
0823 {
0824     if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) {
0825         return std::nullopt;
0826     }
0827 
0828     const auto &bpoints = m_wantedBreakpoints.at(path);
0829     int index = 0;
0830     for (const auto &bp : bpoints) {
0831         if (bp.line == line) {
0832             return index;
0833         }
0834         ++index;
0835     }
0836     return std::nullopt;
0837 }
0838 
0839 bool DapBackend::hasBreakpoint(QUrl const &url, int line) const
0840 {
0841     return findBreakpoint(*resolveFilename(url.path()), line).has_value();
0842 }
0843 
0844 void DapBackend::toggleBreakpoint(QUrl const &url, int line)
0845 {
0846     const auto path = resolveOrWarn(url.path());
0847 
0848     if (!removeBreakpoint(path, line)) {
0849         insertBreakpoint(path, line);
0850     }
0851 }
0852 
0853 bool DapBackend::removeBreakpoint(const QString &path, int line)
0854 {
0855     bool informed = false;
0856     // clear all breakpoints in the same line (there can be more than one)
0857     auto index = findBreakpoint(path, line);
0858     while (index) {
0859         m_wantedBreakpoints[path].removeAt(*index);
0860         m_breakpoints[path].removeAt(*index);
0861         if (!informed) {
0862             informBreakpointRemoved(path, line);
0863             informed = true;
0864         }
0865         index = findBreakpoint(path, line);
0866     }
0867     // clear all breakpoint intents in the same line
0868     index = findBreakpointIntent(path, line);
0869     while (index) {
0870         m_wantedBreakpoints[path].removeAt(*index);
0871         if (!informed) {
0872             informBreakpointRemoved(path, line);
0873             informed = true;
0874         }
0875         index = findBreakpointIntent(path, line);
0876     }
0877 
0878     if (!informed) {
0879         return false;
0880     }
0881 
0882     // update breakpoint table for this file
0883     pushRequest();
0884     m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true);
0885 
0886     return true;
0887 }
0888 
0889 void DapBackend::insertBreakpoint(const QString &path, int line)
0890 {
0891     if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) {
0892         m_wantedBreakpoints[path] = {dap::SourceBreakpoint(line)};
0893         m_breakpoints[path] = {std::nullopt};
0894     } else {
0895         m_wantedBreakpoints[path] << dap::SourceBreakpoint(line);
0896         m_breakpoints[path] << std::nullopt;
0897     }
0898 
0899     pushRequest();
0900     m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true);
0901 }
0902 
0903 void DapBackend::movePC(QUrl const &url, int line)
0904 {
0905     if (!m_client)
0906         return;
0907 
0908     if (m_state != State::Stopped)
0909         return;
0910 
0911     if (!m_currentThread)
0912         return;
0913 
0914     if (!m_client->adapterCapabilities().supportsGotoTargetsRequest)
0915         return;
0916 
0917     const auto path = resolveOrWarn(url.path());
0918 
0919     pushRequest();
0920     m_client->requestGotoTargets(path, line);
0921 }
0922 
0923 void DapBackend::runToCursor(QUrl const &url, int line)
0924 {
0925     if (!m_client)
0926         return;
0927 
0928     if (!m_client->adapterCapabilities().supportsHitConditionalBreakpoints)
0929         return;
0930 
0931     const auto path = resolveOrWarn(url.path());
0932 
0933     dap::SourceBreakpoint bp(line);
0934     bp.hitCondition = QStringLiteral("<=1");
0935 
0936     if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) {
0937         m_wantedBreakpoints[path] = {std::move(bp)};
0938         m_breakpoints[path] = {std::nullopt};
0939     } else {
0940         m_wantedBreakpoints[path] << std::move(bp);
0941         m_breakpoints[path] << std::nullopt;
0942     }
0943 
0944     m_runToCursor = Cursor{line, path};
0945     pushRequest();
0946     m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true);
0947 }
0948 
0949 void DapBackend::cmdEval(const QString &cmd)
0950 {
0951     int start = cmd.indexOf(QLatin1Char(' '));
0952 
0953     QString expression;
0954     if (start >= 0) {
0955         expression = cmd.mid(start).trimmed();
0956     }
0957     if (expression.isEmpty()) {
0958         Q_EMIT outputError(newLine(i18n("syntax error: expression not found")));
0959         return;
0960     }
0961 
0962     std::optional<int> frameId = std::nullopt;
0963     if (m_currentFrame)
0964         frameId = m_frames[*m_currentFrame].id;
0965 
0966     pushRequest();
0967     m_client->requestWatch(expression, frameId);
0968 }
0969 
0970 void DapBackend::cmdJump(const QString &cmd)
0971 {
0972     const static QRegularExpression rx_goto(QStringLiteral(R"--(^j[a-z]*\s+(\d+)(?:\s+(\S+))?$)--"));
0973 
0974     const auto match = rx_goto.match(cmd);
0975     if (!match.hasMatch()) {
0976         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
0977         return;
0978     }
0979 
0980     const auto &txtLine = match.captured(1);
0981     bool ok = false;
0982     const int line = txtLine.toInt(&ok);
0983     if (!ok) {
0984         Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine)));
0985         return;
0986     }
0987 
0988     QString path = match.captured(2);
0989     if (path.isNull()) {
0990         if (!m_currentFrame) {
0991             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
0992             return;
0993         }
0994         const auto &frame = this->m_frames[*m_currentFrame];
0995         if (!frame.source) {
0996             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
0997             return;
0998         }
0999         path = frame.source->unifiedId();
1000     }
1001 
1002     this->movePC(QUrl::fromLocalFile(path), line);
1003 }
1004 
1005 void DapBackend::cmdRunToCursor(const QString &cmd)
1006 {
1007     const static QRegularExpression rx_goto(QStringLiteral(R"--(^to?\s+(\d+)(?:\s+(\S+))?$)--"));
1008 
1009     const auto match = rx_goto.match(cmd);
1010     if (!match.hasMatch()) {
1011         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1012         return;
1013     }
1014 
1015     const auto &txtLine = match.captured(1);
1016     bool ok = false;
1017     const int line = txtLine.toInt(&ok);
1018     if (!ok) {
1019         Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine)));
1020         return;
1021     }
1022 
1023     QString path = match.captured(2);
1024     if (path.isNull()) {
1025         if (!m_currentFrame) {
1026             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1027             return;
1028         }
1029         const auto &frame = this->m_frames[*m_currentFrame];
1030         if (!frame.source) {
1031             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1032             return;
1033         }
1034         path = frame.source->unifiedId();
1035     }
1036 
1037     this->runToCursor(QUrl::fromLocalFile(path), line);
1038 }
1039 
1040 void DapBackend::cmdPause(const QString &cmd)
1041 {
1042     if (!m_client)
1043         return;
1044 
1045     const static QRegularExpression rx_pause(QStringLiteral(R"--(^s[a-z]*(?:\s+(\d+))?\s*$)--"));
1046 
1047     const auto match = rx_pause.match(cmd);
1048     if (!match.hasMatch()) {
1049         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1050         return;
1051     }
1052 
1053     const auto &txtThread = match.captured(1);
1054 
1055     int threadId;
1056 
1057     if (!txtThread.isNull()) {
1058         bool ok = false;
1059         threadId = txtThread.toInt(&ok);
1060         if (!ok) {
1061             Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread)));
1062             return;
1063         }
1064     } else if (m_currentThread) {
1065         threadId = *m_currentThread;
1066     } else {
1067         Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd)));
1068         return;
1069     }
1070 
1071     m_client->requestPause(threadId);
1072 }
1073 
1074 void DapBackend::cmdContinue(const QString &cmd)
1075 {
1076     if (!m_client)
1077         return;
1078 
1079     const static QRegularExpression rx_cont(QStringLiteral(R"--(^c[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--"));
1080 
1081     const auto match = rx_cont.match(cmd);
1082     if (!match.hasMatch()) {
1083         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1084         return;
1085     }
1086 
1087     const auto &txtThread = match.captured(QStringLiteral("ID"));
1088 
1089     int threadId;
1090 
1091     if (!txtThread.isNull()) {
1092         bool ok = false;
1093         threadId = txtThread.toInt(&ok);
1094         if (!ok) {
1095             Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread)));
1096             return;
1097         }
1098     } else if (m_currentThread) {
1099         threadId = *m_currentThread;
1100     } else {
1101         Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd)));
1102         return;
1103     }
1104 
1105     const auto only = match.captured(QStringLiteral("ONLY"));
1106 
1107     m_client->requestContinue(threadId, !only.isNull());
1108 }
1109 
1110 void DapBackend::cmdStepIn(const QString &cmd)
1111 {
1112     if (!m_client)
1113         return;
1114 
1115     const static QRegularExpression rx_in(QStringLiteral(R"--(^in?(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--"));
1116 
1117     const auto match = rx_in.match(cmd);
1118     if (!match.hasMatch()) {
1119         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1120         return;
1121     }
1122 
1123     const auto &txtThread = match.captured(QStringLiteral("ID"));
1124 
1125     int threadId;
1126 
1127     if (!txtThread.isNull()) {
1128         bool ok = false;
1129         threadId = txtThread.toInt(&ok);
1130         if (!ok) {
1131             Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread)));
1132             return;
1133         }
1134     } else if (m_currentThread) {
1135         threadId = *m_currentThread;
1136     } else {
1137         Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd)));
1138         return;
1139     }
1140 
1141     const auto only = match.captured(QStringLiteral("ONLY"));
1142 
1143     m_client->requestStepIn(threadId, !only.isNull());
1144 }
1145 
1146 void DapBackend::cmdStepOut(const QString &cmd)
1147 {
1148     if (!m_client)
1149         return;
1150 
1151     const static QRegularExpression rx_out(QStringLiteral(R"--(^o[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--"));
1152 
1153     const auto match = rx_out.match(cmd);
1154     if (!match.hasMatch()) {
1155         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1156         return;
1157     }
1158 
1159     const auto &txtThread = match.captured(QStringLiteral("ID"));
1160 
1161     int threadId;
1162 
1163     if (!txtThread.isNull()) {
1164         bool ok = false;
1165         threadId = txtThread.toInt(&ok);
1166         if (!ok) {
1167             Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread)));
1168             return;
1169         }
1170     } else if (m_currentThread) {
1171         threadId = *m_currentThread;
1172     } else {
1173         Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd)));
1174         return;
1175     }
1176 
1177     const auto only = match.captured(QStringLiteral("ONLY"));
1178 
1179     m_client->requestStepOut(threadId, !only.isNull());
1180 }
1181 
1182 void DapBackend::cmdNext(const QString &cmd)
1183 {
1184     if (!m_client)
1185         return;
1186 
1187     const static QRegularExpression rx_next(QStringLiteral(R"--(^n[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--"));
1188 
1189     const auto match = rx_next.match(cmd);
1190     if (!match.hasMatch()) {
1191         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1192         return;
1193     }
1194 
1195     const auto &txtThread = match.captured(QStringLiteral("ID"));
1196 
1197     int threadId;
1198 
1199     if (!txtThread.isNull()) {
1200         bool ok = false;
1201         threadId = txtThread.toInt(&ok);
1202         if (!ok) {
1203             Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread)));
1204             return;
1205         }
1206     } else if (m_currentThread) {
1207         threadId = *m_currentThread;
1208     } else {
1209         Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd)));
1210         return;
1211     }
1212 
1213     const auto only = match.captured(QStringLiteral("ONLY"));
1214 
1215     m_client->requestNext(threadId, !only.isNull());
1216 }
1217 
1218 void DapBackend::cmdHelp(const QString & /*cmd*/)
1219 {
1220     QStringList out = {QString(), i18n("Available commands:")};
1221 
1222     const QString tpl = QStringLiteral("* %1");
1223 
1224     out << tpl.arg(QStringLiteral("h[elp]")) << tpl.arg(QStringLiteral("p[rint] [expression]")) << tpl.arg(QStringLiteral("c[ontinue] [only] [threadId]"))
1225         << tpl.arg(QStringLiteral("n[ext] [only] [threadId]")) << tpl.arg(QStringLiteral("i[n] [only] [threadId]"))
1226         << tpl.arg(QStringLiteral("o[ut] [only] [threadId]")) << tpl.arg(QStringLiteral("s[top] [threadId]"));
1227 
1228     if (m_client->adapterCapabilities().supportsGotoTargetsRequest) {
1229         out << tpl.arg(QStringLiteral("j[ump] <line> [file]"));
1230     }
1231     if (m_client->adapterCapabilities().supportsHitConditionalBreakpoints) {
1232         out << tpl.arg(QStringLiteral("t[o] <line> [file]"));
1233     }
1234 
1235     if (m_client->adapterCapabilities().supportsModulesRequest) {
1236         out << tpl.arg(QStringLiteral("m[odules]"));
1237     }
1238 
1239     QString bcmd = QStringLiteral("b[reakpoint] <line>");
1240     if (m_client->adapterCapabilities().supportsConditionalBreakpoints) {
1241         bcmd += QStringLiteral(" [when {condition}]");
1242     }
1243     if (m_client->adapterCapabilities().supportsHitConditionalBreakpoints) {
1244         bcmd += QStringLiteral(" [hitcount {condition}]");
1245     }
1246     bcmd += QStringLiteral(" [on <file>]");
1247 
1248     out << tpl.arg(QStringLiteral("bl[ist]")) << tpl.arg(bcmd) << tpl.arg(QStringLiteral("boff <line> [file]"));
1249 
1250     out << tpl.arg(QStringLiteral("w[hereami]"));
1251 
1252     Q_EMIT outputText(out.join(QStringLiteral("\n")));
1253 }
1254 
1255 void DapBackend::cmdListModules(const QString &)
1256 {
1257     if (!m_client)
1258         return;
1259 
1260     if (!m_client->adapterCapabilities().supportsModulesRequest) {
1261         return;
1262     }
1263 
1264     pushRequest();
1265     m_client->requestModules();
1266 }
1267 
1268 void DapBackend::cmdListBreakpoints(const QString &)
1269 {
1270     int bId = 0;
1271     for (const auto &[url, breakpoints] : m_breakpoints) {
1272         const auto &sourceId = url;
1273         const auto &defs = m_wantedBreakpoints[sourceId];
1274         int pointIdx = 0;
1275         for (const auto &b : breakpoints) {
1276             const auto &def = defs[pointIdx];
1277             Q_EMIT outputText(newLine(printBreakpoint(sourceId, def, b, bId)));
1278             ++pointIdx;
1279             ++bId;
1280         }
1281     }
1282 }
1283 
1284 void DapBackend::cmdBreakpointOn(const QString &cmd)
1285 {
1286     static const QRegularExpression rx_bon(
1287         QStringLiteral(R"--(^b\s+(\d+)(?:\s+when\s+\{(?P<COND>.+)\})?(?:\s*hitcont\s+\{(?P<HIT>.+)\})?(?:\s+on\s+(?P<SOURCE>.+))?\s*$)--"));
1288 
1289     const auto match = rx_bon.match(cmd);
1290     if (!match.hasMatch()) {
1291         Q_EMIT outputText(newLine(i18n("syntax error: %1", cmd)));
1292         return;
1293     }
1294     const auto &txtLine = match.captured(1);
1295     bool ok = false;
1296     const int line = txtLine.toInt(&ok);
1297     if (!ok) {
1298         Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine)));
1299         return;
1300     }
1301 
1302     dap::SourceBreakpoint bp(line);
1303 
1304     bp.condition = match.captured(QStringLiteral("COND"));
1305     if (bp.condition->isNull()) {
1306         bp.condition = std::nullopt;
1307     } else if (!m_client->adapterCapabilities().supportsConditionalBreakpoints) {
1308         Q_EMIT outputError(newLine(i18n("conditional breakpoints are not supported by the server")));
1309         return;
1310     }
1311 
1312     bp.hitCondition = match.captured(QStringLiteral("HIT"));
1313     if (bp.hitCondition->isNull()) {
1314         bp.hitCondition = std::nullopt;
1315     } else if (!m_client->adapterCapabilities().supportsHitConditionalBreakpoints) {
1316         Q_EMIT outputError(newLine(i18n("hit conditional breakpoints are not supported by the server")));
1317         return;
1318     }
1319     QString path = match.captured(QStringLiteral("SOURCE"));
1320     if (path.isNull()) {
1321         if (!m_currentFrame) {
1322             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1323             return;
1324         }
1325         const auto &frame = this->m_frames[*m_currentFrame];
1326         if (!frame.source) {
1327             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1328             return;
1329         }
1330         path = resolveOrWarn(frame.source->unifiedId());
1331     } else {
1332         path = resolveOrWarn(path);
1333     }
1334 
1335     if (findBreakpoint(path, bp.line) || findBreakpointIntent(path, bp.line)) {
1336         Q_EMIT outputError(newLine(i18n("line %1 already has a breakpoint", txtLine)));
1337         return;
1338     }
1339 
1340     m_wantedBreakpoints[path] << std::move(bp);
1341     m_breakpoints[path] << std::nullopt;
1342 
1343     pushRequest();
1344     m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true);
1345 }
1346 
1347 void DapBackend::cmdBreakpointOff(const QString &cmd)
1348 {
1349     const static QRegularExpression rx_boff(QStringLiteral(R"--(^bo[a-z]*?\s+(\d+)(?:\s+(\S+))?$)--"));
1350 
1351     const auto match = rx_boff.match(cmd);
1352     if (!match.hasMatch()) {
1353         Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd)));
1354         return;
1355     }
1356 
1357     const auto &txtLine = match.captured(1);
1358     bool ok = false;
1359     const int line = txtLine.toInt(&ok);
1360     if (!ok) {
1361         Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine)));
1362         return;
1363     }
1364 
1365     QString path = match.captured(2);
1366     if (path.isNull()) {
1367         if (!m_currentFrame) {
1368             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1369             return;
1370         }
1371         const auto &frame = this->m_frames[*m_currentFrame];
1372         if (!frame.source) {
1373             Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd)));
1374             return;
1375         }
1376         path = frame.source->unifiedId();
1377     }
1378     path = resolveOrWarn(path);
1379 
1380     if (!removeBreakpoint(path, line)) {
1381         Q_EMIT outputError(newLine(i18n("breakpoint not found (%1:%2)", path, line)));
1382     }
1383 }
1384 
1385 void DapBackend::cmdWhereami(const QString &)
1386 {
1387     QStringList parts = {newLine(i18n("Current thread: "))};
1388 
1389     if (m_currentThread) {
1390         parts << QString::number(*m_currentThread);
1391     } else {
1392         parts << i18n("none");
1393     }
1394 
1395     parts << newLine(i18n("Current frame: "));
1396     if (m_currentFrame) {
1397         parts << QString::number(*m_currentFrame);
1398     } else {
1399         parts << i18n("none");
1400     }
1401 
1402     parts << newLine(i18n("Session state: "));
1403     switch (m_state) {
1404     case Initializing:
1405         parts << i18n("initializing");
1406         break;
1407     case Running:
1408         parts << i18n("running");
1409         break;
1410     case Stopped:
1411         parts << i18n("stopped");
1412         break;
1413     case Terminated:
1414         parts << i18n("terminated");
1415         break;
1416     case Disconnected:
1417         parts << i18n("disconnected");
1418         break;
1419     case PostMortem:
1420         parts << i18n("post mortem");
1421         break;
1422     default:
1423         parts << i18n("none");
1424         break;
1425     }
1426 
1427     Q_EMIT outputText(parts.join(QString()));
1428 }
1429 
1430 void DapBackend::issueCommand(QString const &command)
1431 {
1432     if (!m_client)
1433         return;
1434 
1435     if (m_task == Busy) {
1436         m_commandQueue << command;
1437         return;
1438     }
1439 
1440     QString cmd = command.trimmed();
1441 
1442     if (cmd.isEmpty())
1443         return;
1444 
1445     Q_EMIT outputText(QStringLiteral("\n(dap) %1").arg(command));
1446 
1447     if (cmd.startsWith(QLatin1Char('h'))) {
1448         cmdHelp(cmd);
1449     } else if (cmd.startsWith(QLatin1Char('c'))) {
1450         cmdContinue(cmd);
1451     } else if (cmd.startsWith(QLatin1Char('n'))) {
1452         cmdNext(cmd);
1453     } else if (cmd.startsWith(QLatin1Char('o'))) {
1454         cmdStepOut(cmd);
1455     } else if (cmd.startsWith(QLatin1Char('i'))) {
1456         cmdStepIn(cmd);
1457     } else if (cmd.startsWith(QLatin1Char('p'))) {
1458         cmdEval(cmd);
1459     } else if (cmd.startsWith(QLatin1Char('j'))) {
1460         cmdJump(cmd);
1461     } else if (cmd.startsWith(QLatin1Char('t'))) {
1462         cmdRunToCursor(cmd);
1463     } else if (cmd.startsWith(QLatin1Char('m'))) {
1464         cmdListModules(cmd);
1465     } else if (cmd.startsWith(QStringLiteral("bl"))) {
1466         cmdListBreakpoints(cmd);
1467     } else if (cmd.startsWith(QStringLiteral("bo"))) {
1468         cmdBreakpointOff(cmd);
1469     } else if (cmd.startsWith(QLatin1Char('b'))) {
1470         cmdBreakpointOn(cmd);
1471     } else if (cmd.startsWith(QLatin1Char('s'))) {
1472         cmdPause(cmd);
1473     } else if (cmd.startsWith(QLatin1Char('w'))) {
1474         cmdWhereami(cmd);
1475     } else {
1476         Q_EMIT outputError(newLine(i18n("command not found")));
1477     }
1478 }
1479 
1480 QString DapBackend::targetName() const
1481 {
1482     return m_targetName;
1483 }
1484 
1485 void DapBackend::setFileSearchPaths(const QStringList & /*paths*/)
1486 {
1487     // TODO
1488 }
1489 
1490 void DapBackend::slotInterrupt()
1491 {
1492     if (!isRunningState()) {
1493         return;
1494     }
1495 
1496     if (!m_currentThread) {
1497         Q_EMIT outputError(newLine(i18n("missing thread id")));
1498         return;
1499     }
1500 
1501     m_client->requestPause(*m_currentThread);
1502 }
1503 
1504 void DapBackend::slotStepInto()
1505 {
1506     if (!m_client)
1507         return;
1508 
1509     if (m_state != State::Stopped)
1510         return;
1511 
1512     if (!m_currentThread)
1513         return;
1514 
1515     m_client->requestStepIn(*m_currentThread);
1516 }
1517 
1518 void DapBackend::slotStepOut()
1519 {
1520     if (!m_client)
1521         return;
1522 
1523     if (m_state != State::Stopped)
1524         return;
1525 
1526     if (!m_currentThread)
1527         return;
1528 
1529     m_client->requestStepOut(*m_currentThread);
1530 }
1531 
1532 void DapBackend::slotStepOver()
1533 {
1534     if (!m_client)
1535         return;
1536 
1537     if (m_state != State::Stopped)
1538         return;
1539 
1540     if (!m_currentThread)
1541         return;
1542 
1543     m_client->requestNext(*m_currentThread);
1544 }
1545 
1546 void DapBackend::slotContinue()
1547 {
1548     if (!isAttachedState())
1549         return;
1550 
1551     if (m_state == State::Initializing) {
1552         m_client->requestConfigurationDone();
1553     } else if (m_currentThread) {
1554         m_client->requestContinue(*m_currentThread);
1555     }
1556 }
1557 
1558 void DapBackend::shutdownUntil(std::optional<State> state)
1559 {
1560     if (!state) {
1561         m_shutdown.target = std::nullopt;
1562         m_shutdown.userAction = std::nullopt;
1563     } else if (!m_shutdown.target || (*state > m_shutdown.target)) {
1564         // propagate until the deepest state
1565         m_shutdown.target = *state;
1566     }
1567 }
1568 
1569 bool DapBackend::continueShutdown() const
1570 {
1571     return m_restart || (m_shutdown.target && (m_shutdown.target > m_state));
1572 }
1573 
1574 void DapBackend::slotKill()
1575 {
1576     if (!isConnectedState()) {
1577         setState(None);
1578         Q_EMIT readyForInput(false);
1579         Q_EMIT gdbEnded();
1580         return;
1581     }
1582     // if it is running, interrupt instead of killing
1583     if (isRunningState() && !this->canContinue()) {
1584         slotInterrupt();
1585         return;
1586     }
1587 
1588     if (!m_shutdown.userAction) {
1589         if (isRunningState()) {
1590             shutdownUntil(PostMortem);
1591             tryTerminate();
1592         } else {
1593             shutdownUntil(PostMortem);
1594             tryDisconnect();
1595         }
1596     } else {
1597         switch (*m_shutdown.userAction) {
1598         case Polite:
1599             // try and shutdown server
1600             m_shutdown.userAction = Force;
1601             cmdShutdown();
1602             break;
1603         case Force:
1604             // kill server
1605             Q_EMIT outputError(newLine(i18n("killing backend")));
1606             unsetClient();
1607             break;
1608         }
1609     }
1610 }
1611 
1612 void DapBackend::slotReRun()
1613 {
1614     if (!m_client && m_settings) {
1615         start();
1616         return;
1617     }
1618 
1619     m_restart = true;
1620     slotKill();
1621 }
1622 
1623 void DapBackend::slotQueryLocals(bool display)
1624 {
1625     m_queryLocals = display;
1626 
1627     if (!display)
1628         return;
1629 
1630     if (!m_client)
1631         return;
1632 
1633     if (m_currentFrame) {
1634         informStackFrame();
1635         pushRequest();
1636         m_client->requestScopes(m_frames[*m_currentFrame].id);
1637     }
1638 }
1639 
1640 QString DapBackend::slotPrintVariable(const QString &variable)
1641 {
1642     const auto cmd = QStringLiteral("print %1").arg(variable);
1643     issueCommand(cmd);
1644     return cmd;
1645 }
1646 
1647 void DapBackend::changeStackFrame(int index)
1648 {
1649     if (!debuggerRunning())
1650         return;
1651 
1652     if ((m_frames.size() < index) || (index < 0))
1653         return;
1654 
1655     if (m_currentFrame && (*m_currentFrame == index))
1656         return;
1657 
1658     m_currentFrame = index;
1659 
1660     const auto &frame = m_frames[index];
1661     if (frame.source) {
1662         const auto id = frame.source->unifiedId();
1663         Q_EMIT outputText(QStringLiteral("\n") + i18n("Current frame [%3]: %1:%2 (%4)", id, QString::number(frame.line), QString::number(index), frame.name));
1664         // zero-based line
1665         Q_EMIT debugLocationChanged(QUrl::fromLocalFile(resolveOrWarn(id)), frame.line - 1);
1666     }
1667 
1668     Q_EMIT stackFrameChanged(index);
1669 
1670     slotQueryLocals(m_queryLocals);
1671 }
1672 
1673 void DapBackend::changeThread(int index)
1674 {
1675     if (!debuggerRunning())
1676         return;
1677 
1678     if (!m_queryLocals)
1679         return;
1680 
1681     if (m_watchedThread && (*m_watchedThread == index))
1682         return;
1683 
1684     m_watchedThread = index;
1685 
1686     pushRequest();
1687     m_client->requestStackTrace(index);
1688 }
1689 
1690 std::optional<QString> DapBackend::resolveFilename(const QString &filename, bool fallback) const
1691 {
1692     QFileInfo fInfo = QFileInfo(filename);
1693     if (fInfo.exists() && fInfo.isDir()) {
1694         return fInfo.absoluteFilePath();
1695     }
1696 
1697     if (fInfo.isAbsolute()) {
1698         return filename;
1699     }
1700 
1701     // working path
1702     if (!m_workDir.isEmpty()) {
1703         const auto base = QDir(m_workDir);
1704         fInfo = QFileInfo(base.absoluteFilePath(filename));
1705         if (fInfo.exists() && !fInfo.isDir()) {
1706             return fInfo.absoluteFilePath();
1707         }
1708     }
1709 
1710     // executable path
1711     if (!m_file.isEmpty()) {
1712         const auto base = QDir(QFileInfo(m_file).absolutePath());
1713         fInfo = QFileInfo(base.absoluteFilePath(filename));
1714         if (fInfo.exists() && !fInfo.isDir()) {
1715             return fInfo.absoluteFilePath();
1716         }
1717     }
1718 
1719     if (fallback)
1720         return filename;
1721 
1722     return std::nullopt;
1723 }
1724 
1725 QString DapBackend::resolveOrWarn(const QString &filename)
1726 {
1727     const auto path = resolveFilename(filename, false);
1728 
1729     if (path)
1730         return *path;
1731 
1732     Q_EMIT sourceFileNotFound(filename);
1733 
1734     return filename;
1735 }
1736 
1737 #include "moc_dapbackend.cpp"