3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 #import <sys/socket.h>
18 #import <netinet/in.h>
20 #import "GDBpConnection.h"
22 #import "AppDelegate.h"
25 typedef enum _StackFrameComponents
30 } StackFrameComponent
;
32 // GDBpConnection (Private) ////////////////////////////////////////////////////
34 @interface GDBpConnection ()
35 @property (readwrite
, copy
) NSString
* status
;
36 @property (assign
) CFSocketRef socket
;
37 @property (assign
) CFReadStreamRef readStream
;
38 @property (retain
) NSMutableString
* currentPacket
;
39 @property (assign
) CFWriteStreamRef writeStream
;
40 @property (retain
) NSMutableArray
* queuedWrites
;
44 - (void)socketDidAccept
;
45 - (void)socketDisconnected
;
46 - (void)readStreamHasData
;
47 - (void)send
:(NSString
*)command
;
48 - (void)performSend
:(NSString
*)command
;
49 - (void)errorEncountered
:(NSString
*)error
;
51 - (void)handleResponse
:(NSXMLDocument
*)response
;
52 - (void)initReceived
:(NSXMLDocument
*)response
;
53 - (void)updateStatus
:(NSXMLDocument
*)response
;
54 - (void)debuggerStep
:(NSXMLDocument
*)response
;
55 - (void)rebuildStack
:(NSXMLDocument
*)response
;
56 - (void)getStackFrame
:(NSXMLDocument
*)response
;
57 - (void)handleRouted
:(NSArray
*)path response
:(NSXMLDocument
*)response
;
59 - (NSString
*)createCommand
:(NSString
*)cmd
, ...
;
60 - (NSString
*)createRouted
:(NSString
*)routingID command
:(NSString
*)cmd
, ...
;
62 - (StackFrame
*)createStackFrame
:(int)depth
;
63 - (NSString
*)escapedURIPath
:(NSString
*)path
;
66 // CFNetwork Callbacks /////////////////////////////////////////////////////////
68 void ReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
70 NSLog(@
"ReadStreamCallback()");
71 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
74 case kCFStreamEventHasBytesAvailable
:
75 [connection readStreamHasData
];
78 case kCFStreamEventErrorOccurred
:
80 CFErrorRef error
= CFReadStreamCopyError(stream
);
81 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
82 CFReadStreamClose(stream
);
84 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
88 case kCFStreamEventEndEncountered
:
89 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
90 CFReadStreamClose(stream
);
92 [connection socketDisconnected
];
97 void WriteStreamCallback(CFWriteStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
99 NSLog(@
"WriteStreamCallback()");
100 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
103 case kCFStreamEventCanAcceptBytes
:
104 NSLog(@
"can accept bytes");
105 if ([connection.queuedWrites count
] > 0)
107 NSString
* command
= [connection.queuedWrites objectAtIndex
:0];
108 [connection performSend
:command
];
109 [connection.queuedWrites removeObjectAtIndex
:0];
113 case kCFStreamEventErrorOccurred
:
115 CFErrorRef error
= CFWriteStreamCopyError(stream
);
116 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
117 CFWriteStreamClose(stream
);
119 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
123 case kCFStreamEventEndEncountered
:
124 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
125 CFWriteStreamClose(stream
);
127 [connection socketDisconnected
];
132 void SocketAcceptCallback(CFSocketRef socket
,
133 CFSocketCallBackType callbackType
,
138 assert(callbackType
== kCFSocketAcceptCallBack
);
139 NSLog(@
"SocketAcceptCallback()");
141 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
143 CFReadStreamRef readStream
;
144 CFWriteStreamRef writeStream
;
146 // Create the streams on the socket.
147 CFStreamCreatePairWithSocket(kCFAllocatorDefault
,
148 *(CFSocketNativeHandle
*)data
, // Socket handle.
149 &readStream
, // Read stream in-pointer.
150 &writeStream
); // Write stream in-pointer.
152 // Create struct to register callbacks for the stream.
153 CFStreamClientContext context
;
155 context.info
= connection
;
156 context.retain
= NULL
;
157 context.release
= NULL
;
158 context.copyDescription
= NULL
;
160 // Set the client of the read stream.
161 CFOptionFlags readFlags
=
162 kCFStreamEventOpenCompleted |
163 kCFStreamEventHasBytesAvailable |
164 kCFStreamEventErrorOccurred |
165 kCFStreamEventEndEncountered
;
166 if (CFReadStreamSetClient(readStream
, readFlags
, ReadStreamCallback
, &context
))
167 // Schedule in run loop to do asynchronous communication with the engine.
168 CFReadStreamScheduleWithRunLoop(readStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
172 NSLog(@
"Read stream scheduled");
174 // Open the stream now that it's scheduled on the run loop.
175 if (!CFReadStreamOpen(readStream
))
177 CFStreamError error
= CFReadStreamGetError(readStream
);
178 NSLog(@
"error! %@", error
);
182 NSLog(@
"Read stream opened");
184 // Set the client of the write stream.
185 CFOptionFlags writeFlags
=
186 kCFStreamEventOpenCompleted |
187 kCFStreamEventCanAcceptBytes |
188 kCFStreamEventErrorOccurred |
189 kCFStreamEventEndEncountered
;
190 if (CFWriteStreamSetClient(writeStream
, writeFlags
, WriteStreamCallback
, &context
))
191 // Schedule it in the run loop to receive error information.
192 CFWriteStreamScheduleWithRunLoop(writeStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
196 NSLog(@
"Write stream scheduled");
198 // Open the write stream.
199 if (!CFWriteStreamOpen(writeStream
))
201 CFStreamError error
= CFWriteStreamGetError(writeStream
);
202 NSLog(@
"error! %@", error
);
206 NSLog(@
"Write stream opened");
208 connection.readStream
= readStream
;
209 connection.writeStream
= writeStream
;
210 [connection socketDidAccept
];
213 // GDBpConnection //////////////////////////////////////////////////////////////
215 @implementation GDBpConnection
216 @synthesize socket
= socket_
;
217 @synthesize readStream
= readStream_
;
218 @synthesize currentPacket
= currentPacket_
;
219 @synthesize writeStream
= writeStream_
;
220 @synthesize queuedWrites
= queuedWrites_
;
222 @synthesize delegate
;
225 * Creates a new DebuggerConnection and initializes the socket from the given connection
228 - (id)initWithPort
:(int)aPort
230 if (self = [super init
])
235 [[BreakpointManager sharedManager
] setConnection
:self];
243 * Deallocates the object
248 self.currentPacket
= nil;
254 * Gets the port number
262 * Returns the name of the remote host
264 - (NSString
*)remoteHost
268 return @
"(DISCONNECTED)";
270 // TODO: Either impl or remove.
275 * Returns whether or not we have an active connection
283 * Called by SocketWrapper after the connection is successful. This immediately calls
284 * -[SocketWrapper receive] to clear the way for communication, though the information
285 * could be useful server information that we don't use right now.
287 - (void)socketDidAccept
291 stackFrames_
= [[NSMutableDictionary alloc
] init
];
292 self.queuedWrites
= [NSMutableArray array
];
296 * Receives errors from the SocketWrapper and updates the display
298 - (void)errorEncountered
:(NSString
*)error
300 [delegate errorEncountered
:error
];
304 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
305 * created every time you want to debug a page
310 self.status
= @
"Connecting";
315 * Creates an entirely new stack and returns it as an array of StackFrame objects.
317 - (NSArray
*)getCurrentStack
319 NSMutableArray
* stack
= [NSMutableArray array
];
320 NSLog(@
"NOTIMPLEMENTED(): %s", _cmd
);
325 * Tells the debugger to continue running the script. Returns the current stack frame.
329 [self send
:[self createCommand
:@
"run"]];
333 * Tells the debugger to step into the current command.
337 [self send
:[self createCommand
:@
"step_into"]];
341 * Tells the debugger to step out of the current context
345 [self send
:[self createCommand
:@
"step_out"]];
349 * Tells the debugger to step over the current function
353 [self send
:[self createCommand
:@
"step_over"]];
357 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
358 * that requested it so that the child can be attached.
360 - (NSArray
*)getProperty
:(NSString
*)property
362 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"property_get -n \"%@\"", property
]]];
364 NSXMLDocument
* doc
= [self processData
:[socket receive
]];
368 <property> <!-- this is the one we requested -->
369 <property ... /> <!-- these are what we want -->
374 // we now have to detach all the children so we can insert them into another document
375 NSXMLElement
* parent
= (NSXMLElement
*)[[doc rootElement
] childAtIndex
:0];
376 NSArray
* children
= [parent children
];
377 [parent setChildren
:nil];
381 #pragma mark Breakpoints
384 * Send an add breakpoint command
386 - (void)addBreakpoint
:(Breakpoint
*)bp
391 NSString
* file
= [self escapedURIPath
:[bp transformedPath
]];
392 NSString
* cmd
= [self createCommand
:[NSString stringWithFormat
:@
"breakpoint_set -t line -f %@ -n %i", file
, [bp line
]]];
394 NSXMLDocument
* info
= [self processData
:[socket receive
]];
395 [bp setDebuggerId
:[[[[info rootElement
] attributeForName
:@
"id"] stringValue
] intValue
]];
399 * Removes a breakpoint
401 - (void)removeBreakpoint
:(Breakpoint
*)bp
408 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"breakpoint_remove -d %i", [bp debuggerId
]]]];
412 #pragma mark Socket and Stream Callbacks
415 * Creates, connects to, and schedules a CFSocket.
419 // Pass ourselves to the callback so we don't have to use ugly globals.
420 CFSocketContext context
;
423 context.retain
= NULL
;
424 context.release
= NULL
;
425 context.copyDescription
= NULL
;
427 // Create the address structure.
428 struct sockaddr_in address
;
429 memset(&address
, 0, sizeof(address
));
430 address.sin_len
= sizeof(address
);
431 address.sin_family
= AF_INET
;
432 address.sin_port
= htons(port
);
433 address.sin_addr.s_addr
= htonl(INADDR_ANY
);
435 // Create the socket signature.
436 CFSocketSignature signature
;
437 signature.protocolFamily
= PF_INET
;
438 signature.socketType
= SOCK_STREAM
;
439 signature.protocol
= IPPROTO_TCP
;
440 signature.address
= (CFDataRef
)[NSData dataWithBytes
:&address length
:sizeof(address
)];
442 socket_
= CFSocketCreateWithSocketSignature(kCFAllocatorDefault
,
443 &signature
, // Socket signature.
444 kCFSocketAcceptCallBack
, // Callback types.
445 SocketAcceptCallback
, // Callout function pointer.
446 &context
); // Context to pass to callout.
449 [self errorEncountered
:@
"Could not open socket."];
453 // Allow old, yet-to-be recycled sockets to be reused.
455 setsockopt(CFSocketGetNative(socket_
), SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(BOOL));
457 // Schedule the socket on the run loop.
458 CFRunLoopSourceRef source
= CFSocketCreateRunLoopSource(kCFAllocatorDefault
, socket_
, 0);
459 CFRunLoopAddSource(CFRunLoopGetCurrent(), source
, kCFRunLoopCommonModes
);
462 self.status
= @
"Connecting";
466 * Closes a socket and releases the ref.
470 // The socket goes down, so do the streams, which clean themselves up.
471 CFSocketInvalidate(socket_
);
473 [stackFrames_ release
];
474 self.queuedWrites
= nil;
478 * Notification that the socket disconnected.
480 - (void)socketDisconnected
483 [delegate debuggerDisconnected
];
487 * Callback from the CFReadStream that there is data waiting to be read.
489 - (void)readStreamHasData
492 CFIndex bytesRead
= CFReadStreamRead(readStream_
, buffer
, 1024);
493 const char* charBuffer
= (const char*)buffer
;
495 // We haven't finished reading a packet, so just read more data in.
496 if (currentPacketIndex_
< packetSize_
)
498 [currentPacket_ appendFormat
:@
"%s", buffer
];
499 currentPacketIndex_
+= bytesRead
;
501 // Time to read a new packet.
504 // Read the message header: the size.
505 packetSize_
= atoi(charBuffer
);
506 currentPacketIndex_
= bytesRead
- strlen(charBuffer
);
507 self.currentPacket
= [NSMutableString stringWithFormat
:@
"%s", buffer
+ strlen(charBuffer
) + 1];
510 // We have finished reading the packet.
511 if (currentPacketIndex_
>= packetSize_
)
514 currentPacketIndex_
= 0;
516 // Test if we can convert it into an NSXMLDocument.
517 NSError
* error
= nil;
518 NSXMLDocument
* xmlTest
= [[NSXMLDocument alloc
] initWithXMLString
:currentPacket_ options
:NSXMLDocumentTidyXML error
:&error
];
521 NSLog(@
"Could not parse XML? --- %@", error
);
522 NSLog(@
"Error UserInfo: %@", [error userInfo
]);
523 NSLog(@
"This is the XML Document: %@", currentPacket_
);
526 [self handleResponse
:[xmlTest autorelease
]];
531 * Writes a command into the write stream. If the stream is ready for writing,
532 * we do so immediately. If not, the command is queued and will be written
533 * when the stream is ready.
535 - (void)send
:(NSString
*)command
537 if (CFWriteStreamCanAcceptBytes(writeStream_
))
538 [self performSend
:command
];
540 [queuedWrites_ addObject
:command
];
544 * This performs a blocking send. This should ONLY be called when we know we
545 * have write access to the stream. We will busy wait in case we don't do a full
548 - (void)performSend
:(NSString
*)command
552 char* string
= (char*)[command UTF8String
];
553 int stringLength
= strlen(string
);
555 // Log the command if TransportDebug is enabled.
556 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
557 NSLog(@
"--> %@", command
);
559 // Busy wait while writing. BAADD. Should background this operation.
562 if (CFWriteStreamCanAcceptBytes(writeStream_
))
564 // Include the NULL byte in the string when we write.
565 int bytesWritten
= CFWriteStreamWrite(writeStream_
, (UInt8
*)string
, stringLength
+ 1);
566 if (bytesWritten
< 0)
568 NSLog(@
"write error");
571 else if (bytesWritten
< strlen(string
))
573 // Adjust the buffer and wait for another chance to write.
574 stringLength
-= bytesWritten
;
575 memmove(string
, string
+ bytesWritten
, stringLength
);
585 #pragma mark Response Handlers
587 - (void)handleResponse
:(NSXMLDocument
*)response
589 // Check and see if there's an error.
590 NSArray
* error
= [[response rootElement
] elementsForName
:@
"error"];
591 if ([error count
] > 0)
593 NSLog(@
"Xdebug error: %@", error
);
594 [delegate errorEncountered
:[[[[error objectAtIndex
:0] children
] objectAtIndex
:0] stringValue
]];
597 // If TransportDebug is enabled, log the response.
598 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
599 NSLog(@
"<-- %@", response
);
601 // Get the name of the command from the engine's response.
602 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
603 NSString
* transaction
= [[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
];
605 // Dispatch the command response to an appropriate handler.
606 if ([[[response rootElement
] name
] isEqualToString
:@
"init"])
607 [self initReceived
:response
];
608 else if ([command isEqualToString
:@
"status"])
609 [self updateStatus
:response
];
610 else if ([command isEqualToString
:@
"run"] ||
[command isEqualToString
:@
"step_into"] ||
611 [command isEqualToString
:@
"step_over"] ||
[command isEqualToString
:@
"step_out"])
612 [self debuggerStep
:response
];
613 else if ([command isEqualToString
:@
"stack_depth"])
614 [self rebuildStack
:response
];
615 else if ([command isEqualToString
:@
"stack_get"])
616 [self getStackFrame
:response
];
618 NSArray
* routingPath
= [transaction componentsSeparatedByString
:@
"."];
619 if ([routingPath count
] > 1)
620 [self handleRouted
:routingPath response
:response
];
624 * Initial packet received. We've started a brand-new connection to the engine.
626 - (void)initReceived
:(NSXMLDocument
*)response
628 // Register any breakpoints that exist offline.
629 for (Breakpoint
* bp
in [[BreakpointManager sharedManager
] breakpoints
])
630 [self addBreakpoint
:bp
];
632 // Load the debugger to make it look active.
633 [delegate debuggerConnected
];
635 [self send
:[self createCommand
:@
"status"]];
639 * Receiver for status updates. This just freshens up the UI.
641 - (void)updateStatus
:(NSXMLDocument
*)response
643 self.status
= [[[[response rootElement
] attributeForName
:@
"status"] stringValue
] capitalizedString
];
644 if (status
== nil ||
[status isEqualToString
:@
"Stopped"] ||
[status isEqualToString
:@
"Stopping"])
648 [delegate debuggerDisconnected
];
650 self.status
= @
"Stopped";
655 * Step in/out/over and run all take this path. We first get the status of the
656 * debugger and then request fresh stack information.
658 - (void)debuggerStep
:(NSXMLDocument
*)response
660 [self send
:[self createCommand
:@
"status"]];
661 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
662 NSUInteger routingID
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
664 // If this is the run command, tell the delegate that a bunch of updates
665 // are coming. Also remove all existing stack routes and request a new stack.
666 if ([command isEqualToString
:@
"run"])
668 [delegate clobberStack
];
669 [stackFrames_ removeAllObjects
];
670 [self send
:[self createCommand
:@
"stack_depth"]];
673 [self send
:[self createRouted
:[NSString stringWithFormat
:@
"%u", routingID
] command
:@
"stack_get -d 0"]];
677 * We ask for the stack_depth and now we clobber the stack and start rebuilding
680 - (void)rebuildStack
:(NSXMLDocument
*)response
682 NSInteger depth
= [[[[response rootElement
] attributeForName
:@
"depth"] stringValue
] intValue
];
684 // We now need to alloc a bunch of stack frames and get the basic information
686 for (NSInteger i
= 0; i
< depth
; i
++)
688 NSString
* command
= [self createCommand
:@
"stack_get -d %d", i
];
690 // Use the transaction ID to create a routing path.
691 NSNumber
* routingID
= [NSNumber numberWithInt
:transactionID
- 1];
692 [stackFrames_ setObject
:[StackFrame alloc
] forKey
:routingID
];
699 * The initial rebuild of the stack frame. We now have enough to initialize
700 * a StackFrame object.
702 - (void)getStackFrame
:(NSXMLDocument
*)response
704 // Get the routing information.
705 NSUInteger routingID
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
706 NSNumber
* routingNumber
= [NSNumber numberWithInt
:routingID
];
708 // Make sure we initialized this frame in our last |-rebuildStack:|.
709 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
713 NSXMLElement
* xmlframe
= [[[response rootElement
] children
] objectAtIndex
:0];
715 // Initialize the stack frame.
716 [frame initWithIndex
:[[[xmlframe attributeForName
:@
"level"] stringValue
] intValue
]
717 withFilename
:[[xmlframe attributeForName
:@
"filename"] stringValue
]
719 atLine
:[[[xmlframe attributeForName
:@
"lineno"] stringValue
] intValue
]
720 inFunction
:[[xmlframe attributeForName
:@
"where"] stringValue
]
723 // Now that we have an initialized frame, request additional information.
724 NSString
* routingFormat
= @
"%u.%d";
725 NSString
* routingPath
= nil;
727 // Get the source code of the file. Escape % in URL chars.
728 NSString
* escapedFilename
= [frame.filename stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
729 routingPath
= [NSString stringWithFormat
:routingFormat
, routingID
, kStackFrameSource
];
730 [self send
:[self createRouted
:routingPath command
:[NSString stringWithFormat
:@
"source -f %@", escapedFilename
]]];
732 // Get the names of all the contexts.
733 routingPath
= [NSString stringWithFormat
:routingFormat
, routingID
, kStackFrameContexts
];
734 [self send
:[self createRouted
:routingPath command
:@
"context_names -d %d", frame.index
]];
738 * Routed responses are currently only used in getting all the stack frame data.
740 - (void)handleRouted
:(NSArray
*)path response
:(NSXMLDocument
*)response
742 NSLog(@
"routed %@ = %@", path
, response
);
744 // Format of |path|: transactionID.routingID.component
745 StackFrameComponent component
= [[path objectAtIndex
:2] intValue
];
747 // See if we can find the stack frame based on routingID.
748 NSNumber
* routingNumber
= [NSNumber numberWithInt
:[[path objectAtIndex
:1] intValue
]];
749 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
753 if (component
== kStackFrameSource
)
754 frame.source
= [[response rootElement
] value
];
760 * Helper method to create a string command with the -i <transaction id> automatically tacked on. Takes
761 * a variable number of arguments and parses the given command with +[NSString stringWithFormat:]
763 - (NSString
*)createCommand
:(NSString
*)cmd
, ...
767 va_start(argList
, cmd
);
768 NSString
* format
= [[NSString alloc
] initWithFormat
:cmd arguments
:argList
]; // format the command
771 return [NSString stringWithFormat
:@
"%@ -i %d", [format autorelease
], transactionID
++];
775 * Helper to create a command string. This works a lot like |-createCommand:| but
776 * it also takes a routing ID, which is used to route response data to specific
779 - (NSString
*)createRouted
:(NSString
*)routingID command
:(NSString
*)cmd
, ...
782 va_start(argList
, cmd
);
783 NSString
* format
= [[NSString alloc
] initWithFormat
:cmd arguments
:argList
];
786 return [NSString stringWithFormat
:@
"%@ -i %d.%@", [format autorelease
], transactionID
++, routingID
];
790 * Generates a stack frame for the given depth
792 - (StackFrame
*)createStackFrame
:(int)stackDepth
794 // get the names of all the contexts
795 [socket send
:[self createCommand
:@
"context_names -d 0"]];
796 NSXMLElement
* contextNames
= [[self processData
:[socket receive
]] rootElement
];
797 NSMutableArray
* variables
= [NSMutableArray array
];
798 for (NSXMLElement
* context
in [contextNames children
])
800 NSString
* name
= [[context attributeForName
:@
"name"] stringValue
];
801 int cid
= [[[context attributeForName
:@
"id"] stringValue
] intValue
];
803 // fetch the contexts
804 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"context_get -d %d -c %d", stackDepth
, cid
]]];
805 NSArray
* addVars
= [[[self processData
:[socket receive
]] rootElement
] children
];
806 if (addVars
!= nil && name
!= nil)
807 [variables addObjectsFromArray
:addVars
];
814 * Given a file path, this returns a file:// URI and escapes any spaces for the
817 - (NSString
*)escapedURIPath
:(NSString
*)path
819 // Custon GDBp paths are fine.
820 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
823 // Create a temporary URL that will escape all the nasty characters.
824 NSURL
* url
= [NSURL fileURLWithPath
:path
];
825 NSString
* urlString
= [url absoluteString
];
827 // Remove the host because this is a file:// URL;
828 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
830 // Escape % for use in printf-style NSString formatters.
831 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];