diff options
Diffstat (limited to 'lldb/utils/vim-lldb/python-vim-lldb')
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/import_lldb.py | 112 | ||||
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py | 741 | ||||
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/plugin.py | 16 | ||||
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/vim_panes.py | 987 | ||||
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/vim_signs.py | 134 | ||||
-rw-r--r-- | lldb/utils/vim-lldb/python-vim-lldb/vim_ui.py | 462 |
6 files changed, 1286 insertions, 1166 deletions
diff --git a/lldb/utils/vim-lldb/python-vim-lldb/import_lldb.py b/lldb/utils/vim-lldb/python-vim-lldb/import_lldb.py index a2145d50466..41cb42b1bb3 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/import_lldb.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/import_lldb.py @@ -1,61 +1,71 @@ # Locate and load the lldb python module -import os, sys +import os +import sys + def import_lldb(): - """ Find and import the lldb modules. This function tries to find the lldb module by: - 1. Simply by doing "import lldb" in case the system python installation is aware of lldb. If that fails, - 2. Executes the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb - on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid - path, it is added to sys.path and the import is attempted again. If that fails, 3. On Mac OS X the - default Xcode 4.5 installation path. - """ - - # Try simple 'import lldb', in case of a system-wide install or a pre-configured PYTHONPATH - try: - import lldb - return True - except ImportError: - pass - - # Allow overriding default path to lldb executable with the LLDB environment variable - lldb_executable = 'lldb' - if 'LLDB' in os.environ and os.path.exists(os.environ['LLDB']): - lldb_executable = os.environ['LLDB'] - - # Try using builtin module location support ('lldb -P') - from subprocess import check_output, CalledProcessError - try: - with open(os.devnull, 'w') as fnull: - lldb_minus_p_path = check_output("%s -P" % lldb_executable, shell=True, stderr=fnull).strip() - if not os.path.exists(lldb_minus_p_path): - #lldb -P returned invalid path, probably too old - pass - else: - sys.path.append(lldb_minus_p_path) - import lldb - return True - except CalledProcessError: - # Cannot run 'lldb -P' to determine location of lldb python module - pass - except ImportError: - # Unable to import lldb module from path returned by `lldb -P` - pass - - # On Mac OS X, use the try the default path to XCode lldb module - if "darwin" in sys.platform: - xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python/" - sys.path.append(xcode_python_path) + """ Find and import the lldb modules. This function tries to find the lldb module by: + 1. Simply by doing "import lldb" in case the system python installation is aware of lldb. If that fails, + 2. Executes the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb + on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid + path, it is added to sys.path and the import is attempted again. If that fails, 3. On Mac OS X the + default Xcode 4.5 installation path. + """ + + # Try simple 'import lldb', in case of a system-wide install or a + # pre-configured PYTHONPATH try: - import lldb - return True + import lldb + return True except ImportError: - # Unable to import lldb module from default Xcode python path - pass + pass + + # Allow overriding default path to lldb executable with the LLDB + # environment variable + lldb_executable = 'lldb' + if 'LLDB' in os.environ and os.path.exists(os.environ['LLDB']): + lldb_executable = os.environ['LLDB'] + + # Try using builtin module location support ('lldb -P') + from subprocess import check_output, CalledProcessError + try: + with open(os.devnull, 'w') as fnull: + lldb_minus_p_path = check_output( + "%s -P" % + lldb_executable, + shell=True, + stderr=fnull).strip() + if not os.path.exists(lldb_minus_p_path): + # lldb -P returned invalid path, probably too old + pass + else: + sys.path.append(lldb_minus_p_path) + import lldb + return True + except CalledProcessError: + # Cannot run 'lldb -P' to determine location of lldb python module + pass + except ImportError: + # Unable to import lldb module from path returned by `lldb -P` + pass + + # On Mac OS X, use the try the default path to XCode lldb module + if "darwin" in sys.platform: + xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python/" + sys.path.append(xcode_python_path) + try: + import lldb + return True + except ImportError: + # Unable to import lldb module from default Xcode python path + pass - return False + return False if not import_lldb(): - import vim - vim.command('redraw | echo "%s"' % " Error loading lldb module; vim-lldb will be disabled. Check LLDB installation or set LLDB environment variable.") + import vim + vim.command( + 'redraw | echo "%s"' % + " Error loading lldb module; vim-lldb will be disabled. Check LLDB installation or set LLDB environment variable.") diff --git a/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py b/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py index 923e771e6af..ed54633a3ea 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/lldb_controller.py @@ -3,7 +3,9 @@ # This file defines the layer that talks to lldb # -import os, re, sys +import os +import re +import sys import lldb import vim from vim_ui import UI @@ -13,372 +15,399 @@ from vim_ui import UI # ================================================= # Shamelessly copy/pasted from lldbutil.py in the test suite -def state_type_to_str(enum): - """Returns the stateType string given an enum.""" - if enum == lldb.eStateInvalid: - return "invalid" - elif enum == lldb.eStateUnloaded: - return "unloaded" - elif enum == lldb.eStateConnected: - return "connected" - elif enum == lldb.eStateAttaching: - return "attaching" - elif enum == lldb.eStateLaunching: - return "launching" - elif enum == lldb.eStateStopped: - return "stopped" - elif enum == lldb.eStateRunning: - return "running" - elif enum == lldb.eStateStepping: - return "stepping" - elif enum == lldb.eStateCrashed: - return "crashed" - elif enum == lldb.eStateDetached: - return "detached" - elif enum == lldb.eStateExited: - return "exited" - elif enum == lldb.eStateSuspended: - return "suspended" - else: - raise Exception("Unknown StateType enum") -class StepType: - INSTRUCTION = 1 - INSTRUCTION_OVER = 2 - INTO = 3 - OVER = 4 - OUT = 5 -class LLDBController(object): - """ Handles Vim and LLDB events such as commands and lldb events. """ - - # Timeouts (sec) for waiting on new events. Because vim is not multi-threaded, we are restricted to - # servicing LLDB events from the main UI thread. Usually, we only process events that are already - # sitting on the queue. But in some situations (when we are expecting an event as a result of some - # user interaction) we want to wait for it. The constants below set these wait period in which the - # Vim UI is "blocked". Lower numbers will make Vim more responsive, but LLDB will be delayed and higher - # numbers will mean that LLDB events are processed faster, but the Vim UI may appear less responsive at - # times. - eventDelayStep = 2 - eventDelayLaunch = 1 - eventDelayContinue = 1 - - def __init__(self): - """ Creates the LLDB SBDebugger object and initializes the UI class. """ - self.target = None - self.process = None - self.load_dependent_modules = True - - self.dbg = lldb.SBDebugger.Create() - self.commandInterpreter = self.dbg.GetCommandInterpreter() - - self.ui = UI() - - def completeCommand(self, a, l, p): - """ Returns a list of viable completions for command a with length l and cursor at p """ - - assert l[0] == 'L' - # Remove first 'L' character that all commands start with - l = l[1:] - - # Adjust length as string has 1 less character - p = int(p) - 1 - - result = lldb.SBStringList() - num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result) - - if num == -1: - # FIXME: insert completion character... what's a completion character? - pass - elif num == -2: - # FIXME: replace line with result.GetStringAtIndex(0) - pass - - if result.GetSize() > 0: - results = filter(None, [result.GetStringAtIndex(x) for x in range(result.GetSize())]) - return results - else: - return [] - - def doStep(self, stepType): - """ Perform a step command and block the UI for eventDelayStep seconds in order to process - events on lldb's event queue. - FIXME: if the step does not complete in eventDelayStep seconds, we relinquish control to - the main thread to avoid the appearance of a "hang". If this happens, the UI will - update whenever; usually when the user moves the cursor. This is somewhat annoying. - """ - if not self.process: - sys.stderr.write("No process to step") - return - - t = self.process.GetSelectedThread() - if stepType == StepType.INSTRUCTION: - t.StepInstruction(False) - if stepType == StepType.INSTRUCTION_OVER: - t.StepInstruction(True) - elif stepType == StepType.INTO: - t.StepInto() - elif stepType == StepType.OVER: - t.StepOver() - elif stepType == StepType.OUT: - t.StepOut() - - self.processPendingEvents(self.eventDelayStep, True) - - def doSelect(self, command, args): - """ Like doCommand, but suppress output when "select" is the first argument.""" - a = args.split(' ') - return self.doCommand(command, args, "select" != a[0], True) - - def doProcess(self, args): - """ Handle 'process' command. If 'launch' is requested, use doLaunch() instead - of the command interpreter to start the inferior process. - """ - a = args.split(' ') - if len(args) == 0 or (len(a) > 0 and a[0] != 'launch'): - self.doCommand("process", args) - #self.ui.update(self.target, "", self) - else: - self.doLaunch('-s' not in args, "") - - def doAttach(self, process_name): - """ Handle process attach. """ - error = lldb.SBError() - - self.processListener = lldb.SBListener("process_event_listener") - self.target = self.dbg.CreateTarget('') - self.process = self.target.AttachToProcessWithName(self.processListener, process_name, False, error) - if not error.Success(): - sys.stderr.write("Error during attach: " + str(error)) - return - - self.ui.activate() - self.pid = self.process.GetProcessID() - - print "Attached to %s (pid=%d)" % (process_name, self.pid) - - def doDetach(self): - if self.process is not None and self.process.IsValid(): - pid = self.process.GetProcessID() - state = state_type_to_str(self.process.GetState()) - self.process.Detach() - self.processPendingEvents(self.eventDelayLaunch) - - def doLaunch(self, stop_at_entry, args): - """ Handle process launch. """ - error = lldb.SBError() - - fs = self.target.GetExecutable() - exe = os.path.join(fs.GetDirectory(), fs.GetFilename()) - if self.process is not None and self.process.IsValid(): - pid = self.process.GetProcessID() - state = state_type_to_str(self.process.GetState()) - self.process.Destroy() - - launchInfo = lldb.SBLaunchInfo(args.split(' ')) - self.process = self.target.Launch(launchInfo, error) - if not error.Success(): - sys.stderr.write("Error during launch: " + str(error)) - return - - # launch succeeded, store pid and add some event listeners - self.pid = self.process.GetProcessID() - self.processListener = lldb.SBListener("process_event_listener") - self.process.GetBroadcaster().AddListener(self.processListener, lldb.SBProcess.eBroadcastBitStateChanged) - - print "Launched %s %s (pid=%d)" % (exe, args, self.pid) - - if not stop_at_entry: - self.doContinue() +def state_type_to_str(enum): + """Returns the stateType string given an enum.""" + if enum == lldb.eStateInvalid: + return "invalid" + elif enum == lldb.eStateUnloaded: + return "unloaded" + elif enum == lldb.eStateConnected: + return "connected" + elif enum == lldb.eStateAttaching: + return "attaching" + elif enum == lldb.eStateLaunching: + return "launching" + elif enum == lldb.eStateStopped: + return "stopped" + elif enum == lldb.eStateRunning: + return "running" + elif enum == lldb.eStateStepping: + return "stepping" + elif enum == lldb.eStateCrashed: + return "crashed" + elif enum == lldb.eStateDetached: + return "detached" + elif enum == lldb.eStateExited: + return "exited" + elif enum == lldb.eStateSuspended: + return "suspended" else: - self.processPendingEvents(self.eventDelayLaunch) - - def doTarget(self, args): - """ Pass target command to interpreter, except if argument is not one of the valid options, or - is create, in which case try to create a target with the argument as the executable. For example: - target list ==> handled by interpreter - target create blah ==> custom creation of target 'blah' - target blah ==> also creates target blah - """ - target_args = [#"create", - "delete", - "list", - "modules", - "select", - "stop-hook", - "symbols", - "variable"] - - a = args.split(' ') - if len(args) == 0 or (len(a) > 0 and a[0] in target_args): - self.doCommand("target", args) - return - elif len(a) > 1 and a[0] == "create": - exe = a[1] - elif len(a) == 1 and a[0] not in target_args: - exe = a[0] - - err = lldb.SBError() - self.target = self.dbg.CreateTarget(exe, None, None, self.load_dependent_modules, err) - if not self.target: - sys.stderr.write("Error creating target %s. %s" % (str(exe), str(err))) - return - - self.ui.activate() - self.ui.update(self.target, "created target %s" % str(exe), self) - - def doContinue(self): - """ Handle 'contiue' command. - FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param. - """ - if not self.process or not self.process.IsValid(): - sys.stderr.write("No process to continue") - return + raise Exception("Unknown StateType enum") - self.process.Continue() - self.processPendingEvents(self.eventDelayContinue) - def doBreakpoint(self, args): - """ Handle breakpoint command with command interpreter, except if the user calls - "breakpoint" with no other args, in which case add a breakpoint at the line - under the cursor. - """ - a = args.split(' ') - if len(args) == 0: - show_output = False - - # User called us with no args, so toggle the bp under cursor - cw = vim.current.window - cb = vim.current.buffer - name = cb.name - line = cw.cursor[0] - - # Since the UI is responsbile for placing signs at bp locations, we have to - # ask it if there already is one or more breakpoints at (file, line)... - if self.ui.haveBreakpoint(name, line): - bps = self.ui.getBreakpoints(name, line) - args = "delete %s" % " ".join([str(b.GetID()) for b in bps]) - self.ui.deleteBreakpoints(name, line) - else: - args = "set -f %s -l %d" % (name, line) - else: - show_output = True - - self.doCommand("breakpoint", args, show_output) - return - - def doRefresh(self): - """ process pending events and update UI on request """ - status = self.processPendingEvents() - - def doShow(self, name): - """ handle :Lshow <name> """ - if not name: - self.ui.activate() - return - - if self.ui.showWindow(name): - self.ui.update(self.target, "", self) - - def doHide(self, name): - """ handle :Lhide <name> """ - if self.ui.hideWindow(name): - self.ui.update(self.target, "", self) - - def doExit(self): - self.dbg.Terminate() - self.dbg = None - - def getCommandResult(self, command, command_args): - """ Run cmd in the command interpreter and returns (success, output) """ - result = lldb.SBCommandReturnObject() - cmd = "%s %s" % (command, command_args) - - self.commandInterpreter.HandleCommand(cmd, result) - return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError()) - - def doCommand(self, command, command_args, print_on_success = True, goto_file=False): - """ Run cmd in interpreter and print result (success or failure) on the vim status line. """ - (success, output) = self.getCommandResult(command, command_args) - if success: - self.ui.update(self.target, "", self, goto_file) - if len(output) > 0 and print_on_success: - print output - else: - sys.stderr.write(output) - - def getCommandOutput(self, command, command_args=""): - """ runs cmd in the command interpreter andreturns (status, result) """ - result = lldb.SBCommandReturnObject() - cmd = "%s %s" % (command, command_args) - self.commandInterpreter.HandleCommand(cmd, result) - return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError()) - - def processPendingEvents(self, wait_seconds=0, goto_file=True): - """ Handle any events that are queued from the inferior. - Blocks for at most wait_seconds, or if wait_seconds == 0, - process only events that are already queued. - """ +class StepType: + INSTRUCTION = 1 + INSTRUCTION_OVER = 2 + INTO = 3 + OVER = 4 + OUT = 5 - status = None - num_events_handled = 0 - - if self.process is not None: - event = lldb.SBEvent() - old_state = self.process.GetState() - new_state = None - done = False - if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited: - # Early-exit if we are in 'boring' states - pass - else: - while not done and self.processListener is not None: - if not self.processListener.PeekAtNextEvent(event): - if wait_seconds > 0: - # No events on the queue, but we are allowed to wait for wait_seconds - # for any events to show up. - self.processListener.WaitForEvent(wait_seconds, event) - new_state = lldb.SBProcess.GetStateFromEvent(event) - - num_events_handled += 1 - - done = not self.processListener.PeekAtNextEvent(event) - else: - # An event is on the queue, process it here. - self.processListener.GetNextEvent(event) - new_state = lldb.SBProcess.GetStateFromEvent(event) - - # continue if stopped after attaching - if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped: - self.process.Continue() - - # If needed, perform any event-specific behaviour here - num_events_handled += 1 - - if num_events_handled == 0: - pass - else: - if old_state == new_state: - status = "" - self.ui.update(self.target, status, self, goto_file) + +class LLDBController(object): + """ Handles Vim and LLDB events such as commands and lldb events. """ + + # Timeouts (sec) for waiting on new events. Because vim is not multi-threaded, we are restricted to + # servicing LLDB events from the main UI thread. Usually, we only process events that are already + # sitting on the queue. But in some situations (when we are expecting an event as a result of some + # user interaction) we want to wait for it. The constants below set these wait period in which the + # Vim UI is "blocked". Lower numbers will make Vim more responsive, but LLDB will be delayed and higher + # numbers will mean that LLDB events are processed faster, but the Vim UI may appear less responsive at + # times. + eventDelayStep = 2 + eventDelayLaunch = 1 + eventDelayContinue = 1 + + def __init__(self): + """ Creates the LLDB SBDebugger object and initializes the UI class. """ + self.target = None + self.process = None + self.load_dependent_modules = True + + self.dbg = lldb.SBDebugger.Create() + self.commandInterpreter = self.dbg.GetCommandInterpreter() + + self.ui = UI() + + def completeCommand(self, a, l, p): + """ Returns a list of viable completions for command a with length l and cursor at p """ + + assert l[0] == 'L' + # Remove first 'L' character that all commands start with + l = l[1:] + + # Adjust length as string has 1 less character + p = int(p) - 1 + + result = lldb.SBStringList() + num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result) + + if num == -1: + # FIXME: insert completion character... what's a completion + # character? + pass + elif num == -2: + # FIXME: replace line with result.GetStringAtIndex(0) + pass + + if result.GetSize() > 0: + results = filter(None, [result.GetStringAtIndex(x) + for x in range(result.GetSize())]) + return results + else: + return [] + + def doStep(self, stepType): + """ Perform a step command and block the UI for eventDelayStep seconds in order to process + events on lldb's event queue. + FIXME: if the step does not complete in eventDelayStep seconds, we relinquish control to + the main thread to avoid the appearance of a "hang". If this happens, the UI will + update whenever; usually when the user moves the cursor. This is somewhat annoying. + """ + if not self.process: + sys.stderr.write("No process to step") + return + + t = self.process.GetSelectedThread() + if stepType == StepType.INSTRUCTION: + t.StepInstruction(False) + if stepType == StepType.INSTRUCTION_OVER: + t.StepInstruction(True) + elif stepType == StepType.INTO: + t.StepInto() + elif stepType == StepType.OVER: + t.StepOver() + elif stepType == StepType.OUT: + t.StepOut() + + self.processPendingEvents(self.eventDelayStep, True) + + def doSelect(self, command, args): + """ Like doCommand, but suppress output when "select" is the first argument.""" + a = args.split(' ') + return self.doCommand(command, args, "select" != a[0], True) + + def doProcess(self, args): + """ Handle 'process' command. If 'launch' is requested, use doLaunch() instead + of the command interpreter to start the inferior process. + """ + a = args.split(' ') + if len(args) == 0 or (len(a) > 0 and a[0] != 'launch'): + self.doCommand("process", args) + #self.ui.update(self.target, "", self) + else: + self.doLaunch('-s' not in args, "") + + def doAttach(self, process_name): + """ Handle process attach. """ + error = lldb.SBError() + + self.processListener = lldb.SBListener("process_event_listener") + self.target = self.dbg.CreateTarget('') + self.process = self.target.AttachToProcessWithName( + self.processListener, process_name, False, error) + if not error.Success(): + sys.stderr.write("Error during attach: " + str(error)) + return + + self.ui.activate() + self.pid = self.process.GetProcessID() + + print "Attached to %s (pid=%d)" % (process_name, self.pid) + + def doDetach(self): + if self.process is not None and self.process.IsValid(): + pid = self.process.GetProcessID() + state = state_type_to_str(self.process.GetState()) + self.process.Detach() + self.processPendingEvents(self.eventDelayLaunch) + + def doLaunch(self, stop_at_entry, args): + """ Handle process launch. """ + error = lldb.SBError() + + fs = self.target.GetExecutable() + exe = os.path.join(fs.GetDirectory(), fs.GetFilename()) + if self.process is not None and self.process.IsValid(): + pid = self.process.GetProcessID() + state = state_type_to_str(self.process.GetState()) + self.process.Destroy() + + launchInfo = lldb.SBLaunchInfo(args.split(' ')) + self.process = self.target.Launch(launchInfo, error) + if not error.Success(): + sys.stderr.write("Error during launch: " + str(error)) + return + + # launch succeeded, store pid and add some event listeners + self.pid = self.process.GetProcessID() + self.processListener = lldb.SBListener("process_event_listener") + self.process.GetBroadcaster().AddListener( + self.processListener, lldb.SBProcess.eBroadcastBitStateChanged) + + print "Launched %s %s (pid=%d)" % (exe, args, self.pid) + + if not stop_at_entry: + self.doContinue() + else: + self.processPendingEvents(self.eventDelayLaunch) + + def doTarget(self, args): + """ Pass target command to interpreter, except if argument is not one of the valid options, or + is create, in which case try to create a target with the argument as the executable. For example: + target list ==> handled by interpreter + target create blah ==> custom creation of target 'blah' + target blah ==> also creates target blah + """ + target_args = [ # "create", + "delete", + "list", + "modules", + "select", + "stop-hook", + "symbols", + "variable"] + + a = args.split(' ') + if len(args) == 0 or (len(a) > 0 and a[0] in target_args): + self.doCommand("target", args) + return + elif len(a) > 1 and a[0] == "create": + exe = a[1] + elif len(a) == 1 and a[0] not in target_args: + exe = a[0] + + err = lldb.SBError() + self.target = self.dbg.CreateTarget( + exe, None, None, self.load_dependent_modules, err) + if not self.target: + sys.stderr.write( + "Error creating target %s. %s" % + (str(exe), str(err))) + return + + self.ui.activate() + self.ui.update(self.target, "created target %s" % str(exe), self) + + def doContinue(self): + """ Handle 'contiue' command. + FIXME: switch to doCommand("continue", ...) to handle -i ignore-count param. + """ + if not self.process or not self.process.IsValid(): + sys.stderr.write("No process to continue") + return + + self.process.Continue() + self.processPendingEvents(self.eventDelayContinue) + + def doBreakpoint(self, args): + """ Handle breakpoint command with command interpreter, except if the user calls + "breakpoint" with no other args, in which case add a breakpoint at the line + under the cursor. + """ + a = args.split(' ') + if len(args) == 0: + show_output = False + + # User called us with no args, so toggle the bp under cursor + cw = vim.current.window + cb = vim.current.buffer + name = cb.name + line = cw.cursor[0] + + # Since the UI is responsbile for placing signs at bp locations, we have to + # ask it if there already is one or more breakpoints at (file, + # line)... + if self.ui.haveBreakpoint(name, line): + bps = self.ui.getBreakpoints(name, line) + args = "delete %s" % " ".join([str(b.GetID()) for b in bps]) + self.ui.deleteBreakpoints(name, line) + else: + args = "set -f %s -l %d" % (name, line) + else: + show_output = True + + self.doCommand("breakpoint", args, show_output) + return + + def doRefresh(self): + """ process pending events and update UI on request """ + status = self.processPendingEvents() + + def doShow(self, name): + """ handle :Lshow <name> """ + if not name: + self.ui.activate() + return + + if self.ui.showWindow(name): + self.ui.update(self.target, "", self) + + def doHide(self, name): + """ handle :Lhide <name> """ + if self.ui.hideWindow(name): + self.ui.update(self.target, "", self) + + def doExit(self): + self.dbg.Terminate() + self.dbg = None + + def getCommandResult(self, command, command_args): + """ Run cmd in the command interpreter and returns (success, output) """ + result = lldb.SBCommandReturnObject() + cmd = "%s %s" % (command, command_args) + + self.commandInterpreter.HandleCommand(cmd, result) + return (result.Succeeded(), result.GetOutput() + if result.Succeeded() else result.GetError()) + + def doCommand( + self, + command, + command_args, + print_on_success=True, + goto_file=False): + """ Run cmd in interpreter and print result (success or failure) on the vim status line. """ + (success, output) = self.getCommandResult(command, command_args) + if success: + self.ui.update(self.target, "", self, goto_file) + if len(output) > 0 and print_on_success: + print output + else: + sys.stderr.write(output) + + def getCommandOutput(self, command, command_args=""): + """ runs cmd in the command interpreter andreturns (status, result) """ + result = lldb.SBCommandReturnObject() + cmd = "%s %s" % (command, command_args) + self.commandInterpreter.HandleCommand(cmd, result) + return (result.Succeeded(), result.GetOutput() + if result.Succeeded() else result.GetError()) + + def processPendingEvents(self, wait_seconds=0, goto_file=True): + """ Handle any events that are queued from the inferior. + Blocks for at most wait_seconds, or if wait_seconds == 0, + process only events that are already queued. + """ + + status = None + num_events_handled = 0 + + if self.process is not None: + event = lldb.SBEvent() + old_state = self.process.GetState() + new_state = None + done = False + if old_state == lldb.eStateInvalid or old_state == lldb.eStateExited: + # Early-exit if we are in 'boring' states + pass + else: + while not done and self.processListener is not None: + if not self.processListener.PeekAtNextEvent(event): + if wait_seconds > 0: + # No events on the queue, but we are allowed to wait for wait_seconds + # for any events to show up. + self.processListener.WaitForEvent( + wait_seconds, event) + new_state = lldb.SBProcess.GetStateFromEvent(event) + + num_events_handled += 1 + + done = not self.processListener.PeekAtNextEvent(event) + else: + # An event is on the queue, process it here. + self.processListener.GetNextEvent(event) + new_state = lldb.SBProcess.GetStateFromEvent(event) + + # continue if stopped after attaching + if old_state == lldb.eStateAttaching and new_state == lldb.eStateStopped: + self.process.Continue() + + # If needed, perform any event-specific behaviour here + num_events_handled += 1 + + if num_events_handled == 0: + pass + else: + if old_state == new_state: + status = "" + self.ui.update(self.target, status, self, goto_file) def returnCompleteCommand(a, l, p): - """ Returns a "\n"-separated string with possible completion results - for command a with length l and cursor at p. - """ - separator = "\n" - results = ctrl.completeCommand(a, l, p) - vim.command('return "%s%s"' % (separator.join(results), separator)) + """ Returns a "\n"-separated string with possible completion results + for command a with length l and cursor at p. + """ + separator = "\n" + results = ctrl.completeCommand(a, l, p) + vim.command('return "%s%s"' % (separator.join(results), separator)) + def returnCompleteWindow(a, l, p): - """ Returns a "\n"-separated string with possible completion results - for commands that expect a window name parameter (like hide/show). - FIXME: connect to ctrl.ui instead of hardcoding the list here - """ - separator = "\n" - results = ['breakpoints', 'backtrace', 'disassembly', 'locals', 'threads', 'registers'] - vim.command('return "%s%s"' % (separator.join(results), separator)) + """ Returns a "\n"-separated string with possible completion results + for commands that expect a window name parameter (like hide/show). + FIXME: connect to ctrl.ui instead of hardcoding the list here + """ + separator = "\n" + results = [ + 'breakpoints', + 'backtrace', + 'disassembly', + 'locals', + 'threads', + 'registers'] + vim.command('return "%s%s"' % (separator.join(results), separator)) global ctrl ctrl = LLDBController() diff --git a/lldb/utils/vim-lldb/python-vim-lldb/plugin.py b/lldb/utils/vim-lldb/python-vim-lldb/plugin.py index 694783a95b0..c59546c745c 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/plugin.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/plugin.py @@ -1,14 +1,16 @@ -# Try to import all dependencies, catch and handle the error gracefully if it fails. +# Try to import all dependencies, catch and handle the error gracefully if +# it fails. import import_lldb try: - import lldb - import vim + import lldb + import vim except ImportError: - sys.stderr.write("Unable to load vim/lldb module. Check lldb is on the path is available (or LLDB is set) and that script is invoked inside Vim with :pyfile") - pass + sys.stderr.write( + "Unable to load vim/lldb module. Check lldb is on the path is available (or LLDB is set) and that script is invoked inside Vim with :pyfile") + pass else: - # Everthing went well, so use import to start the plugin controller - from lldb_controller import * + # Everthing went well, so use import to start the plugin controller + from lldb_controller import * diff --git a/lldb/utils/vim-lldb/python-vim-lldb/vim_panes.py b/lldb/utils/vim-lldb/python-vim-lldb/vim_panes.py index ec537199922..b0c804d1962 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/vim_panes.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/vim_panes.py @@ -6,8 +6,8 @@ # - get_content() - returns a string with the pane contents # # Optionally, to highlight text, implement: -# - get_highlights() - returns a map -# +# - get_highlights() - returns a map +# # And call: # - define_highlight(unique_name, colour) # at some point in the constructor. @@ -16,7 +16,7 @@ # If the pane shows some key-value data that is in the context of a # single frame, inherit from FrameKeyValuePane and implement: # - get_frame_content(self, SBFrame frame) -# +# # # If the pane presents some information that can be retrieved with # a simple LLDB command while the subprocess is stopped, inherit @@ -27,12 +27,12 @@ # Optionally, you can implement: # - get_selected_line() # to highlight a selected line and place the cursor there. -# +# # # FIXME: implement WatchlistPane to displayed watched expressions -# FIXME: define interface for interactive panes, like catching enter +# FIXME: define interface for interactive panes, like catching enter # presses to change selected frame/thread... -# +# import lldb import vim @@ -44,6 +44,8 @@ import sys # ============================================================== # Shamelessly copy/pasted from lldbutil.py in the test suite + + def get_description(obj, option=None): """Calls lldb_obj.GetDescription() and returns a string, or None. @@ -70,512 +72,552 @@ def get_description(obj, option=None): if not success: return None return stream.GetData() - + + def get_selected_thread(target): - """ Returns a tuple with (thread, error) where thread == None if error occurs """ - process = target.GetProcess() - if process is None or not process.IsValid(): - return (None, VimPane.MSG_NO_PROCESS) + """ Returns a tuple with (thread, error) where thread == None if error occurs """ + process = target.GetProcess() + if process is None or not process.IsValid(): + return (None, VimPane.MSG_NO_PROCESS) + + thread = process.GetSelectedThread() + if thread is None or not thread.IsValid(): + return (None, VimPane.MSG_NO_THREADS) + return (thread, "") - thread = process.GetSelectedThread() - if thread is None or not thread.IsValid(): - return (None, VimPane.MSG_NO_THREADS) - return (thread, "") def get_selected_frame(target): - """ Returns a tuple with (frame, error) where frame == None if error occurs """ - (thread, error) = get_selected_thread(target) - if thread is None: - return (None, error) + """ Returns a tuple with (frame, error) where frame == None if error occurs """ + (thread, error) = get_selected_thread(target) + if thread is None: + return (None, error) + + frame = thread.GetSelectedFrame() + if frame is None or not frame.IsValid(): + return (None, VimPane.MSG_NO_FRAME) + return (frame, "") - frame = thread.GetSelectedFrame() - if frame is None or not frame.IsValid(): - return (None, VimPane.MSG_NO_FRAME) - return (frame, "") def _cmd(cmd): - vim.command("call confirm('%s')" % cmd) - vim.command(cmd) + vim.command("call confirm('%s')" % cmd) + vim.command(cmd) + def move_cursor(line, col=0): - """ moves cursor to specified line and col """ - cw = vim.current.window - if cw.cursor[0] != line: - vim.command("execute \"normal %dgg\"" % line) + """ moves cursor to specified line and col """ + cw = vim.current.window + if cw.cursor[0] != line: + vim.command("execute \"normal %dgg\"" % line) + def winnr(): - """ Returns currently selected window number """ - return int(vim.eval("winnr()")) + """ Returns currently selected window number """ + return int(vim.eval("winnr()")) + def bufwinnr(name): - """ Returns window number corresponding with buffer name """ - return int(vim.eval("bufwinnr('%s')" % name)) + """ Returns window number corresponding with buffer name """ + return int(vim.eval("bufwinnr('%s')" % name)) + def goto_window(nr): - """ go to window number nr""" - if nr != winnr(): - vim.command(str(nr) + ' wincmd w') + """ go to window number nr""" + if nr != winnr(): + vim.command(str(nr) + ' wincmd w') + def goto_next_window(): - """ go to next window. """ - vim.command('wincmd w') - return (winnr(), vim.current.buffer.name) + """ go to next window. """ + vim.command('wincmd w') + return (winnr(), vim.current.buffer.name) + def goto_previous_window(): - """ go to previously selected window """ - vim.command("execute \"normal \\<c-w>p\"") + """ go to previously selected window """ + vim.command("execute \"normal \\<c-w>p\"") + def have_gui(): - """ Returns True if vim is in a gui (Gvim/MacVim), False otherwise. """ - return int(vim.eval("has('gui_running')")) == 1 + """ Returns True if vim is in a gui (Gvim/MacVim), False otherwise. """ + return int(vim.eval("has('gui_running')")) == 1 + class PaneLayout(object): - """ A container for a (vertical) group layout of VimPanes """ + """ A container for a (vertical) group layout of VimPanes """ + + def __init__(self): + self.panes = {} + + def havePane(self, name): + """ Returns true if name is a registered pane, False otherwise """ + return name in self.panes + + def prepare(self, panes=[]): + """ Draw panes on screen. If empty list is provided, show all. """ + + # If we can't select a window contained in the layout, we are doing a + # first draw + first_draw = not self.selectWindow(True) + did_first_draw = False + + # Prepare each registered pane + for name in self.panes: + if name in panes or len(panes) == 0: + if first_draw: + # First window in layout will be created with :vsp, and + # closed later + vim.command(":vsp") + first_draw = False + did_first_draw = True + self.panes[name].prepare() + + if did_first_draw: + # Close the split window + vim.command(":q") + + self.selectWindow(False) + + def contains(self, bufferName=None): + """ Returns True if window with name bufferName is contained in the layout, False otherwise. + If bufferName is None, the currently selected window is checked. + """ + if not bufferName: + bufferName = vim.current.buffer.name + + for p in self.panes: + if bufferName is not None and bufferName.endswith(p): + return True + return False + + def selectWindow(self, select_contained=True): + """ Selects a window contained in the layout (if select_contained = True) and returns True. + If select_contained = False, a window that is not contained is selected. Returns False + if no group windows can be selected. + """ + if select_contained == self.contains(): + # Simple case: we are already selected + return True + + # Otherwise, switch to next window until we find a contained window, or + # reach the first window again. + first = winnr() + (curnum, curname) = goto_next_window() + + while not select_contained == self.contains( + curname) and curnum != first: + (curnum, curname) = goto_next_window() + + return self.contains(curname) == select_contained + + def hide(self, panes=[]): + """ Hide panes specified. If empty list provided, hide all. """ + for name in self.panes: + if name in panes or len(panes) == 0: + self.panes[name].destroy() + + def registerForUpdates(self, p): + self.panes[p.name] = p + + def update(self, target, controller): + for name in self.panes: + self.panes[name].update(target, controller) - def __init__(self): - self.panes = {} - def havePane(self, name): - """ Returns true if name is a registered pane, False otherwise """ - return name in self.panes +class VimPane(object): + """ A generic base class for a pane that displays stuff """ + CHANGED_VALUE_HIGHLIGHT_NAME_GUI = 'ColorColumn' + CHANGED_VALUE_HIGHLIGHT_NAME_TERM = 'lldb_changed' + CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM = 'darkred' + + SELECTED_HIGHLIGHT_NAME_GUI = 'Cursor' + SELECTED_HIGHLIGHT_NAME_TERM = 'lldb_selected' + SELECTED_HIGHLIGHT_COLOUR_TERM = 'darkblue' + + MSG_NO_TARGET = "Target does not exist." + MSG_NO_PROCESS = "Process does not exist." + MSG_NO_THREADS = "No valid threads." + MSG_NO_FRAME = "No valid frame." + + # list of defined highlights, so we avoid re-defining them + highlightTypes = [] + + def __init__(self, owner, name, open_below=False, height=3): + self.owner = owner + self.name = name + self.buffer = None + self.maxHeight = 20 + self.openBelow = open_below + self.height = height + self.owner.registerForUpdates(self) + + def isPrepared(self): + """ check window is OK """ + if self.buffer is None or len( + dir(self.buffer)) == 0 or bufwinnr(self.name) == -1: + return False + return True - def prepare(self, panes = []): - """ Draw panes on screen. If empty list is provided, show all. """ + def prepare(self, method='new'): + """ check window is OK, if not then create """ + if not self.isPrepared(): + self.create(method) + + def on_create(self): + pass + + def destroy(self): + """ destroy window """ + if self.buffer is None or len(dir(self.buffer)) == 0: + return + vim.command('bdelete ' + self.name) + + def create(self, method): + """ create window """ + + if method != 'edit': + belowcmd = "below" if self.openBelow else "" + vim.command('silent %s %s %s' % (belowcmd, method, self.name)) + else: + vim.command('silent %s %s' % (method, self.name)) + + self.window = vim.current.window + + # Set LLDB pane options + vim.command("setlocal buftype=nofile") # Don't try to open a file + vim.command("setlocal noswapfile") # Don't use a swap file + vim.command("set nonumber") # Don't display line numbers + # vim.command("set nowrap") # Don't wrap text + + # Save some parameters and reference to buffer + self.buffer = vim.current.buffer + self.width = int(vim.eval("winwidth(0)")) + self.height = int(vim.eval("winheight(0)")) + + self.on_create() + goto_previous_window() + + def update(self, target, controller): + """ updates buffer contents """ + self.target = target + if not self.isPrepared(): + # Window is hidden, or otherwise not ready for an update + return + + original_cursor = self.window.cursor + + # Select pane + goto_window(bufwinnr(self.name)) + + # Clean and update content, and apply any highlights. + self.clean() + + if self.write(self.get_content(target, controller)): + self.apply_highlights() + + cursor = self.get_selected_line() + if cursor is None: + # Place the cursor at its original position in the window + cursor_line = min(original_cursor[0], len(self.buffer)) + cursor_col = min( + original_cursor[1], len( + self.buffer[ + cursor_line - 1])) + else: + # Place the cursor at the location requested by a VimPane + # implementation + cursor_line = min(cursor, len(self.buffer)) + cursor_col = self.window.cursor[1] + + self.window.cursor = (cursor_line, cursor_col) + + goto_previous_window() + + def get_selected_line(self): + """ Returns the line number to move the cursor to, or None to leave + it where the user last left it. + Subclasses implement this to define custom behaviour. + """ + return None - # If we can't select a window contained in the layout, we are doing a first draw - first_draw = not self.selectWindow(True) - did_first_draw = False + def apply_highlights(self): + """ Highlights each set of lines in each highlight group """ + highlights = self.get_highlights() + for highlightType in highlights: + lines = highlights[highlightType] + if len(lines) == 0: + continue + + cmd = 'match %s /' % highlightType + lines = ['\%' + '%d' % line + 'l' for line in lines] + cmd += '\\|'.join(lines) + cmd += '/' + vim.command(cmd) + + def define_highlight(self, name, colour): + """ Defines highlihght """ + if name in VimPane.highlightTypes: + # highlight already defined + return + + vim.command( + "highlight %s ctermbg=%s guibg=%s" % + (name, colour, colour)) + VimPane.highlightTypes.append(name) + + def write(self, msg): + """ replace buffer with msg""" + self.prepare() + + msg = str(msg.encode("utf-8", "replace")).split('\n') + try: + self.buffer.append(msg) + vim.command("execute \"normal ggdd\"") + except vim.error: + # cannot update window; happens when vim is exiting. + return False + + move_cursor(1, 0) + return True - # Prepare each registered pane - for name in self.panes: - if name in panes or len(panes) == 0: - if first_draw: - # First window in layout will be created with :vsp, and closed later - vim.command(":vsp") - first_draw = False - did_first_draw = True - self.panes[name].prepare() + def clean(self): + """ clean all datas in buffer """ + self.prepare() + vim.command(':%d') + #self.buffer[:] = None - if did_first_draw: - # Close the split window - vim.command(":q") + def get_content(self, target, controller): + """ subclasses implement this to provide pane content """ + assert(0 and "pane subclass must implement this") + pass - self.selectWindow(False) + def get_highlights(self): + """ Subclasses implement this to provide pane highlights. + This function is expected to return a map of: + { highlight_name ==> [line_number, ...], ... } + """ + return {} - def contains(self, bufferName = None): - """ Returns True if window with name bufferName is contained in the layout, False otherwise. - If bufferName is None, the currently selected window is checked. - """ - if not bufferName: - bufferName = vim.current.buffer.name - for p in self.panes: - if bufferName is not None and bufferName.endswith(p): - return True - return False +class FrameKeyValuePane(VimPane): - def selectWindow(self, select_contained = True): - """ Selects a window contained in the layout (if select_contained = True) and returns True. - If select_contained = False, a window that is not contained is selected. Returns False - if no group windows can be selected. - """ - if select_contained == self.contains(): - # Simple case: we are already selected - return True + def __init__(self, owner, name, open_below): + """ Initialize parent, define member variables, choose which highlight + to use based on whether or not we have a gui (MacVim/Gvim). + """ + + VimPane.__init__(self, owner, name, open_below) + + # Map-of-maps key/value history { frame --> { variable_name, + # variable_value } } + self.frameValues = {} + + if have_gui(): + self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_GUI + else: + self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM + self.define_highlight(VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM, + VimPane.CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM) + + def format_pair(self, key, value, changed=False): + """ Formats a key/value pair. Appends a '*' if changed == True """ + marker = '*' if changed else ' ' + return "%s %s = %s\n" % (marker, key, value) + + def get_content(self, target, controller): + """ Get content for a frame-aware pane. Also builds the list of lines that + need highlighting (i.e. changed values.) + """ + if target is None or not target.IsValid(): + return VimPane.MSG_NO_TARGET + + self.changedLines = [] + + (frame, err) = get_selected_frame(target) + if frame is None: + return err + + output = get_description(frame) + lineNum = 1 + + # Retrieve the last values displayed for this frame + frameId = get_description(frame.GetBlock()) + if frameId in self.frameValues: + frameOldValues = self.frameValues[frameId] + else: + frameOldValues = {} + + # Read the frame variables + vals = self.get_frame_content(frame) + for (key, value) in vals: + lineNum += 1 + if len(frameOldValues) == 0 or ( + key in frameOldValues and frameOldValues[key] == value): + output += self.format_pair(key, value) + else: + output += self.format_pair(key, value, True) + self.changedLines.append(lineNum) + + # Save values as oldValues + newValues = {} + for (key, value) in vals: + newValues[key] = value + self.frameValues[frameId] = newValues + + return output + + def get_highlights(self): + ret = {} + ret[self.changedHighlight] = self.changedLines + return ret - # Otherwise, switch to next window until we find a contained window, or reach the first window again. - first = winnr() - (curnum, curname) = goto_next_window() - while not select_contained == self.contains(curname) and curnum != first: - (curnum, curname) = goto_next_window() +class LocalsPane(FrameKeyValuePane): + """ Pane that displays local variables """ - return self.contains(curname) == select_contained + def __init__(self, owner, name='locals'): + FrameKeyValuePane.__init__(self, owner, name, open_below=True) - def hide(self, panes = []): - """ Hide panes specified. If empty list provided, hide all. """ - for name in self.panes: - if name in panes or len(panes) == 0: - self.panes[name].destroy() + # FIXME: allow users to customize display of args/locals/statics/scope + self.arguments = True + self.show_locals = True + self.show_statics = True + self.show_in_scope_only = True - def registerForUpdates(self, p): - self.panes[p.name] = p + def format_variable(self, var): + """ Returns a Tuple of strings "(Type) Name", "Value" for SBValue var """ + val = var.GetValue() + if val is None: + # If the value is too big, SBValue.GetValue() returns None; replace + # with ... + val = "..." - def update(self, target, controller): - for name in self.panes: - self.panes[name].update(target, controller) + return ("(%s) %s" % (var.GetTypeName(), var.GetName()), "%s" % val) + def get_frame_content(self, frame): + """ Returns list of key-value pairs of local variables in frame """ + vals = frame.GetVariables(self.arguments, + self.show_locals, + self.show_statics, + self.show_in_scope_only) + return [self.format_variable(x) for x in vals] -class VimPane(object): - """ A generic base class for a pane that displays stuff """ - CHANGED_VALUE_HIGHLIGHT_NAME_GUI = 'ColorColumn' - CHANGED_VALUE_HIGHLIGHT_NAME_TERM = 'lldb_changed' - CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM = 'darkred' - - SELECTED_HIGHLIGHT_NAME_GUI = 'Cursor' - SELECTED_HIGHLIGHT_NAME_TERM = 'lldb_selected' - SELECTED_HIGHLIGHT_COLOUR_TERM = 'darkblue' - - MSG_NO_TARGET = "Target does not exist." - MSG_NO_PROCESS = "Process does not exist." - MSG_NO_THREADS = "No valid threads." - MSG_NO_FRAME = "No valid frame." - - # list of defined highlights, so we avoid re-defining them - highlightTypes = [] - - def __init__(self, owner, name, open_below=False, height=3): - self.owner = owner - self.name = name - self.buffer = None - self.maxHeight = 20 - self.openBelow = open_below - self.height = height - self.owner.registerForUpdates(self) - - def isPrepared(self): - """ check window is OK """ - if self.buffer == None or len(dir(self.buffer)) == 0 or bufwinnr(self.name) == -1: - return False - return True - - def prepare(self, method = 'new'): - """ check window is OK, if not then create """ - if not self.isPrepared(): - self.create(method) - - def on_create(self): - pass - - def destroy(self): - """ destroy window """ - if self.buffer == None or len(dir(self.buffer)) == 0: - return - vim.command('bdelete ' + self.name) - - def create(self, method): - """ create window """ - - if method != 'edit': - belowcmd = "below" if self.openBelow else "" - vim.command('silent %s %s %s' % (belowcmd, method, self.name)) - else: - vim.command('silent %s %s' % (method, self.name)) - - self.window = vim.current.window - - # Set LLDB pane options - vim.command("setlocal buftype=nofile") # Don't try to open a file - vim.command("setlocal noswapfile") # Don't use a swap file - vim.command("set nonumber") # Don't display line numbers - #vim.command("set nowrap") # Don't wrap text - - # Save some parameters and reference to buffer - self.buffer = vim.current.buffer - self.width = int( vim.eval("winwidth(0)") ) - self.height = int( vim.eval("winheight(0)") ) - - self.on_create() - goto_previous_window() - - def update(self, target, controller): - """ updates buffer contents """ - self.target = target - if not self.isPrepared(): - # Window is hidden, or otherwise not ready for an update - return - - original_cursor = self.window.cursor - - # Select pane - goto_window(bufwinnr(self.name)) - - # Clean and update content, and apply any highlights. - self.clean() - - if self.write(self.get_content(target, controller)): - self.apply_highlights() - - cursor = self.get_selected_line() - if cursor is None: - # Place the cursor at its original position in the window - cursor_line = min(original_cursor[0], len(self.buffer)) - cursor_col = min(original_cursor[1], len(self.buffer[cursor_line - 1])) - else: - # Place the cursor at the location requested by a VimPane implementation - cursor_line = min(cursor, len(self.buffer)) - cursor_col = self.window.cursor[1] - - self.window.cursor = (cursor_line, cursor_col) - - goto_previous_window() - - def get_selected_line(self): - """ Returns the line number to move the cursor to, or None to leave - it where the user last left it. - Subclasses implement this to define custom behaviour. - """ - return None - - def apply_highlights(self): - """ Highlights each set of lines in each highlight group """ - highlights = self.get_highlights() - for highlightType in highlights: - lines = highlights[highlightType] - if len(lines) == 0: - continue - - cmd = 'match %s /' % highlightType - lines = ['\%' + '%d' % line + 'l' for line in lines] - cmd += '\\|'.join(lines) - cmd += '/' - vim.command(cmd) - - def define_highlight(self, name, colour): - """ Defines highlihght """ - if name in VimPane.highlightTypes: - # highlight already defined - return - - vim.command("highlight %s ctermbg=%s guibg=%s" % (name, colour, colour)) - VimPane.highlightTypes.append(name) - - def write(self, msg): - """ replace buffer with msg""" - self.prepare() - - msg = str(msg.encode("utf-8", "replace")).split('\n') - try: - self.buffer.append(msg) - vim.command("execute \"normal ggdd\"") - except vim.error: - # cannot update window; happens when vim is exiting. - return False - - move_cursor(1, 0) - return True - - def clean(self): - """ clean all datas in buffer """ - self.prepare() - vim.command(':%d') - #self.buffer[:] = None - - def get_content(self, target, controller): - """ subclasses implement this to provide pane content """ - assert(0 and "pane subclass must implement this") - pass - - def get_highlights(self): - """ Subclasses implement this to provide pane highlights. - This function is expected to return a map of: - { highlight_name ==> [line_number, ...], ... } - """ - return {} +class RegistersPane(FrameKeyValuePane): + """ Pane that displays the contents of registers """ -class FrameKeyValuePane(VimPane): - def __init__(self, owner, name, open_below): - """ Initialize parent, define member variables, choose which highlight - to use based on whether or not we have a gui (MacVim/Gvim). - """ + def __init__(self, owner, name='registers'): + FrameKeyValuePane.__init__(self, owner, name, open_below=True) - VimPane.__init__(self, owner, name, open_below) + def format_register(self, reg): + """ Returns a tuple of strings ("name", "value") for SBRegister reg. """ + name = reg.GetName() + val = reg.GetValue() + if val is None: + val = "..." + return (name, val.strip()) - # Map-of-maps key/value history { frame --> { variable_name, variable_value } } - self.frameValues = {} + def get_frame_content(self, frame): + """ Returns a list of key-value pairs ("name", "value") of registers in frame """ - if have_gui(): - self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_GUI - else: - self.changedHighlight = VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM - self.define_highlight(VimPane.CHANGED_VALUE_HIGHLIGHT_NAME_TERM, - VimPane.CHANGED_VALUE_HIGHLIGHT_COLOUR_TERM) - - def format_pair(self, key, value, changed = False): - """ Formats a key/value pair. Appends a '*' if changed == True """ - marker = '*' if changed else ' ' - return "%s %s = %s\n" % (marker, key, value) - - def get_content(self, target, controller): - """ Get content for a frame-aware pane. Also builds the list of lines that - need highlighting (i.e. changed values.) - """ - if target is None or not target.IsValid(): - return VimPane.MSG_NO_TARGET + result = [] + for register_sets in frame.GetRegisters(): + # hack the register group name into the list of registers... + result.append((" = = %s =" % register_sets.GetName(), "")) - self.changedLines = [] + for reg in register_sets: + result.append(self.format_register(reg)) + return result - (frame, err) = get_selected_frame(target) - if frame is None: - return err - output = get_description(frame) - lineNum = 1 +class CommandPane(VimPane): + """ Pane that displays the output of an LLDB command """ - # Retrieve the last values displayed for this frame - frameId = get_description(frame.GetBlock()) - if frameId in self.frameValues: - frameOldValues = self.frameValues[frameId] - else: - frameOldValues = {} - - # Read the frame variables - vals = self.get_frame_content(frame) - for (key, value) in vals: - lineNum += 1 - if len(frameOldValues) == 0 or (key in frameOldValues and frameOldValues[key] == value): - output += self.format_pair(key, value) - else: - output += self.format_pair(key, value, True) - self.changedLines.append(lineNum) - - # Save values as oldValues - newValues = {} - for (key, value) in vals: - newValues[key] = value - self.frameValues[frameId] = newValues - - return output - - def get_highlights(self): - ret = {} - ret[self.changedHighlight] = self.changedLines - return ret + def __init__(self, owner, name, open_below, process_required=True): + VimPane.__init__(self, owner, name, open_below) + self.process_required = process_required -class LocalsPane(FrameKeyValuePane): - """ Pane that displays local variables """ - def __init__(self, owner, name = 'locals'): - FrameKeyValuePane.__init__(self, owner, name, open_below=True) - - # FIXME: allow users to customize display of args/locals/statics/scope - self.arguments = True - self.show_locals = True - self.show_statics = True - self.show_in_scope_only = True - - def format_variable(self, var): - """ Returns a Tuple of strings "(Type) Name", "Value" for SBValue var """ - val = var.GetValue() - if val is None: - # If the value is too big, SBValue.GetValue() returns None; replace with ... - val = "..." - - return ("(%s) %s" % (var.GetTypeName(), var.GetName()), "%s" % val) - - def get_frame_content(self, frame): - """ Returns list of key-value pairs of local variables in frame """ - vals = frame.GetVariables(self.arguments, - self.show_locals, - self.show_statics, - self.show_in_scope_only) - return [self.format_variable(x) for x in vals] + def setCommand(self, command, args=""): + self.command = command + self.args = args -class RegistersPane(FrameKeyValuePane): - """ Pane that displays the contents of registers """ - def __init__(self, owner, name = 'registers'): - FrameKeyValuePane.__init__(self, owner, name, open_below=True) - - def format_register(self, reg): - """ Returns a tuple of strings ("name", "value") for SBRegister reg. """ - name = reg.GetName() - val = reg.GetValue() - if val is None: - val = "..." - return (name, val.strip()) - - def get_frame_content(self, frame): - """ Returns a list of key-value pairs ("name", "value") of registers in frame """ - - result = [] - for register_sets in frame.GetRegisters(): - # hack the register group name into the list of registers... - result.append((" = = %s =" % register_sets.GetName(), "")) - - for reg in register_sets: - result.append(self.format_register(reg)) - return result + def get_content(self, target, controller): + output = "" + if not target: + output = VimPane.MSG_NO_TARGET + elif self.process_required and not target.GetProcess(): + output = VimPane.MSG_NO_PROCESS + else: + (success, output) = controller.getCommandOutput( + self.command, self.args) + return output -class CommandPane(VimPane): - """ Pane that displays the output of an LLDB command """ - def __init__(self, owner, name, open_below, process_required=True): - VimPane.__init__(self, owner, name, open_below) - self.process_required = process_required - - def setCommand(self, command, args = ""): - self.command = command - self.args = args - - def get_content(self, target, controller): - output = "" - if not target: - output = VimPane.MSG_NO_TARGET - elif self.process_required and not target.GetProcess(): - output = VimPane.MSG_NO_PROCESS - else: - (success, output) = controller.getCommandOutput(self.command, self.args) - return output class StoppedCommandPane(CommandPane): - """ Pane that displays the output of an LLDB command when the process is - stopped; otherwise displays process status. This class also implements - highlighting for a single line (to show a single-line selected entity.) - """ - def __init__(self, owner, name, open_below): - """ Initialize parent and define highlight to use for selected line. """ - CommandPane.__init__(self, owner, name, open_below) - if have_gui(): - self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_GUI - else: - self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_TERM - self.define_highlight(VimPane.SELECTED_HIGHLIGHT_NAME_TERM, - VimPane.SELECTED_HIGHLIGHT_COLOUR_TERM) - - def get_content(self, target, controller): - """ Returns the output of a command that relies on the process being stopped. - If the process is not in 'stopped' state, the process status is returned. + """ Pane that displays the output of an LLDB command when the process is + stopped; otherwise displays process status. This class also implements + highlighting for a single line (to show a single-line selected entity.) """ - output = "" - if not target or not target.IsValid(): - output = VimPane.MSG_NO_TARGET - elif not target.GetProcess() or not target.GetProcess().IsValid(): - output = VimPane.MSG_NO_PROCESS - elif target.GetProcess().GetState() == lldb.eStateStopped: - (success, output) = controller.getCommandOutput(self.command, self.args) - else: - (success, output) = controller.getCommandOutput("process", "status") - return output - def get_highlights(self): - """ Highlight the line under the cursor. Users moving the cursor has - no effect on the selected line. - """ - ret = {} - line = self.get_selected_line() - if line is not None: - ret[self.selectedHighlight] = [line] - return ret - return ret - - def get_selected_line(self): - """ Subclasses implement this to control where the cursor (and selected highlight) - is placed. - """ - return None + def __init__(self, owner, name, open_below): + """ Initialize parent and define highlight to use for selected line. """ + CommandPane.__init__(self, owner, name, open_below) + if have_gui(): + self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_GUI + else: + self.selectedHighlight = VimPane.SELECTED_HIGHLIGHT_NAME_TERM + self.define_highlight(VimPane.SELECTED_HIGHLIGHT_NAME_TERM, + VimPane.SELECTED_HIGHLIGHT_COLOUR_TERM) + + def get_content(self, target, controller): + """ Returns the output of a command that relies on the process being stopped. + If the process is not in 'stopped' state, the process status is returned. + """ + output = "" + if not target or not target.IsValid(): + output = VimPane.MSG_NO_TARGET + elif not target.GetProcess() or not target.GetProcess().IsValid(): + output = VimPane.MSG_NO_PROCESS + elif target.GetProcess().GetState() == lldb.eStateStopped: + (success, output) = controller.getCommandOutput( + self.command, self.args) + else: + (success, output) = controller.getCommandOutput("process", "status") + return output + + def get_highlights(self): + """ Highlight the line under the cursor. Users moving the cursor has + no effect on the selected line. + """ + ret = {} + line = self.get_selected_line() + if line is not None: + ret[self.selectedHighlight] = [line] + return ret + return ret + + def get_selected_line(self): + """ Subclasses implement this to control where the cursor (and selected highlight) + is placed. + """ + return None + class DisassemblyPane(CommandPane): - """ Pane that displays disassembly around PC """ - def __init__(self, owner, name = 'disassembly'): - CommandPane.__init__(self, owner, name, open_below=True) + """ Pane that displays disassembly around PC """ + + def __init__(self, owner, name='disassembly'): + CommandPane.__init__(self, owner, name, open_below=True) + + # FIXME: let users customize the number of instructions to disassemble + self.setCommand("disassemble", "-c %d -p" % self.maxHeight) - # FIXME: let users customize the number of instructions to disassemble - self.setCommand("disassemble", "-c %d -p" % self.maxHeight) class ThreadPane(StoppedCommandPane): - """ Pane that displays threads list """ - def __init__(self, owner, name = 'threads'): - StoppedCommandPane.__init__(self, owner, name, open_below=False) - self.setCommand("thread", "list") + """ Pane that displays threads list """ + + def __init__(self, owner, name='threads'): + StoppedCommandPane.__init__(self, owner, name, open_below=False) + self.setCommand("thread", "list") # FIXME: the function below assumes threads are listed in sequential order, # which turns out to not be the case. Highlighting of selected thread @@ -592,27 +634,36 @@ class ThreadPane(StoppedCommandPane): # else: # return thread.GetIndexID() + 1 + class BacktracePane(StoppedCommandPane): - """ Pane that displays backtrace """ - def __init__(self, owner, name = 'backtrace'): - StoppedCommandPane.__init__(self, owner, name, open_below=False) - self.setCommand("bt", "") + """ Pane that displays backtrace """ + def __init__(self, owner, name='backtrace'): + StoppedCommandPane.__init__(self, owner, name, open_below=False) + self.setCommand("bt", "") - def get_selected_line(self): - """ Returns the line number in the buffer with the selected frame. - Formula: selected_line = selected_frame_id + 2 - FIXME: the above formula hack does not work when the function return - value is printed in the bt window; the wrong line is highlighted. - """ + def get_selected_line(self): + """ Returns the line number in the buffer with the selected frame. + Formula: selected_line = selected_frame_id + 2 + FIXME: the above formula hack does not work when the function return + value is printed in the bt window; the wrong line is highlighted. + """ + + (frame, err) = get_selected_frame(self.target) + if frame is None: + return None + else: + return frame.GetFrameID() + 2 - (frame, err) = get_selected_frame(self.target) - if frame is None: - return None - else: - return frame.GetFrameID() + 2 class BreakpointsPane(CommandPane): - def __init__(self, owner, name = 'breakpoints'): - super(BreakpointsPane, self).__init__(owner, name, open_below=False, process_required=False) - self.setCommand("breakpoint", "list") + + def __init__(self, owner, name='breakpoints'): + super( + BreakpointsPane, + self).__init__( + owner, + name, + open_below=False, + process_required=False) + self.setCommand("breakpoint", "list") diff --git a/lldb/utils/vim-lldb/python-vim-lldb/vim_signs.py b/lldb/utils/vim-lldb/python-vim-lldb/vim_signs.py index 926cc29a7fc..94f79ecebe8 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/vim_signs.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/vim_signs.py @@ -3,71 +3,79 @@ import vim + class VimSign(object): - SIGN_TEXT_BREAKPOINT_RESOLVED = "B>" - SIGN_TEXT_BREAKPOINT_UNRESOLVED = "b>" - SIGN_TEXT_PC = "->" - SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue' - - # unique sign id (for ':[sign/highlight] define) - sign_id = 1 - - # unique name id (for ':sign place') - name_id = 1 - - # Map of {(sign_text, highlight_colour) --> sign_name} - defined_signs = {} - - def __init__(self, sign_text, buffer, line_number, highlight_colour=None): - """ Define the sign and highlight (if applicable) and show the sign. """ - - # Get the sign name, either by defining it, or looking it up in the map of defined signs - key = (sign_text, highlight_colour) - if not key in VimSign.defined_signs: - name = self.define(sign_text, highlight_colour) - else: - name = VimSign.defined_signs[key] - - self.show(name, buffer.number, line_number) - pass - - def define(self, sign_text, highlight_colour): - """ Defines sign and highlight (if highlight_colour is not None). """ - sign_name = "sign%d" % VimSign.name_id - if highlight_colour is None: - vim.command("sign define %s text=%s" % (sign_name, sign_text)) - else: - self.highlight_name = "highlight%d" % VimSign.name_id - vim.command("highlight %s ctermbg=%s guibg=%s" % (self.highlight_name, - highlight_colour, - highlight_colour)) - vim.command("sign define %s text=%s linehl=%s texthl=%s" % (sign_name, - sign_text, - self.highlight_name, - self.highlight_name)) - VimSign.defined_signs[(sign_text, highlight_colour)] = sign_name - VimSign.name_id += 1 - return sign_name - - - def show(self, name, buffer_number, line_number): - self.id = VimSign.sign_id - VimSign.sign_id += 1 - vim.command("sign place %d name=%s line=%d buffer=%s" % (self.id, name, line_number, buffer_number)) - pass - - def hide(self): - vim.command("sign unplace %d" % self.id) - pass + SIGN_TEXT_BREAKPOINT_RESOLVED = "B>" + SIGN_TEXT_BREAKPOINT_UNRESOLVED = "b>" + SIGN_TEXT_PC = "->" + SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue' + + # unique sign id (for ':[sign/highlight] define) + sign_id = 1 + + # unique name id (for ':sign place') + name_id = 1 + + # Map of {(sign_text, highlight_colour) --> sign_name} + defined_signs = {} + + def __init__(self, sign_text, buffer, line_number, highlight_colour=None): + """ Define the sign and highlight (if applicable) and show the sign. """ + + # Get the sign name, either by defining it, or looking it up in the map + # of defined signs + key = (sign_text, highlight_colour) + if key not in VimSign.defined_signs: + name = self.define(sign_text, highlight_colour) + else: + name = VimSign.defined_signs[key] + + self.show(name, buffer.number, line_number) + pass + + def define(self, sign_text, highlight_colour): + """ Defines sign and highlight (if highlight_colour is not None). """ + sign_name = "sign%d" % VimSign.name_id + if highlight_colour is None: + vim.command("sign define %s text=%s" % (sign_name, sign_text)) + else: + self.highlight_name = "highlight%d" % VimSign.name_id + vim.command( + "highlight %s ctermbg=%s guibg=%s" % + (self.highlight_name, highlight_colour, highlight_colour)) + vim.command( + "sign define %s text=%s linehl=%s texthl=%s" % + (sign_name, sign_text, self.highlight_name, self.highlight_name)) + VimSign.defined_signs[(sign_text, highlight_colour)] = sign_name + VimSign.name_id += 1 + return sign_name + + def show(self, name, buffer_number, line_number): + self.id = VimSign.sign_id + VimSign.sign_id += 1 + vim.command("sign place %d name=%s line=%d buffer=%s" % + (self.id, name, line_number, buffer_number)) + pass + + def hide(self): + vim.command("sign unplace %d" % self.id) + pass + class BreakpointSign(VimSign): - def __init__(self, buffer, line_number, is_resolved): - txt = VimSign.SIGN_TEXT_BREAKPOINT_RESOLVED if is_resolved else VimSign.SIGN_TEXT_BREAKPOINT_UNRESOLVED - super(BreakpointSign, self).__init__(txt, buffer, line_number) + + def __init__(self, buffer, line_number, is_resolved): + txt = VimSign.SIGN_TEXT_BREAKPOINT_RESOLVED if is_resolved else VimSign.SIGN_TEXT_BREAKPOINT_UNRESOLVED + super(BreakpointSign, self).__init__(txt, buffer, line_number) + class PCSign(VimSign): - def __init__(self, buffer, line_number, is_selected_thread): - super(PCSign, self).__init__(VimSign.SIGN_TEXT_PC, - buffer, - line_number, - VimSign.SIGN_HIGHLIGHT_COLOUR_PC if is_selected_thread else None) + + def __init__(self, buffer, line_number, is_selected_thread): + super( + PCSign, + self).__init__( + VimSign.SIGN_TEXT_PC, + buffer, + line_number, + VimSign.SIGN_HIGHLIGHT_COLOUR_PC if is_selected_thread else None) diff --git a/lldb/utils/vim-lldb/python-vim-lldb/vim_ui.py b/lldb/utils/vim-lldb/python-vim-lldb/vim_ui.py index 4be346b96f0..33eb6650466 100644 --- a/lldb/utils/vim-lldb/python-vim-lldb/vim_ui.py +++ b/lldb/utils/vim-lldb/python-vim-lldb/vim_ui.py @@ -1,235 +1,255 @@ # LLDB UI state in the Vim user interface. -import os, re, sys +import os +import re +import sys import lldb import vim from vim_panes import * from vim_signs import * + def is_same_file(a, b): - """ returns true if paths a and b are the same file """ - a = os.path.realpath(a) - b = os.path.realpath(b) - return a in b or b in a + """ returns true if paths a and b are the same file """ + a = os.path.realpath(a) + b = os.path.realpath(b) + return a in b or b in a + class UI: - def __init__(self): - """ Declare UI state variables """ - - # Default panes to display - self.defaultPanes = ['breakpoints', 'backtrace', 'locals', 'threads', 'registers', 'disassembly'] - - # map of tuples (filename, line) --> SBBreakpoint - self.markedBreakpoints = {} - - # Currently shown signs - self.breakpointSigns = {} - self.pcSigns = [] - - # Container for panes - self.paneCol = PaneLayout() - - # All possible LLDB panes - self.backtracePane = BacktracePane(self.paneCol) - self.threadPane = ThreadPane(self.paneCol) - self.disassemblyPane = DisassemblyPane(self.paneCol) - self.localsPane = LocalsPane(self.paneCol) - self.registersPane = RegistersPane(self.paneCol) - self.breakPane = BreakpointsPane(self.paneCol) - - def activate(self): - """ Activate UI: display default set of panes """ - self.paneCol.prepare(self.defaultPanes) - - def get_user_buffers(self, filter_name=None): - """ Returns a list of buffers that are not a part of the LLDB UI. That is, they - are not contained in the PaneLayout object self.paneCol. - """ - ret = [] - for w in vim.windows: - b = w.buffer - if not self.paneCol.contains(b.name): - if filter_name is None or filter_name in b.name: - ret.append(b) - return ret - - def update_pc(self, process, buffers, goto_file): - """ Place the PC sign on the PC location of each thread's selected frame """ - - def GetPCSourceLocation(thread): - """ Returns a tuple (thread_index, file, line, column) that represents where - the PC sign should be placed for a thread. - """ - - frame = thread.GetSelectedFrame() - frame_num = frame.GetFrameID() - le = frame.GetLineEntry() - while not le.IsValid() and frame_num < thread.GetNumFrames(): - frame_num += 1 - le = thread.GetFrameAtIndex(frame_num).GetLineEntry() - - if le.IsValid(): - path = os.path.join(le.GetFileSpec().GetDirectory(), le.GetFileSpec().GetFilename()) - return (thread.GetIndexID(), path, le.GetLine(), le.GetColumn()) - return None - - - # Clear all existing PC signs - del_list = [] - for sign in self.pcSigns: - sign.hide() - del_list.append(sign) - for sign in del_list: - self.pcSigns.remove(sign) - del sign - - # Select a user (non-lldb) window - if not self.paneCol.selectWindow(False): - # No user window found; avoid clobbering by splitting - vim.command(":vsp") - - # Show a PC marker for each thread - for thread in process: - loc = GetPCSourceLocation(thread) - if not loc: - # no valid source locations for PCs. hide all existing PC markers - continue - - buf = None - (tid, fname, line, col) = loc - buffers = self.get_user_buffers(fname) - is_selected = thread.GetIndexID() == process.GetSelectedThread().GetIndexID() - if len(buffers) == 1: - buf = buffers[0] - if buf != vim.current.buffer: - # Vim has an open buffer to the required file: select it - vim.command('execute ":%db"' % buf.number) - elif is_selected and vim.current.buffer.name not in fname and os.path.exists(fname) and goto_file: - # FIXME: If current buffer is modified, vim will complain when we try to switch away. - # Find a way to detect if the current buffer is modified, and...warn instead? - vim.command('execute ":e %s"' % fname) - buf = vim.current.buffer - elif len(buffers) > 1 and goto_file: - #FIXME: multiple open buffers match PC location - continue - else: - continue - - self.pcSigns.append(PCSign(buf, line, is_selected)) - - if is_selected and goto_file: - # if the selected file has a PC marker, move the cursor there too - curname = vim.current.buffer.name - if curname is not None and is_same_file(curname, fname): - move_cursor(line, 0) - elif move_cursor: - print "FIXME: not sure where to move cursor because %s != %s " % (vim.current.buffer.name, fname) - - def update_breakpoints(self, target, buffers): - """ Decorates buffer with signs corresponding to breakpoints in target. """ - - def GetBreakpointLocations(bp): - """ Returns a list of tuples (resolved, filename, line) where a breakpoint was resolved. """ - if not bp.IsValid(): - sys.stderr.write("breakpoint is invalid, no locations") - return [] - - ret = [] - numLocs = bp.GetNumLocations() - for i in range(numLocs): - loc = bp.GetLocationAtIndex(i) - desc = get_description(loc, lldb.eDescriptionLevelFull) - match = re.search('at\ ([^:]+):([\d]+)', desc) - try: - lineNum = int(match.group(2).strip()) - ret.append((loc.IsResolved(), match.group(1), lineNum)) - except ValueError as e: - sys.stderr.write("unable to parse breakpoint location line number: '%s'" % match.group(2)) - sys.stderr.write(str(e)) - - return ret - - - if target is None or not target.IsValid(): - return - - needed_bps = {} - for bp_index in range(target.GetNumBreakpoints()): - bp = target.GetBreakpointAtIndex(bp_index) - locations = GetBreakpointLocations(bp) - for (is_resolved, file, line) in GetBreakpointLocations(bp): - for buf in buffers: - if file in buf.name: - needed_bps[(buf, line, is_resolved)] = bp - - # Hide any signs that correspond with disabled breakpoints - del_list = [] - for (b, l, r) in self.breakpointSigns: - if (b, l, r) not in needed_bps: - self.breakpointSigns[(b, l, r)].hide() - del_list.append((b, l, r)) - for d in del_list: - del self.breakpointSigns[d] - - # Show any signs for new breakpoints - for (b, l, r) in needed_bps: - bp = needed_bps[(b, l, r)] - if self.haveBreakpoint(b.name, l): - self.markedBreakpoints[(b.name, l)].append(bp) - else: - self.markedBreakpoints[(b.name, l)] = [bp] - - if (b, l, r) not in self.breakpointSigns: - s = BreakpointSign(b, l, r) - self.breakpointSigns[(b, l, r)] = s - - def update(self, target, status, controller, goto_file=False): - """ Updates debugger info panels and breakpoint/pc marks and prints - status to the vim status line. If goto_file is True, the user's - cursor is moved to the source PC location in the selected frame. - """ - - self.paneCol.update(target, controller) - self.update_breakpoints(target, self.get_user_buffers()) - - if target is not None and target.IsValid(): - process = target.GetProcess() - if process is not None and process.IsValid(): - self.update_pc(process, self.get_user_buffers, goto_file) - - if status is not None and len(status) > 0: - print status - - def haveBreakpoint(self, file, line): - """ Returns True if we have a breakpoint at file:line, False otherwise """ - return (file, line) in self.markedBreakpoints - - def getBreakpoints(self, fname, line): - """ Returns the LLDB SBBreakpoint object at fname:line """ - if self.haveBreakpoint(fname, line): - return self.markedBreakpoints[(fname, line)] - else: - return None - - def deleteBreakpoints(self, name, line): - del self.markedBreakpoints[(name, line)] - - def showWindow(self, name): - """ Shows (un-hides) window pane specified by name """ - if not self.paneCol.havePane(name): - sys.stderr.write("unknown window: %s" % name) - return False - self.paneCol.prepare([name]) - return True - - def hideWindow(self, name): - """ Hides window pane specified by name """ - if not self.paneCol.havePane(name): - sys.stderr.write("unknown window: %s" % name) - return False - self.paneCol.hide([name]) - return True + + def __init__(self): + """ Declare UI state variables """ + + # Default panes to display + self.defaultPanes = [ + 'breakpoints', + 'backtrace', + 'locals', + 'threads', + 'registers', + 'disassembly'] + + # map of tuples (filename, line) --> SBBreakpoint + self.markedBreakpoints = {} + + # Currently shown signs + self.breakpointSigns = {} + self.pcSigns = [] + + # Container for panes + self.paneCol = PaneLayout() + + # All possible LLDB panes + self.backtracePane = BacktracePane(self.paneCol) + self.threadPane = ThreadPane(self.paneCol) + self.disassemblyPane = DisassemblyPane(self.paneCol) + self.localsPane = LocalsPane(self.paneCol) + self.registersPane = RegistersPane(self.paneCol) + self.breakPane = BreakpointsPane(self.paneCol) + + def activate(self): + """ Activate UI: display default set of panes """ + self.paneCol.prepare(self.defaultPanes) + + def get_user_buffers(self, filter_name=None): + """ Returns a list of buffers that are not a part of the LLDB UI. That is, they + are not contained in the PaneLayout object self.paneCol. + """ + ret = [] + for w in vim.windows: + b = w.buffer + if not self.paneCol.contains(b.name): + if filter_name is None or filter_name in b.name: + ret.append(b) + return ret + + def update_pc(self, process, buffers, goto_file): + """ Place the PC sign on the PC location of each thread's selected frame """ + + def GetPCSourceLocation(thread): + """ Returns a tuple (thread_index, file, line, column) that represents where + the PC sign should be placed for a thread. + """ + + frame = thread.GetSelectedFrame() + frame_num = frame.GetFrameID() + le = frame.GetLineEntry() + while not le.IsValid() and frame_num < thread.GetNumFrames(): + frame_num += 1 + le = thread.GetFrameAtIndex(frame_num).GetLineEntry() + + if le.IsValid(): + path = os.path.join( + le.GetFileSpec().GetDirectory(), + le.GetFileSpec().GetFilename()) + return ( + thread.GetIndexID(), + path, + le.GetLine(), + le.GetColumn()) + return None + + # Clear all existing PC signs + del_list = [] + for sign in self.pcSigns: + sign.hide() + del_list.append(sign) + for sign in del_list: + self.pcSigns.remove(sign) + del sign + + # Select a user (non-lldb) window + if not self.paneCol.selectWindow(False): + # No user window found; avoid clobbering by splitting + vim.command(":vsp") + + # Show a PC marker for each thread + for thread in process: + loc = GetPCSourceLocation(thread) + if not loc: + # no valid source locations for PCs. hide all existing PC + # markers + continue + + buf = None + (tid, fname, line, col) = loc + buffers = self.get_user_buffers(fname) + is_selected = thread.GetIndexID() == process.GetSelectedThread().GetIndexID() + if len(buffers) == 1: + buf = buffers[0] + if buf != vim.current.buffer: + # Vim has an open buffer to the required file: select it + vim.command('execute ":%db"' % buf.number) + elif is_selected and vim.current.buffer.name not in fname and os.path.exists(fname) and goto_file: + # FIXME: If current buffer is modified, vim will complain when we try to switch away. + # Find a way to detect if the current buffer is modified, + # and...warn instead? + vim.command('execute ":e %s"' % fname) + buf = vim.current.buffer + elif len(buffers) > 1 and goto_file: + # FIXME: multiple open buffers match PC location + continue + else: + continue + + self.pcSigns.append(PCSign(buf, line, is_selected)) + + if is_selected and goto_file: + # if the selected file has a PC marker, move the cursor there + # too + curname = vim.current.buffer.name + if curname is not None and is_same_file(curname, fname): + move_cursor(line, 0) + elif move_cursor: + print "FIXME: not sure where to move cursor because %s != %s " % (vim.current.buffer.name, fname) + + def update_breakpoints(self, target, buffers): + """ Decorates buffer with signs corresponding to breakpoints in target. """ + + def GetBreakpointLocations(bp): + """ Returns a list of tuples (resolved, filename, line) where a breakpoint was resolved. """ + if not bp.IsValid(): + sys.stderr.write("breakpoint is invalid, no locations") + return [] + + ret = [] + numLocs = bp.GetNumLocations() + for i in range(numLocs): + loc = bp.GetLocationAtIndex(i) + desc = get_description(loc, lldb.eDescriptionLevelFull) + match = re.search('at\ ([^:]+):([\d]+)', desc) + try: + lineNum = int(match.group(2).strip()) + ret.append((loc.IsResolved(), match.group(1), lineNum)) + except ValueError as e: + sys.stderr.write( + "unable to parse breakpoint location line number: '%s'" % + match.group(2)) + sys.stderr.write(str(e)) + + return ret + + if target is None or not target.IsValid(): + return + + needed_bps = {} + for bp_index in range(target.GetNumBreakpoints()): + bp = target.GetBreakpointAtIndex(bp_index) + locations = GetBreakpointLocations(bp) + for (is_resolved, file, line) in GetBreakpointLocations(bp): + for buf in buffers: + if file in buf.name: + needed_bps[(buf, line, is_resolved)] = bp + + # Hide any signs that correspond with disabled breakpoints + del_list = [] + for (b, l, r) in self.breakpointSigns: + if (b, l, r) not in needed_bps: + self.breakpointSigns[(b, l, r)].hide() + del_list.append((b, l, r)) + for d in del_list: + del self.breakpointSigns[d] + + # Show any signs for new breakpoints + for (b, l, r) in needed_bps: + bp = needed_bps[(b, l, r)] + if self.haveBreakpoint(b.name, l): + self.markedBreakpoints[(b.name, l)].append(bp) + else: + self.markedBreakpoints[(b.name, l)] = [bp] + + if (b, l, r) not in self.breakpointSigns: + s = BreakpointSign(b, l, r) + self.breakpointSigns[(b, l, r)] = s + + def update(self, target, status, controller, goto_file=False): + """ Updates debugger info panels and breakpoint/pc marks and prints + status to the vim status line. If goto_file is True, the user's + cursor is moved to the source PC location in the selected frame. + """ + + self.paneCol.update(target, controller) + self.update_breakpoints(target, self.get_user_buffers()) + + if target is not None and target.IsValid(): + process = target.GetProcess() + if process is not None and process.IsValid(): + self.update_pc(process, self.get_user_buffers, goto_file) + + if status is not None and len(status) > 0: + print status + + def haveBreakpoint(self, file, line): + """ Returns True if we have a breakpoint at file:line, False otherwise """ + return (file, line) in self.markedBreakpoints + + def getBreakpoints(self, fname, line): + """ Returns the LLDB SBBreakpoint object at fname:line """ + if self.haveBreakpoint(fname, line): + return self.markedBreakpoints[(fname, line)] + else: + return None + + def deleteBreakpoints(self, name, line): + del self.markedBreakpoints[(name, line)] + + def showWindow(self, name): + """ Shows (un-hides) window pane specified by name """ + if not self.paneCol.havePane(name): + sys.stderr.write("unknown window: %s" % name) + return False + self.paneCol.prepare([name]) + return True + + def hideWindow(self, name): + """ Hides window pane specified by name """ + if not self.paneCol.havePane(name): + sys.stderr.write("unknown window: %s" % name) + return False + self.paneCol.hide([name]) + return True global ui ui = UI() |