/**
* Tells the debugger to step into the current command.
*/
-- (void)stepIn
-{
- NSNumber* tx = [client_ sendCommandWithFormat:@"step_into"];
- [self recordCallback:@selector(debuggerStep:) forTransaction:tx];
+- (void)stepIn {
+ [client_ sendCommandWithFormat:@"step_into" handler:^(NSXMLDocument* message) {
+ [self debuggerStep:message];
+ }];
}
/**
[client_ connectOnPort:port_];
}
-- (void)debuggerEngine:(ProtocolClient*)client receivedMessage:(NSXMLDocument*)message
-{
- // Check and see if there's an error.
+- (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
+ [self handleInitialResponse:message];
+}
+
+- (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
NSArray* error = [[message rootElement] elementsForName:@"error"];
if ([error count] > 0) {
NSLog(@"Xdebug error: %@", error);
NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
[self errorEncountered:errorMessage];
}
+}
- if ([[[message rootElement] name] isEqualToString:@"init"]) {
- [self handleInitialResponse:message];
- return;
- }
-
+- (void)debuggerEngine:(ProtocolClient*)client receivedMessage:(NSXMLDocument*)message {
[self handleResponse:message];
}
@protocol ProtocolClientDelegate;
+typedef void (^ProtocolClientMessageHandler)(NSXMLDocument*);
+
// ProtocolClient sends string commands to a DBGP <http://www.xdebug.org/docs-dbgp.php>
// debugger engine and receives XML packets in response. This class ensures
// proper sequencing of the messages.
// safe and schedules the request on the |runLoop_|.
- (NSNumber*)sendCommandWithFormat:(NSString*)format, ...;
+// Sends a command with the given |format| to the debugger. When a response is
+// received, |handler| is invoked. If an error occurs or the connection is
+// interrupted, the delegate will be notified.
+- (void)sendCommandWithFormat:(NSString*)format
+ handler:(ProtocolClientMessageHandler)handler,
+ ...;
+
// Sends a command to the debugger. The command must have a substring |{txn}|
// within it, which will be replaced with the transaction ID. Use this if
// |-sendCommandWithFormat:|'s insertion of the transaction ID is incorrect.
@protocol ProtocolClientDelegate
- (void)debuggerEngineConnected:(ProtocolClient*)client;
- (void)debuggerEngineDisconnected:(ProtocolClient*)client;
-
+- (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message;
+- (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message;
- (void)debuggerEngine:(ProtocolClient*)client receivedMessage:(NSXMLDocument*)message;
@end
// state of the debugger.
id<ProtocolClientDelegate> _delegate; // weak
+ // A map between transaction ID and handler block for that message.
+ NSMutableDictionary<NSNumber*, ProtocolClientMessageHandler>* _dispatchTable;
+
// The next transaction ID to assign.
- NSInteger _nextID;
+ int _nextID;
// Records the last read and written transaction IDs. These are only used in
// creating LogEntry objects.
- (id)initWithDelegate:(id<ProtocolClientDelegate>)delegate {
if ((self = [super init])) {
_delegate = delegate;
+ _dispatchTable = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
+ [_dispatchTable release];
[super dealloc];
}
return callbackKey;
}
+- (void)sendCommandWithFormat:(NSString*)format
+ handler:(ProtocolClientMessageHandler)handler, ... {
+ // Collect varargs and format command.
+ va_list args;
+ va_start(args, handler);
+ NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
+ va_end(args);
+
+ int transaction = _nextID++;
+ NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %d", [command autorelease], transaction];
+
+ assert(_messageQueue);
+ [_dispatchTable setObject:[[handler copy] autorelease] forKey:@(transaction)];
+ [_messageQueue sendMessage:taggedCommand];
+}
+
- (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ... {
// Collect varargs and format command.
va_list args;
- (void)messageQueueDidDisconnect:(MessageQueue*)queue {
[_messageQueue release];
_messageQueue = nil;
+ [_dispatchTable removeAllObjects];
[_delegate debuggerEngineDisconnected:self];
}
// Callback for when a message has been sent.
-- (void)messageQueue:(MessageQueue*)queue didSendMessage:(NSString*)message
-{
+- (void)messageQueue:(MessageQueue*)queue didSendMessage:(NSString*)message {
NSInteger tag = [self transactionIDFromCommand:message];
_lastWrittenID = tag;
}
// Callback with the message content when one has been receieved.
-- (void)messageQueue:(MessageQueue*)queue didReceiveMessage:(NSString*)message
-{
+- (void)messageQueue:(MessageQueue*)queue didReceiveMessage:(NSString*)message {
+ // Record this message in the transaction log.
LoggingController* logger = [[AppDelegate instance] loggingController];
LogEntry* entry = [LogEntry newReceiveEntry:message];
entry.lastReadTransactionID = _lastReadID;
entry.lastWrittenTransactionID = _lastWrittenID;
[logger recordEntry:entry];
- // Test if we can convert it into an NSXMLDocument.
+ // Parse the XML and test for errors.
NSError* error = nil;
NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
options:NSXMLDocumentTidyXML
[self messageQueue:queue error:error];
return;
}
+ int transactionID = [self transactionIDFromResponse:xml];
- _lastReadID = [self transactionIDFromResponse:xml];
+ _lastReadID = transactionID;
entry.lastReadTransactionID = _lastReadID;
- [_delegate debuggerEngine:self receivedMessage:xml];
+ if ([[[xml rootElement] elementsForName:@"error"] count] > 0) {
+ // Handle back-end errors.
+ [_delegate protocolClient:self receivedErrorMessage:xml];
+ } else if ([[[xml rootElement] name] isEqualToString:@"init"]) {
+ // Handle the initial connection message.
+ [_delegate protocolClient:self receivedInitialMessage:xml];
+ } else {
+ // Dispatch the handler for the message.
+ ProtocolClientMessageHandler handler = [_dispatchTable objectForKey:@(transactionID)];
+ if (handler) {
+ handler(xml);
+ [_dispatchTable removeObjectForKey:@(transactionID)];
+ } else {
+ // TODO(rsesek): Remove this path once the backend rewrite is complete.
+ [_delegate debuggerEngine:self receivedMessage:xml];
+ }
+ }
}
@end