class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') SHA_SHORTCUT_LENGTH = 8 ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) self._icon_loader = IconLoader() self.setWindowIcon(self._icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch_index = None self._diff_window = None self._review_note = None self._init_ui() self._create_actions() self._create_toolbar() self._application.directory_changed.connect(self._on_directory_changed) self._application.file_changed.connect(self._on_file_changed) ############################################## def _init_ui(self): # Table models are set in application central_widget = QtWidgets.QWidget(self) self.setCentralWidget(central_widget) top_vertical_layout = QtWidgets.QVBoxLayout(central_widget) self._message_box = MessageBox(self) top_vertical_layout.addWidget(self._message_box) horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) vertical_layout = QtWidgets.QVBoxLayout() horizontal_layout.addLayout(vertical_layout) horizontal_layout2 = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout2) button = QtWidgets.QPushButton('Diff') button.clicked.connect(self._on_diff_ab) horizontal_layout2.addWidget(button) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['edit-delete@svg']) horizontal_layout2.addWidget(button) self._diff_a = QtWidgets.QLineEdit(self) button.clicked.connect(lambda: self._diff_a.clear()) horizontal_layout2.addWidget(self._diff_a) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['media-playlist-repeat@svg']) button.clicked.connect(self._on_diff_exchange) horizontal_layout2.addWidget(button) self._diff_b = QtWidgets.QLineEdit(self) horizontal_layout2.addWidget(self._diff_b) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['edit-delete@svg']) button.clicked.connect(lambda: self._diff_b.clear()) horizontal_layout2.addWidget(button) # for widget in (self._diff_a, self._diff_b): # widget.editingFinished.connect(self._on_diff_ab) self._branch_name = QtWidgets.QLineEdit(self) self._branch_name.setReadOnly(True) vertical_layout.addWidget(self._branch_name) self._row_count = QtWidgets.QLabel('') vertical_layout.addWidget(self._row_count) row = 0 grid_layout = QtWidgets.QGridLayout() horizontal_layout.addLayout(grid_layout) label = QtWidgets.QLabel('Committer Filter') committer_filter = QtWidgets.QLineEdit() committer_filter.textChanged.connect(self._on_committer_filter_changed) for i, widget in enumerate((label, committer_filter)): grid_layout.addWidget(widget, row, i) row += 1 horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) label = QtWidgets.QLabel('Message Filter') message_filter = QtWidgets.QLineEdit() message_filter.textChanged.connect(self._on_message_filter_changed) for i, widget in enumerate((label, message_filter)): grid_layout.addWidget(widget, row, i) row += 1 horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) label = QtWidgets.QLabel('SHA Filter') sha_filter = QtWidgets.QLineEdit() sha_filter.textChanged.connect(self._on_sha_filter_changed) for i, widget in enumerate((label, sha_filter)): grid_layout.addWidget(widget, row, i) row += 1 splitter = QtWidgets.QSplitter() top_vertical_layout.addWidget(splitter) splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() splitter.addWidget(self._log_table) bottom_widget = QtWidgets.QWidget() splitter.addWidget(bottom_widget) bottom_horizontal_layout = QtWidgets.QHBoxLayout() bottom_widget.setLayout(bottom_horizontal_layout) vertical_layout = QtWidgets.QVBoxLayout() bottom_horizontal_layout.addLayout(vertical_layout) self._commit_table = QtWidgets.QTableView() vertical_layout.addWidget(self._commit_table) vertical_layout = QtWidgets.QVBoxLayout() bottom_horizontal_layout.addLayout(vertical_layout) self._commit_sha = QtWidgets.QLineEdit() self._commit_sha.setReadOnly(True) vertical_layout.addWidget(self._commit_sha) self._parent_labels = [] for i in range(2): horizontal_layout = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout) button = QtWidgets.QPushButton('Go') button.clicked.connect( lambda state, index=i: self._on_go_clicked(index)) horizontal_layout.addWidget(button) parent = QtWidgets.QLineEdit() parent.setReadOnly(True) horizontal_layout.addWidget(parent) self._parent_labels.append(parent) self._review_comment = QtWidgets.QTextEdit() vertical_layout.addWidget(self._review_comment) horizontal_layout = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout) save_button = QtWidgets.QPushButton('Save') save_button.clicked.connect(self._on_save_review) horizontal_layout.addItem( QtWidgets.QSpacerItem(0, 10, QSizePolicy.Expanding)) horizontal_layout.addWidget(save_button) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._on_clicked_table) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def finish_table_connections(self): self._log_table.selectionModel().currentRowChanged.connect( self._update_commit_table) #!# Fixme: reopen diff viewer window when repository change #!# self._commit_table.selectionModel().currentRowChanged.connect(self._on_clicked_table) ############################################## def _create_actions(self): self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in ( self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(self._icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action, ): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_directory_changed(self, path): self._logger.info(path) self._reload_repository() self._diff = self._application.repository.diff(**self._diff_kwargs) if self._diff_window is not None: if self.number_of_patches: self._current_patch_index = 0 self._diff_window.update_patch_index() self.reload_current_patch() else: self._diff_window.close() ############################################## def _on_file_changed(self, path): self._logger.info(path) repository = self._application.repository if path == repository.join_repository_path(repository.INDEX_PATH): self._diff = self._application.repository.diff(**self._diff_kwargs) else: message = 'File {} changed'.format(path) self.show_message(message) self.reload_current_patch() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _reset_parent(self): self._current_commit = None for parent in self._parent_labels: parent.clear() ############################################## def _update_commit_table(self, index=None): self._commit_sha.clear() self._reset_parent() if self._review_note is not None: self._on_save_review() self._review_note = None self._review_comment.clear() if index is not None: index = self._application.log_table_filter.mapToSource(index) index = index.row() else: index = 0 # Diff a=old b=new commit if index: self._current_revision = index # log_table_model = self._log_table.model() log_table_model = self._application.log_table_model self._current_commit = log_table_model[index] sha = self._current_commit.hex self._commit_sha.setText('Commit: {} / {}'.format( sha[:self.SHA_SHORTCUT_LENGTH], sha)) if len(self._current_commit.parents) > len(self._parent_labels): self.show_message('Fixme: More than 2 parents') for commit, parent_label in zip(self._current_commit.parents, self._parent_labels): parent_label.setText('Parent: {} ({})'.format( commit.hex[:self.SHA_SHORTCUT_LENGTH], commit.message)) parent_label.setCursorPosition(0) self._review_note = self._application.review[sha] if self._review_note is not None: self._review_comment.setText(self._review_note.text) else: self._review_note = ReviewNote(sha) commit_a = self._current_commit.parents[0] # take first parent kwargs = dict(a=commit_a, b=self._current_commit) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff_kwargs = kwargs self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _on_clicked_table(self, index): # called when a commit row is clicked self._logger.info('') self._current_patch_index = index.row() self.reload_current_patch() ############################################## @property def current_patch_index(self): return self._current_patch_index ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _create_diff_viewer_window(self): self._logger.info("Open Diff Viewer") from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow repository = self._application.repository self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() ############################################## def _on_diff_window_closed(self): self._application.unwatch_files() # Fixme: only current patch ! self._diff_window = None self._logger.info("Diff Viewer closed") ############################################## def _show_patch(self, patch): self._logger.info('') self._application.unwatch_files() if self._diff_window is None: self._create_diff_viewer_window() delta = patch.delta old_path = delta.old_file.path new_path = delta.new_file.path if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + new_path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (old_path, new_path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, new_path) elif delta.status == git.GIT_DELTA_DELETED: paths = (old_path, None) repository = self._application.repository texts = [ repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id) ] metadatas = [ dict(path=old_path, document_type='file', last_modification_date=None), dict(path=new_path, document_type='file', last_modification_date=None), ] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) self._application.watch(new_path) else: self._logger.info( 'revision {} Binary '.format(self._current_revision) + new_path) # Fixme: show image pdf ... # Monitor file change ############################################## @property def _last_path_index(self): return len(self._diff) - 1 ############################################## def _next_previous_patch(self, forward): if forward: if self._current_patch_index < self._last_path_index: patch_index = self._current_patch_index + 1 else: patch_index = 0 else: if self._current_patch_index >= 1: patch_index = self._current_patch_index - 1 else: patch_index = self._last_path_index self._current_patch_index = patch_index patch = self._diff[patch_index] self._show_patch(patch) ############################################## def previous_patch(self): self._next_previous_patch(forward=False) ############################################## def next_patch(self): self._next_previous_patch(forward=True) ############################################## def reload_current_patch(self): if self._current_patch_index is not None: patch = self._diff[self._current_patch_index] self._show_patch(patch) ############################################## def _on_committer_filter_changed(self, text): log_table_filter = self._application.log_table_filter log_table_filter.setFilterRegExp( QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn( LogTableModel.COLUMN_ENUM.committer) self._on_log_table_filter_changed( ) # seems to just work, no need to connect signal ############################################## def _on_message_filter_changed(self, text): log_table_filter = self._application.log_table_filter log_table_filter.setFilterRegExp( QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn(LogTableModel.COLUMN_ENUM.message) self._on_log_table_filter_changed() ############################################## def _on_sha_filter_changed(self, text): log_table_filter = self._application.log_table_filter if text: # Fixme: ??? # regexp = '^' + text regexp = text else: regexp = '' log_table_filter.setFilterRegExp( QRegExp(regexp, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn(LogTableModel.COLUMN_ENUM.sha) self._on_log_table_filter_changed() ############################################## def _on_log_table_filter_changed(self): log_table_filter = self._application.log_table_filter self._row_count.setText('{} commits'.format( log_table_filter.rowCount())) ############################################## def _on_save_review(self): self._review_note.text = self._review_comment.toPlainText() self._application.review.add(self._review_note) self._application.review.save() ############################################## def _on_go_clicked(self, parent_index): if self._current_commit is not None: try: parent_commit_sha = str( self._current_commit.parent_ids[parent_index]) index = self._application.log_table_model.find_commit( parent_commit_sha) if index is not None: self._logger.info('Found parent commit {} {}'.format( parent_index, parent_commit_sha)) self._log_table.selectRow(index.row()) except IndexError: pass ############################################## def _on_diff_exchange(self): diff_a = self._diff_a.text() diff_b = self._diff_b.text() self._diff_a.setText(diff_b) self._diff_b.setText(diff_a) self._on_diff_ab() ############################################## def _on_diff_ab(self): diff_a = self._diff_a.text() diff_b = self._diff_b.text() if diff_a and diff_b: try: kwargs = dict(a=diff_a, b=diff_b) self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() except ValueError as e: self._logger.warning(e) self.show_message(str(e))
class DiffViewerMainWindow(MainWindowBase): _logger = _module_logger.getChild('DiffViewerMainWindow') closed = pyqtSignal() ############################################## def __init__(self, parent=None, repository=None): # Note: parent is None when diff viewer is the main application # Fixme: subclass standard and/ git diff super().__init__(title='CodeReview Diff Viewer', parent=parent) self._repository = repository self._staged = False # Fixme: only git self._current_path = None self._init_ui() self._create_actions() self._create_toolbar() icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) self._lexer_cache = LexerCache() if self.is_main_window: self._application.directory_changed.connect(self._on_file_system_changed) self._application.file_changed.connect(self._on_file_system_changed) ############################################## def _init_ui(self): self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) self._diff_view = DiffView() for widget in (self._message_box, self._diff_view): self._vertical_layout.addWidget(widget) ############################################## def _create_actions(self): icon_loader = IconLoader() self._previous_file_action = \ QtWidgets.QAction(icon_loader['go-previous@svg'], 'Previous', self, toolTip='Previous file', shortcut='Ctrl+P', triggered=self._previous_file, ) self._next_file_action = \ QtWidgets.QAction(icon_loader['go-next@svg'], 'Next', self, toolTip='Next file', shortcut='Ctrl+N', triggered=self._next_file, ) self._refresh_action = \ QtWidgets.QAction(icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._refresh, ) self._line_number_action = \ QtWidgets.QAction(icon_loader['line-number-mode@svg'], 'Line Number Mode', self, toolTip='Line Number Mode', shortcut='Ctrl+N', checkable=True, triggered=self._set_document_models, ) self._align_action = \ QtWidgets.QAction(icon_loader['align-mode@svg'], 'Align Mode', self, toolTip='Align Mode', shortcut='Ctrl+L', checkable=True, triggered=self._set_document_models, ) self._complete_action = \ QtWidgets.QAction(icon_loader['complete-mode@svg'], 'Complete Mode', self, toolTip='Complete Mode', shortcut='Ctrl+A', checkable=True, triggered=self._set_document_models, ) self._highlight_action = \ QtWidgets.QAction(icon_loader['highlight@svg'], 'Highlight', self, toolTip='Highlight text', shortcut='Ctrl+H', checkable=True, triggered=self._refresh, ) # Fixme: only git if self._repository: self._stage_action = \ QtWidgets.QAction('Stage', self, toolTip='Stage file', shortcut='Ctrl+S', checkable=True, triggered=self._stage, ) else: self._stage_action = None ############################################## def _create_toolbar(self): self._algorithm_combobox = QtWidgets.QComboBox(self) for algorithm in ('Patience',): self._algorithm_combobox.addItem(algorithm, algorithm) self._algorithm_combobox.currentIndexChanged.connect(self._refresh) self._lines_of_context_combobox = QtWidgets.QComboBox(self) for number_of_lines_of_context in (3, 6, 12): self._lines_of_context_combobox.addItem(str(number_of_lines_of_context), number_of_lines_of_context) self._lines_of_context_combobox.currentIndexChanged.connect(self._refresh) self._font_size_combobox = QtWidgets.QComboBox(self) min_font_size, max_font_size = 4, 20 application_font_size = QtWidgets.QApplication.font().pointSize() min_font_size = min(min_font_size, application_font_size) max_font_size = max(max_font_size, application_font_size) for font_size in range(min_font_size, max_font_size +1): self._font_size_combobox.addItem(str(font_size), font_size) self._font_size_combobox.setCurrentIndex(application_font_size - min_font_size) self._font_size_combobox.currentIndexChanged.connect(self._on_font_size_change) self._on_font_size_change(refresh=False) if self._repository: self._status_label = QtWidgets.QLabel('Status: ???') else: self._status_label = None items = [ self._algorithm_combobox, QtWidgets.QLabel(' '), # Fixme QtWidgets.QLabel('Context:'), self._lines_of_context_combobox, QtWidgets.QLabel(' '), # Fixme QtWidgets.QLabel('Font Size:'), self._font_size_combobox, self._line_number_action, self._align_action, self._complete_action, self._highlight_action, self._refresh_action, self._stage_action, self._status_label, ] if self._application._main_window is not None: self._patch_index_label = QtWidgets.QLabel() self.update_patch_index() items.extend(( self._previous_file_action, self._patch_index_label, self._next_file_action, )) self._tool_bar = self.addToolBar('Diff Viewer') for item in items: if item is not None: # for self._stage_action if isinstance(item, QtWidgets.QAction): self._tool_bar.addAction(item) else: self._tool_bar.addWidget(item) ############################################## def init_menu(self): super().init_menu() ############################################## @property def is_main_window(self): return self.parent() is None # return self._application.main_window is self @property def is_not_main_window(self): return self.parent() is not None # return self._application.main_window is self ############################################## def closeEvent(self, event): # Fixme: else the window is reopened ??? if self.is_main_window: self._application.directory_changed.disconnect(self._on_file_system_changed) self._application.file_changed.disconnect(self._on_file_system_changed) else: self.closed.emit() super().closeEvent(event) ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ################################################ def open_files(self, file1, file2, show=False): # Fixme: Actually it only supports file diff paths = (file1, file2) texts = (None, None) metadatas = [dict(path=file1, document_type='file', last_modification_date=None), dict(path=file2, document_type='file', last_modification_date=None)] self.diff_documents(paths, texts, metadatas, show=show) ############################################## def _absolute_path(self, path): return os.path.join(self._workdir, path) ############################################## def _read_file(self, path): with open(self._absolute_path(path)) as f: text = f.read() return text ############################################## def _is_directory(self, path): if path is None: return False else: return os.path.isdir(self._absolute_path(path)) ############################################## def _get_lexer(self, path, text): if path is None: return None return self._lexer_cache.guess(path, text) ############################################## def diff_documents(self, paths, texts, metadatas=None, workdir='', show=False): self._paths = list(paths) self._texts = list(texts) self._metadatas = metadatas self._workdir = workdir file1, file2 = self._paths if self._is_directory(file1) or self._is_directory(file2): self._highlighted_documents = [TextDocumentModel(metadata) for metadata in self._metadatas] else: self.diff_text_documents(show) self._set_document_models() if self._repository: self._staged = self._repository.is_staged(file2) self._status_label.clear() if self._repository.is_modified(file2): self._status_label.setText('<font color="red">Modified !</font>') if self._staged: self._logger.info("File {} is staged".format(file2)) self._stage_action.blockSignals(True) self._stage_action.setChecked(self._staged) self._stage_action.blockSignals(False) # Fixme: # Useless if application is LogBrowserApplication # file_system_watcher = self._application.file_system_watcher # files = file_system_watcher.files() # if files: # file_system_watcher.removePaths(files) # self._logger.info("Monitor {}".format(file2)) # file_system_watcher.addPath(file2) ############################################## def diff_text_documents(self, show=False): OLD, NEW = list(range(2)) for i in (OLD, NEW): if self._paths[i] is None: self._texts[i] = '' elif self._texts[i] is None: self._texts[i] = self._read_file(self._paths[i]) lexers = [self._get_lexer(path, text) for path, text in zip(self._paths, self._texts)] raw_text_documents = [RawTextDocument(text) for text in self._texts] highlight = self._highlight_action.isChecked() number_of_lines_of_context = self._lines_of_context_combobox.currentData() self._highlighted_documents = [] if not show: file_diff = TwoWayFileDiffFactory().process(* raw_text_documents, number_of_lines_of_context=number_of_lines_of_context) document_models = TextDocumentDiffModelFactory().process(file_diff) for raw_text_document, document_model, lexer in zip(raw_text_documents, document_models, lexers): if lexer is not None and highlight: highlighted_text = HighlightedText(raw_text_document, lexer) highlighted_document = highlight_document(document_model, highlighted_text) else: highlighted_document = document_model self._highlighted_documents.append(highlighted_document) else: # Only show the document # Fixme: broken, chunk_type is ??? # self._diff_view.set_document_models(self._highlighted_documents, complete_mode) # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/DiffWidget.py", line 333, in set_document_models # cursor.begin_block(side, text_block.frame_type) # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/DiffWidget.py", line 99, in begin_block # if ((side == LEFT and frame_type == chunk_type.insert) or # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/Tools/EnumFactory.py", line 107, in __eq__ # return self._value == int(other) # TypeError: int() argument must be a string or a number, not 'NoneType' for raw_text_document, lexer in zip(raw_text_documents, self._lexers): highlighted_document = highlight_text(raw_text_document, lexer) self._highlighted_documents.append(highlighted_document) if self._metadatas is not None: for highlighted_document, metadata in zip(self._highlighted_documents, self._metadatas): highlighted_document.metadata = metadata ################################################ def _set_document_models(self): aligned_mode = self._align_action.isChecked() complete_mode = self._complete_action.isChecked() line_number_mode = self._line_number_action.isChecked() # Fixme: right way ? self._diff_view.set_document_models(self._highlighted_documents, aligned_mode, complete_mode, line_number_mode) ############################################## def _on_font_size_change(self, index=None, refresh=True): self._diff_view.set_font(self._font_size_combobox.currentData()) if refresh: self._refresh() # Fixme: block position are not updated ############################################## def _on_file_system_changed(self, path): # only used for main window self._logger.info(path) self._refresh() ############################################## def _refresh(self): if self.is_main_window: # Fixme: better way ??? texts = (None, None) # to force to reread files self.diff_documents(self._paths, texts, self._metadatas, self._workdir) # Fixme: show ??? else: main_window = self.parent() main_window.reload_current_patch() ############################################## def update_patch_index(self): main_window = self.parent() self._patch_index_label.setText('{}/{}'.format( main_window.current_patch_index +1, main_window.number_of_patches)) ############################################## def _previous_next_file(self, forward): # Fixme: only for Git if self.is_not_main_window: main_window = self.parent() if forward: main_window.next_patch() else: main_window.previous_patch() self.update_patch_index() # else: directory diff is not implemented ############################################## def _previous_file(self): self._previous_next_file(False) ############################################## def _next_file(self): self._previous_next_file(True) ############################################## def _stage(self): # Fixme: only for Git file_path = self._paths[1] if self._staged: self._repository.unstage(file_path) action = 'Unstage' else: self._repository.stage(file_path) action = 'Stage' self._logger.info("{} {}".format(action, file_path)) self._staged = not self._staged
class DiffViewerMainWindow(MainWindowBase): _logger = _module_logger.getChild('DiffViewerMainWindow') closed = pyqtSignal() ############################################## def __init__(self, parent=None): super(DiffViewerMainWindow, self).__init__(title='CodeReview Diff Viewer', parent=parent) self._current_path = None self._init_ui() self._create_actions() self._create_toolbar() icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) ############################################## def _init_ui(self): self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) self._diff_view = DiffView() for widget in (self._message_box, self._diff_view): self._vertical_layout.addWidget(widget) ############################################## def _create_actions(self): icon_loader = IconLoader() self._previous_file_action = \ QtWidgets.QAction(icon_loader['go-previous'], 'Previous', self, toolTip='Previous file', shortcut='Ctrl+P', triggered=self._previous_file, ) self._next_file_action = \ QtWidgets.QAction(icon_loader['go-next'], 'Next', self, toolTip='Next file', shortcut='Ctrl+N', triggered=self._next_file, ) self._refresh_action = \ QtWidgets.QAction(icon_loader['view-refresh'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._refresh, ) self._line_number_action = \ QtWidgets.QAction(icon_loader['line-number-mode@svg'], 'Line Number Mode', self, toolTip='Line Number Mode', shortcut='Ctrl+N', checkable=True, triggered=self._set_document_models, ) self._align_action = \ QtWidgets.QAction(icon_loader['align-mode@svg'], 'Align Mode', self, toolTip='Align Mode', shortcut='Ctrl+L', checkable=True, triggered=self._set_document_models, ) self._complete_action = \ QtWidgets.QAction(icon_loader['complete-mode@svg'], 'Complete Mode', self, toolTip='Complete Mode', shortcut='Ctrl+A', checkable=True, triggered=self._set_document_models, ) self._highlight_action = \ QtWidgets.QAction(icon_loader['highlight@svg'], 'Highlight', self, toolTip='Highlight text', shortcut='Ctrl+H', checkable=True, triggered=self._refresh, ) ############################################## def _create_toolbar(self): self._algorithm_combobox = QtWidgets.QComboBox(self) for algorithm in ('Patience',): self._algorithm_combobox.addItem(algorithm, algorithm) self._algorithm_combobox.currentIndexChanged.connect(self._refresh) self._lines_of_context_combobox = QtWidgets.QComboBox(self) for number_of_lines_of_context in (3, 6, 12): self._lines_of_context_combobox.addItem(str(number_of_lines_of_context), number_of_lines_of_context) self._lines_of_context_combobox.currentIndexChanged.connect(self._refresh) self._font_size_combobox = QtWidgets.QComboBox(self) min_font_size, max_font_size = 4, 20 application_font_size = QtWidgets.QApplication.font().pointSize() min_font_size = min(min_font_size, application_font_size) max_font_size = max(max_font_size, application_font_size) for font_size in range(min_font_size, max_font_size +1): self._font_size_combobox.addItem(str(font_size), font_size) self._font_size_combobox.setCurrentIndex(application_font_size - min_font_size) self._font_size_combobox.currentIndexChanged.connect(self._on_font_size_change) self._on_font_size_change(refresh=False) items = [ self._algorithm_combobox, QtWidgets.QLabel(' '), # Fixme QtWidgets.QLabel('Context:'), self._lines_of_context_combobox, QtWidgets.QLabel(' '), # Fixme QtWidgets.QLabel('Font Size:'), self._font_size_combobox, self._line_number_action, self._align_action, self._complete_action, self._highlight_action, self._refresh_action, ] if self._application._main_window is not None: self._patch_index_label = QtWidgets.QLabel() self._update_patch_index() items.extend(( self._previous_file_action, self._patch_index_label, self._next_file_action, )) self._tool_bar = self.addToolBar('Diff Viewer') for item in items: if isinstance(item, QtWidgets.QAction): self._tool_bar.addAction(item) else: self._tool_bar.addWidget(item) ############################################## def init_menu(self): super(DiffViewerMainWindow, self).init_menu() ############################################## def closeEvent(self, event): if self._application.main_window is not self: self.closed.emit() super(DiffViewerMainWindow, self).closeEvent(event) ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ################################################ def open_files(self, file1, file2, show=False): paths = (file1, file2) texts = (None, None) metadatas = [dict(path=file1, document_type='file', last_modification_date=None), dict(path=file2, document_type='file', last_modification_date=None)] self.diff_documents(paths, texts, metadatas, show=show) ############################################## def _absolute_path(self, path): return os.path.join(self._workdir, path) ############################################## def _read_file(self, path): with open(self._absolute_path(path)) as f: text = f.read() return text ############################################## def _is_directory(self, path): if path is None: return False else: return os.path.isdir(self._absolute_path(path)) ############################################## @staticmethod def _get_lexer(path, text): if path is None: return None try: # get_lexer_for_filename(filename) return pygments_lexers.guess_lexer_for_filename(path, text, stripnl=False) except pygments_lexers.ClassNotFound: try: return pygments_lexers.guess_lexer(text, stripnl=False) except pygments_lexers.ClassNotFound: return None ############################################## def diff_documents(self, paths, texts, metadatas=None, workdir='', show=False): self._paths = list(paths) self._texts = list(texts) self._metadatas = metadatas self._workdir = workdir file1, file2 = self._paths if self._is_directory(file1) or self._is_directory(file2): self._highlighted_documents = [TextDocumentModel(metadata) for metadata in self._metadatas] else: self.diff_text_documents(show) self._set_document_models() ############################################## def diff_text_documents(self, show=False): OLD, NEW = list(range(2)) for i in (OLD, NEW): if self._paths[i] is None: self._texts[i] = '' elif self._texts[i] is None: self._texts[i] = self._read_file(self._paths[i]) lexers = [self._get_lexer(path, text) for path, text in zip(self._paths, self._texts)] raw_text_documents = [RawTextDocument(text) for text in self._texts] highlight = self._highlight_action.isChecked() number_of_lines_of_context = self._lines_of_context_combobox.currentData() self._highlighted_documents = [] if not show: file_diff = TwoWayFileDiffFactory().process(* raw_text_documents, number_of_lines_of_context=number_of_lines_of_context) document_models = TextDocumentDiffModelFactory().process(file_diff) for raw_text_document, document_model, lexer in zip(raw_text_documents, document_models, lexers): if lexer is not None and highlight: highlighted_text = HighlightedText(raw_text_document, lexer) highlighted_document = highlight_document(document_model, highlighted_text) else: highlighted_document = document_model self._highlighted_documents.append(highlighted_document) else: # Only show the document # Fixme: broken, chunk_type is ??? # self._diff_view.set_document_models(self._highlighted_documents, complete_mode) # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/DiffWidget.py", line 333, in set_document_models # cursor.begin_block(side, text_block.frame_type) # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/DiffWidget.py", line 99, in begin_block # if ((side == LEFT and frame_type == chunk_type.insert) or # File "/home/gv/fabrice/unison-osiris/git-python/CodeReview/Tools/EnumFactory.py", line 107, in __eq__ # return self._value == int(other) # TypeError: int() argument must be a string or a number, not 'NoneType' for raw_text_document, lexer in zip(raw_text_documents, self._lexers): highlighted_document = highlight_text(raw_text_document, lexer) self._highlighted_documents.append(highlighted_document) if self._metadatas is not None: for highlighted_document, metadata in zip(self._highlighted_documents, self._metadatas): highlighted_document.metadata = metadata ################################################ def _set_document_models(self): aligned_mode = self._align_action.isChecked() complete_mode = self._complete_action.isChecked() line_number_mode = self._line_number_action.isChecked() # Fixme: right way ? self._diff_view.set_document_models(self._highlighted_documents, aligned_mode, complete_mode, line_number_mode) ############################################## def _on_font_size_change(self, index=None, refresh=True): self._diff_view.set_font(self._font_size_combobox.currentData()) if refresh: self._refresh() # Fixme: block position are not updated ############################################## def _refresh(self): main_window = self.parent() if main_window is not None: main_window.reload_current_patch() else: # Fixme: better way ??? texts = (None, None) # to force to reread files self.diff_documents(self._paths, texts, self._metadatas, self._workdir) # Fixme: show ??? ############################################## def _update_patch_index(self): main_window = self.parent() self._patch_index_label.setText('{}/{}'.format(main_window.current_patch +1, main_window.number_of_patches)) ############################################## def _previous_file(self): main_window = self.parent() if main_window is not None: main_window.previous_patch() self._update_patch_index() ############################################## def _next_file(self): main_window = self.parent() if main_window is not None: main_window.next_patch() self._update_patch_index()
class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch = None self._diff_window = None self._init_ui() self._create_actions() self._create_toolbar() self._application.file_system_changed.connect( self._on_file_system_changed) ############################################## def _init_ui(self): # Table models are set in application self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) splitter = QtWidgets.QSplitter() splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() self._commit_table = QtWidgets.QTableView() for widget in (self._message_box, splitter): self._vertical_layout.addWidget(widget) for widget in (self._log_table, self._commit_table): splitter.addWidget(widget) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._show_patch) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def _create_actions(self): icon_loader = IconLoader() self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in ( self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action, ): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_file_system_changed(self, path): self._logger.info('File system changed {}'.format(path)) self._reload_repository() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _update_commit_table(self, index=None): if index is not None: index = index.row() else: index = 0 if index: self._current_revision = index log_table_model = self._log_table.model() commit1 = log_table_model[index] try: commit2 = log_table_model[index + 1] kwargs = dict(a=commit2, b=commit1) # Fixme: except IndexError: kwargs = dict(a=commit1) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _show_patch(self, index): self._current_patch = index.row() self._show_current_patch() ############################################## def _on_diff_window_closed(self): self._diff_window = None ############################################## @property def current_patch(self): return self._current_patch ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _show_current_patch(self): repository = self._application.repository if self._diff_window is None: from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() patch = self._diff[self._current_patch] delta = patch.delta if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + delta.new_file.path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (delta.old_file.path, delta.new_file.path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, delta.new_file.path) elif delta.status == git.GIT_DELTA_DELETED: paths = (delta.old_file.path, None) texts = [ repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id) ] metadatas = [ dict(path=delta.old_file.path, document_type='file', last_modification_date=None), dict(path=delta.new_file.path, document_type='file', last_modification_date=None) ] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) else: self._logger.info( 'revision {} Binary '.format(self._current_revision) + delta.new_file.path) # Fixme: show image pdf ... ############################################## @property def _last_path_index(self): return len(self._diff) - 1 ############################################## def previous_patch(self): if self._current_patch >= 1: self._current_patch -= 1 else: self._current_patch = self._last_path_index self._show_current_patch() ############################################## def next_patch(self): if self._current_patch < self._last_path_index: self._current_patch += 1 else: self._current_patch = 0 self._show_current_patch() ############################################## def reload_current_patch(self): self._show_current_patch()
class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch_index = None self._diff_window = None self._init_ui() self._create_actions() self._create_toolbar() self._application.directory_changed.connect(self._on_directory_changed) self._application.file_changed.connect(self._on_file_changed) ############################################## def _init_ui(self): # Table models are set in application self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) splitter = QtWidgets.QSplitter() splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() self._commit_table = QtWidgets.QTableView() for widget in (self._message_box, splitter): self._vertical_layout.addWidget(widget) for widget in (self._log_table, self._commit_table): splitter.addWidget(widget) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._on_clicked_table) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def finish_table_connections(self): self._log_table.selectionModel().currentRowChanged.connect(self._update_commit_table) #!# Fixme: reopen diff viewer window when repository change #!# self._commit_table.selectionModel().currentRowChanged.connect(self._on_clicked_table) ############################################## def _create_actions(self): icon_loader = IconLoader() self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in (self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action,): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_directory_changed(self, path): self._logger.info(path) self._reload_repository() self._diff = self._application.repository.diff(**self._diff_kwargs) if self._diff_window is not None: if self.number_of_patches: self._current_patch_index = 0 self._diff_window.update_patch_index() self.reload_current_patch() else: self._diff_window.close() ############################################## def _on_file_changed(self, path): self._logger.info(path) repository = self._application.repository if path == repository.join_repository_path(repository.INDEX_PATH): self._diff = self._application.repository.diff(**self._diff_kwargs) else: message = 'File {} changed'.format(path) self.show_message(message) self.reload_current_patch() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _update_commit_table(self, index=None): if index is not None: index = index.row() else: index = 0 if index: self._current_revision = index log_table_model = self._log_table.model() commit1 = log_table_model[index] try: commit2 = log_table_model[index +1] kwargs = dict(a=commit2, b=commit1) # Fixme: except IndexError: kwargs = dict(a=commit1) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff_kwargs = kwargs self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _on_clicked_table(self, index): # called when a commit row is clicked self._logger.info('') self._current_patch_index = index.row() self.reload_current_patch() ############################################## @property def current_patch_index(self): return self._current_patch_index ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _create_diff_viewer_window(self): self._logger.info("Open Diff Viewer") from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow repository = self._application.repository self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() ############################################## def _on_diff_window_closed(self): self._application.unwatch_files() # Fixme: only current patch ! self._diff_window = None self._logger.info("Diff Viewer closed") ############################################## def _show_patch(self, patch): self._logger.info("") self._application.unwatch_files() if self._diff_window is None: self._create_diff_viewer_window() delta = patch.delta old_path = delta.old_file.path new_path = delta.new_file.path if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + new_path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (old_path, new_path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, new_path) elif delta.status == git.GIT_DELTA_DELETED: paths = (old_path, None) repository = self._application.repository texts = [repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id)] metadatas = [dict(path=old_path, document_type='file', last_modification_date=None), dict(path=new_path, document_type='file', last_modification_date=None)] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) self._application.watch(new_path) else: self._logger.info('revision {} Binary '.format(self._current_revision) + new_path) # Fixme: show image pdf ... # Monitor file change ############################################## @property def _last_path_index(self): return len(self._diff) -1 ############################################## def _next_previous_patch(self, forward): if forward: if self._current_patch_index < self._last_path_index: patch_index = self._current_patch_index + 1 else: patch_index = 0 else: if self._current_patch_index >= 1: patch_index = self._current_patch_index - 1 else: patch_index = self._last_path_index self._current_patch_index = patch_index patch = self._diff[patch_index] self._show_patch(patch) ############################################## def previous_patch(self): self._next_previous_patch(forward=False) ############################################## def next_patch(self): self._next_previous_patch(forward=True) ############################################## def reload_current_patch(self): if self._current_patch_index is not None: patch = self._diff[self._current_patch_index] self._show_patch(patch)