3 from PyQt4
import QtGui
4 from PyQt4
import QtCore
5 from PyQt4
.QtCore
import Qt
, SIGNAL
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
,
45 self
.apply_section
, QtCore
.Qt
.Key_H
)
46 self
.action_process_selection
= qtutils
.add_action(self
,
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'),
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'),
68 self
.action_apply_selection
.setIcon(qtutils
.apply_icon())
70 model
.add_message_observer(model
.message_diff_text_changed
,
73 self
.connect(self
, SIGNAL('copyAvailable(bool)'),
74 self
.enable_selection_actions
)
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'),
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])))
92 menu
.addAction(qtutils
.icon('add.svg'),
93 self
.tr('Stage Section'),
95 menu
.addAction(self
.action_stage_selection
)
97 menu
.addAction(qtutils
.icon('undo.svg'),
98 self
.tr('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'),
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])))
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'),
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()))
129 menu
.addAction(qtutils
.icon('edit-copy.svg'),
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()
139 scrollvalue
= scrollbar
.value()
141 QtGui
.QTextEdit
.setPlainText(self
, text
)
143 scrollbar
.setValue(scrollvalue
)
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())
159 and contents
[offset
-1]
160 and contents
[offset
-1] != '\n'):
162 data
= contents
[offset
:]
164 line
, rest
= data
.split('\n', 1)
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
:
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?',
214 icon
=qtutils
.icon('undo.svg')):
216 self
.process_diff_selection(staged
=False, apply_to_worktree
=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',
227 icon
=qtutils
.icon('undo.svg')):
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,
235 """Implement un/staging of selected lines or sections."""
236 offset
, selection
= self
.offset_and_selection()
237 cola
.notifier().broadcast(signals
.apply_diff_selection
,