class App(Common): """Main application class.""" def __init__(self, common, efmmgr, backendStr: str, proxyCmd: str, clientCmd: str): """ctor.""" super().__init__(common) self.efmmgr = efmmgr self._last_command: Union[str, None] = None # Create new tab for the debugging view and split horizontally self.vim.command('tabnew' ' | setlocal nowinfixwidth' ' | setlocal nowinfixheight' ' | silent wincmd o') # Get the selected backend module backend_maps: Dict[str, Type[base.BaseBackend]] = { "gdb": Gdb, "bashdb": BashDB, "lldb": Lldb, "pdb": Pdb, } self.backend = backend_maps[backendStr]() # Initialize current line tracking self.cursor = Cursor(common) # Go to the other window and spawn gdb client self.client = Client(common, proxyCmd, clientCmd) # Initialize connection to the side channel self.proxy = Proxy(common, self.client) # Initialize breakpoint tracking breakpoint_impl = self.backend.create_breakpoint_impl(self.proxy) self.breakpoint = Breakpoint(common, self.proxy, breakpoint_impl) # Initialize the keymaps subsystem self.keymaps = Keymaps(common) # Initialize the windowing subsystem self.win = Win(common, self.cursor, self.client, self.breakpoint, self.keymaps) # Initialize the parser parser_adapter = ParserAdapter(common, self.cursor, self.win) self.parser = self.backend.create_parser_impl(common, parser_adapter) # Set initial keymaps in the terminal window. self.keymaps.dispatch_set_t() self.keymaps.dispatch_set() # Setup 'errorformat' for the given backend. self.efmmgr.setup(self.backend.get_error_formats()) # Start insert mode in the GDB window self.vim.feedkeys("i") def start(self): """Spawn the debugger, the parser should be ready by now.""" self.client.start() self.vim.command("doautocmd User NvimGdbStart") def cleanup(self, tab): """Finish up the debugging session.""" self.vim.command("doautocmd User NvimGdbCleanup") # Remove from 'errorformat' for the given backend. self.efmmgr.teardown(self.backend.get_error_formats()) # Clean up the breakpoint signs self.breakpoint.reset_signs() # Clean up the current line sign self.cursor.hide() # Clean up the windows and buffers self.win.cleanup() # Close connection to the side channel self.proxy.cleanup() # Close the debugger backend self.client.cleanup() # Close the windows and the tab for tabpage in self.vim.tabpages: if tabpage.handle == tab: self.vim.command(f"tabclose! {tabpage.number}") def _get_command(self, cmd): return self.backend.translate_command(cmd) def send(self, *args): """Send a command to the debugger.""" if args: command = self._get_command(args[0]).format(*args[1:]) self.client.send_line(command) self._last_command = command # Remember the command for testing else: self.client.interrupt() def custom_command(self, cmd): """Execute a custom debugger command and return its output.""" return self.proxy.query("handle-command " + cmd) def create_watch(self, cmd): """Create a window to watch for a debugger expression. The output of the expression or command will be displayed in that window. """ self.vim.command("vnew | set readonly buftype=nowrite") self.keymaps.dispatch_set() buf = self.vim.current.buffer buf.name = cmd cur_tabpage = self.vim.current.tabpage.number augroup_name = f"NvimGdbTab{cur_tabpage}_{buf.number}" self.vim.command(f"augroup {augroup_name}") self.vim.command("autocmd!") self.vim.command("autocmd User NvimGdbQuery" f" call nvim_buf_set_lines({buf.number}, 0, -1, 0," f" split(GdbCustomCommand('{cmd}'), '\\n'))") self.vim.command("augroup END") # Destroy the autowatch automatically when the window is gone. self.vim.command("autocmd BufWinLeave <buffer> call" f" nvimgdb#ClearAugroup('{augroup_name}')") # Destroy the watch buffer. self.vim.command("autocmd BufWinLeave <buffer> call timer_start(100," f" {{ -> execute('bwipeout! {buf.number}') }})") # Return the cursor to the previous window self.vim.command("wincmd l") def breakpoint_toggle(self): """Toggle breakpoint in the cursor line.""" if self.parser.is_running(): # pause first self.client.interrupt() buf = self.vim.current.buffer file_name = self.vim.call("expand", '#%d:p' % buf.handle) line_nr = self.vim.call("line", ".") breaks = self.breakpoint.get_for_file(file_name, line_nr) if breaks: # There already is a breakpoint on this line: remove del_br = self._get_command('delete_breakpoints') self.client.send_line(f"{del_br} {breaks[-1]}") else: set_br = self._get_command('breakpoint') self.client.send_line(f"{set_br} {file_name}:{line_nr}") def breakpoint_clear_all(self): """Clear all breakpoints.""" if self.parser.is_running(): # pause first self.client.interrupt() # The breakpoint signs will be requeried later automatically self.send('delete_breakpoints') def on_tab_enter(self): """Actions to execute when a tabpage is entered.""" # Restore the signs as they may have been spoiled if self.parser.is_paused(): self.cursor.show() # Ensure breakpoints are shown if are queried dynamically self.win.query_breakpoints() def on_tab_leave(self): """Actions to execute when a tabpage is left.""" # Hide the signs self.cursor.hide() self.breakpoint.clear_signs() def on_buf_enter(self): """Actions to execute when a buffer is entered.""" # Apply keymaps to the jump window only. if self.vim.current.buffer.options['buftype'] != 'terminal' \ and self.win.is_jump_window_active(): # Make sure the cursor stay visible at all times scroll_off = self.config.get_or('set_scroll_off', None) if scroll_off is not None: self.vim.command("if !&scrolloff" f" | setlocal scrolloff={str(scroll_off)}" " | endif") self.keymaps.dispatch_set() # Ensure breakpoints are shown if are queried dynamically self.win.query_breakpoints() def on_buf_leave(self): """Actions to execute when a buffer is left.""" if self.vim.current.buffer.options['buftype'] == 'terminal': # Move the cursor to the end of the buffer self.vim.command("$") return if self.win.is_jump_window_active(): self.keymaps.dispatch_unset() def lopen(self, kind, mods): """Load backtrace or breakpoints into the location list.""" cmd = '' if kind == "backtrace": cmd = self.backend.translate_command('bt') elif kind == "breakpoints": cmd = self.backend.translate_command('info breakpoints') else: self.logger.warning("Unknown lopen kind %s", kind) return self.win.lopen(cmd, kind, mods) def get_for_llist(self, kind, cmd): output = self.custom_command(cmd) lines = re.split(r'[\r\n]+', output) if kind == "backtrace": return lines elif kind == "breakpoints": return self.backend.llist_filter_breakpoints(lines) else: self.logger.warning("Unknown lopen kind %s", kind)
class App: def __init__(self, vim, logger, backendStr, proxyCmd, clientCmd): self.vim = vim self.log = lambda msg: logger.log('app', msg) # Prepare configuration: keymaps, hooks, parameters etc. self.config = getConfig(vim) self.defineSigns(self.config) # Create new tab for the debugging view and split horizontally vim.command( 'tabnew | setlocal nowinfixwidth | setlocal nowinfixheight | exe "normal \<c-w>o"' ) vim.command(self.config["split_command"]) if len(vim.current.tabpage.windows) != 2: raise Exception( "The split_command should result in exactly two windows") # Enumerate the available windows wins = vim.current.tabpage.windows wcli, wjump = wins[1], wins[0] # Import the desired backend module self.backend = importlib.import_module("gdb.backend." + backendStr).init() # Create a temporary unique directory for all the sockets. self.sockDir = SockDir() # Initialize current line tracking self.cursor = Cursor(vim) # Go to the other window and spawn gdb client self.client = Client(vim, wcli, proxyCmd, clientCmd, self.sockDir) # Initialize connection to the side channel self.proxy = Proxy(vim, self.client.getProxyAddr(), self.sockDir) # Initialize breakpoint tracking self.breakpoint = Breakpoint(vim, self.config, self.proxy) # Initialize the keymaps subsystem self.keymaps = Keymaps(vim, self.config) # Initialize the windowing subsystem self.win = Win(vim, wjump, self.cursor, self.client, self.breakpoint, self.keymaps) # Initialize the SCM self.scm = self.backend["initScm"](vim, logger, self.cursor, self.win) # Set initial keymaps in the terminal window. self.keymaps.dispatchSetT() self.keymaps.dispatchSet() # Start insert mode in the GDB window vim.feedkeys("i") def start(self): # The SCM should be ready by now, spawn the debugger! self.client.start() def cleanup(self): # Clean up the breakpoint signs self.breakpoint.resetSigns() # Clean up the current line sign self.cursor.hide() # Close connection to the side channel self.proxy.cleanup() # Close the windows and the tab tabCount = len(self.vim.tabpages) self.client.delBuffer() if tabCount == len(self.vim.tabpages): self.vim.command("tabclose") self.client.cleanup() self.sockDir.cleanup() def defineSigns(self, config): # Define the sign for current line the debugged program is executing. self.vim.command("sign define GdbCurrentLine text=" + config["sign_current_line"]) # Define signs for the breakpoints. breaks = config["sign_breakpoint"] for i in range(len(breaks)): self.vim.command('sign define GdbBreakpoint%d text=%s' % ((i + 1), breaks[i])) def getCommand(self, cmd): return self.backend.get(cmd, cmd) def send(self, *args): if args: command = self.backend.get(args[0], args[0]).format(*args[1:]) self.client.sendLine(command) self.lastCommand = command # Remember the command for testing else: self.client.interrupt() def customCommand(self, cmd): return self.proxy.query("handle-command " + cmd) def breakpointToggle(self): if self.scm.isRunning(): # pause first self.client.interrupt() buf = self.vim.current.buffer fileName = self.vim.call("expand", '#%d:p' % buf.handle) lineNr = self.vim.call("line", ".") breaks = self.breakpoint.getForFile(fileName, lineNr) if breaks: # There already is a breakpoint on this line: remove self.client.sendLine( "%s %d" % (self.getCommand('delete_breakpoints'), breaks[-1])) else: self.client.sendLine( "%s %s:%s" % (self.getCommand('breakpoint'), fileName, lineNr)) def breakpointClearAll(self): if self.scm.isRunning(): # pause first self.client.interrupt() # The breakpoint signs will be requeried later automatically self.send('delete_breakpoints') def onTabEnter(self): # Restore the signs as they may have been spoiled if self.scm.isPaused(): self.cursor.show() # Ensure breakpoints are shown if are queried dynamically self.win.queryBreakpoints() def onTabLeave(self): # Hide the signs self.cursor.hide() self.breakpoint.clearSigns() def onBufEnter(self): if self.vim.current.buffer.options['buftype'] != 'terminal': # Make sure the cursor stay visible at all times self.vim.command("if !&scrolloff | setlocal scrolloff=5 | endif") self.keymaps.dispatchSet() # Ensure breakpoints are shown if are queried dynamically self.win.queryBreakpoints() def onBufLeave(self): if self.vim.current.buffer.options['buftype'] != 'terminal': self.keymaps.dispatchUnset()
class App(Common): '''Main application class.''' def __init__(self, common, backendStr, proxyCmd, clientCmd): super().__init__(common) self._last_command = None # Create new tab for the debugging view and split horizontally self.vim.command('tabnew' ' | setlocal nowinfixwidth' ' | setlocal nowinfixheight' ' | silent wincmd o') self.vim.command(self.config.get("split_command")) if len(self.vim.current.tabpage.windows) != 2: raise Exception("The split_command should result in exactly two" " windows") # Enumerate the available windows wins = self.vim.current.tabpage.windows wcli, wjump = wins[1], wins[0] # Initialize current line tracking self.cursor = Cursor(common) # Go to the other window and spawn gdb client self.client = Client(common, wcli, proxyCmd, clientCmd) # Initialize connection to the side channel self.proxy = Proxy(common, self.client) # Initialize breakpoint tracking self.breakpoint = Breakpoint(common, self.proxy) # Initialize the keymaps subsystem self.keymaps = Keymaps(common) # Initialize the windowing subsystem self.win = Win(common, wjump, self.cursor, self.client, self.breakpoint) # Get the selected backend module backend_maps = { "gdb": GdbParser, "bashdb": BashDBParser, "lldb": LldbParser, "pdb": PdbParser } backend_class = backend_maps[backendStr] # Initialize the parser self.parser = backend_class(common, self.cursor, self.win) # Set initial keymaps in the terminal window. self.keymaps.dispatch_set_t() self.keymaps.dispatch_set() # Start insert mode in the GDB window self.vim.feedkeys("i") def start(self): '''The parser should be ready by now, spawn the debugger!''' self.client.start() self.vim.command("doautocmd User NvimGdbStart") def cleanup(self): '''Finish up the debugging session.''' self.vim.command("doautocmd User NvimGdbCleanup") # Clean up the breakpoint signs self.breakpoint.reset_signs() # Clean up the current line sign self.cursor.hide() # Close connection to the side channel self.proxy.cleanup() # Close the windows and the tab tab_count = len(self.vim.tabpages) self.client.del_buffer() if tab_count == len(self.vim.tabpages): self.vim.command("tabclose") self.client.cleanup() def _get_command(self, cmd): return self.parser.command_map.get(cmd, cmd) def send(self, *args): '''Send a command to the debugger.''' if args: command = self._get_command(args[0]).format(*args[1:]) self.client.send_line(command) self._last_command = command # Remember the command for testing else: self.client.interrupt() def custom_command(self, cmd): '''Execute a custom debugger command and return its output.''' return self.proxy.query("handle-command " + cmd) def breakpoint_toggle(self): '''Toggle breakpoint in the cursor line.''' if self.parser.is_running(): # pause first self.client.interrupt() buf = self.vim.current.buffer file_name = self.vim.call("expand", '#%d:p' % buf.handle) line_nr = self.vim.call("line", ".") breaks = self.breakpoint.get_for_file(file_name, line_nr) if breaks: # There already is a breakpoint on this line: remove del_br = self._get_command('delete_breakpoints') self.client.send_line(f"{del_br} {breaks[-1]}") else: set_br = self._get_command('breakpoint') self.client.send_line(f"{set_br} {file_name}:{line_nr}") def breakpoint_clear_all(self): '''Clear all breakpoints.''' if self.parser.is_running(): # pause first self.client.interrupt() # The breakpoint signs will be requeried later automatically self.send('delete_breakpoints') def on_tab_enter(self): '''Actions to execute when a tabpage is entered.''' # Restore the signs as they may have been spoiled if self.parser.is_paused(): self.cursor.show() # Ensure breakpoints are shown if are queried dynamically self.win.query_breakpoints() def on_tab_leave(self): '''Actions to execute when a tabpage is left.''' # Hide the signs self.cursor.hide() self.breakpoint.clear_signs() def on_buf_enter(self): '''Actions to execute when a buffer is entered.''' # Apply keymaps to the jump window only. if self.vim.current.buffer.options['buftype'] != 'terminal' \ and self.win.is_jump_window_active(): # Make sure the cursor stay visible at all times scroll_off = self.config.get_or('set_scroll_off', None) if scroll_off is not None: self.vim.command("if !&scrolloff" f" | setlocal scrolloff={str(scroll_off)}" " | endif") self.keymaps.dispatch_set() # Ensure breakpoints are shown if are queried dynamically self.win.query_breakpoints() def on_buf_leave(self): '''Actions to execute when a buffer is left.''' if self.vim.current.buffer.options['buftype'] != 'terminal': self.keymaps.dispatch_unset()