From 6b707bc9abd46bd1c335ef7ad18b0a9674775ea5 Mon Sep 17 00:00:00 2001 From: Eithan Shavit Date: Tue, 3 Nov 2015 09:09:43 -0800 Subject: [PATCH 1/3] Adding --depth option to border command to recursively border subviews --- commands/FBDisplayCommands.py | 73 ++++++++++++++++++++++++++++++----- fblldbviewhelpers.py | 18 +++++++++ 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/commands/FBDisplayCommands.py b/commands/FBDisplayCommands.py index ee4046c..0d7c062 100644 --- a/commands/FBDisplayCommands.py +++ b/commands/FBDisplayCommands.py @@ -28,11 +28,25 @@ def lldbcommands(): class FBDrawBorderCommand(fb.FBCommand): + colors = [ + "black", + "gray", + "red", + "green", + "blue", + "cyan", + "yellow", + "magenta", + "orange", + "purple", + "brown", + ] + def name(self): return 'border' def description(self): - return 'Draws a border around . Color and width can be optionally provided.' + return 'Draws a border around . Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews.' def args(self): return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to border. NSViews must be layer-backed.') ] @@ -40,21 +54,43 @@ def args(self): def options(self): return [ fb.FBCommandArgument(short='-c', long='--color', arg='color', type='string', default='red', help='A color name such as \'red\', \'green\', \'magenta\', etc.'), - fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.') + fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.'), + fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to border. Each level gets a different color beginning with the provided or default color'), ] def run(self, args, options): - colorClassName = 'UIColor' - isMac = runtimeHelpers.isMacintoshArch() + def setBorder(layer, width, color, colorClass): + lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, width)) + lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClass, color)) + depth = int(options.depth) + isMac = runtimeHelpers.isMacintoshArch() + color = options.color + assert color in self.colors, "Color must be one of the following: {}".format(" ".join(self.colors)) + colorClassName = 'UIColor' 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)[%s %sColor] CGColor]]' % (layer, colorClassName, options.color)) + if viewHelpers.isUIView(args[0]): + prevLevel = 0 + for view, level in viewHelpers.subviewsOfView(args[0]): + if level > depth: + break + if prevLevel != level: + color = self.nextColorAfterColor(color) + prevLevel = level + layer = viewHelpers.convertToLayer(view) + setBorder(layer, options.width, color, colorClassName) + else: + assert depth <= 0, "Recursive bordering is only supported for UIViews" + layer = viewHelpers.convertToLayer(args[0]) + setBorder(layer, options.width, color, colorClassName) + lldb.debugger.HandleCommand('caflush') + def nextColorAfterColor(self, color): + assert color in self.colors, "{} is not a supported color".format(color) + return self.colors[(self.colors.index(color)+1) % len(self.colors)] class FBRemoveBorderCommand(fb.FBCommand): def name(self): @@ -63,14 +99,31 @@ def name(self): def description(self): return 'Removes border around .' + def options(self): + return [ + fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to unborder.') + ] + def args(self): 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]) - lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0)) - lldb.debugger.HandleCommand('caflush') + def setUnborder(layer): + lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0)) + + depth = int(options.depth) + if viewHelpers.isUIView(args[0]): + for view, level in viewHelpers.subviewsOfView(args[0]): + if level > depth: + break + layer = viewHelpers.convertToLayer(view) + setUnborder(layer) + else: + assert depth <= 0, "Recursive unbordering is only supported for UIViews" + layer = viewHelpers.convertToLayer(args[0]) + setUnborder(layer) + lldb.debugger.HandleCommand('caflush') class FBMaskViewCommand(fb.FBCommand): def name(self): diff --git a/fblldbviewhelpers.py b/fblldbviewhelpers.py index a6534d8..7b26f5a 100644 --- a/fblldbviewhelpers.py +++ b/fblldbviewhelpers.py @@ -55,6 +55,24 @@ def convertToLayer(viewOrLayer): else: raise Exception('Argument must be a CALayer, UIView, or NSView.') +def isUIView(obj): + return fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj) + +# Generates a BFS of the views tree starting at the given view as root. +# Yields a tuple of the current view in the tree and its level (view, level) +def subviewsOfView(view): + views = [(view, 0)] + yield views[0] + while views: + (view, level) = views.pop(0) + subviews = fb.evaluateExpression('(id)[%s subviews]' % view) + subviewsCount = int(fb.evaluateExpression('(int)[(id)%s count]' % subviews)) + if subviewsCount > 0: + for i in range(0, subviewsCount): + subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i)) + views.append((subview, level+1)) + yield (subview, level+1) + def upwardsRecursiveDescription(view, maxDepth=0): if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view): return None From cbfc2013949b13845562110a82ba765d0b4c6d75 Mon Sep 17 00:00:00 2001 From: Eithan Shavit Date: Wed, 4 Nov 2015 23:13:15 -0800 Subject: [PATCH 2/3] Support for NSView in recursive bordering --- commands/FBDisplayCommands.py | 8 ++++---- fblldbviewhelpers.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/commands/FBDisplayCommands.py b/commands/FBDisplayCommands.py index 0d7c062..32af937 100644 --- a/commands/FBDisplayCommands.py +++ b/commands/FBDisplayCommands.py @@ -71,7 +71,7 @@ def setBorder(layer, width, color, colorClass): if isMac: colorClassName = 'NSColor' - if viewHelpers.isUIView(args[0]): + if viewHelpers.isView(args[0]): prevLevel = 0 for view, level in viewHelpers.subviewsOfView(args[0]): if level > depth: @@ -82,7 +82,7 @@ def setBorder(layer, width, color, colorClass): layer = viewHelpers.convertToLayer(view) setBorder(layer, options.width, color, colorClassName) else: - assert depth <= 0, "Recursive bordering is only supported for UIViews" + assert depth <= 0, "Recursive bordering is only supported for UIViews or NSViews" layer = viewHelpers.convertToLayer(args[0]) setBorder(layer, options.width, color, colorClassName) @@ -112,14 +112,14 @@ def setUnborder(layer): lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0)) depth = int(options.depth) - if viewHelpers.isUIView(args[0]): + if viewHelpers.isView(args[0]): for view, level in viewHelpers.subviewsOfView(args[0]): if level > depth: break layer = viewHelpers.convertToLayer(view) setUnborder(layer) else: - assert depth <= 0, "Recursive unbordering is only supported for UIViews" + assert depth <= 0, "Recursive unbordering is only supported for UIViews or NSViews" layer = viewHelpers.convertToLayer(args[0]) setUnborder(layer) diff --git a/fblldbviewhelpers.py b/fblldbviewhelpers.py index 7b26f5a..124de57 100644 --- a/fblldbviewhelpers.py +++ b/fblldbviewhelpers.py @@ -58,6 +58,12 @@ def convertToLayer(viewOrLayer): def isUIView(obj): return fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj) +def isNSView(obj): + return fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % obj) + +def isView(obj): + return isUIView(obj) or isNSView(obj) + # Generates a BFS of the views tree starting at the given view as root. # Yields a tuple of the current view in the tree and its level (view, level) def subviewsOfView(view): @@ -67,11 +73,10 @@ def subviewsOfView(view): (view, level) = views.pop(0) subviews = fb.evaluateExpression('(id)[%s subviews]' % view) subviewsCount = int(fb.evaluateExpression('(int)[(id)%s count]' % subviews)) - if subviewsCount > 0: - for i in range(0, subviewsCount): - subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i)) - views.append((subview, level+1)) - yield (subview, level+1) + for i in xrange(subviewsCount): + subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i)) + views.append((subview, level+1)) + yield (subview, level+1) def upwardsRecursiveDescription(view, maxDepth=0): if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view): From 8116d9daa5b301a18e2eec7bf9152f37cbdc9108 Mon Sep 17 00:00:00 2001 From: Eithan Shavit Date: Thu, 5 Nov 2015 12:19:13 -0800 Subject: [PATCH 3/3] Fix isView method --- commands/FBDisplayCommands.py | 16 ++++++++++------ fblldbviewhelpers.py | 5 +++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/commands/FBDisplayCommands.py b/commands/FBDisplayCommands.py index 32af937..a1cab0b 100644 --- a/commands/FBDisplayCommands.py +++ b/commands/FBDisplayCommands.py @@ -63,6 +63,7 @@ def setBorder(layer, width, color, colorClass): lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, width)) lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClass, color)) + obj = args[0] depth = int(options.depth) isMac = runtimeHelpers.isMacintoshArch() color = options.color @@ -71,9 +72,9 @@ def setBorder(layer, width, color, colorClass): if isMac: colorClassName = 'NSColor' - if viewHelpers.isView(args[0]): + if viewHelpers.isView(obj): prevLevel = 0 - for view, level in viewHelpers.subviewsOfView(args[0]): + for view, level in viewHelpers.subviewsOfView(obj): if level > depth: break if prevLevel != level: @@ -82,8 +83,9 @@ def setBorder(layer, width, color, colorClass): layer = viewHelpers.convertToLayer(view) setBorder(layer, options.width, color, colorClassName) else: + # `obj` is not a view, make sure recursive bordering is not requested assert depth <= 0, "Recursive bordering is only supported for UIViews or NSViews" - layer = viewHelpers.convertToLayer(args[0]) + layer = viewHelpers.convertToLayer(obj) setBorder(layer, options.width, color, colorClassName) lldb.debugger.HandleCommand('caflush') @@ -111,16 +113,18 @@ def run(self, args, options): def setUnborder(layer): lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0)) + obj = args[0] depth = int(options.depth) - if viewHelpers.isView(args[0]): - for view, level in viewHelpers.subviewsOfView(args[0]): + if viewHelpers.isView(obj): + for view, level in viewHelpers.subviewsOfView(obj): if level > depth: break layer = viewHelpers.convertToLayer(view) setUnborder(layer) else: + # `obj` is not a view, make sure recursive unbordering is not requested assert depth <= 0, "Recursive unbordering is only supported for UIViews or NSViews" - layer = viewHelpers.convertToLayer(args[0]) + layer = viewHelpers.convertToLayer(obj) setUnborder(layer) lldb.debugger.HandleCommand('caflush') diff --git a/fblldbviewhelpers.py b/fblldbviewhelpers.py index 124de57..13d3910 100644 --- a/fblldbviewhelpers.py +++ b/fblldbviewhelpers.py @@ -10,6 +10,7 @@ import lldb import fblldbbase as fb +import fblldbobjcruntimehelpers as runtimeHelpers def flushCoreAnimationTransaction(): lldb.debugger.HandleCommand('expr (void)[CATransaction flush]') @@ -56,10 +57,10 @@ def convertToLayer(viewOrLayer): raise Exception('Argument must be a CALayer, UIView, or NSView.') def isUIView(obj): - return fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj) + return not runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj) def isNSView(obj): - return fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % obj) + return runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % obj) def isView(obj): return isUIView(obj) or isNSView(obj)