diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
commit | 9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch) | |
tree | dd2a1ddf0476664c2b823409c36cbccd52662ca7 /utils/vim-lldb/python-vim-lldb/vim_panes.py | |
parent | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff) | |
download | src-9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc.tar.gz src-9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc.zip |
Vendor import of lldb trunk r256945:vendor/lldb/lldb-trunk-r256945
Notes
Notes:
svn path=/vendor/lldb/dist/; revision=293262
svn path=/vendor/lldb/lldb-trunk-r256945/; revision=293263; tag=vendor/lldb/lldb-trunk-r256945
Diffstat (limited to 'utils/vim-lldb/python-vim-lldb/vim_panes.py')
-rw-r--r-- | utils/vim-lldb/python-vim-lldb/vim_panes.py | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/utils/vim-lldb/python-vim-lldb/vim_panes.py b/utils/vim-lldb/python-vim-lldb/vim_panes.py new file mode 100644 index 000000000000..ec537199922c --- /dev/null +++ b/utils/vim-lldb/python-vim-lldb/vim_panes.py @@ -0,0 +1,618 @@ +# +# This file contains implementations of the LLDB display panes in VIM +# +# The most generic way to define a new window is to inherit from VimPane +# and to implement: +# - get_content() - returns a string with the pane contents +# +# Optionally, to highlight text, implement: +# - get_highlights() - returns a map +# +# And call: +# - define_highlight(unique_name, colour) +# at some point in the constructor. +# +# +# 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 +# from StoppedCommandPane and call: +# - self.setCommand(command, command_args) +# at some point in the constructor. +# +# 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 +# presses to change selected frame/thread... +# + +import lldb +import vim + +import sys + +# ============================================================== +# Get the description of an lldb object or None if not available +# ============================================================== + +# 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. + + For SBTarget, SBBreakpointLocation, and SBWatchpoint lldb objects, an extra + option can be passed in to describe the detailed level of description + desired: + o lldb.eDescriptionLevelBrief + o lldb.eDescriptionLevelFull + o lldb.eDescriptionLevelVerbose + """ + method = getattr(obj, 'GetDescription') + if not method: + return None + tuple = (lldb.SBTarget, lldb.SBBreakpointLocation, lldb.SBWatchpoint) + if isinstance(obj, tuple): + if option is None: + option = lldb.eDescriptionLevelBrief + + stream = lldb.SBStream() + if option is None: + success = method(stream) + else: + success = method(stream, option) + 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) + + 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) + + 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) + +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) + +def 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)) + +def goto_window(nr): + """ 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) + +def goto_previous_window(): + """ 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 + +class PaneLayout(object): + """ 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) + + +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 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). + """ + + 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 + +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] + +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 + +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. + """ + 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) + + # 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") + +# FIXME: the function below assumes threads are listed in sequential order, +# which turns out to not be the case. Highlighting of selected thread +# will be disabled until this can be fixed. LLDB prints a '*' anyways +# beside the selected thread, so this is not too big of a problem. +# def get_selected_line(self): +# """ Place the cursor on the line with the selected entity. +# Subclasses should override this to customize selection. +# Formula: selected_line = selected_thread_id + 1 +# """ +# (thread, err) = get_selected_thread(self.target) +# if thread is None: +# return None +# 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", "") + + + 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 + +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") |