CHANGES: trivial formatting
[git-cola.git] / cola / sequenceeditor.py
blob631c65f6df1d8d0181b34243192929b36b8e5c9c
1 import sys
2 import re
3 from argparse import ArgumentParser
4 from functools import partial
6 from cola import app # prints a message if Qt cannot be found
7 from qtpy import QtGui
8 from qtpy import QtWidgets
9 from qtpy.QtCore import Qt
10 from qtpy.QtCore import Signal
12 # pylint: disable=ungrouped-imports
13 from cola import core
14 from cola import difftool
15 from cola import gitcmds
16 from cola import hotkeys
17 from cola import icons
18 from cola import qtutils
19 from cola import utils
20 from cola.i18n import N_
21 from cola.models import dag
22 from cola.models import prefs
23 from cola.widgets import defs
24 from cola.widgets import filelist
25 from cola.widgets import diff
26 from cola.widgets import standard
27 from cola.widgets import text
30 PICK = 'pick'
31 REWORD = 'reword'
32 EDIT = 'edit'
33 FIXUP = 'fixup'
34 SQUASH = 'squash'
35 UPDATE_REF = 'update-ref'
36 EXEC = 'exec'
37 COMMANDS = (
38 PICK,
39 REWORD,
40 EDIT,
41 FIXUP,
42 SQUASH,
44 COMMAND_IDX = {cmd_: idx_ for idx_, cmd_ in enumerate(COMMANDS)}
45 ABBREV = {
46 'p': PICK,
47 'r': REWORD,
48 'e': EDIT,
49 'f': FIXUP,
50 's': SQUASH,
51 'x': EXEC,
52 'u': UPDATE_REF,
56 def main():
57 """Start a git-cola-sequence-editor session"""
58 args = parse_args()
59 context = app.application_init(args)
60 view = new_window(context, args.filename)
61 app.application_run(context, view, start=view.start, stop=stop)
62 return view.status
65 def stop(context, _view):
66 """All done, cleanup"""
67 context.view.stop()
68 context.runtask.wait()
71 def parse_args():
72 parser = ArgumentParser()
73 parser.add_argument(
74 'filename', metavar='<filename>', help='git-rebase-todo file to edit'
76 app.add_common_arguments(parser)
77 return parser.parse_args()
80 def new_window(context, filename):
81 window = MainWindow(context)
82 editor = Editor(context, filename, parent=window)
83 window.set_editor(editor)
84 return window
87 def unabbrev(cmd):
88 """Expand shorthand commands into their full name"""
89 return ABBREV.get(cmd, cmd)
92 class MainWindow(standard.MainWindow):
93 """The main git-cola application window"""
95 def __init__(self, context, parent=None):
96 super().__init__(parent)
97 self.context = context
98 self.status = 1
99 # If the user closes the window without confirmation it's considered cancelled.
100 self.cancelled = True
101 self.editor = None
102 default_title = '%s - git cola sequence editor' % core.getcwd()
103 title = core.getenv('GIT_COLA_SEQ_EDITOR_TITLE', default_title)
104 self.setWindowTitle(title)
105 self.show_help_action = qtutils.add_action(
106 self, N_('Show Help'), partial(show_help, context), hotkeys.QUESTION
108 self.menubar = QtWidgets.QMenuBar(self)
109 self.help_menu = self.menubar.addMenu(N_('Help'))
110 self.help_menu.addAction(self.show_help_action)
111 self.setMenuBar(self.menubar)
113 qtutils.add_close_action(self)
114 self.init_state(context.settings, self.init_window_size)
116 def init_window_size(self):
117 """Set the window size on the first initial view"""
118 if utils.is_darwin():
119 width, height = qtutils.desktop_size()
120 self.resize(width, height)
121 else:
122 self.showMaximized()
124 def set_editor(self, editor):
125 self.editor = editor
126 self.setCentralWidget(editor)
127 editor.cancel.connect(self.close)
128 editor.rebase.connect(self.rebase)
129 editor.setFocus()
131 def start(self, _context, _view):
132 """Start background tasks"""
133 self.editor.start()
135 def stop(self):
136 """Stop background tasks"""
137 self.editor.stop()
139 def rebase(self):
140 """Exit the editor and initiate a rebase"""
141 self.status = self.editor.save()
142 self.close()
145 class Editor(QtWidgets.QWidget):
146 cancel = Signal()
147 rebase = Signal()
149 def __init__(self, context, filename, parent=None):
150 super().__init__(parent)
152 self.widget_version = 1
153 self.context = context
154 self.filename = filename
155 self.comment_char = comment_char = prefs.comment_char(context)
157 self.diff = diff.DiffWidget(context, self)
158 self.tree = RebaseTreeWidget(context, comment_char, self)
159 self.filewidget = filelist.FileWidget(context, self, remarks=True)
160 self.setFocusProxy(self.tree)
162 self.rebase_button = qtutils.create_button(
163 text=core.getenv('GIT_COLA_SEQ_EDITOR_ACTION', N_('Rebase')),
164 tooltip=N_('Accept changes and rebase\nShortcut: Ctrl+Enter'),
165 icon=icons.ok(),
166 default=True,
169 self.extdiff_button = qtutils.create_button(
170 text=N_('Launch Diff Tool'),
171 tooltip=N_('Launch external diff tool\nShortcut: Ctrl+D'),
173 self.extdiff_button.setEnabled(False)
175 self.help_button = qtutils.create_button(
176 text=N_('Help'), tooltip=N_('Show help\nShortcut: ?'), icon=icons.question()
179 self.cancel_button = qtutils.create_button(
180 text=N_('Cancel'),
181 tooltip=N_('Cancel rebase\nShortcut: Ctrl+Q'),
182 icon=icons.close(),
185 top = qtutils.splitter(Qt.Horizontal, self.tree, self.filewidget)
186 top.setSizes([75, 25])
188 main_split = qtutils.splitter(Qt.Vertical, top, self.diff)
189 main_split.setSizes([25, 75])
191 controls_layout = qtutils.hbox(
192 defs.no_margin,
193 defs.button_spacing,
194 self.cancel_button,
195 qtutils.STRETCH,
196 self.help_button,
197 self.extdiff_button,
198 self.rebase_button,
200 layout = qtutils.vbox(defs.no_margin, defs.spacing, main_split, controls_layout)
201 self.setLayout(layout)
203 self.action_rebase = qtutils.add_action(
204 self,
205 N_('Rebase'),
206 self.rebase.emit,
207 hotkeys.CTRL_RETURN,
208 hotkeys.CTRL_ENTER,
211 self.tree.commits_selected.connect(self.commits_selected)
212 self.tree.commits_selected.connect(self.filewidget.commits_selected)
213 self.tree.commits_selected.connect(self.diff.commits_selected)
214 self.tree.external_diff.connect(self.external_diff)
216 self.filewidget.files_selected.connect(self.diff.files_selected)
217 self.filewidget.remark_toggled.connect(self.remark_toggled_for_files)
219 # `git` calls are expensive. When user toggles a remark of all commits touching
220 # selected paths the GUI freezes for a while on a big enough sequence. This
221 # cache is used (commit ID to paths tuple) to minimize calls to git.
222 self.oid_to_paths = {}
223 self.task = None # A task fills the cache in the background.
224 self.running = False # This flag stops it.
226 qtutils.connect_button(self.rebase_button, self.rebase.emit)
227 qtutils.connect_button(self.extdiff_button, self.external_diff)
228 qtutils.connect_button(self.help_button, partial(show_help, context))
229 qtutils.connect_button(self.cancel_button, self.cancel.emit)
231 def start(self):
232 insns = core.read(self.filename)
233 self.parse_sequencer_instructions(insns)
235 # Assume that the tree is filled at this point.
236 self.running = True
237 self.task = qtutils.SimpleTask(self.calculate_oid_to_paths)
238 self.context.runtask.start(self.task)
240 def stop(self):
241 self.running = False
243 # signal callbacks
244 def commits_selected(self, commits):
245 self.extdiff_button.setEnabled(bool(commits))
247 def remark_toggled_for_files(self, remark, filenames):
248 filenames = set(filenames)
250 items = self.tree.items()
251 touching_items = []
253 for item in items:
254 if not item.is_commit():
255 continue
256 oid = item.oid
257 paths = self.paths_touched_by_oid(oid)
258 if filenames.intersection(paths):
259 touching_items.append(item)
261 self.tree.toggle_remark_of_items(remark, touching_items)
263 def external_diff(self):
264 items = self.tree.selected_items()
265 if not items:
266 return
267 item = items[0]
268 difftool.diff_expression(self.context, self, item.oid + '^!', hide_expr=True)
270 # helpers
272 def paths_touched_by_oid(self, oid):
273 try:
274 return self.oid_to_paths[oid]
275 except KeyError:
276 pass
278 paths = gitcmds.changed_files(self.context, oid)
279 self.oid_to_paths[oid] = paths
281 return paths
283 def calculate_oid_to_paths(self):
284 """Fills the oid_to_paths cache in the background"""
285 for item in self.tree.items():
286 if not self.running:
287 return
288 self.paths_touched_by_oid(item.oid)
290 def parse_sequencer_instructions(self, insns):
291 idx = 1
292 re_comment_char = re.escape(self.comment_char)
293 exec_rgx = re.compile(r'^\s*(%s)?\s*(x|exec)\s+(.+)$' % re_comment_char)
294 update_ref_rgx = re.compile(
295 r'^\s*(%s)?\s*(u|update-ref)\s+(.+)$' % re_comment_char
297 # The upper bound of 40 below must match git.OID_LENGTH.
298 # We'll have to update this to the new hash length when that happens.
299 pick_rgx = re.compile(
301 r'^\s*(%s)?\s*'
302 + r'(p|pick|r|reword|e|edit|f|fixup|s|squash)'
303 + r'\s+([0-9a-f]{7,40})'
304 + r'\s+(.+)$'
306 % re_comment_char
308 for line in insns.splitlines():
309 match = pick_rgx.match(line)
310 if match:
311 enabled = match.group(1) is None
312 command = unabbrev(match.group(2))
313 oid = match.group(3)
314 summary = match.group(4)
315 self.tree.add_item(idx, enabled, command, oid=oid, summary=summary)
316 idx += 1
317 continue
318 match = exec_rgx.match(line)
319 if match:
320 enabled = match.group(1) is None
321 command = unabbrev(match.group(2))
322 cmdexec = match.group(3)
323 self.tree.add_item(idx, enabled, command, cmdexec=cmdexec)
324 idx += 1
325 continue
326 match = update_ref_rgx.match(line)
327 if match:
328 enabled = match.group(1) is None
329 command = unabbrev(match.group(2))
330 branch = match.group(3)
331 self.tree.add_item(idx, enabled, command, branch=branch)
332 idx += 1
333 continue
335 self.tree.decorate(self.tree.items())
336 self.tree.refit()
337 self.tree.select_first()
339 def save(self, string=None):
340 """Save the instruction sheet"""
342 if string is None:
343 lines = [item.value() for item in self.tree.items()]
344 # sequencer instructions
345 string = '\n'.join(lines) + '\n'
347 try:
348 core.write(self.filename, string)
349 status = 0
350 except (OSError, ValueError) as exc:
351 msg, details = utils.format_exception(exc)
352 sys.stderr.write(msg + '\n\n' + details)
353 status = 128
354 return status
357 # pylint: disable=too-many-ancestors
358 class RebaseTreeWidget(standard.DraggableTreeWidget):
359 commits_selected = Signal(object)
360 external_diff = Signal()
361 move_rows = Signal(object, object)
363 def __init__(self, context, comment_char, parent):
364 super().__init__(parent=parent)
365 self.context = context
366 self.comment_char = comment_char
367 # header
368 self.setHeaderLabels([
369 N_('#'),
370 N_('Enabled'),
371 N_('Command'),
372 N_('SHA-1'),
373 N_('Remarks'),
374 N_('Summary'),
376 self.header().setStretchLastSection(True)
377 self.setColumnCount(6)
378 self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
380 # actions
381 self.copy_oid_action = qtutils.add_action(
382 self, N_('Copy SHA-1'), self.copy_oid, QtGui.QKeySequence.Copy
385 self.external_diff_action = qtutils.add_action(
386 self, N_('Launch Diff Tool'), self.external_diff.emit, hotkeys.DIFF
389 self.toggle_enabled_action = qtutils.add_action(
390 self, N_('Toggle Enabled'), self.toggle_enabled, hotkeys.PRIMARY_ACTION
393 self.action_pick = qtutils.add_action(
394 self, N_('Pick'), lambda: self.set_selected_to(PICK), *hotkeys.REBASE_PICK
397 self.action_reword = qtutils.add_action(
398 self,
399 N_('Reword'),
400 lambda: self.set_selected_to(REWORD),
401 *hotkeys.REBASE_REWORD,
404 self.action_edit = qtutils.add_action(
405 self, N_('Edit'), lambda: self.set_selected_to(EDIT), *hotkeys.REBASE_EDIT
408 self.action_fixup = qtutils.add_action(
409 self,
410 N_('Fixup'),
411 lambda: self.set_selected_to(FIXUP),
412 *hotkeys.REBASE_FIXUP,
415 self.action_squash = qtutils.add_action(
416 self,
417 N_('Squash'),
418 lambda: self.set_selected_to(SQUASH),
419 *hotkeys.REBASE_SQUASH,
422 self.action_shift_down = qtutils.add_action(
423 self, N_('Shift Down'), self.shift_down, hotkeys.MOVE_DOWN_TERTIARY
426 self.action_shift_up = qtutils.add_action(
427 self, N_('Shift Up'), self.shift_up, hotkeys.MOVE_UP_TERTIARY
430 self.toggle_remark_actions = tuple(
431 qtutils.add_action(
432 self,
434 lambda remark=r: self.toggle_remark(remark),
435 hotkeys.hotkey(Qt.CTRL | getattr(Qt, 'Key_' + r)),
437 for r in map(str, range(10))
440 # pylint: disable=no-member
441 self.itemChanged.connect(self.item_changed)
442 self.itemSelectionChanged.connect(self.selection_changed)
443 self.move_rows.connect(self.move)
444 self.items_moved.connect(self.decorate)
446 def add_item(
447 self, idx, enabled, command, oid='', summary='', cmdexec='', branch=''
449 comment_char = self.comment_char
450 item = RebaseTreeWidgetItem(
451 idx,
452 enabled,
453 command,
454 oid=oid,
455 summary=summary,
456 cmdexec=cmdexec,
457 branch=branch,
458 comment_char=comment_char,
459 parent=self,
461 self.invisibleRootItem().addChild(item)
463 def decorate(self, items):
464 for item in items:
465 item.decorate(self)
467 def refit(self):
468 """Resize columns to fit content"""
469 for i in range(RebaseTreeWidgetItem.COLUMN_COUNT - 1):
470 self.resizeColumnToContents(i)
472 def item_changed(self, item, column):
473 """Validate item ordering when toggling their enabled state"""
474 if column == item.ENABLED_COLUMN:
475 self.validate()
477 def validate(self):
478 invalid_first_choice = {FIXUP, SQUASH}
479 for item in self.items():
480 if item.is_enabled() and item.is_commit():
481 if item.command in invalid_first_choice:
482 item.reset_command(PICK)
483 break
485 def set_selected_to(self, command):
486 for i in self.selected_items():
487 i.reset_command(command)
488 self.validate()
490 def set_command(self, item, command):
491 item.reset_command(command)
492 self.validate()
494 def copy_oid(self):
495 item = self.selected_item()
496 if item is None:
497 return
498 clipboard = item.oid or item.cmdexec
499 qtutils.set_clipboard(clipboard)
501 def selection_changed(self):
502 item = self.selected_item()
503 if item is None or not item.is_commit():
504 return
505 context = self.context
506 oid = item.oid
507 params = dag.DAG(oid, 2)
508 repo = dag.RepoReader(context, params)
509 commits = []
510 for commit in repo.get():
511 commits.append(commit)
512 if commits:
513 commits = commits[-1:]
514 self.commits_selected.emit(commits)
516 def toggle_enabled(self):
517 """Toggle the enabled state of each selected item"""
518 for item in self.selected_items():
519 item.toggle_enabled()
521 def select_first(self):
522 items = self.items()
523 if not items:
524 return
525 idx = self.model().index(0, 0)
526 if idx.isValid():
527 self.setCurrentIndex(idx)
529 def shift_down(self):
530 sel_items = self.selected_items()
531 all_items = self.items()
532 sel_idx = sorted([all_items.index(item) for item in sel_items])
533 if not sel_idx:
534 return
535 idx = sel_idx[0] + 1
536 if not (
537 idx > len(all_items) - len(sel_items)
538 or all_items[sel_idx[-1]] is all_items[-1]
540 self.move_rows.emit(sel_idx, idx)
542 def shift_up(self):
543 sel_items = self.selected_items()
544 all_items = self.items()
545 sel_idx = sorted([all_items.index(item) for item in sel_items])
546 if not sel_idx:
547 return
548 idx = sel_idx[0] - 1
549 if idx >= 0:
550 self.move_rows.emit(sel_idx, idx)
552 def toggle_remark(self, remark):
553 """Toggle remarks for all selected items"""
554 items = self.selected_items()
555 self.toggle_remark_of_items(remark, items)
557 def toggle_remark_of_items(self, remark, items):
558 """Toggle remarks for the specified items"""
559 for item in items:
560 if remark in item.remarks:
561 item.remove_remark(remark)
562 else:
563 item.add_remark(remark)
565 def move(self, src_idxs, dst_idx):
566 moved_items = []
567 src_base = sorted(src_idxs)[0]
568 for idx in reversed(sorted(src_idxs)):
569 item = self.invisibleRootItem().takeChild(idx)
570 moved_items.insert(0, [dst_idx + (idx - src_base), item])
572 for item in moved_items:
573 self.invisibleRootItem().insertChild(item[0], item[1])
574 self.setCurrentItem(item[1])
576 if moved_items:
577 moved_items = [item[1] for item in moved_items]
578 # If we've moved to the top then we need to re-decorate all items.
579 # Otherwise, we can decorate just the new items.
580 if dst_idx == 0:
581 self.decorate(self.items())
582 else:
583 self.decorate(moved_items)
585 for item in moved_items:
586 item.setSelected(True)
587 self.validate()
589 # Qt events
591 def dropEvent(self, event):
592 super().dropEvent(event)
593 self.validate()
595 def contextMenuEvent(self, event):
596 items = self.selected_items()
597 menu = qtutils.create_menu(N_('Actions'), self)
598 menu.addAction(self.action_pick)
599 menu.addAction(self.action_reword)
600 menu.addAction(self.action_edit)
601 menu.addAction(self.action_fixup)
602 menu.addAction(self.action_squash)
603 menu.addSeparator()
604 menu.addAction(self.toggle_enabled_action)
605 menu.addSeparator()
606 menu.addAction(self.copy_oid_action)
607 self.copy_oid_action.setDisabled(len(items) > 1)
608 menu.addAction(self.external_diff_action)
609 self.external_diff_action.setDisabled(len(items) > 1)
610 menu.addSeparator()
611 menu_toggle_remark = menu.addMenu(N_('Toggle Remark'))
612 for action in self.toggle_remark_actions:
613 menu_toggle_remark.addAction(action)
614 menu.exec_(self.mapToGlobal(event.pos()))
617 class ComboBox(QtWidgets.QComboBox):
618 validate = Signal()
621 class RebaseTreeWidgetItem(QtWidgets.QTreeWidgetItem):
622 """A single data row in the rebase tree widget"""
624 NUMBER_COLUMN = 0
625 ENABLED_COLUMN = 1
626 COMMAND_COLUMN = 2
627 COMMIT_COLUMN = 3
628 REMARKS_COLUMN = 4
629 SUMMARY_COLUMN = 5
630 COLUMN_COUNT = 6
631 OID_LENGTH = 7
633 COLORS = {
634 '0': ('white', 'darkred'),
635 '1': ('black', 'salmon'),
636 '2': ('black', 'sandybrown'),
637 '3': ('black', 'yellow'),
638 '4': ('black', 'yellowgreen'),
639 '5': ('white', 'forestgreen'),
640 '6': ('white', 'dodgerblue'),
641 '7': ('white', 'royalblue'),
642 '8': ('white', 'slateblue'),
643 '9': ('black', 'rosybrown'),
646 def __init__(
647 self,
648 idx,
649 enabled,
650 command,
651 oid='',
652 summary='',
653 cmdexec='',
654 branch='',
655 comment_char='#',
656 remarks=tuple(),
657 parent=None,
659 QtWidgets.QTreeWidgetItem.__init__(self, parent)
660 self.combo = None
661 self.command = command
662 self.idx = idx
663 self.oid = oid
664 self.summary = summary
665 self.cmdexec = cmdexec
666 self.branch = branch
667 self.comment_char = comment_char
668 self._parent = parent
670 # if core.abbrev is set to a higher value then we will notice by
671 # simply tracking the longest oid we've seen
672 oid_len = self.OID_LENGTH
673 self.__class__.OID_LENGTH = max(len(oid), oid_len)
675 self.setText(self.NUMBER_COLUMN, '%02d' % idx)
676 self.set_enabled(enabled)
677 # checkbox on 1
678 # combo box on 2
679 if self.is_exec():
680 self.setText(self.COMMIT_COLUMN, '')
681 self.setText(self.SUMMARY_COLUMN, cmdexec)
682 elif self.is_update_ref():
683 self.setText(self.COMMIT_COLUMN, '')
684 self.setText(self.SUMMARY_COLUMN, branch)
685 else:
686 self.setText(self.COMMIT_COLUMN, oid)
687 self.setText(self.SUMMARY_COLUMN, summary)
689 self.set_remarks(remarks)
691 flags = self.flags() | Qt.ItemIsUserCheckable
692 flags = flags | Qt.ItemIsDragEnabled
693 flags = flags & ~Qt.ItemIsDropEnabled
694 self.setFlags(flags)
696 def __eq__(self, other):
697 return self is other
699 def __hash__(self):
700 return self.oid
702 def copy(self):
703 return self.__class__(
704 self.idx,
705 self.is_enabled(),
706 self.command,
707 oid=self.oid,
708 summary=self.summary,
709 cmdexec=self.cmdexec,
710 branch=self.branch,
711 comment_char=self.comment_char,
712 remarks=self.remarks,
715 def decorate(self, parent):
716 if self.is_exec():
717 items = [EXEC]
718 idx = 0
719 elif self.is_update_ref():
720 items = [UPDATE_REF]
721 idx = 0
722 else:
723 items = COMMANDS
724 idx = COMMAND_IDX[self.command]
725 combo = self.combo = ComboBox()
726 combo.setEditable(False)
727 combo.addItems(items)
728 combo.setCurrentIndex(idx)
729 combo.setEnabled(self.is_commit())
731 signal = combo.currentIndexChanged
732 # pylint: disable=no-member
733 signal.connect(lambda x: self.set_command_and_validate(combo))
734 combo.validate.connect(parent.validate)
736 parent.setItemWidget(self, self.COMMAND_COLUMN, combo)
738 def is_exec(self):
739 return self.command == EXEC
741 def is_update_ref(self):
742 return self.command == UPDATE_REF
744 def is_commit(self):
745 return bool(
746 not (self.is_exec() or self.is_update_ref()) and self.oid and self.summary
749 def value(self):
750 """Return the serialized representation of an item"""
751 if self.is_enabled():
752 comment = ''
753 else:
754 comment = self.comment_char + ' '
755 if self.is_exec():
756 return f'{comment}{self.command} {self.cmdexec}'
757 if self.is_update_ref():
758 return f'{comment}{self.command} {self.branch}'
759 return f'{comment}{self.command} {self.oid} {self.summary}'
761 def is_enabled(self):
762 """Is the item enabled?"""
763 return self.checkState(self.ENABLED_COLUMN) == Qt.Checked
765 def set_enabled(self, enabled):
766 """Enable the item by checking its enabled checkbox"""
767 self.setCheckState(self.ENABLED_COLUMN, enabled and Qt.Checked or Qt.Unchecked)
769 def toggle_enabled(self):
770 """Toggle the enabled state of the item"""
771 self.set_enabled(not self.is_enabled())
773 def add_remark(self, remark):
774 """Add a remark to the item"""
775 self.set_remarks(tuple(sorted(set(self.remarks + (remark,)))))
777 def remove_remark(self, remark):
778 """Remove a remark from the item"""
779 self.set_remarks(tuple(r for r in self.remarks if r != remark))
781 def set_remarks(self, remarks):
782 """Set the remarks and update the remark display"""
783 self.remarks = remarks
784 label = QtWidgets.QLabel()
785 text = ''
786 for remark in remarks:
787 fg_color, bg_color = self.COLORS[remark]
788 text += f"""
789 <span style="
790 color: {fg_color};
791 background-color: {bg_color};
792 ">&nbsp;{remark} </span>
794 label.setText(text)
795 self._parent.setItemWidget(self, self.REMARKS_COLUMN, label)
796 self._parent.resizeColumnToContents(self.REMARKS_COLUMN)
798 def set_command(self, command):
799 """Set the item to a different command, no-op for exec items"""
800 if self.is_exec():
801 return
802 self.command = command
804 def refresh(self):
805 """Update the view to match the updated state"""
806 if self.is_commit():
807 command = self.command
808 self.combo.setCurrentIndex(COMMAND_IDX[command])
810 def reset_command(self, command):
811 """Set and refresh the item in one shot"""
812 self.set_command(command)
813 self.refresh()
815 def set_command_and_validate(self, combo):
816 """Set the command and validate the command order"""
817 command = COMMANDS[combo.currentIndex()]
818 self.set_command(command)
819 self.combo.validate.emit()
822 def show_help(context):
823 help_text = N_(
825 Commands
826 --------
827 pick = use commit
828 reword = use commit, but edit the commit message
829 edit = use commit, but stop for amending
830 squash = use commit, but meld into previous commit
831 fixup = like "squash", but discard this commit's log message
832 exec = run command (the rest of the line) using shell
833 update-ref = update branches that point to commits
835 These lines can be re-ordered; they are executed from top to bottom.
837 If you disable a line here THAT COMMIT WILL BE LOST.
839 However, if you disable everything, the rebase will be aborted.
841 Keyboard Shortcuts
842 ------------------
843 ? = show help
844 j = move down
845 k = move up
846 J = shift row down
847 K = shift row up
849 1, p = pick
850 2, r = reword
851 3, e = edit
852 4, f = fixup
853 5, s = squash
854 spacebar = toggle enabled
856 ctrl+enter = accept changes and rebase
857 ctrl+q = cancel and abort the rebase
858 ctrl+d = launch difftool
861 title = N_('Help - git-cola-sequence-editor')
862 return text.text_dialog(context, help_text, title)