widgets.recent: Refresh after editing the commit count
[git-cola.git] / cola / widgets / diff.py
blobef82962a77bbee301d3650321b486013caeb18c1
1 import os
3 from PyQt4 import QtGui
4 from PyQt4 import QtCore
5 from PyQt4.QtCore import Qt, SIGNAL
7 import cola
8 from cola import guicmds
9 from cola import qtutils
10 from cola import signals
11 from cola.prefs import diff_font
12 from cola.prefs import tab_width
13 from cola.qt import DiffSyntaxHighlighter
14 from cola.qtutils import SLOT
17 class DiffView(QtGui.QTextEdit):
18 def __init__(self, parent):
19 QtGui.QTextEdit.__init__(self, parent)
20 self.setMinimumSize(QtCore.QSize(1, 1))
21 self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
22 self.setAcceptRichText(False)
23 self.setCursorWidth(2)
24 self.setTextInteractionFlags(Qt.TextSelectableByKeyboard |
25 Qt.TextSelectableByMouse)
26 # Diff/patch syntax highlighter
27 self.syntax = DiffSyntaxHighlighter(self.document())
28 self.setFont(diff_font())
29 self.set_tab_width(tab_width())
31 def set_tab_width(self, tab_width):
32 display_font = self.font()
33 space_width = QtGui.QFontMetrics(display_font).width(' ')
34 self.setTabStopWidth(tab_width * space_width)
37 class DiffTextEdit(DiffView):
38 def __init__(self, parent):
39 DiffView.__init__(self, parent)
40 self.model = model = cola.model()
42 # Install diff shortcut keys for stage/unstage
43 self.action_process_section = qtutils.add_action(self,
44 'Process Section',
45 self.apply_section, QtCore.Qt.Key_H)
46 self.action_process_selection = qtutils.add_action(self,
47 'Process Selection',
48 self.apply_selection, QtCore.Qt.Key_S)
49 # Context menu actions
50 self.action_stage_selection = qtutils.add_action(self,
51 self.tr('Stage &Selected Lines'),
52 self.stage_selection)
53 self.action_stage_selection.setIcon(qtutils.icon('add.svg'))
55 self.action_revert_selection = qtutils.add_action(self,
56 self.tr('Revert Selected Lines...'),
57 self.revert_selection)
58 self.action_revert_selection.setIcon(qtutils.icon('undo.svg'))
60 self.action_unstage_selection = qtutils.add_action(self,
61 self.tr('Unstage &Selected Lines'),
62 self.unstage_selection)
63 self.action_unstage_selection.setIcon(qtutils.icon('remove.svg'))
65 self.action_apply_selection = qtutils.add_action(self,
66 self.tr('Apply Diff Selection to Work Tree'),
67 self.stage_selection)
68 self.action_apply_selection.setIcon(qtutils.apply_icon())
70 model.add_message_observer(model.message_diff_text_changed,
71 self.setPlainText)
73 self.connect(self, SIGNAL('copyAvailable(bool)'),
74 self.enable_selection_actions)
76 # Qt overrides
77 def contextMenuEvent(self, event):
78 """Create the context menu for the diff display."""
79 menu = QtGui.QMenu(self)
80 staged, modified, unmerged, untracked = cola.selection()
82 if self.mode == self.model.mode_worktree:
83 if modified and modified[0] in cola.model().submodules:
84 menu.addAction(qtutils.icon('add.svg'),
85 self.tr('Stage'),
86 SLOT(signals.stage, modified))
87 menu.addAction(qtutils.git_icon(),
88 self.tr('Launch git-cola'),
89 SLOT(signals.open_repo,
90 os.path.abspath(modified[0])))
91 elif modified:
92 menu.addAction(qtutils.icon('add.svg'),
93 self.tr('Stage Section'),
94 self.stage_section)
95 menu.addAction(self.action_stage_selection)
96 menu.addSeparator()
97 menu.addAction(qtutils.icon('undo.svg'),
98 self.tr('Revert Section...'),
99 self.revert_section)
100 menu.addAction(self.action_revert_selection)
102 elif self.mode == self.model.mode_index:
103 if staged and staged[0] in cola.model().submodules:
104 menu.addAction(qtutils.icon('remove.svg'),
105 self.tr('Unstage'),
106 SLOT(signals.unstage, staged))
107 menu.addAction(qtutils.git_icon(),
108 self.tr('Launch git-cola'),
109 SLOT(signals.open_repo,
110 os.path.abspath(staged[0])))
111 else:
112 menu.addAction(qtutils.icon('remove.svg'),
113 self.tr('Unstage Section'),
114 self.unstage_section)
115 menu.addAction(self.action_unstage_selection)
117 elif self.mode == self.model.mode_branch:
118 menu.addAction(qtutils.apply_icon(),
119 self.tr('Apply Diff to Work Tree'),
120 self.stage_section)
121 menu.addAction(self.action_apply_selection)
123 elif self.mode == self.model.mode_grep:
124 menu.addAction(qtutils.icon('open.svg'),
125 self.tr('Launch Editor'),
126 lambda: guicmds.goto_grep(self.selected_line()))
128 menu.addSeparator()
129 menu.addAction(qtutils.icon('edit-copy.svg'),
130 'Copy', self.copy)
131 menu.addAction(qtutils.icon('edit-select-all.svg'),
132 'Select All', self.selectAll)
133 menu.exec_(self.mapToGlobal(event.pos()))
135 def setPlainText(self, text):
136 """setPlainText(str) while retaining scrollbar positions"""
137 scrollbar = self.verticalScrollBar()
138 if scrollbar:
139 scrollvalue = scrollbar.value()
140 if text is not None:
141 QtGui.QTextEdit.setPlainText(self, text)
142 if scrollbar:
143 scrollbar.setValue(scrollvalue)
145 # Accessors
146 mode = property(lambda self: self.model.mode)
148 def offset_and_selection(self):
149 cursor = self.textCursor()
150 offset = cursor.position()
151 selection = unicode(cursor.selection().toPlainText())
152 return offset, selection
154 def selected_line(self):
155 cursor = self.textCursor()
156 offset = cursor.position()
157 contents = unicode(self.toPlainText())
158 while (offset >= 1
159 and contents[offset-1]
160 and contents[offset-1] != '\n'):
161 offset -= 1
162 data = contents[offset:]
163 if '\n' in data:
164 line, rest = data.split('\n', 1)
165 else:
166 line = data
167 return line
169 # Mutators
170 def enable_selection_actions(self, enabled):
171 self.action_apply_selection.setEnabled(enabled)
172 self.action_revert_selection.setEnabled(enabled)
173 self.action_unstage_selection.setEnabled(enabled)
174 self.action_stage_selection.setEnabled(enabled)
176 def apply_section(self):
177 staged, modified, unmerged, untracked = cola.single_selection()
178 if self.mode == self.model.mode_worktree and modified:
179 self.stage_section()
180 elif self.mode == self.model.mode_index:
181 self.unstage_section()
183 def apply_selection(self):
184 staged, modified, unmerged, untracked = cola.single_selection()
185 if self.mode == self.model.mode_worktree and modified:
186 self.stage_selection()
187 elif self.mode == self.model.mode_index:
188 self.unstage_selection()
190 def stage_section(self):
191 """Stage a specific section."""
192 self.process_diff_selection(staged=False)
194 def stage_selection(self):
195 """Stage selected lines."""
196 self.process_diff_selection(staged=False, selected=True)
198 def unstage_section(self, cached=True):
199 """Unstage a section."""
200 self.process_diff_selection(staged=True)
202 def unstage_selection(self):
203 """Unstage selected lines."""
204 self.process_diff_selection(staged=True, selected=True)
206 def revert_section(self):
207 """Destructively remove a section from a worktree file."""
208 if not qtutils.confirm('Revert Section?',
209 'This operation drops uncommitted changes.\n'
210 'These changes cannot be recovered.',
211 'Revert the uncommitted changes?',
212 'Revert Section',
213 default=False,
214 icon=qtutils.icon('undo.svg')):
215 return
216 self.process_diff_selection(staged=False, apply_to_worktree=True,
217 reverse=True)
219 def revert_selection(self):
220 """Destructively check out content for the selected file from $head."""
221 if not qtutils.confirm('Revert Selected Lines?',
222 'This operation drops uncommitted changes.\n'
223 'These changes cannot be recovered.',
224 'Revert the uncommitted changes?',
225 'Revert Selected Lines',
226 default=False,
227 icon=qtutils.icon('undo.svg')):
228 return
229 self.process_diff_selection(staged=False, apply_to_worktree=True,
230 reverse=True, selected=True)
232 def process_diff_selection(self, selected=False,
233 staged=True, apply_to_worktree=False,
234 reverse=False):
235 """Implement un/staging of selected lines or sections."""
236 offset, selection = self.offset_and_selection()
237 cola.notifier().broadcast(signals.apply_diff_selection,
238 staged,
239 selected,
240 offset,
241 selection,
242 apply_to_worktree)