diff --git a/doc/Vdebug.txt b/doc/Vdebug.txt index af55122d..f61cfa12 100644 --- a/doc/Vdebug.txt +++ b/doc/Vdebug.txt @@ -443,7 +443,8 @@ the current debugger status, connection details and a help message. This shows the current position of the debugger. The file will automatically change as the debugger pauses in different parts of the code, and the current -line is shown with a "->" sign in the margin. +line is shown centered vertically in the source window, with a "->" sign +in the margin. Don't edit the contents of the file when in debug mode, and especially don't edit without saving - this causes problems when swapping between files. @@ -472,8 +473,8 @@ strings to indicate relationships. The compact version has a variable on each line, and works better for smaller screens. To set this option, see |VdebugOptions-watch_window_style|. -The watch window automatically updates every time the debugger pauses, so -nothing needs to be done on your part. +The watch window automatically updates every time the debugger pauses, +or a stack jump is made on the stack window (see next topic). You can also see variables from different contexts. For example, PHP has normal context variables and global variables, and the debugging engine differentiates @@ -498,7 +499,12 @@ below show the path the script has taken. Like the watch window, the stack will update automatically every time the debugger pauses. You can jump to a place in the stack by putting your cursor over a line and pressing () or by double clicking with your mouse -(if you have mouse support enabled). +(if you have mouse support enabled). The watch window will be updated +with the file name and line number of the file in the current stack +depth (i.e. the index in the stack activated by ou double +click - the "stack jump"), and the stack window and the source window +will have a "#>" sign in their left margin marking the current stack +depth and the corresponding source line number, respectively. ------------------------------------------------------------------------------ 4.2.4 The status window *VdebugStatusWindow* diff --git a/plugin/python/vdebug/dbgp.py b/plugin/python/vdebug/dbgp.py index 0617041e..630338d3 100644 --- a/plugin/python/vdebug/dbgp.py +++ b/plugin/python/vdebug/dbgp.py @@ -7,6 +7,7 @@ import vdebug.log import base64 import time +import re """ Response objects for the DBGP module.""" @@ -119,9 +120,31 @@ def __init__(self,response,cmd,cmd_args,api): Response.__init__(self,response,cmd,cmd_args,api) self.properties = [] + def get_page(self, cmd_args): + opts = re.compile('-p (\d+)') + page = None + result = opts.search(cmd_args) + if result != None: + page = int(result.group(1)) + return page + + def show_children(self): + return self.cmd == 'property_get' + def get_context(self): + page = self.get_page(self.cmd_args) for c in list(self.as_xml()): - self.create_properties(ContextProperty(c)) + self.create_properties(ContextProperty(c, init_children=self.show_children())) + if page == 0: + """ Loop through pages 2 to num_pages """ + for p in xrange(1, self.properties[0].num_pages): + nth_cmd_args = self.cmd_args.replace('-p ' + str(page), '-p ' + str(p)) + nth_prop = self.api.send_cmd(self.cmd, nth_cmd_args, ContextGetResponse) + """ First row is duplicated on pages > 0 so delete it """ + nth_page = nth_prop.get_context() + del nth_page[0] + + self.properties.extend(nth_page) return self.properties @@ -335,11 +358,11 @@ def stack_get(self): """ return self.send_cmd('stack_get','',StackGetResponse) - def context_get(self,context = 0): + def context_get(self,context = 0, depth = 0): """Get the context variables. """ return self.send_cmd('context_get',\ - '-c %i' % int(context),\ + '-c %i -d %i' % (int(context), int(depth)),\ ContextGetResponse) def context_names(self): @@ -347,10 +370,10 @@ def context_names(self): """ return self.send_cmd('context_names','',ContextNamesResponse) - def property_get(self,name): + def property_get(self,name, depth = 0, page = 0): """Get a property. """ - return self.send_cmd('property_get','-n %s -d 0' % name,ContextGetResponse) + return self.send_cmd('property_get','-n %s -d %i -p %i' % (name, int(depth), int(page)),ContextGetResponse) def detach(self): """Tell the debugger to detach itself from this @@ -525,8 +548,11 @@ class ContextProperty: ns = '{urn:debugger_protocol_v1}' - def __init__(self,node,parent = None,depth = 0): + def __init__(self,node,parent = None,depth = 0, init_children = True): self.parent = parent + """ Every child property will have a zero-based page property set """ + if self.parent: + self.page = parent.page self.__determine_type(node) self._determine_displayname(node) self.encoding = node.get('encoding') @@ -538,7 +564,8 @@ def __init__(self,node,parent = None,depth = 0): self._determine_children(node) self.__determine_value(node) - self.__init_children(node) + if init_children: + self.__init_children(node) if self.type == 'scalar': self.size = len(self.value) - 2 @@ -606,6 +633,23 @@ def _determine_children(self,node): children = int(children) self.num_declared_children = children self.has_children = children > 0 + """ If the current element has children, initialize page properties """ + self.page = None + self.pagesize = None + self.num_pages = 0 + if self.has_children: + self.page = node.get('page') + self.pagesize = node.get('pagesize') + if self.page == None or self.pagesize == None: + self.page = 0 + self.pagesize = 0 + self.num_pages = 0 + else: + self.page = int(self.page) + self.pagesize = int(self.pagesize) + self.num_pages = self.num_declared_children / self.pagesize + if (self.num_declared_children % self.pagesize): + self.num_pages += 1 self.children = [] def __init_children(self,node): diff --git a/plugin/python/vdebug/event.py b/plugin/python/vdebug/event.py index 81d3bbb0..ffdf4310 100644 --- a/plugin/python/vdebug/event.py +++ b/plugin/python/vdebug/event.py @@ -131,20 +131,23 @@ class StackWindowLineSelectEvent(Event): """Move the the currently selected file and line in the stack window """ def execute(self,runner): - lineno = vim.current.window.cursor[0] + stacklineno = vim.current.window.cursor[0] - vdebug.log.Log("User action in stack window, line %s" % lineno,\ + vdebug.log.Log("User action in stack window, line %s" % stacklineno,\ vdebug.log.Logger.DEBUG) - line = runner.ui.stackwin.buffer[lineno-1] - if line.find(" @ ") == -1: - return False - filename_pos = line.find(" @ ") + 3 - file_and_line = line[filename_pos:] + file_and_line = runner.ui.stackwin.get_file_and_line(stacklineno) line_pos = file_and_line.rfind(":") file = vdebug.util.LocalFilePath(file_and_line[:line_pos]) + + """ Setting stack depth and current file_and_line in runner """ + runner.set_context_stack_info(stacklineno - 1, file_and_line) + lineno = file_and_line[line_pos+1:] runner.ui.sourcewin.set_file(file) runner.ui.sourcewin.set_line(lineno) + runner.ui.sourcewin.place_stack_sign(lineno) + runner.ui.stackwin.place_stack_sign(stacklineno) + runner.get_context(0) class WatchWindowPropertyGetEvent(Event): """Open a tree node in the watch window. @@ -159,10 +162,11 @@ def execute(self,runner): eq_index = line.find('=') if eq_index == -1: - raise EventError("Cannot read the selected property") + return name = line[pointer_index+step:eq_index-1] - context_res = runner.api.property_get(name) + """ Refactoring property_get with multiple pages """ + context_res = runner.property_get(name) rend = vdebug.ui.vimui.ContextGetResponseRenderer(context_res) output = rend.render(pointer_index - 1) if vdebug.opts.Options.get('watch_window_style') == 'expanded': diff --git a/plugin/python/vdebug/runner.py b/plugin/python/vdebug/runner.py index d828ae23..7fc03690 100644 --- a/plugin/python/vdebug/runner.py +++ b/plugin/python/vdebug/runner.py @@ -69,6 +69,11 @@ def open(self): status = self.api.step_into() else: status = self.api.run() + + """ Updating stack on open, for watchwin correct title """ + if status in ["running", "break"]: + self.update_stack() + self.set_context_stack_info(0) self.refresh(status) except Exception: self.close() @@ -95,7 +100,7 @@ def save_code(self,code): return code def refresh(self,status): - """The main action performed after a deubugger step. + """The main action performed after a debugger step. Updates the status window, current stack, source file and line and watch window.""" @@ -128,6 +133,11 @@ def refresh(self,status): self.cur_file,\ self.cur_lineno) + """ Removing stack signs """ + self.ui.stackwin.remove_stack_sign() + self.ui.sourcewin.remove_stack_sign() + self.set_context_stack_info(0) + if self.saved_code != '': self.eval(self.saved_code) else: @@ -138,14 +148,15 @@ def get_context(self,context_id = 0): self.ui.tracewin.clean() name = self.context_names[context_id] vdebug.log.Log("Getting %s variables" % name) - context_res = self.api.context_get(context_id) + context_res = self.api.context_get(context_id, self.context_stack_depth) rend = vdebug.ui.vimui.ContextGetResponseRenderer(\ - context_res,"%s at %s:%s" \ - %(name,self.ui.sourcewin.file,self.cur_lineno),\ + context_res,"%s at %s" \ + %(name, self.context_file_and_line),\ self.context_names, context_id) self.ui.watchwin.accept_renderer(rend) + self.ui.watchwin.open_child_properties(self) if self.ui.tracewin.is_tracing(): try: @@ -157,6 +168,8 @@ def get_context(self,context_id = 0): except vdebug.dbgp.EvalError: self.ui.tracewin.render_in_error_case() + def property_get(self, name): + return self.api.property_get(name, self.context_stack_depth) def toggle_breakpoint_window(self): """Open or close the breakpoint window. @@ -345,6 +358,13 @@ def update_stack(self): renderer = vdebug.ui.vimui.StackGetResponseRenderer(res) self.ui.stackwin.accept_renderer(renderer) return res + + def set_context_stack_info(self, stack_depth, file_and_line = ''): + """ stack_depth parameter is zero-based """ + self.context_stack_depth = stack_depth + if (file_and_line == ''): + file_and_line = self.ui.stackwin.get_file_and_line(stack_depth + 1) + self.context_file_and_line = file_and_line def detach(self): """Detach the debugger engine, and allow it to continue execution. diff --git a/plugin/python/vdebug/ui/vimui.py b/plugin/python/vdebug/ui/vimui.py index 6e9ab927..ff297869 100644 --- a/plugin/python/vdebug/ui/vimui.py +++ b/plugin/python/vdebug/ui/vimui.py @@ -4,6 +4,7 @@ import vim import vdebug.log import vdebug.opts +import vdebug.event class Ui(vdebug.ui.interface.Ui): """Ui layer which manages the Vim windows. @@ -218,6 +219,7 @@ class SourceWindow(vdebug.ui.interface.Window): file = None pointer_sign_id = '6145' breakpoint_sign_id = '6146' + stack_sign_id = '6147' def __init__(self,ui,winno): self.winno = str(winno) @@ -244,7 +246,8 @@ def set_file(self,file): def set_line(self,lineno): self.focus() - vim.command("normal %sgg" % str(lineno)) + """ Centering display vertically on lineno """ + vim.command("normal %szz" % str(lineno)) def get_file(self): self.focus() @@ -265,6 +268,17 @@ def place_pointer(self,line): def remove_pointer(self): vim.command('sign unplace %s' % self.pointer_sign_id) + def place_stack_sign(self, line): + self.remove_stack_sign() + vdebug.log.Log("Placing stack sign on line "+str(line),\ + vdebug.log.Logger.INFO) + vim.command('sign place '+self.stack_sign_id+\ + ' name=stack line='+str(line)+\ + ' file='+self.file) + + def remove_stack_sign(self): + vim.command('sign unplace %s' % self.stack_sign_id) + class Window(vdebug.ui.interface.Window): name = "WINDOW" open_cmd = "new" @@ -279,6 +293,9 @@ def __init__(self,ui,open_cmd): def getwinnr(self): return int(vim.eval("bufwinnr('"+self.name+"')")) + def focus(self): + vim.command(str(self.getwinnr())+"wincmd w") + def set_height(self,height): height = int(height) minheight = int(vim.eval("&winminheight")) @@ -431,6 +448,7 @@ def write(self, msg, return_focus = True): class StackWindow(Window): name = "DebuggerStack" + stack_sign_id = '6148' def on_create(self): self.command('inoremap '+\ @@ -447,6 +465,24 @@ def on_create(self): def write(self, msg, return_focus = True): Window.write(self, msg, after="normal gg") + def place_stack_sign(self, line): + self.remove_stack_sign() + self.focus() + vim.command('sign place ' + self.stack_sign_id +\ + ' name=stack' +\ + ' line=' + str(line) +\ + ' buffer=' + vim.eval("bufnr('%')")) + + def remove_stack_sign(self): + vim.command('sign unplace %s' % self.stack_sign_id) + + def get_file_and_line(self, stacklineno): + line = self.buffer[stacklineno - 1] + if line.find(" @ ") == -1: + return False + filename_pos = line.find(" @ ") + 3 + return line[filename_pos:] + class WatchWindow(Window): name = "DebuggerWatch" @@ -465,6 +501,17 @@ def on_create(self): def write(self, msg, return_focus = True): Window.write(self, msg, after="normal gg") + def open_child_properties(self, runner): + self.focus() + last_line_idx = int(vim.eval("line('$')")) - 1 + first_line_idx = -1 + for idx in xrange(last_line_idx, first_line_idx, -1): + self.command('normal ' + str(idx+1) + 'G') + line = vim.current.buffer[idx] + if line.startswith(' ' + vdebug.opts.Options.get('marker_closed_tree')): + vdebug.event.WatchWindowPropertyGetEvent().execute(runner) + self.command('normal gg') + class StatusWindow(Window): name = "DebuggerStatus" @@ -540,7 +587,7 @@ def render(self): %{'num':s.get('level'),'where':where,\ 'file':str(file.as_local()),'line':s.get('lineno')} string += line + "\n" - return string + return string[:-1] class ContextGetResponseRenderer(ResponseRenderer): diff --git a/plugin/vdebug.vim b/plugin/vdebug.vim index 9c0c09e0..7a1644c9 100644 --- a/plugin/vdebug.vim +++ b/plugin/vdebug.vim @@ -131,10 +131,17 @@ end if hlexists("DbgBreakptSign") == 0 hi default DbgBreakptSign term=reverse ctermfg=White ctermbg=Green guifg=#ffffff guibg=#00ff00 end +if hlexists("DbgStackLine") == 0 + hi default DbgStackLine term=reverse ctermfg=White ctermbg=Blue guifg=#ffffff guibg=#0000ff +end +if hlexists("DbgStackSign") == 0 + hi default DbgStackSign term=reverse ctermfg=White ctermbg=Blue guifg=#ffffff guibg=#0000ff +end " Signs and highlighted lines for breakpoints, etc. sign define current text=-> texthl=DbgCurrentSign linehl=DbgCurrentLine sign define breakpt text=B> texthl=DbgBreakptSign linehl=DbgBreakptLine +sign define stack text=#> texthl=DbgStackSign linehl=DbgStackLine function! s:BreakpointTypes(A,L,P) let arg_to_cursor = strpart(a:L,11,a:P) diff --git a/tests/test_dbgp_response.py b/tests/test_dbgp_response.py index c302884c..c943f7fc 100644 --- a/tests/test_dbgp_response.py +++ b/tests/test_dbgp_response.py @@ -121,39 +121,36 @@ class ContextGetTest(unittest.TestCase): type="string" size="3" encoding="base64"> +type="uninitialized"> """ def test_properties_are_objects(self): res = vdebug.dbgp.ContextGetResponse(self.response,"","",Mock()) context = res.get_context() - assert len(context) == 23 + """ + Children are only fetched in UI. + Every context get command, or property get in Watch window, + Trigger a property_get command for the property currently focused on watchwin. + This fetches all pages, and therefore + Here every child property is bypassed + """ + assert len(context) == 5 self.assertIsInstance(context[0],vdebug.dbgp.ContextProperty) def test_int_property_attributes(self): @@ -165,6 +162,7 @@ def test_int_property_attributes(self): assert prop.type == "int" assert prop.value == "4" assert prop.has_children == False + assert prop.page == None def test_array_property_attributes(self): res = vdebug.dbgp.ContextGetResponse(self.response,"","",Mock()) @@ -175,18 +173,57 @@ def test_array_property_attributes(self): assert prop.type == "array" assert prop.value == "" assert prop.has_children == True - assert prop.child_count() == 4 + """ + Children are only fetched in UI. + Every context get command, or property get in Watch window, + Trigger a property_get command for the property currently focused on watchwin. + This fetches all pages, and therefore + Here every child property is bypassed + """ + assert prop.child_count() == 0 + assert prop.page == 0 + + def test_even_num_pages(self): + res = vdebug.dbgp.ContextGetResponse(self.response,"","",Mock()) + context = res.get_context() + prop = context[3] - def test_string_property_attributes(self): + assert prop.display_name == "$even_num_pages_attr" + assert prop.type == "array" + assert prop.value == "" + assert prop.has_children == True + """ + Children are only fetched in UI. + Every context get command, or property get in Watch window, + Trigger a property_get command for the property currently focused on watchwin. + This fetches all pages, and therefore + Here every child property is bypassed + """ + assert prop.child_count() == 0 + assert prop.page == 0 + assert prop.pagesize == 2 + assert prop.num_pages == 2 + + def test_odd_num_pages(self): res = vdebug.dbgp.ContextGetResponse(self.response,"","",Mock()) context = res.get_context() - prop = context[2] + prop = context[4] - assert prop.display_name == "$argv[0]" - assert prop.type == "string" - assert prop.value == "`/usr/local/bin/cake`" - assert prop.has_children == False - assert prop.size == "19" + assert prop.display_name == "$odd_num_pages_attr" + assert prop.type == "array" + assert prop.value == "" + assert prop.has_children == True + """ + Children are only fetched in UI. + Every context get command, or property get in Watch window, + Trigger a property_get command for the property currently focused on watchwin. + This fetches all pages, and therefore + Here every child property is bypassed + """ + assert prop.child_count() == 0 + assert prop.page == 0 + assert prop.pagesize == 2 + assert prop.num_pages == 3 class ContextGetAlternateTest(unittest.TestCase): response = """