mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
725 lines
22 KiB
C++
725 lines
22 KiB
C++
#include "debuggerclientdap.hpp"
|
|
#include "messages.hpp"
|
|
#include <eepp/core/string.hpp>
|
|
#include <eepp/system/log.hpp>
|
|
|
|
using namespace EE::System;
|
|
|
|
namespace ecode::dap {
|
|
|
|
constexpr int MAX_HEADER_SIZE = 1 << 16;
|
|
|
|
template <typename T>
|
|
inline DebuggerClientDap::ResponseHandler
|
|
makeResponseHandler( void ( T::*member )( const Response& response, const nlohmann::json& request ),
|
|
T* object ) {
|
|
return [object, member]( const Response& response, const nlohmann::json& request ) {
|
|
return ( object->*member )( response, request );
|
|
};
|
|
}
|
|
|
|
DebuggerClientDap::DebuggerClientDap( const ProtocolSettings& protocolSettings,
|
|
std::unique_ptr<Bus>&& bus ) :
|
|
mBus( std::move( bus ) ), mProtocol( protocolSettings ) {}
|
|
|
|
DebuggerClientDap::~DebuggerClientDap() {
|
|
mBus.reset();
|
|
}
|
|
|
|
void DebuggerClientDap::makeRequest( const std::string_view& command,
|
|
const nlohmann::json& arguments, ResponseHandler onFinish ) {
|
|
nlohmann::json jsonCmd = {
|
|
{ "seq", mIdx.load() },
|
|
{ "type", "request" },
|
|
{ "command", command },
|
|
{ "arguments", arguments.empty() ? nlohmann::json::object() : arguments } };
|
|
|
|
std::string cmd = jsonCmd.dump();
|
|
std::string msg( String::format( "Content-Length: %zu\r\n\r\n%s", cmd.size(), cmd ) );
|
|
|
|
Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug,
|
|
"DebuggerClientDap::makeRequest:" );
|
|
Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, msg );
|
|
|
|
mBus->write( msg.data(), msg.size() );
|
|
|
|
mRequests[mIdx] = { std::string{ command }, arguments, onFinish };
|
|
|
|
mIdx.fetch_add( 1, std::memory_order_relaxed );
|
|
}
|
|
|
|
bool DebuggerClientDap::isServerConnected() const {
|
|
return ( mState != State::None ) && ( mState != State::Failed ) &&
|
|
( mBus->state() == Bus::State::Running );
|
|
}
|
|
|
|
bool DebuggerClientDap::supportsTerminate() const {
|
|
return mAdapterCapabilities.supportsTerminateRequest &&
|
|
( mProtocol.launchRequest.value( DAP_COMMAND, "" ) == DAP_LAUNCH );
|
|
}
|
|
|
|
bool DebuggerClientDap::start() {
|
|
bool started = mBus->start();
|
|
if ( started )
|
|
mBus->startAsyncRead( [this]( const char* bytes, size_t n ) { asyncRead( bytes, n ); } );
|
|
else {
|
|
Log::warning( "DebuggerClientDap::start: could not initialize the debugger" );
|
|
return false;
|
|
}
|
|
mStarted = started;
|
|
mLaunched = false;
|
|
mConfigured = false;
|
|
if ( mState != State::None ) {
|
|
Log::warning( "DebuggerClientDap::start: trying to re-start has no effect" );
|
|
return false;
|
|
}
|
|
requestInitialize();
|
|
return started;
|
|
}
|
|
|
|
void DebuggerClientDap::processResponseInitialize( const Response& response,
|
|
const nlohmann::json& ) {
|
|
if ( mState != State::Initializing ) {
|
|
Log::warning(
|
|
"DebuggerClientDap::processResponseInitialize: unexpected initialize response" );
|
|
setState( State::None );
|
|
return;
|
|
}
|
|
|
|
if ( !response.success && response.isCancelled() ) {
|
|
Log::warning( "DebuggerClientDap::processResponseInitialize: InitializeResponse error: %s",
|
|
response.message );
|
|
if ( response.errorBody ) {
|
|
Log::warning( "DebuggerClientDap::processResponseInitialize: error %ld %s",
|
|
response.errorBody->id, response.errorBody->format );
|
|
}
|
|
setState( State::None );
|
|
return;
|
|
}
|
|
|
|
// get server capabilities
|
|
mAdapterCapabilities = Capabilities( response.body );
|
|
for ( auto listener : mListeners )
|
|
listener->capabilitiesReceived( mAdapterCapabilities );
|
|
|
|
requestLaunchCommand();
|
|
}
|
|
|
|
void DebuggerClientDap::requestLaunchCommand() {
|
|
if ( mState != State::Initializing ) {
|
|
Log::warning(
|
|
"DebuggerClientDap::requestLaunchCommand: trying to launch in an unexpected state" );
|
|
return;
|
|
}
|
|
|
|
if ( mProtocol.launchCommand.empty() )
|
|
return;
|
|
|
|
makeRequest( mProtocol.launchCommand, mProtocol.launchRequest,
|
|
[this]( const Response& response, const auto& ) {
|
|
if ( response.success ) {
|
|
mLaunched = true;
|
|
checkRunning();
|
|
for ( auto listener : mListeners )
|
|
listener->launched();
|
|
} else {
|
|
if ( response.errorBody ) {
|
|
Log::warning( "DebuggerClientDap::requestLaunchCommand: error %ld %s",
|
|
response.errorBody->id, response.errorBody->format );
|
|
}
|
|
setState( State::Failed );
|
|
}
|
|
} );
|
|
}
|
|
|
|
void DebuggerClientDap::requestInitialize() {
|
|
const nlohmann::json capabilities{
|
|
{ DAP_CLIENT_ID, "ecode-dap" },
|
|
{ DAP_CLIENT_NAME, "ecode dap" },
|
|
{ "locale", mProtocol.locale },
|
|
{ DAP_ADAPTER_ID, "ecode-dap" },
|
|
{ DAP_LINES_START_AT1, mProtocol.linesStartAt1 },
|
|
{ DAP_COLUMNS_START_AT2, mProtocol.columnsStartAt1 },
|
|
{ DAP_PATH, ( mProtocol.pathFormatURI ? DAP_URI : DAP_PATH ) },
|
|
{ DAP_SUPPORTS_VARIABLE_TYPE, true },
|
|
{ DAP_SUPPORTS_VARIABLE_PAGING, false },
|
|
{ DAP_SUPPORTS_RUN_IN_TERMINAL_REQUEST, false },
|
|
{ DAP_SUPPORTS_MEMORY_REFERENCES, false },
|
|
{ DAP_SUPPORTS_PROGRESS_REPORTING, false },
|
|
{ DAP_SUPPORTS_INVALIDATED_EVENT, false },
|
|
{ DAP_SUPPORTS_MEMORY_EVENT, false } };
|
|
|
|
setState( State::Initializing );
|
|
makeRequest( DAP_INITIALIZE, capabilities,
|
|
makeResponseHandler( &DebuggerClientDap::processResponseInitialize, this ) );
|
|
}
|
|
|
|
void DebuggerClientDap::asyncRead( const char* bytes, size_t n ) {
|
|
std::string_view read( bytes, n );
|
|
mBuffer += read;
|
|
|
|
while ( true ) {
|
|
const auto info = readHeader();
|
|
if ( !info )
|
|
break;
|
|
const auto data = mBuffer.substr( info->payloadStart, info->payloadLength );
|
|
if ( data.size() < info->payloadLength )
|
|
break;
|
|
mBuffer.erase( 0, info->payloadStart + info->payloadLength );
|
|
|
|
#ifndef EE_DEBUG
|
|
try {
|
|
#endif
|
|
auto message = json::parse( data );
|
|
|
|
if ( mDebug ) {
|
|
Log::debug( "DebuggerClientDap::asyncRead:" );
|
|
Log::debug( message.dump() );
|
|
}
|
|
|
|
processProtocolMessage( message );
|
|
#ifndef EE_DEBUG
|
|
} catch ( const json::exception& e ) {
|
|
Log::error( "DebuggerClientDap::asyncRead: JSON bad format: %s", e.what() );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void DebuggerClientDap::processProtocolMessage( const nlohmann::json& msg ) {
|
|
const auto type = msg.value( DAP_TYPE, "" );
|
|
|
|
if ( DAP_RESPONSE == type ) {
|
|
processResponse( msg );
|
|
} else if ( DAP_EVENT == type ) {
|
|
processEvent( msg );
|
|
} else {
|
|
Log::warning( "DebuggerClientDap::processProtocolMessage: unknown, empty or unexpected "
|
|
"ProtocolMessage::%s (%s)",
|
|
DAP_TYPE, type );
|
|
}
|
|
}
|
|
|
|
void DebuggerClientDap::processResponse( const nlohmann::json& msg ) {
|
|
const Response response( msg );
|
|
|
|
// check sequence
|
|
if ( ( response.request_seq < 0 ) || 0 == mRequests.count( response.request_seq ) ) {
|
|
Log::error( "DebuggerClientDap::processResponse: unexpected requested seq in response" );
|
|
return;
|
|
}
|
|
|
|
const auto request = mRequests.extract( response.request_seq ).mapped();
|
|
|
|
// check response
|
|
if ( response.command != request.command ) {
|
|
Log::error(
|
|
"DebuggerClientDap::processResponse: unexpected command in response: %s (expected: %s)",
|
|
response.command, request.command );
|
|
}
|
|
|
|
if ( response.isCancelled() )
|
|
Log::debug( "DebuggerClientDap::processResponse: request cancelled: %s", response.command );
|
|
|
|
if ( !response.success )
|
|
return errorResponse( response.message, response.errorBody );
|
|
|
|
if ( request.handler ) {
|
|
request.handler( response, request.arguments );
|
|
}
|
|
}
|
|
|
|
void DebuggerClientDap::errorResponse( const std::string& summary,
|
|
const std::optional<Message>& message ) {
|
|
for ( auto listener : mListeners )
|
|
listener->errorResponse( summary, message );
|
|
}
|
|
|
|
void DebuggerClientDap::processEvent( const nlohmann::json& msg ) {
|
|
const std::string event = msg.value( DAP_EVENT, "" );
|
|
const auto body = msg.contains( DAP_BODY ) ? msg[DAP_BODY] : nlohmann::json{};
|
|
|
|
if ( "initialized"sv == event ) {
|
|
processEventInitialized();
|
|
} else if ( "terminated"sv == event ) {
|
|
processEventTerminated();
|
|
} else if ( "exited"sv == event ) {
|
|
processEventExited( body );
|
|
} else if ( DAP_OUTPUT == event ) {
|
|
processEventOutput( body );
|
|
} else if ( "process"sv == event ) {
|
|
processEventProcess( body );
|
|
} else if ( "thread"sv == event ) {
|
|
processEventThread( body );
|
|
} else if ( "stopped"sv == event ) {
|
|
processEventStopped( body );
|
|
} else if ( "module"sv == event ) {
|
|
processEventModule( body );
|
|
} else if ( "continued"sv == event ) {
|
|
processEventContinued( body );
|
|
} else if ( DAP_BREAKPOINT == event ) {
|
|
processEventBreakpoint( body );
|
|
} else {
|
|
Log::info( "DebuggerClientDap::processEvent: unsupported event: %s", event );
|
|
}
|
|
}
|
|
|
|
void DebuggerClientDap::processEventInitialized() {
|
|
if ( ( mState != State::Initializing ) ) {
|
|
Log::error( "DebuggerClientDap::processEventInitialized: unexpected initialized event" );
|
|
return;
|
|
}
|
|
setState( State::Initialized );
|
|
|
|
configurationDone();
|
|
}
|
|
|
|
void DebuggerClientDap::processEventTerminated() {
|
|
setState( State::Terminated );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventExited( const nlohmann::json& body ) {
|
|
const int exitCode = body.value( "exitCode", -1 );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggeeExited( exitCode );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventOutput( const nlohmann::json& body ) {
|
|
Output output( body );
|
|
for ( auto listener : mListeners )
|
|
listener->outputProduced( output );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventProcess( const nlohmann::json& body ) {
|
|
ProcessInfo processInfo( body );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggingProcess( processInfo );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventThread( const nlohmann::json& body ) {
|
|
ThreadEvent threadEvent( body );
|
|
for ( auto listener : mListeners )
|
|
listener->threadChanged( threadEvent );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventStopped( const nlohmann::json& body ) {
|
|
StoppedEvent stoppedEvent( body );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggeeStopped( stoppedEvent );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventModule( const nlohmann::json& body ) {
|
|
ModuleEvent moduleEvent( body );
|
|
for ( auto listener : mListeners )
|
|
listener->moduleChanged( moduleEvent );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventContinued( const nlohmann::json& body ) {
|
|
ContinuedEvent continuedEvent( body );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggeeContinued( continuedEvent );
|
|
}
|
|
|
|
void DebuggerClientDap::processEventBreakpoint( const nlohmann::json& body ) {
|
|
BreakpointEvent breakpointEvent( body );
|
|
for ( auto listener : mListeners )
|
|
listener->breakpointChanged( breakpointEvent );
|
|
}
|
|
|
|
std::optional<DebuggerClientDap::HeaderInfo> DebuggerClientDap::readHeader() {
|
|
Uint64 length = std::string::npos;
|
|
Uint64 start = 0;
|
|
Uint64 end = std::string::npos;
|
|
|
|
auto discardExploredBuffer = [this, length, start, end]() mutable {
|
|
mBuffer.erase( 0, end );
|
|
length = end = std::string::npos;
|
|
start = 0;
|
|
};
|
|
|
|
while ( true ) {
|
|
end = mBuffer.find( DAP_SEP, start );
|
|
if ( end == std::string::npos ) {
|
|
if ( mBuffer.size() > MAX_HEADER_SIZE )
|
|
mBuffer.clear();
|
|
length = std::string::npos;
|
|
break; // PENDING
|
|
}
|
|
|
|
const auto header = mBuffer.substr( start, end - start );
|
|
while ( std::string_view{ mBuffer }.substr( end, 2 ) == DAP_SEP )
|
|
end += DAP_SEP_SIZE;
|
|
|
|
// header block separator
|
|
if ( header.size() == 0 ) {
|
|
if ( length < 0 ) {
|
|
// unexpected end of header
|
|
Log::error( "DebuggerClientDap::readHeader unexpected end of header block" );
|
|
discardExploredBuffer();
|
|
continue;
|
|
}
|
|
break; // END HEADER (length>0, end>0)
|
|
}
|
|
|
|
// parse field
|
|
const auto sep = header.find_first_of( ":" );
|
|
if ( sep == std::string::npos ) {
|
|
Log::error( "DebuggerClientDap::readHeader cannot parse header field: ", header );
|
|
discardExploredBuffer();
|
|
continue; // CONTINUE HEADER
|
|
}
|
|
|
|
// parse content-length
|
|
if ( String::startsWith( header, DAP_CONTENT_LENGTH ) ) {
|
|
std::string lengthStr( header.substr( sep + 1, header.size() - sep ) );
|
|
String::trimInPlace( lengthStr );
|
|
if ( !String::fromString( length, lengthStr ) ) {
|
|
Log::error( "DebuggerClientDap::readHeader invalid value: ", header );
|
|
discardExploredBuffer();
|
|
continue; // CONTINUE HEADER
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
|
|
if ( length < 0 || length == std::string::npos )
|
|
return std::nullopt;
|
|
|
|
return HeaderInfo{ end, length };
|
|
}
|
|
|
|
bool DebuggerClientDap::resume( int threadId, bool singleThread ) {
|
|
nlohmann::json arguments{ { DAP_THREAD_ID, threadId } };
|
|
if ( singleThread )
|
|
arguments[DAP_SINGLE_THREAD] = true;
|
|
makeRequest( "continue", arguments,
|
|
[this]( const Response& response, const nlohmann::json& request ) {
|
|
if ( response.success ) {
|
|
ContinuedEvent continuedEvent(
|
|
request.value( DAP_THREAD_ID, 1 ),
|
|
response.body.value( DAP_ALL_THREADS_CONTINUED, true ) );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggeeContinued( continuedEvent );
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::pause( int threadId ) {
|
|
nlohmann::json arguments{ { DAP_THREAD_ID, threadId } };
|
|
makeRequest( "pause", arguments );
|
|
return true;
|
|
}
|
|
|
|
void DebuggerClientDap::processResponseNext( const Response& response,
|
|
const nlohmann::json& request ) {
|
|
if ( response.success ) {
|
|
ContinuedEvent continuedEvent( request.value( DAP_THREAD_ID, 1 ),
|
|
!response.body.value( DAP_SINGLE_THREAD, false ) );
|
|
for ( auto listener : mListeners )
|
|
listener->debuggeeContinued( continuedEvent );
|
|
}
|
|
}
|
|
|
|
bool DebuggerClientDap::stepOver( int threadId, bool singleThread ) {
|
|
nlohmann::json arguments{ { DAP_THREAD_ID, threadId } };
|
|
if ( singleThread )
|
|
arguments[DAP_SINGLE_THREAD] = true;
|
|
makeRequest( "next", arguments,
|
|
makeResponseHandler( &DebuggerClientDap::processResponseNext, this ) );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::goTo( int threadId, int targetId ) {
|
|
const nlohmann::json arguments{ { DAP_THREAD_ID, threadId }, { DAP_TARGET_ID, targetId } };
|
|
makeRequest( "goto", arguments,
|
|
makeResponseHandler( &DebuggerClientDap::processResponseNext, this ) );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::stepInto( int threadId, bool singleThread ) {
|
|
nlohmann::json arguments{ { DAP_THREAD_ID, threadId } };
|
|
if ( singleThread )
|
|
arguments[DAP_SINGLE_THREAD] = true;
|
|
makeRequest( "stepIn", arguments,
|
|
makeResponseHandler( &DebuggerClientDap::processResponseNext, this ) );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::stepOut( int threadId, bool singleThread ) {
|
|
nlohmann::json arguments{ { DAP_THREAD_ID, threadId } };
|
|
if ( singleThread )
|
|
arguments[DAP_SINGLE_THREAD] = true;
|
|
makeRequest( "stepOut", arguments,
|
|
makeResponseHandler( &DebuggerClientDap::processResponseNext, this ) );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::terminate( bool restart ) {
|
|
nlohmann::json arguments;
|
|
if ( restart )
|
|
arguments["restart"] = true;
|
|
makeRequest( "terminate", arguments );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::disconnect( bool restart ) {
|
|
nlohmann::json arguments;
|
|
if ( restart )
|
|
arguments["restart"] = true;
|
|
|
|
makeRequest( "disconnect", arguments,
|
|
[this]( const Response& response, const nlohmann::json& ) {
|
|
if ( response.success ) {
|
|
for ( auto listener : mListeners )
|
|
listener->serverDisconnected();
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::threads() {
|
|
makeRequest( DAP_THREADS, {}, [this]( const Response& response, const nlohmann::json& ) {
|
|
if ( response.success ) {
|
|
auto threads( Thread::parseList( response.body[DAP_THREADS] ) );
|
|
for ( auto listener : mListeners )
|
|
listener->threads( threads );
|
|
} else {
|
|
for ( auto listener : mListeners )
|
|
listener->threads( {} );
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::stackTrace( int threadId, int startFrame, int levels ) {
|
|
const nlohmann::json arguments{
|
|
{ DAP_THREAD_ID, threadId }, { "startFrame", startFrame }, { "levels", levels } };
|
|
|
|
makeRequest( "stackTrace", arguments,
|
|
[this]( const Response& response, const nlohmann::json& request ) {
|
|
const int threadId = request.value( DAP_THREAD_ID, 1 );
|
|
if ( response.success ) {
|
|
StackTraceInfo stackTraceInfo( response.body );
|
|
for ( auto listener : mListeners )
|
|
listener->stackTrace( threadId, stackTraceInfo );
|
|
} else {
|
|
StackTraceInfo stackTraceInfo;
|
|
for ( auto listener : mListeners )
|
|
listener->stackTrace( threadId, stackTraceInfo );
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::scopes( int frameId ) {
|
|
const nlohmann::json arguments{ { DAP_FRAME_ID, frameId } };
|
|
makeRequest( DAP_SCOPES, arguments,
|
|
[this]( const Response& response, const nlohmann::json& request ) {
|
|
const int frameId = request.value( DAP_FRAME_ID, 1 );
|
|
if ( response.success ) {
|
|
auto scopes( Scope::parseList( response.body[DAP_SCOPES] ) );
|
|
for ( auto listener : mListeners )
|
|
listener->scopes( frameId, scopes );
|
|
} else {
|
|
std::vector<Scope> scopes;
|
|
for ( auto listener : mListeners )
|
|
listener->scopes( frameId, scopes );
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::variables( int variablesReference, Variable::Type filter, int start,
|
|
int count ) {
|
|
nlohmann::json arguments{
|
|
{ DAP_VARIABLES_REFERENCE, variablesReference },
|
|
{ DAP_START, start },
|
|
{ DAP_COUNT, count },
|
|
};
|
|
|
|
switch ( filter ) {
|
|
case Variable::Type::Indexed:
|
|
arguments[DAP_FILTER] = "indexed";
|
|
break;
|
|
case Variable::Type::Named:
|
|
arguments[DAP_FILTER] = "named";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
makeRequest( DAP_VARIABLES, arguments,
|
|
[this]( const Response& response, const nlohmann::json& request ) {
|
|
const int variablesReference = request.value( DAP_VARIABLES_REFERENCE, 0 );
|
|
|
|
if ( response.success ) {
|
|
auto variableList( Variable::parseList( response.body[DAP_VARIABLES] ) );
|
|
for ( auto listener : mListeners )
|
|
listener->variables( variablesReference, variableList );
|
|
} else {
|
|
std::vector<Variable> variableList;
|
|
for ( auto listener : mListeners )
|
|
listener->variables( variablesReference, variableList );
|
|
}
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::modules( int start, int count ) {
|
|
makeRequest( DAP_MODULES, { { DAP_START, start }, { DAP_COUNT, count } },
|
|
[this]( const auto& response, const auto& ) {
|
|
if ( response.success ) {
|
|
ModulesInfo info( response.body );
|
|
for ( auto listener : mListeners )
|
|
listener->modules( info );
|
|
} else {
|
|
ModulesInfo info;
|
|
for ( auto listener : mListeners )
|
|
listener->modules( info );
|
|
}
|
|
} );
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::evaluate( const std::string& expression, const std::string& context,
|
|
std::optional<int> frameId ) {
|
|
nlohmann::json arguments{ { DAP_EXPRESSION, expression } };
|
|
if ( !context.empty() )
|
|
arguments[DAP_CONTEXT] = context;
|
|
if ( frameId )
|
|
arguments[DAP_FRAME_ID] = *frameId;
|
|
|
|
makeRequest( "evaluate", arguments, [this]( const auto& response, const auto& request ) {
|
|
auto expression = request.value( DAP_EXPRESSION, "" );
|
|
if ( response.success ) {
|
|
EvaluateInfo info( response.body );
|
|
|
|
for ( auto listener : mListeners )
|
|
listener->expressionEvaluated( expression, info );
|
|
} else {
|
|
for ( auto listener : mListeners )
|
|
listener->expressionEvaluated( expression, std::nullopt );
|
|
}
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::setBreakpoints( const std::string& path,
|
|
const std::vector<dap::SourceBreakpoint> breakpoints,
|
|
bool sourceModified ) {
|
|
return setBreakpoints( Source( path ), breakpoints, sourceModified );
|
|
}
|
|
|
|
bool DebuggerClientDap::setBreakpoints( const dap::Source& source,
|
|
const std::vector<dap::SourceBreakpoint> breakpoints,
|
|
bool sourceModified ) {
|
|
nlohmann::json bpoints = nlohmann::json::array();
|
|
for ( const auto& item : breakpoints )
|
|
bpoints.push_back( item.toJson() );
|
|
|
|
nlohmann::json arguments{ { DAP_SOURCE, source.toJson() },
|
|
{ DAP_BREAKPOINTS, bpoints },
|
|
{ "sourceModified", sourceModified } };
|
|
|
|
makeRequest( "setBreakpoints", arguments,
|
|
[this]( const Response& response, const nlohmann::json& request ) {
|
|
const auto source = Source( request[DAP_SOURCE] );
|
|
if ( response.success ) {
|
|
const auto resp = response.body;
|
|
if ( resp.contains( DAP_BREAKPOINTS ) ) {
|
|
std::vector<Breakpoint> breakpoints;
|
|
breakpoints.reserve( resp[DAP_BREAKPOINTS].size() );
|
|
for ( const auto& item : resp[DAP_BREAKPOINTS] )
|
|
breakpoints.emplace_back( item );
|
|
|
|
for ( auto listener : mListeners )
|
|
listener->sourceBreakpoints( source.path,
|
|
source.sourceReference.value_or( 0 ),
|
|
breakpoints );
|
|
} else {
|
|
std::vector<Breakpoint> breakpoints;
|
|
breakpoints.reserve( resp[DAP_LINES].size() );
|
|
for ( const auto& item : resp[DAP_LINES] )
|
|
breakpoints.emplace_back( item.get<int>() );
|
|
|
|
for ( auto listener : mListeners )
|
|
listener->sourceBreakpoints( source.path,
|
|
source.sourceReference.value_or( 0 ),
|
|
breakpoints );
|
|
}
|
|
} else {
|
|
for ( auto listener : mListeners )
|
|
listener->sourceBreakpoints(
|
|
source.path, source.sourceReference.value_or( 0 ), std::nullopt );
|
|
}
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::gotoTargets( const std::string& path, const int line,
|
|
const std::optional<int> column ) {
|
|
return gotoTargets( Source( path ), line, column );
|
|
}
|
|
|
|
bool DebuggerClientDap::gotoTargets( const Source& source, const int line,
|
|
const std::optional<int> column ) {
|
|
nlohmann::json arguments{ { DAP_SOURCE, source.toJson() }, { DAP_LINE, line } };
|
|
if ( column )
|
|
arguments[DAP_COLUMN] = *column;
|
|
|
|
makeRequest( "gotoTargets", arguments, [this]( const auto& response, const auto& req ) {
|
|
const auto source = Source( req[DAP_SOURCE] );
|
|
const int line = req.value( DAP_LINE, 1 );
|
|
if ( response.success ) {
|
|
auto list = GotoTarget::parseList( response.body["targets"] );
|
|
for ( auto listener : mListeners )
|
|
listener->gotoTargets( source, line, list );
|
|
} else {
|
|
std::vector<GotoTarget> list;
|
|
for ( auto listener : mListeners )
|
|
listener->gotoTargets( source, line, list );
|
|
}
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DebuggerClientDap::watch( const std::string& expression, std::optional<int> frameId ) {
|
|
return evaluate( expression, "watch", frameId );
|
|
}
|
|
|
|
bool DebuggerClientDap::configurationDone() {
|
|
if ( mState != State::Initialized ) {
|
|
Log::warning( "DebuggerClientDap::requestConfigurationDone: trying to configure in an "
|
|
"unexpected status" );
|
|
return false;
|
|
}
|
|
|
|
if ( !mAdapterCapabilities.supportsConfigurationDoneRequest ) {
|
|
for ( auto listener : mListeners )
|
|
listener->configured();
|
|
return true;
|
|
}
|
|
|
|
makeRequest( "configurationDone", nlohmann::json{},
|
|
[this]( const auto& response, const auto& ) {
|
|
if ( response.success ) {
|
|
mConfigured = true;
|
|
checkRunning();
|
|
for ( auto listener : mListeners )
|
|
listener->configured();
|
|
}
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ecode::dap
|