class Pugdebug(QObject): breakpoints = [] def __init__(self): """Initialize the application Initializes all the different parts of the application. Creates the PugdebugDebugger object, sets up the application UI, connects signals to slots. """ super(Pugdebug, self).__init__() self.debugger = PugdebugDebugger() self.formatter = PugdebugFormatter() # UI elements self.main_window = PugdebugMainWindow() self.file_browser = self.main_window.get_file_browser() self.projects_browser = self.main_window.get_projects_browser() self.settings = self.main_window.get_settings() self.document_viewer = self.main_window.get_document_viewer() self.variable_viewer = self.main_window.get_variable_viewer() self.stacktrace_viewer = self.main_window.get_stacktrace_viewer() self.breakpoint_viewer = self.main_window.get_breakpoint_viewer() self.expression_viewer = self.main_window.get_expression_viewer() self.documents = PugdebugDocuments() self.setup_file_browser() self.setup_projects_browser() self.connect_signals() def setup_file_browser(self): """Setup the file browser Sets the model on the file browser and hides the not needed columns. """ project_root = get_setting('path/project_root') model = PugdebugFileBrowser(self) model.set_path(project_root) self.file_browser.setModel(model) self.file_browser.setRootIndex(model.start_index) self.file_browser.hide_columns() def setup_projects_browser(self): """Setup the projects browser Sets the model on the projects browser. """ model = PugdebugProjects(self) self.projects_browser.setModel(model) def connect_signals(self): """Connect all signals to their slots Connect file browser signals, settings signals, document viewer signals toolbar action signals, debugger signals. """ self.connect_file_browser_signals() self.connect_projects_browser_signals() self.connect_settings_signals() self.connect_document_viewer_signals() self.connect_toolbar_action_signals() self.connect_debugger_signals() self.connect_expression_viewer_signals() self.connect_stacktrace_viewer_signals() self.connect_breakpoint_viewer_signals() def connect_file_browser_signals(self): """Connect file browser signals Connects the file browser's activated signal to the slot that gets called when a file browser item is activated. """ self.file_browser.activated.connect(self.file_browser_item_activated) def connect_projects_browser_signals(self): """Connect projects browser signals Connects the projects browser's activated signal to the slot that gets called when a project browser item is activated. Connects the signal that gets emitted from the main window when a new project gets created. """ self.projects_browser.activated.connect( self.projects_browser_item_activated ) self.main_window.new_project_created_signal.connect( self.handle_new_project_created ) def connect_settings_signals(self): """Connect settings signals Connects the signal that gets fired when project root gets changed. """ self.settings.settings_changed_signal.connect( self.handle_settings_changed ) def connect_document_viewer_signals(self): """Connect document viewer signals Connects the signal that gets fired when a tab widget is being closed. It will call the method that will close the document. """ self.document_viewer.tabCloseRequested.connect(self.close_document) def connect_toolbar_action_signals(self): """Connect toolbar action signals Connect signals that get emitted when the toolbar actions get triggered. Connect signals when the start/stop debug actions are triggered are connected. Connect signals when the run and step continuation commands are triggered are connected. """ self.main_window.start_listening_action.triggered.connect( self.start_listening ) self.main_window.stop_listening_action.triggered.connect( self.stop_listening ) self.main_window.stop_debug_action.triggered.connect(self.stop_debug) self.main_window.detach_debug_action.triggered.connect( self.detach_debug ) self.main_window.run_debug_action.triggered.connect(self.run_debug) self.main_window.step_over_action.triggered.connect(self.step_over) self.main_window.step_into_action.triggered.connect(self.step_into) self.main_window.step_out_action.triggered.connect(self.step_out) def connect_debugger_signals(self): """Connect debugger signals Connecting signals that get emitted when a session starts or stops, when a step command is completed, when variables are read, when stacktraces are read, when a breakpoint gets removed, when breakpoints are read, when one or more expressions are evaluated. Connect signal that gets emitted when an error happens during the debugging session. """ # Start/stop signals self.debugger.server_stopped_signal.connect( self.handle_server_stopped_listening ) self.debugger.debugging_started_signal.connect( self.handle_debugging_started ) self.debugger.debugging_post_start_signal.connect( self.handle_debugging_post_start ) self.debugger.debugging_stopped_signal.connect( self.handle_debugging_stopped ) # Step command signals self.debugger.step_command_signal.connect(self.handle_step_command) # Variables signals self.debugger.got_all_variables_signal.connect( self.handle_got_all_variables ) # Stacktraces signals self.debugger.got_stacktraces_signal.connect( self.handle_got_stacktraces ) # Breakpoints signals self.debugger.breakpoint_removed_signal.connect( self.handle_breakpoint_removed ) self.debugger.breakpoints_listed_signal.connect( self.handle_breakpoints_listed ) # Expression signals self.debugger.expression_evaluated_signal.connect( self.handle_expression_evaluated ) self.debugger.expressions_evaluated_signal.connect( self.handle_expressions_evaluated ) # Error signals self.debugger.error_signal.connect( self.handle_error ) def connect_expression_viewer_signals(self): self.expression_viewer.expression_added_signal.connect( self.handle_expression_added_or_changed ) self.expression_viewer.expression_changed_signal.connect( self.handle_expression_added_or_changed ) def connect_stacktrace_viewer_signals(self): self.stacktrace_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file ) def connect_breakpoint_viewer_signals(self): self.breakpoint_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file ) def handle_new_project_created(self, project_name): """Handle when a new project gets created Reload the projects in the projects browser. Find the project that was just created and load it. """ self.projects_browser.load_projects() project = self.projects_browser.load_project_by_name(project_name) if project is not None: self.load_project(project) def projects_browser_item_activated(self, index): """Handle when a projects browser item gets activated Find the project and load it. """ project = self.projects_browser.model().get_project_by_index(index) self.load_project(project) def load_project(self, project): """Load a project Get the settings for the project and load them as the current application settings. Set the current project name in the window title. Set the current project name setting in application settings. """ project_settings = project.get_settings() project_name = project.get_project_name() set_setting('current_project', project_name) changed_settings = save_settings(project_settings) self.handle_settings_changed(changed_settings) self.main_window.set_window_title(project_name) def file_browser_item_activated(self, index): """Handle when file browser item gets activated Find the path of the activated item and open that document. """ path = self.file_browser.model().get_file_path(index) if path is not None: self.open_document(path, False) def open_document(self, path, map_paths=True): """Open a document If a document is not already open, open it and add it as a new tab. If the document is already open, focus the tab with that document. If the path of the document should be mapped, and the file can't be found after mapping, show an error message and stop the debugging session. """ path = self.__get_path_mapped_to_local(path, map_paths) if not self.documents.is_document_open(path): document_model = self.documents.open_document(path) document_widget = PugdebugDocument( document_model, self.formatter ) # For every new document that gets opened, connect to the double # clicked signal of that document document_widget.document_double_clicked_signal.connect( self.handle_document_double_click ) # Handle when a document gets changed outside of pugdebug self.documents.document_changed.connect( self.handle_document_changed ) # Add the newly opened document to the document viewer's tab stack self.document_viewer.add_tab( document_widget, document_model.filename, path ) else: # Just focus the tab that has the opened document index = self.document_viewer.find_tab_index_by_path(path) self.document_viewer.setCurrentIndex(index) def handle_document_double_click(self, path, line_number): """Handle when a document gets double clicked We get the path of the document that was double clicked and the line number of the line that was double clicked inside that document. If there is no breakpoint set on that line, we set it. If there is a breakpoint set on that line, we remove it. """ path = self.__get_path_mapped_to_remote(path) breakpoint = self.get_breakpoint(path, line_number) if breakpoint is None: breakpoint = {'filename': path, 'lineno': line_number} self.set_breakpoint(breakpoint) else: self.remove_breakpoint(breakpoint) def handle_document_changed(self, document_model): """Handle when a document gets chaned Pass on to the document widget the new document model. Remove stale breakpoints. """ path = document_model.path document_widget = self.document_viewer.get_document_by_path(path) document_widget.handle_document_changed(document_model) self.remove_stale_breakpoints(path) def close_document(self, tab_index): """Close a document Get the document from the tab. Delete the document. Remove the tab. """ document_widget = self.document_viewer.get_document(tab_index) path = document_widget.get_path() self.documents.close_document(path) document_widget.deleteLater() self.document_viewer.close_tab(tab_index) self.remove_stale_breakpoints(path) def focus_current_line(self): """Focus the current line Focus the line where the debugger stopped in the file that is being debugged. """ current_file = self.debugger.get_current_file() current_line = self.debugger.get_current_line() self.jump_to_line_in_file(current_file, current_line, True) def jump_to_line_in_file(self, file, line, is_current=False): """Jump to a line in a file. Show the document, and scroll to the given line. """ self.open_document(file) document_widget = self.document_viewer.get_current_document() document_widget.move_to_line(line, is_current) def handle_settings_changed(self, changed_settings): """Handle when settings have changed. Given argument is a set of settings's names which have been changed. """ if has_setting('current_project'): project_name = get_setting('current_project') project = self.projects_browser.load_project_by_name(project_name) if project is not None: project.set_settings(changed_settings) changed_setting_keys = changed_settings.keys() if 'path/project_root' in changed_setting_keys: self.handle_project_root_changed() features = ['debugger/max_depth', 'debugger/max_children', 'debugger/max_data'] if any(True for feature in features if feature in changed_setting_keys): self.handle_debugger_features_changed() def handle_project_root_changed(self): """Handle when the project root is changed Update the file browser's model to the new root. """ project_root = get_setting('path/project_root') model = self.file_browser.model() model.set_path(project_root) self.file_browser.setModel(model) self.file_browser.setRootIndex(model.start_index) def handle_debugger_features_changed(self): if self.debugger.is_connected(): self.debugger.set_debugger_features() def start_listening(self): """Start listening to new incomming connections Clear the variable viewer. Clear the stacktrace viewer. Remove all line highlights. Start a debugging session. """ break_at_first_line = int(get_setting('debugger/break_at_first_line')) start_debugging = True if break_at_first_line == 0 and len(self.breakpoints) == 0: messageBox = QMessageBox() messageBox.setText("There are no breakpoints set and the break at" " first line setting is turned off.") messageBox.setInformativeText("Are you sure you want to start" " debugging?") messageBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) answer = messageBox.exec_() if answer == QMessageBox.No: start_debugging = False if start_debugging: self.variable_viewer.clear() self.stacktrace_viewer.clear() self.document_viewer.remove_line_highlights() self.debugger.start_listening() self.main_window.set_debugging_status(1) def handle_debugging_started(self): """Handle when debugging starts This handle should be called when a new connection is established with xdebug. When debugging multiple requests, for example ajax requests, this should be called for every request, as every request has its own connection. Sets initial breakpoints, breakpoints that were set before the debugging session started. Open the index file in the document viewer. """ self.main_window.set_debugging_status(3) self.main_window.toggle_actions(True) # Check if path to index file is correct after mapping it index_file = self.debugger.get_index_file() path = self.__get_path_mapped_to_local(index_file) if path is False: self.handle_error( "File does not exist after mapping. " "Is the path map correct?" ) self.stop_debug() return post_start_data = { 'breakpoints': self.breakpoints } self.debugger.post_start_command(post_start_data) def handle_debugging_post_start(self): """Handle post start debugging If the code should not break at first line, run the debugger. """ break_at_first_line = int(get_setting('debugger/break_at_first_line')) if break_at_first_line == 0: self.run_debug() else: self.step_into() def stop_listening(self): self.debugger.stop_listening() def handle_server_stopped_listening(self): self.main_window.set_debugging_status(0) def stop_debug(self): """Stop a debugging session If the debugging session has a currently active connection, it will stop that connection. If there is no active connections, the debugging session will tell the server to stop listening to new connections. """ self.debugger.stop_debug() def handle_debugging_stopped(self): """Handle when debugging stops This handler should be called when the connection to xdebug is terminated. """ self.main_window.toggle_actions(False) self.main_window.set_debugging_status(2) self.expression_viewer.clear_values() def detach_debug(self): """Detach a debugging session. The debugging session will end, but the debuged script will terminate normally. """ self.debugger.detach_debug() def handle_step_command(self): """Handle step command This handler should be called when one of the step commands is executed and the reply message from xdebug is read. If the debugger is in a breaking state, focus the current line in the current file. If the debugger is in a stopping state, stop the debugging session. """ self.main_window.set_debugging_status(3) if self.debugger.is_breaking(): self.focus_current_line() post_step_data = { 'expressions': self.expression_viewer.get_expressions() } self.debugger.post_step_command(post_step_data) elif self.debugger.is_stopped(): self.stop_debug() elif self.debugger.is_stopping(): self.stop_debug() def run_debug(self): """Issue a run continuation command on the debugger This gets called when the "Run" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.run_debug() def step_over(self): """Issue a step over continuation command on the debugger This gets called when the "Step over" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_over() def step_into(self): """Issue a step into continuation command on the debugger This gets called when the "Step into" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_into() def step_out(self): """Issue a step out continuation command on the debugger This gets called when the "Step out" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_out() def handle_got_all_variables(self, variables): """Handle when all variables are retrieved from xdebug Set the variables on the variable viewer. """ self.variable_viewer.set_variables(variables) def handle_got_stacktraces(self, stacktraces): """Handle when stacktraces are retrieved from xdebug Set the stacktraces on the stacktrace viewer. """ self.stacktrace_viewer.set_stacktraces(stacktraces) def set_breakpoint(self, breakpoint): """Set a breakpoint If there is no active debugging session, add the breakpoint data to the breakpoints, highlight the breakpoints on the line numbers of the documents, and show them in the breakpoint viewer. If there is an active debugging session, tell the debugger to set the breakpoint. """ if not self.debugger.is_connected(): self.breakpoints.append(breakpoint) path = breakpoint['filename'] path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return self.debugger.set_breakpoint(breakpoint) def remove_breakpoint(self, breakpoint): """Remove a breakpoint If there is no active debugging session, just remove the breakpoint from the breakpoints, rehighlight the line numbers for breakpoint markers and update the breakpoint viewer. If there is an active debugging session, tell the debugger to remove the breakpoint. """ if not self.debugger.is_connected(): path = breakpoint['filename'] line_number = breakpoint['lineno'] for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and breakpoint['lineno'] == line_number): self.breakpoints.remove(breakpoint) path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return if 'id' in breakpoint: breakpoint_id = int(breakpoint['id']) self.debugger.remove_breakpoint(breakpoint_id) def remove_stale_breakpoints(self, path): """Remove stale breakpoints for a file Breakpoints get stale when a file gets closed. Breakpoints get stale when a file gets changed outside of the application. """ remote_path = self.__get_path_mapped_to_remote(path) breakpoints = list(filter( lambda breakpoint: breakpoint['filename'] != remote_path, self.breakpoints )) self.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) def handle_breakpoint_removed(self, breakpoint_id): """Handle when a breakpoint gets removed This slot is called when a breakpoint is removed through the debugger. It relists the breakpoints and rehighlights the breakpoint markers on the line numbers. """ path = None line_number = None for breakpoint in self.breakpoints: if int(breakpoint['id']) == breakpoint_id: path = breakpoint['filename'] line_number = breakpoint['lineno'] self.debugger.list_breakpoints() if path is not None and line_number is not None: path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() def get_breakpoint(self, path, line_number): """Get a breakpoint by it's path and line number If the breakpoint can be found in the breakpoints from a debugging session, return that breakpoint. If the breakpoint can be found in the init breakpoints, return that. Finally return None. """ for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and int(breakpoint['lineno']) == line_number): return breakpoint return None def handle_breakpoints_listed(self, breakpoints): """Handle when debugger lists breakpoints Show the breakpoints in the breakpoint viewer and rehighlight the breakpoint markers. """ self.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) for breakpoint in breakpoints: path = self.__get_path_mapped_to_local(breakpoint['filename']) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() def handle_expression_evaluated(self, index, result): """Handle when an expression is evaluated""" self.expression_viewer.set_evaluated(index, result) def handle_expressions_evaluated(self, results): """Handle when a list of expressions is evaluated""" for index, result in enumerate(results): self.expression_viewer.set_evaluated(index, result) def handle_expression_added_or_changed(self, index, expression): """Handle when an expression is added, or an existing one is changed. """ if self.debugger.is_connected(): self.debugger.evaluate_expression(index, expression) def handle_error(self, error): """Handle when an error occurs Show the error in an error message window. """ em = QErrorMessage(self.main_window) em.showMessage(error) def __get_path_mapped_to_local(self, path, map_paths=True): """Get a path mapped to local Turns a path like /var/www into /home/user/local/path """ path_map = get_setting('path/path_mapping') if (len(path_map) > 0 and map_paths is True and path.find(path_map) == 0): path_map = path_map.rstrip('/') path = path[len(path_map):] path = "%s%s" % (self.file_browser.model().rootPath(), path) if not os.path.isfile(path): return False return path def __get_path_mapped_to_remote(self, path): """Get a path mapped to remote Turns a path like /home/user/local/path to /var/www """ path_map = get_setting('path/path_mapping') root_path = self.file_browser.model().rootPath() if len(path_map) > 0 and path.find(root_path) == 0: path_map = path_map.rstrip('/') root_path = root_path.rstrip('/') path = path[len(root_path):] path = "%s%s" % (path_map, path) return path def run(self): """Run the application! """ self.main_window.show()
class Pugdebug(QObject): breakpoints = [] def __init__(self): """Initialize the application Initializes all the different parts of the application. Creates the PugdebugDebugger object, sets up the application UI, connects signals to slots. """ super(Pugdebug, self).__init__() self.debugger = PugdebugDebugger() # UI elements self.main_window = PugdebugMainWindow() self.file_browser = self.main_window.get_file_browser() self.projects_browser = self.main_window.get_projects_browser() self.document_viewer = self.main_window.get_document_viewer() self.variable_viewer = self.main_window.get_variable_viewer() self.stacktrace_viewer = self.main_window.get_stacktrace_viewer() self.breakpoint_viewer = self.main_window.get_breakpoint_viewer() self.expression_viewer = self.main_window.get_expression_viewer() self.documents = PugdebugDocuments() self.connect_signals() signal.signal(signal.SIGINT, signal.SIG_DFL) if settings.get_last_files('project/' + projects.active() + '/recent_files'): for item in settings.get_last_files('project/' + projects.active() + '/recent_files'): self.open_document(item, False) def connect_signals(self): """Connect all signals to their slots Connect settings signals, document viewer signals toolbar action signals, debugger signals. """ file_browser.file_activated().connect(self.open_local_document) self.connect_search_files_signals() self.connect_document_viewer_signals() self.connect_documents_signals() self.connect_toolbar_action_signals() self.connect_debugger_signals() self.connect_expression_viewer_signals() self.connect_stacktrace_viewer_signals() self.connect_breakpoint_viewer_signals() def connect_search_files_signals(self): """Connect search for files signals Connects the signal that is emited when a file search result is selected from the search for files dialog. """ self.main_window.search_file_selected_signal.connect( self.open_document) def connect_document_viewer_signals(self): """Connect document viewer signals Connects the signal that gets fired when a tab widget is being closed. It will call the method that will close the document. """ self.document_viewer.tabCloseRequested.connect(self.close_document) def connect_documents_signals(self): """Connect documents signals Connects the signals that get fired when a document model signal happens. Either when a document gets changed, or when a document gets removed. """ # Handle when a document gets changed outside of pugdebug self.documents.document_changed.connect(self.handle_document_changed) # Handle when a document gets removed outside of pugdebug self.documents.document_removed.connect(self.handle_document_removed) def connect_toolbar_action_signals(self): """Connect toolbar action signals Connect signals that get emitted when the toolbar actions get triggered. Connect signals when the start/stop debug actions are triggered are connected. Connect signals when the run and step continuation commands are triggered are connected. """ self.main_window.start_listening_action.triggered.connect( self.start_listening) self.main_window.stop_listening_action.triggered.connect( self.stop_listening) self.main_window.stop_debug_action.triggered.connect(self.stop_debug) self.main_window.detach_debug_action.triggered.connect( self.detach_debug) self.main_window.run_debug_action.triggered.connect(self.run_debug) self.main_window.step_over_action.triggered.connect(self.step_over) self.main_window.step_into_action.triggered.connect(self.step_into) self.main_window.step_out_action.triggered.connect(self.step_out) def connect_debugger_signals(self): """Connect debugger signals Connecting signals that get emitted when a session starts or stops, when a step command is completed, when variables are read, when stacktraces are read, when a breakpoint gets removed, when breakpoints are read, when one or more expressions are evaluated. Connect signal that gets emitted when an error happens during the debugging session. """ # Start/stop signals self.debugger.server_stopped_signal.connect( self.handle_server_stopped_listening) self.debugger.debugging_started_signal.connect( self.handle_debugging_started) self.debugger.debugging_post_start_signal.connect( self.handle_debugging_post_start) self.debugger.debugging_stopped_signal.connect( self.handle_debugging_stopped) # Step command signals self.debugger.step_command_signal.connect(self.handle_step_command) # Variables signals self.debugger.got_all_variables_signal.connect( self.handle_got_all_variables) # Stacktraces signals self.debugger.got_stacktraces_signal.connect( self.handle_got_stacktraces) # Breakpoints signals self.debugger.breakpoint_removed_signal.connect( self.handle_breakpoint_removed) self.debugger.breakpoints_listed_signal.connect( self.handle_breakpoints_listed) # Expression signals self.debugger.expression_evaluated_signal.connect( self.handle_expression_evaluated) self.debugger.expressions_evaluated_signal.connect( self.handle_expressions_evaluated) # Error signals self.debugger.error_signal.connect(self.handle_error) def connect_expression_viewer_signals(self): self.expression_viewer.expression_added_signal.connect( self.handle_expression_added_or_changed) self.expression_viewer.expression_changed_signal.connect( self.handle_expression_added_or_changed) def connect_stacktrace_viewer_signals(self): self.stacktrace_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file) def connect_breakpoint_viewer_signals(self): self.breakpoint_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file) def open_local_document(self, path): settings.set_last_file( 'project/' + projects.active() + '/recent_files', path) return self.open_document(path, False) def open_document(self, path, map_paths=True): """Open a document If a document is not already open, open it and add it as a new tab. If the document is already open, focus the tab with that document. If the path of the document should be mapped, and the file can't be found after mapping, show an error message and stop the debugging session. """ path = self.__get_path_mapped_to_local(path, map_paths) logging.debug("Opening document for path %s" % path) if not self.documents.is_document_open(path): logging.debug("Opening new document") document_model = self.documents.open_document(path) document_widget = PugdebugDocument(document_model) # For every new document that gets opened, connect to the double # clicked signal of that document document_widget.document_double_clicked_signal.connect( self.handle_document_double_click) # Add the newly opened document to the document viewer's tab stack self.document_viewer.add_tab(document_widget, document_model.filename, path) else: logging.debug("Focusing opened document") # Just focus the tab that has the opened document index = self.document_viewer.find_tab_index_by_path(path) self.document_viewer.setCurrentIndex(index) def handle_document_double_click(self, path, line_number): """Handle when a document gets double clicked We get the path of the document that was double clicked and the line number of the line that was double clicked inside that document. If there is no breakpoint set on that line, we set it. If there is a breakpoint set on that line, we remove it. """ local_path = path path = self.__get_path_mapped_to_remote(path) logging.debug("Getting a breakpoint on %s:%s" % (path, line_number)) breakpoint = self.get_breakpoint(path, line_number) if breakpoint is None: logging.debug("Setting breakpoint") breakpoint = { 'filename': path, 'local_filename': local_path, 'lineno': line_number } self.set_breakpoint(breakpoint) else: logging.debug("Removing breakpoint") self.remove_breakpoint(breakpoint) def handle_document_changed(self, document_model): """Handle when a document gets chaned Pass on to the document widget the new document model. Remove stale breakpoints. """ path = document_model.path logging.debug("Document changed: %s" % path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.handle_document_changed(document_model) self.remove_stale_breakpoints(path) def handle_document_removed(self, document_model): """Handle when a document gets removed outside of pugdebug """ path = document_model.path logging.debug("Document removed: %s" % path) tab_index = self.document_viewer.find_tab_index_by_path(path) self.close_document(tab_index) def close_document(self, tab_index): """Close a document Get the document from the tab. Delete the document. Remove the tab. """ document_widget = self.document_viewer.get_document(tab_index) path = document_widget.get_path() logging.debug("Closing document: %s" % path) self.documents.close_document(path) document_widget.deleteLater() self.document_viewer.close_tab(tab_index) self.remove_stale_breakpoints(path) settings.remove_last_file( 'project/' + projects.active() + '/recent_files', path) def focus_current_line(self): """Focus the current line Focus the line where the debugger stopped in the file that is being debugged. """ current_file = self.debugger.get_current_file() current_line = self.debugger.get_current_line() logging.debug("Focusing current line: %s:%s" % (current_file, current_line)) self.jump_to_line_in_file(current_file, current_line, True) def jump_to_line_in_file(self, file, line, is_current=False): """Jump to a line in a file. Show the document, and scroll to the given line. """ self.open_document(file) current = 'current ' if is_current else '' logging.debug("Jumping to %sline in file: %s:%s" % (current, file, line)) document_widget = self.document_viewer.get_current_document() document_widget.move_to_line(line, is_current) def start_listening(self): """Start listening to new incomming connections Clear the variable viewer. Clear the stacktrace viewer. Remove all line highlights. Start a debugging session. """ logging.debug("Start listening") break_at_first_line = settings.value('project/' + projects.active() + '/debugger/break_at_first_line') logging.debug("Break at first line: %s" % ('Yes' if break_at_first_line else 'No')) start_debugging = True if not break_at_first_line and len(self.breakpoints) == 0: messageBox = QMessageBox() messageBox.setText("There are no breakpoints set and the break at" " first line setting is turned off.") messageBox.setInformativeText("Are you sure you want to start" " debugging?") messageBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) answer = messageBox.exec_() if answer == QMessageBox.No: start_debugging = False logging.debug("Don't start debugging, no breakpoints") if start_debugging: self.variable_viewer.clear() self.stacktrace_viewer.clear() self.document_viewer.remove_line_highlights() self.debugger.start_listening() self.main_window.set_debugging_status(1) def handle_debugging_started(self): """Handle when debugging starts This handle should be called when a new connection is established with xdebug. When debugging multiple requests, for example ajax requests, this should be called for every request, as every request has its own connection. Sets initial breakpoints, breakpoints that were set before the debugging session started. Open the index file in the document viewer. """ logging.debug("Debugging started") self.main_window.set_debugging_status(3) self.main_window.toggle_actions(True) # Check if path to index file is correct after mapping it index_file = self.debugger.get_index_file() path = self.__get_path_mapped_to_local(index_file) logging.debug("Index file: %s" % index_file) logging.debug("Mapped path: %s" % path) if path is False: logging.debug("Index file not mapped") self.handle_error("File does not exist after mapping. " "Is the path map correct?") self.stop_debug() return post_start_data = {'breakpoints': self.breakpoints} self.debugger.post_start_command(post_start_data) def handle_debugging_post_start(self): """Handle post start debugging If the code should not break at first line, run the debugger. """ logging.debug("Post start") break_at_first_line = settings.value('project/' + projects.active() + '/debugger/break_at_first_line') logging.debug("Break at first line: %s" % ('Yes' if break_at_first_line else 'No')) if not break_at_first_line: self.run_debug() else: self.step_into() def stop_listening(self): """Stop listening to connections """ logging.debug("Stop listening") self.debugger.stop_listening() def handle_server_stopped_listening(self): """Handle server stopped listening """ logging.debug("Server stopped listening") self.main_window.set_debugging_status(0) def stop_debug(self): """Stop a debugging session If the debugging session has a currently active connection, it will stop that connection. If there is no active connections, the debugging session will tell the server to stop listening to new connections. """ logging.debug("Stop debugging") self.debugger.stop_debug() def handle_debugging_stopped(self): """Handle when debugging stops This handler should be called when the connection to xdebug is terminated. """ logging.debug("Debugging stopped") self.main_window.toggle_actions(False) self.main_window.set_debugging_status(2) self.expression_viewer.clear_values() def detach_debug(self): """Detach a debugging session. The debugging session will end, but the debuged script will terminate normally. """ logging.debug("Detach debugger") self.debugger.detach_debug() def handle_step_command(self): """Handle step command This handler should be called when one of the step commands is executed and the reply message from xdebug is read. If the debugger is in a breaking state, focus the current line in the current file. If the debugger is in a stopping state, stop the debugging session. """ logging.debug("Step command") self.main_window.set_debugging_status(3) if self.debugger.is_breaking(): logging.debug("Debugger is breaking") self.focus_current_line() post_step_data = { 'expressions': self.expression_viewer.get_expressions() } self.debugger.post_step_command(post_step_data) elif self.debugger.is_stopped(): logging.debug("Debugger is stopped") self.stop_debug() elif self.debugger.is_stopping(): logging.debug("Debugger is stopping") self.stop_debug() def run_debug(self): """Issue a run continuation command on the debugger This gets called when the "Run" action button is pressed. """ logging.debug("Run command") self.main_window.set_debugging_status(4) self.debugger.run_debug() def step_over(self): """Issue a step over continuation command on the debugger This gets called when the "Step over" action button is pressed. """ logging.debug("Step over command") self.main_window.set_debugging_status(4) self.debugger.step_over() def step_into(self): """Issue a step into continuation command on the debugger This gets called when the "Step into" action button is pressed. """ logging.debug("Step into command") self.main_window.set_debugging_status(4) self.debugger.step_into() def step_out(self): """Issue a step out continuation command on the debugger This gets called when the "Step out" action button is pressed. """ logging.debug("Step out command") self.main_window.set_debugging_status(4) self.debugger.step_out() def handle_got_all_variables(self, variables): """Handle when all variables are retrieved from xdebug Set the variables on the variable viewer. """ logging.debug("Setting variables received from debugger") self.variable_viewer.set_variables(variables) def handle_got_stacktraces(self, stacktraces): """Handle when stacktraces are retrieved from xdebug Set the stacktraces on the stacktrace viewer. """ logging.debug("Setting stacktraces received from debugger") self.stacktrace_viewer.set_stacktraces(stacktraces) def set_breakpoint(self, breakpoint): """Set a breakpoint If there is no active debugging session, add the breakpoint data to the breakpoints, highlight the breakpoints on the line numbers of the documents, and show them in the breakpoint viewer. If there is an active debugging session, tell the debugger to set the breakpoint. """ logging.debug("Set a breakpoint") if not self.debugger.is_connected(): logging.debug("Debugger is not connected, appending breakpoint") self.breakpoints.append(breakpoint) document_widget = self.document_viewer.get_document_by_path( breakpoint['local_filename']) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return logging.debug("Setting a breakpoint on the debugger") self.debugger.set_breakpoint(breakpoint) def remove_breakpoint(self, breakpoint): """Remove a breakpoint If there is no active debugging session, just remove the breakpoint from the breakpoints, rehighlight the line numbers for breakpoint markers and update the breakpoint viewer. If there is an active debugging session, tell the debugger to remove the breakpoint. """ logging.debug("Remove a breakpoint") if not self.debugger.is_connected(): logging.debug("Debugger is not connected, removing breakpoint") path = breakpoint['filename'] local_path = breakpoint['local_filename'] line_number = breakpoint['lineno'] for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and breakpoint['lineno'] == line_number): self.breakpoints.remove(breakpoint) document_widget = self.document_viewer.get_document_by_path( local_path) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return if 'id' in breakpoint: breakpoint_id = int(breakpoint['id']) logging.debug("Removing breakpoint: %s" % breakpoint_id) self.debugger.remove_breakpoint(breakpoint_id) def remove_stale_breakpoints(self, path): """Remove stale breakpoints for a file Breakpoints get stale when a file gets closed. Breakpoints get stale when a file gets changed outside of the application. """ remote_path = self.__get_path_mapped_to_remote(path) logging.debug("Removing stale breakpoints: %s" % remote_path) breakpoints = list( filter(lambda breakpoint: breakpoint['filename'] != remote_path, self.breakpoints)) Pugdebug.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) def handle_breakpoint_removed(self, breakpoint_id): """Handle when a breakpoint gets removed This slot is called when a breakpoint is removed through the debugger. It relists the breakpoints and rehighlights the breakpoint markers on the line numbers. """ path = None line_number = None logging.debug("Breakpoint removed") for breakpoint in self.breakpoints: if int(breakpoint['id']) == breakpoint_id: logging.debug("Found removed breakpoint: %s" % breakpoint_id) path = breakpoint['filename'] line_number = breakpoint['lineno'] self.debugger.list_breakpoints() logging.debug("%s:%s" % (path, line_number)) if path is not None and line_number is not None: path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() def get_breakpoint(self, path, line_number): """Get a breakpoint by it's path and line number If the breakpoint can be found in the breakpoints from a debugging session, return that breakpoint. If the breakpoint can be found in the init breakpoints, return that. Finally return None. """ for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and int(breakpoint['lineno']) == line_number): return breakpoint return None def handle_breakpoints_listed(self, breakpoints): """Handle when debugger lists breakpoints Show the breakpoints in the breakpoint viewer and rehighlight the breakpoint markers. """ logging.debug("Breakpoints listed") for breakpoint in breakpoints: breakpoint['local_filename'] = self.__get_path_mapped_to_local( breakpoint['filename']) Pugdebug.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) for breakpoint in breakpoints: document_widget = self.document_viewer.get_document_by_path( breakpoint['local_filename']) if document_widget is not None: document_widget.rehighlight_breakpoint_lines() def handle_expression_evaluated(self, index, result): """Handle when an expression is evaluated""" logging.debug("Expression evaluated") self.expression_viewer.set_evaluated(index, result) def handle_expressions_evaluated(self, results): """Handle when a list of expressions is evaluated""" logging.debug("Expressions evaluated") for index, result in enumerate(results): self.expression_viewer.set_evaluated(index, result) def handle_expression_added_or_changed(self, index, expression): """Handle when an expression is added, or an existing one is changed. """ logging.debug("Expression added or modified") if self.debugger.is_connected(): self.debugger.evaluate_expression(index, expression) def handle_error(self, error): """Handle when an error occurs Show the error in an error message window. """ em = QErrorMessage(self.main_window) em.showMessage(error) def __get_path_mapped_to_local(self, path, map_paths=True): """Get a path mapped to local Turns a path like /var/www into /home/user/local/path """ with settings.open_group('project/' + projects.active()): root_path = settings.value('path/project_root') path_map = settings.value('path/path_mapping') if (len(path_map) > 0 and map_paths is True and path.find(path_map) == 0): path_map = path_map.rstrip('/') path = path[len(path_map):] path = "%s%s" % (root_path, path) if not os.path.isfile(path): return False return path def __get_path_mapped_to_remote(self, path): """Get a path mapped to remote Turns a path like /home/user/local/path to /var/www """ with settings.open_group('project/' + projects.active()): root_path = settings.value('path/project_root') path_map = settings.value('path/path_mapping') if len(path_map) > 0 and path.find(root_path) == 0: path_map = path_map.rstrip('/') root_path = root_path.rstrip('/') path = path[len(root_path):] path = "%s%s" % (path_map, path) return path def run(self): """Run the application! """ self.main_window.show()
class Pugdebug(QObject): breakpoints = [] def __init__(self): """Initialize the application Initializes all the different parts of the application. Creates the PugdebugDebugger object, sets up the application UI, connects signals to slots. """ super(Pugdebug, self).__init__() self.debugger = PugdebugDebugger() self.formatter = PugdebugFormatter() # UI elements self.main_window = PugdebugMainWindow() self.file_browser = self.main_window.get_file_browser() self.projects_browser = self.main_window.get_projects_browser() self.settings = self.main_window.get_settings() self.document_viewer = self.main_window.get_document_viewer() self.variable_viewer = self.main_window.get_variable_viewer() self.stacktrace_viewer = self.main_window.get_stacktrace_viewer() self.breakpoint_viewer = self.main_window.get_breakpoint_viewer() self.expression_viewer = self.main_window.get_expression_viewer() self.documents = PugdebugDocuments() self.setup_file_browser() self.setup_projects_browser() self.connect_signals() def setup_file_browser(self): """Setup the file browser Sets the model on the file browser and hides the not needed columns. """ project_root = get_setting('path/project_root') model = PugdebugFileBrowser(self) model.set_path(project_root) self.file_browser.setModel(model) self.file_browser.setRootIndex(model.start_index) self.file_browser.hide_columns() def setup_projects_browser(self): """Setup the projects browser Sets the model on the projects browser. """ model = PugdebugProjects(self) self.projects_browser.setModel(model) def connect_signals(self): """Connect all signals to their slots Connect file browser signals, settings signals, document viewer signals toolbar action signals, debugger signals. """ self.connect_file_browser_signals() self.connect_projects_browser_signals() self.connect_settings_signals() self.connect_document_viewer_signals() self.connect_toolbar_action_signals() self.connect_debugger_signals() self.connect_expression_viewer_signals() self.connect_stacktrace_viewer_signals() self.connect_breakpoint_viewer_signals() def connect_file_browser_signals(self): """Connect file browser signals Connects the file browser's activated signal to the slot that gets called when a file browser item is activated. """ self.file_browser.activated.connect(self.file_browser_item_activated) def connect_projects_browser_signals(self): """Connect projects browser signals Connects the projects browser's activated signal to the slot that gets called when a project browser item is activated. Connects the signal that gets emitted from the main window when a new project gets created. """ self.projects_browser.activated.connect( self.projects_browser_item_activated ) self.main_window.new_project_created_signal.connect( self.handle_new_project_created ) def connect_settings_signals(self): """Connect settings signals Connects the signal that gets fired when project root gets changed. """ self.settings.settings_changed_signal.connect( self.handle_settings_changed ) def connect_document_viewer_signals(self): """Connect document viewer signals Connects the signal that gets fired when a tab widget is being closed. It will call the method that will close the document. """ self.document_viewer.tabCloseRequested.connect(self.close_document) def connect_toolbar_action_signals(self): """Connect toolbar action signals Connect signals that get emitted when the toolbar actions get triggered. Connect signals when the start/stop debug actions are triggered are connected. Connect signals when the run and step continuation commands are triggered are connected. """ self.main_window.start_listening_action.triggered.connect( self.start_listening ) self.main_window.stop_listening_action.triggered.connect( self.stop_listening ) self.main_window.stop_debug_action.triggered.connect(self.stop_debug) self.main_window.detach_debug_action.triggered.connect( self.detach_debug ) self.main_window.run_debug_action.triggered.connect(self.run_debug) self.main_window.step_over_action.triggered.connect(self.step_over) self.main_window.step_into_action.triggered.connect(self.step_into) self.main_window.step_out_action.triggered.connect(self.step_out) def connect_debugger_signals(self): """Connect debugger signals Connecting signals that get emitted when a session starts or stops, when a step command is completed, when variables are read, when stacktraces are read, when a breakpoint gets removed, when breakpoints are read, when one or more expressions are evaluated. Connect signal that gets emitted when an error happens during the debugging session. """ # Start/stop signals self.debugger.server_stopped_signal.connect( self.handle_server_stopped_listening ) self.debugger.debugging_started_signal.connect( self.handle_debugging_started ) self.debugger.debugging_post_start_signal.connect( self.handle_debugging_post_start ) self.debugger.debugging_stopped_signal.connect( self.handle_debugging_stopped ) # Step command signals self.debugger.step_command_signal.connect(self.handle_step_command) # Variables signals self.debugger.got_all_variables_signal.connect( self.handle_got_all_variables ) # Stacktraces signals self.debugger.got_stacktraces_signal.connect( self.handle_got_stacktraces ) # Breakpoints signals self.debugger.breakpoint_removed_signal.connect( self.handle_breakpoint_removed ) self.debugger.breakpoints_listed_signal.connect( self.handle_breakpoints_listed ) # Expression signals self.debugger.expression_evaluated_signal.connect( self.handle_expression_evaluated ) self.debugger.expressions_evaluated_signal.connect( self.handle_expressions_evaluated ) # Error signals self.debugger.error_signal.connect( self.handle_error ) def connect_expression_viewer_signals(self): self.expression_viewer.expression_added_signal.connect( self.handle_expression_added_or_changed ) self.expression_viewer.expression_changed_signal.connect( self.handle_expression_added_or_changed ) def connect_stacktrace_viewer_signals(self): self.stacktrace_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file ) def connect_breakpoint_viewer_signals(self): self.breakpoint_viewer.item_double_clicked_signal.connect( self.jump_to_line_in_file ) def handle_new_project_created(self, project_name): """Handle when a new project gets created Reload the projects in the projects browser. Find the project that was just created and load it. """ self.projects_browser.load_projects() project = self.projects_browser.load_project_by_name(project_name) if project is not None: self.load_project(project) def projects_browser_item_activated(self, index): """Handle when a projects browser item gets activated Find the project and load it. """ project = self.projects_browser.model().get_project_by_index(index) self.load_project(project) def load_project(self, project): """Load a project Get the settings for the project and load them as the current application settings. Set the current project name in the window title. Set the current project name setting in application settings. """ project_settings = project.get_settings() project_name = project.get_project_name() set_setting('current_project', project_name) changed_settings = save_settings(project_settings) self.handle_settings_changed(changed_settings) self.main_window.set_window_title(project_name) def file_browser_item_activated(self, index): """Handle when file browser item gets activated Find the path of the activated item and open that document. """ path = self.file_browser.model().get_file_path(index) if path is not None: self.open_document(path, False) def open_document(self, path, map_paths=True): """Open a document If a document is not already open, open it and add it as a new tab. If the document is already open, focus the tab with that document. If the path of the document should be mapped, and the file can't be found after mapping, show an error message and stop the debugging session. """ path = self.__get_path_mapped_to_local(path, map_paths) if not self.documents.is_document_open(path): document_model = self.documents.open_document(path) document_widget = PugdebugDocument( document_model, self.formatter ) # For every new document that gets opened, connect to the double # clicked signal of that document document_widget.document_double_clicked_signal.connect( self.handle_document_double_click ) # Handle when a document gets changed outside of pugdebug self.documents.document_changed.connect( self.handle_document_changed ) # Add the newly opened document to the document viewer's tab stack self.document_viewer.add_tab( document_widget, document_model.filename, path ) else: # Just focus the tab that has the opened document index = self.document_viewer.find_tab_index_by_path(path) self.document_viewer.setCurrentIndex(index) def handle_document_double_click(self, path, line_number): """Handle when a document gets double clicked We get the path of the document that was double clicked and the line number of the line that was double clicked inside that document. If there is no breakpoint set on that line, we set it. If there is a breakpoint set on that line, we remove it. """ path = self.__get_path_mapped_to_remote(path) breakpoint = self.get_breakpoint(path, line_number) if breakpoint is None: breakpoint = {'filename': path, 'lineno': line_number} self.set_breakpoint(breakpoint) else: self.remove_breakpoint(breakpoint) def handle_document_changed(self, document_model): """Handle when a document gets chaned Pass on to the document widget the new document model. Remove stale breakpoints. """ path = document_model.path document_widget = self.document_viewer.get_document_by_path(path) document_widget.handle_document_changed(document_model) self.remove_stale_breakpoints(path) def close_document(self, tab_index): """Close a document Get the document from the tab. Delete the document. Remove the tab. """ document_widget = self.document_viewer.get_document(tab_index) path = document_widget.get_path() self.documents.close_document(path) document_widget.deleteLater() self.document_viewer.close_tab(tab_index) self.remove_stale_breakpoints(path) def focus_current_line(self): """Focus the current line Focus the line where the debugger stopped in the file that is being debugged. """ current_file = self.debugger.get_current_file() current_line = self.debugger.get_current_line() self.jump_to_line_in_file(current_file, current_line, True) def jump_to_line_in_file(self, file, line, is_current=False): """Jump to a line in a file. Show the document, and scroll to the given line. """ self.open_document(file) document_widget = self.document_viewer.get_current_document() document_widget.move_to_line(line, is_current) def handle_settings_changed(self, changed_settings): """Handle when settings have changed. Given argument is a set of settings's names which have been changed. """ if has_setting('current_project'): project_name = get_setting('current_project') project = self.projects_browser.load_project_by_name(project_name) if project is not None: project.set_settings(changed_settings) changed_setting_keys = changed_settings.keys() if 'path/project_root' in changed_setting_keys: self.handle_project_root_changed() features = ['debugger/max_depth', 'debugger/max_children', 'debugger/max_data'] if any(True for feature in features if feature in changed_setting_keys): self.handle_debugger_features_changed() def handle_project_root_changed(self): """Handle when the project root is changed Update the file browser's model to the new root. """ project_root = get_setting('path/project_root') model = self.file_browser.model() model.set_path(project_root) self.file_browser.setModel(model) self.file_browser.setRootIndex(model.start_index) def handle_debugger_features_changed(self): if self.debugger.is_connected(): self.debugger.set_debugger_features() def start_listening(self): """Start listening to new incomming connections Clear the variable viewer. Clear the stacktrace viewer. Remove all line highlights. Start a debugging session. """ break_at_first_line = int(get_setting('debugger/break_at_first_line')) start_debugging = True if break_at_first_line == 0 and len(self.breakpoints) == 0: messageBox = QMessageBox() messageBox.setText("There are no breakpoints set and the break at" " first line setting is turned off.") messageBox.setInformativeText("Are you sure you want to start" " debugging?") messageBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) answer = messageBox.exec_() if answer == QMessageBox.No: start_debugging = False if start_debugging: self.variable_viewer.clear() self.stacktrace_viewer.clear() self.document_viewer.remove_line_highlights() self.debugger.start_listening() self.main_window.set_debugging_status(1) def handle_debugging_started(self): """Handle when debugging starts This handle should be called when a new connection is established with xdebug. When debugging multiple requests, for example ajax requests, this should be called for every request, as every request has its own connection. Sets initial breakpoints, breakpoints that were set before the debugging session started. Open the index file in the document viewer. """ self.main_window.set_debugging_status(3) self.main_window.toggle_actions(True) # Check if path to index file is correct after mapping it index_file = self.debugger.get_index_file() path = self.__get_path_mapped_to_local(index_file) if path is False: self.handle_error( "File does not exist after mapping. " "Is the path map correct?" ) self.stop_debug() return post_start_data = { 'breakpoints': self.breakpoints } self.debugger.post_start_command(post_start_data) def handle_debugging_post_start(self): """Handle post start debugging If the code should not break at first line, run the debugger. """ break_at_first_line = int(get_setting('debugger/break_at_first_line')) if break_at_first_line == 0: self.run_debug() else: self.step_into() def stop_listening(self): self.debugger.stop_listening() def handle_server_stopped_listening(self): self.main_window.set_debugging_status(0) def stop_debug(self): """Stop a debugging session If the debugging session has a currently active connection, it will stop that connection. If there is no active connections, the debugging session will tell the server to stop listening to new connections. """ self.debugger.stop_debug() def handle_debugging_stopped(self): """Handle when debugging stops This handler should be called when the connection to xdebug is terminated. """ self.main_window.toggle_actions(False) self.main_window.set_debugging_status(2) self.expression_viewer.clear_values() def detach_debug(self): """Detach a debugging session. The debugging session will end, but the debuged script will terminate normally. """ self.debugger.detach_debug() def handle_step_command(self): """Handle step command This handler should be called when one of the step commands is executed and the reply message from xdebug is read. If the debugger is in a breaking state, focus the current line in the current file. If the debugger is in a stopping state, stop the debugging session. """ self.main_window.set_debugging_status(3) if self.debugger.is_breaking(): self.focus_current_line() post_step_data = { 'expressions': self.expression_viewer.get_expressions() } self.debugger.post_step_command(post_step_data) elif self.debugger.is_stopped(): self.stop_debug() elif self.debugger.is_stopping(): self.stop_debug() def run_debug(self): """Issue a run continuation command on the debugger This gets called when the "Run" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.run_debug() def step_over(self): """Issue a step over continuation command on the debugger This gets called when the "Step over" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_over() def step_into(self): """Issue a step into continuation command on the debugger This gets called when the "Step into" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_into() def step_out(self): """Issue a step out continuation command on the debugger This gets called when the "Step out" action button is pressed. """ self.main_window.set_debugging_status(4) self.debugger.step_out() def handle_got_all_variables(self, variables): """Handle when all variables are retrieved from xdebug Set the variables on the variable viewer. """ self.variable_viewer.set_variables(variables) def handle_got_stacktraces(self, stacktraces): """Handle when stacktraces are retrieved from xdebug Set the stacktraces on the stacktrace viewer. """ self.stacktrace_viewer.set_stacktraces(stacktraces) def set_breakpoint(self, breakpoint): """Set a breakpoint If there is no active debugging session, add the breakpoint data to the breakpoints, highlight the breakpoints on the line numbers of the documents, and show them in the breakpoint viewer. If there is an active debugging session, tell the debugger to set the breakpoint. """ if not self.debugger.is_connected(): self.breakpoints.append(breakpoint) path = breakpoint['filename'] path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return self.debugger.set_breakpoint(breakpoint) def remove_breakpoint(self, breakpoint): """Remove a breakpoint If there is no active debugging session, just remove the breakpoint from the breakpoints, rehighlight the line numbers for breakpoint markers and update the breakpoint viewer. If there is an active debugging session, tell the debugger to remove the breakpoint. """ if not self.debugger.is_connected(): path = breakpoint['filename'] line_number = breakpoint['lineno'] for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and breakpoint['lineno'] == line_number): self.breakpoints.remove(breakpoint) path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() self.breakpoint_viewer.set_breakpoints(self.breakpoints) return if 'id' in breakpoint: breakpoint_id = int(breakpoint['id']) self.debugger.remove_breakpoint(breakpoint_id) def remove_stale_breakpoints(self, path): """Remove stale breakpoints for a file Breakpoints get stale when a file gets closed. Breakpoints get stale when a file gets changed outside of the application. """ remote_path = self.__get_path_mapped_to_remote(path) breakpoints = list(filter( lambda breakpoint: breakpoint['filename'] != remote_path, self.breakpoints )) self.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) def handle_breakpoint_removed(self, breakpoint_id): """Handle when a breakpoint gets removed This slot is called when a breakpoint is removed through the debugger. It relists the breakpoints and rehighlights the breakpoint markers on the line numbers. """ path = None line_number = None for breakpoint in self.breakpoints: if int(breakpoint['id']) == breakpoint_id: path = breakpoint['filename'] line_number = breakpoint['lineno'] self.debugger.list_breakpoints() if path is not None and line_number is not None: path = self.__get_path_mapped_to_local(path) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() def get_breakpoint(self, path, line_number): """Get a breakpoint by it's path and line number If the breakpoint can be found in the breakpoints from a debugging session, return that breakpoint. If the breakpoint can be found in the init breakpoints, return that. Finally return None. """ for breakpoint in self.breakpoints: if (breakpoint['filename'] == path and int(breakpoint['lineno']) == line_number): return breakpoint return None def handle_breakpoints_listed(self, breakpoints): """Handle when debugger lists breakpoints Show the breakpoints in the breakpoint viewer and rehighlight the breakpoint markers. """ self.breakpoints = breakpoints self.breakpoint_viewer.set_breakpoints(breakpoints) for breakpoint in breakpoints: path = self.__get_path_mapped_to_local(breakpoint['filename']) document_widget = self.document_viewer.get_document_by_path(path) document_widget.rehighlight_breakpoint_lines() def handle_expression_evaluated(self, index, result): """Handle when an expression is evaluated""" self.expression_viewer.set_evaluated(index, result) def handle_expressions_evaluated(self, results): """Handle when a list of expressions is evaluated""" for index, result in enumerate(results): self.expression_viewer.set_evaluated(index, result) def handle_expression_added_or_changed(self, index, expression): """Handle when an expression is added, or an existing one is changed. """ if self.debugger.is_connected(): self.debugger.evaluate_expression(index, expression) def handle_error(self, error): """Handle when an error occurs Show the error in an error message window. """ em = QErrorMessage(self.main_window) em.showMessage(error) def __get_path_mapped_to_local(self, path, map_paths=True): """Get a path mapped to local Turns a path like /var/www into /home/user/local/path """ path_map = get_setting('path/path_mapping') if (len(path_map) > 0 and map_paths is True and path.find(path_map) == 0): path = path[len(path_map):] path = "%s%s" % (self.file_browser.model().rootPath(), path) if not os.path.isfile(path): return False return path def __get_path_mapped_to_remote(self, path): """Get a path mapped to remote Turns a path like /home/user/local/path to /var/www """ path_map = get_setting('path/path_mapping') root_path = self.file_browser.model().rootPath() if len(path_map) > 0 and path.find(root_path) == 0: path = path[len(root_path):] path = "%s%s" % (path_map, path) return path def run(self): """Run the application! """ self.main_window.show()