Skip to content

Commit

Permalink
Merge pull request #45 from facebook/OS-X-support
Browse files Browse the repository at this point in the history
OS X Support (for major commands)
  • Loading branch information
arigrant committed Jul 9, 2014
2 parents 35da901 + 6cfb42e commit 200599d
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 38 deletions.
33 changes: 17 additions & 16 deletions README.md
Expand Up @@ -25,22 +25,23 @@ The commands will be available the next time `Xcode` starts.

## Commands
There are many commands; here's a few:

|Command |Description|
|----------------------------------|-----------|
|pviews |Print the recursive view description for the key window.|
|pvc |Print the recursive view controller description for the key window.|
|visualize |Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.|
|fv |Find a view in the hierarchy whose class name matches the provided regex.|
|fvc |Find a view controller in the hierarchy whose class name matches the provided regex.|
|show/hide |Show or hide the given view or layer. You don't even have to continue the process to see the changes!|
|mask/unmask |Overlay a view or layer with a transparent rectangle to visualize where it is.|
|border/unborder |Add a border to a view or layer to visualize where it is.|
|caflush |Flush the render server (equivalent to a "repaint" if no animations are in-flight).)|
|bmessage |Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.|
|wivar |Set a watchpoint on an instance variable of an object.|
|presponder |Print the responder chain starting from the given object.|
|... |... and many more!|
*(Compatibility with iOS/Mac indicated at right)*

|Command |Description |iOS |OS X |
|-----------------|----------------|-------|-------|
|pviews |Print the recursive view description for the key window.|Yes|Yes|
|pvc |Print the recursive view controller description for the key window.|Yes|No|
|visualize |Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.|Yes|No|
|fv |Find a view in the hierarchy whose class name matches the provided regex.|Yes|No|
|fvc |Find a view controller in the hierarchy whose class name matches the provided regex.|Yes|No|
|show/hide |Show or hide the given view or layer. You don't even have to continue the process to see the changes!|Yes|Yes|
|mask/unmask |Overlay a view or layer with a transparent rectangle to visualize where it is.|Yes|No|
|border/unborder |Add a border to a view or layer to visualize where it is.|Yes|Yes|
|caflush |Flush the render server (equivalent to a "repaint" if no animations are in-flight).)|Yes|Yes|
|bmessage |Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.|Yes|Yes|
|wivar |Set a watchpoint on an instance variable of an object.|Yes|Yes|
|presponder |Print the responder chain starting from the given object.|Yes|Yes|
|... |... and many more!|

To see the list of **all** of the commands execute the help command in `LLDB`.

Expand Down
19 changes: 13 additions & 6 deletions commands/FBDisplayCommands.py
Expand Up @@ -13,6 +13,7 @@

import fblldbviewhelpers as viewHelpers
import fblldbbase as fb
import fblldbobjcruntimehelpers as runtimeHelpers

def lldbcommands():
return [
Expand All @@ -34,7 +35,7 @@ def description(self):
return 'Draws a border around <viewOrLayer>. Color and width can be optionally provided.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to border.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to border. NSViews must be layer-backed.') ]

def options(self):
return [
Expand All @@ -43,9 +44,15 @@ def options(self):
]

def run(self, args, options):
colorClassName = 'UIColor'
isMac = runtimeHelpers.isMacintoshArch()

if isMac:
colorClassName = 'NSColor'

layer = viewHelpers.convertToLayer(args[0])
lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, options.width))
lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[UIColor %sColor] CGColor]]' % (layer, options.color))
lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClassName, options.color))
lldb.debugger.HandleCommand('caflush')


Expand All @@ -57,7 +64,7 @@ def description(self):
return 'Removes border around <viewOrLayer>.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to unborder.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to unborder.') ]

def run(self, args, options):
layer = viewHelpers.convertToLayer(args[0])
Expand All @@ -73,7 +80,7 @@ def description(self):
return 'Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer\'s bounds'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to mask.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to mask.') ]

def options(self):
return [
Expand Down Expand Up @@ -120,7 +127,7 @@ def description(self):
return 'Show a view or layer.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to show.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to show.') ]

def run(self, args, options):
viewHelpers.setViewHidden(args[0], False)
Expand All @@ -134,7 +141,7 @@ def description(self):
return 'Hide a view or layer.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to hide.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to hide.') ]

def run(self, args, options):
viewHelpers.setViewHidden(args[0], True)
2 changes: 1 addition & 1 deletion commands/FBFindCommands.py
Expand Up @@ -12,8 +12,8 @@

import lldb
import fblldbbase as fb
import fblldbviewcontrollerhelpers as vcHelpers
import fblldbobjcruntimehelpers as objc
import fblldbviewcontrollerhelpers as vcHelpers

def lldbcommands():
return [
Expand Down
11 changes: 9 additions & 2 deletions commands/FBFlickerCommands.py
Expand Up @@ -14,6 +14,7 @@
import fblldbbase as fb
import fblldbviewhelpers as viewHelpers
import fblldbinputhelpers as inputHelpers
import fblldbobjcruntimehelpers as runtimeHelpers

def lldbcommands():
return [
Expand All @@ -30,7 +31,7 @@ def description(self):
return 'Quickly show and hide a view to quickly help visualize where it is.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView*', help='The view to border.') ]
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView*', help='The view to flicker.') ]

def run(self, arguments, options):
object = fb.evaluateObjectExpression(arguments[0])
Expand Down Expand Up @@ -107,7 +108,13 @@ def inputCallback(self, input):
print '\nThere are no sibling views to this view.\n'
self.setCurrentView(v)
elif input == 'p':
lldb.debugger.HandleCommand('po [(id)' + oldView + ' recursiveDescription]')
recusionName = 'recursiveDescription'
isMac = runtimeHelpers.isMacintoshArch()

if isMac:
recursionName = '_subtreeDescription'

lldb.debugger.HandleCommand('po [(id)' + oldView + ' ' + recusionName + ']')
else:
print '\nI really have no idea what you meant by \'' + input + '\'... =\\\n'

Expand Down
29 changes: 24 additions & 5 deletions commands/FBPrintCommands.py
Expand Up @@ -14,6 +14,7 @@
import fblldbbase as fb
import fblldbviewcontrollerhelpers as vcHelpers
import fblldbviewhelpers as viewHelpers
import fblldbobjcruntimehelpers as runtimeHelpers

def lldbcommands():
return [
Expand Down Expand Up @@ -44,10 +45,17 @@ def options(self):
]

def args(self):
return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the description of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ]
return [ fb.FBCommandArgument(arg='aView', type='UIView*/NSView*', help='The view to print the description of.', default='__keyWindow_dynamic__') ]

def run(self, arguments, options):
maxDepth = int(options.depth)
isMac = runtimeHelpers.isMacintoshArch()

if (arguments[0] == '__keyWindow_dynamic__'):
arguments[0] = '(id)[[UIApplication sharedApplication] keyWindow]'

if isMac:
arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentView]'

if options.upwards:
view = arguments[0]
Expand All @@ -57,7 +65,11 @@ def run(self, arguments, options):
else:
print 'Failed to walk view hierarchy. Make sure you pass a view, not any other kind of object or expression.'
else:
description = fb.evaluateExpressionValue('(id)[' + arguments[0] + ' recursiveDescription]').GetObjectDescription()
printingMethod = 'recursiveDescription'
if (isMac):
printingMethod = '_subtreeDescription'

description = fb.evaluateExpressionValue('(id)[' + arguments[0] + ' ' + printingMethod + ']').GetObjectDescription()
if maxDepth > 0:
separator = re.escape(" | ")
prefixToRemove = separator * maxDepth + " "
Expand Down Expand Up @@ -85,9 +97,16 @@ def description(self):
return 'Print the recursion description of <aViewController>.'

def args(self):
return [ fb.FBCommandArgument(arg='aViewController', type='UIViewController*', help='The view controller to print the description of.', default='(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]') ]
return [ fb.FBCommandArgument(arg='aViewController', type='UIViewController*', help='The view controller to print the description of.', default='__keyWindow_rootVC_dynamic__') ]

def run(self, arguments, options):
isMac = runtimeHelpers.isMacintoshArch()

if (arguments[0] == '__keyWindow_rootVC_dynamic__'):
arguments[0] = '(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]'
if (isMac):
arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentViewController]'

print vcHelpers.viewControllerRecursiveDescription(arguments[0])


Expand Down Expand Up @@ -142,8 +161,8 @@ def args(self):

def run(self, arguments, options):
startResponder = arguments[0]
if not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[UIResponder class]]'):
print 'Whoa, ' + startResponder + ' is not a UIResponder. =('
if not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[UIResponder class]]') and not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[NSResponder class]]'):
print 'Whoa, ' + startResponder + ' is not a UI/NSResponder. =('
return

_printIterative(startResponder, _responderChain)
Expand Down
15 changes: 14 additions & 1 deletion fblldbobjcruntimehelpers.py
Expand Up @@ -9,7 +9,6 @@

import lldb
import fblldbbase as fb
import re

def objc_getClass(className):
command = '(void*)objc_getClass("{}")'.format(className)
Expand Down Expand Up @@ -42,6 +41,8 @@ def currentArch():
return arch

def functionPreambleExpressionForSelf():
import re

arch = currentArch()
expressionForSelf = None
if arch == 'i386':
Expand All @@ -55,6 +56,8 @@ def functionPreambleExpressionForSelf():
return expressionForSelf

def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex):
import re

arch = currentArch()
expresssion = None
if arch == 'i386':
Expand All @@ -73,3 +76,13 @@ def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex):
raise Exception("Current implementation can not return object at index greater than 1 for arm32")
expresssion = '(id)$r' + str(parameterIndex + 2)
return expresssion

def isMacintoshArch():
arch = currentArch()
if not arch == 'x86_64':
return False

nsClassName ='NSApplication'
command = '(void*)objc_getClass("{}")'.format(nsClassName)

return (fb.evaluateBooleanExpression(command + '!= nil'))
14 changes: 9 additions & 5 deletions fblldbviewcontrollerhelpers.py
Expand Up @@ -9,6 +9,7 @@

import lldb
import fblldbbase as fb
import fblldbobjcruntimehelpers as runtimeHelpers

def viewControllerRecursiveDescription(vc):
return _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(fb.evaluateObjectExpression(vc), '', '', '')
Expand All @@ -28,6 +29,8 @@ def _viewControllerDescription(viewController):


def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(vc, string, prefix, childPrefix):
isMac = runtimeHelpers.isMacintoshArch()

s = '%s%s%s\n' % (prefix, '' if prefix == '' else ' ', _viewControllerDescription(vc))

nextPrefix = childPrefix + ' |'
Expand All @@ -39,11 +42,12 @@ def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(vc, string, pref
viewController = fb.evaluateExpression('(id)[(id)[%s childViewControllers] objectAtIndex:%d]' % (vc, i))
s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(viewController, string, nextPrefix, nextPrefix)

isModal = fb.evaluateBooleanExpression('%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s' % (vc, vc, vc))
if not isMac:
isModal = fb.evaluateBooleanExpression('%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s' % (vc, vc, vc))

if isModal:
modalVC = fb.evaluateObjectExpression('(id)[(id)%s presentedViewController]' % (vc))
s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(modalVC, string, childPrefix + ' *M' , nextPrefix)
s += '\n// \'*M\' means the view controller is presented modally.'
if isModal:
modalVC = fb.evaluateObjectExpression('(id)[(id)%s presentedViewController]' % (vc))
s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(modalVC, string, childPrefix + ' *M' , nextPrefix)
s += '\n// \'*M\' means the view controller is presented modally.'

return string + s
4 changes: 2 additions & 2 deletions fblldbviewhelpers.py
Expand Up @@ -53,10 +53,10 @@ def convertToLayer(viewOrLayer):
elif fb.evaluateBooleanExpression('[(id)%s respondsToSelector:(SEL)@selector(layer)]' % viewOrLayer):
return fb.evaluateExpression('(CALayer *)[%s layer]' % viewOrLayer)
else:
raise Exception('Argument must be a CALayer or a UIView')
raise Exception('Argument must be a CALayer, UIView, or NSView.')

def upwardsRecursiveDescription(view, maxDepth=0):
if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view):
if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view):
return None

currentView = view
Expand Down

0 comments on commit 200599d

Please sign in to comment.