completion: micro-optimizations to improve startup time
[git-cola.git] / cola / widgets / completion.py
blobe33a8c05e7ec49fca682ca115d7805d7eb1399f4
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 import re
4 from qtpy import QtCore
5 from qtpy import QtGui
6 from qtpy import QtWidgets
7 from qtpy.QtCore import Qt
8 from qtpy.QtCore import Signal
10 from ..models import prefs
11 from .. import core
12 from .. import gitcmds
13 from .. import icons
14 from .. import qtutils
15 from .. import utils
16 from . import defs
17 from .text import HintedLineEdit
20 class ValidateRegex(object):
21 def __init__(self, regex):
22 self.regex = re.compile(regex) # regex to scrub
24 def validate(self, string, idx):
25 """Scrub and validate the user-supplied input"""
26 state = QtGui.QValidator.Acceptable
27 if self.regex.search(string):
28 string = self.regex.sub('', string) # scrub matching bits
29 idx = min(idx - 1, len(string))
30 return (state, string, idx)
33 class RemoteValidator(QtGui.QValidator):
34 """Prevent invalid remote names"""
36 def __init__(self, parent=None):
37 super(RemoteValidator, self).__init__(parent)
38 self._validate = ValidateRegex(r'[ \t\\/]')
40 def validate(self, string, idx):
41 return self._validate.validate(string, idx)
44 class BranchValidator(QtGui.QValidator):
45 """Prevent invalid branch names"""
47 def __init__(self, git, parent=None):
48 super(BranchValidator, self).__init__(parent)
49 self._git = git
50 self._validate = ValidateRegex(r'[ \t\\]') # forward-slash is okay
52 def validate(self, string, idx):
53 """Scrub and validate the user-supplied input"""
54 state, string, idx = self._validate.validate(string, idx)
55 if string: # Allow empty strings
56 status, _, _ = self._git.check_ref_format(string, branch=True)
57 if status != 0:
58 # The intermediate string, when deleting characters, might
59 # end in a name that is invalid to Git, but we must allow it
60 # otherwise we won't be able to delete it using backspace.
61 if string.endswith('/') or string.endswith('.'):
62 state = self.Intermediate
63 else:
64 state = self.Invalid
65 return (state, string, idx)
68 def _is_case_sensitive(text):
69 return bool([char for char in text if char.isupper()])
72 class CompletionLineEdit(HintedLineEdit):
73 """A lineedit with advanced completion abilities"""
75 activated = Signal()
76 changed = Signal()
77 cleared = Signal()
78 enter = Signal()
79 up = Signal()
80 down = Signal()
82 # Activation keys will cause a selected completion item to be chosen
83 ACTIVATION_KEYS = (Qt.Key_Return, Qt.Key_Enter)
85 # Navigation keys trigger signals that widgets can use for customization
86 NAVIGATION_KEYS = {
87 Qt.Key_Return: 'enter',
88 Qt.Key_Enter: 'enter',
89 Qt.Key_Up: 'up',
90 Qt.Key_Down: 'down',
93 def __init__(self, context, model_factory, hint='', parent=None):
94 HintedLineEdit.__init__(self, context, hint, parent=parent)
95 # Tracks when the completion popup was active during key events
97 self.context = context
98 # The most recently selected completion item
99 self._selection = None
101 # Create a completion model
102 completion_model = model_factory(context, self)
103 completer = Completer(completion_model, self)
104 completer.setWidget(self)
105 self._completer = completer
106 self._completion_model = completion_model
108 # The delegate highlights matching completion text in the popup widget
109 self._delegate = HighlightDelegate(self)
110 completer.popup().setItemDelegate(self._delegate)
112 # pylint: disable=no-member
113 self.textChanged.connect(self._text_changed)
114 self._completer.activated.connect(self.choose_completion)
115 self._completion_model.updated.connect(
116 self._completions_updated, type=Qt.QueuedConnection
118 self.destroyed.connect(self.dispose)
120 def __del__(self):
121 self.dispose()
123 # pylint: disable=unused-argument
124 def dispose(self, *args):
125 self._completer.dispose()
127 def completion_selection(self):
128 """Return the last completion's selection"""
129 return self._selection
131 def complete(self):
132 """Trigger the completion popup to appear and offer completions"""
133 self._completer.complete()
135 def refresh(self):
136 """Refresh the completion model"""
137 return self._completer.model().update()
139 def popup(self):
140 """Return the completer's popup"""
141 return self._completer.popup()
143 def _text_changed(self, full_text):
144 match_text = self._last_word()
145 self._do_text_changed(full_text, match_text)
146 self.complete_last_word()
148 def _do_text_changed(self, full_text, match_text):
149 case_sensitive = _is_case_sensitive(match_text)
150 if case_sensitive:
151 self._completer.setCaseSensitivity(Qt.CaseSensitive)
152 else:
153 self._completer.setCaseSensitivity(Qt.CaseInsensitive)
154 self._delegate.set_highlight_text(match_text, case_sensitive)
155 self._completer.set_match_text(full_text, match_text, case_sensitive)
157 def update_matches(self):
158 text = self._last_word()
159 case_sensitive = _is_case_sensitive(text)
160 self._completer.setCompletionPrefix(text)
161 self._completer.model().update_matches(case_sensitive)
163 def choose_completion(self, completion):
165 This is the event handler for the QCompleter.activated(QString) signal,
166 it is called when the user selects an item in the completer popup.
168 if not completion:
169 self._do_text_changed('', '')
170 return
171 words = self._words()
172 if words and not self._ends_with_whitespace():
173 words.pop()
175 words.append(completion)
176 text = core.list2cmdline(words)
177 self.setText(text)
178 self.changed.emit()
179 self._do_text_changed(text, '')
180 self.popup().hide()
182 def _words(self):
183 return utils.shell_split(self.value())
185 def _ends_with_whitespace(self):
186 value = self.value()
187 return value != value.rstrip()
189 def _last_word(self):
190 if self._ends_with_whitespace():
191 return ''
192 words = self._words()
193 if not words:
194 return self.value()
195 if not words[-1]:
196 return ''
197 return words[-1]
199 def complete_last_word(self):
200 self.update_matches()
201 self.complete()
203 def close_popup(self):
204 if self.popup().isVisible():
205 self.popup().close()
207 def _completions_updated(self):
208 popup = self.popup()
209 if not popup.isVisible():
210 return
211 # Select the first item
212 idx = self._completion_model.index(0, 0)
213 selection = QtCore.QItemSelection(idx, idx)
214 mode = QtCore.QItemSelectionModel.Select
215 popup.selectionModel().select(selection, mode)
217 def selected_completion(self):
218 """Return the selected completion item"""
219 popup = self.popup()
220 if not popup.isVisible():
221 return None
222 model = popup.selectionModel()
223 indexes = model.selectedIndexes()
224 if not indexes:
225 return None
226 idx = indexes[0]
227 item = self._completion_model.itemFromIndex(idx)
228 if not item:
229 return None
230 return item.text()
232 def select_completion(self):
233 """Choose the selected completion option from the completion popup"""
234 result = False
235 visible = self.popup().isVisible()
236 if visible:
237 selection = self.selected_completion()
238 if selection:
239 self.choose_completion(selection)
240 result = True
241 return result
243 # Qt overrides
244 def event(self, event):
245 """Override QWidget::event() for tab completion"""
246 event_type = event.type()
248 if (
249 event_type == QtCore.QEvent.KeyPress
250 and event.key() == Qt.Key_Tab
251 and self.select_completion()
253 return True
255 # Make sure the popup goes away during teardown
256 if event_type == QtCore.QEvent.Hide:
257 self.close_popup()
259 return super(CompletionLineEdit, self).event(event)
261 def keyPressEvent(self, event):
262 """Process completion and navigation events"""
263 super(CompletionLineEdit, self).keyPressEvent(event)
264 visible = self.popup().isVisible()
266 # Hide the popup when the field is empty
267 is_empty = not self.value()
268 if is_empty:
269 self.cleared.emit()
270 if visible:
271 self.popup().hide()
273 # Activation keys select the completion when pressed and emit the
274 # activated signal. Navigation keys have lower priority, and only
275 # emit when it wasn't already handled as an activation event.
276 key = event.key()
277 if key in self.ACTIVATION_KEYS and visible:
278 if self.select_completion():
279 self.activated.emit()
280 return
282 navigation = self.NAVIGATION_KEYS.get(key, None)
283 if navigation:
284 signal = getattr(self, navigation)
285 signal.emit()
288 class GatherCompletionsThread(QtCore.QThread):
290 items_gathered = Signal(object)
292 def __init__(self, model):
293 QtCore.QThread.__init__(self)
294 self.model = model
295 self.case_sensitive = False
296 self.running = False
298 def dispose(self):
299 self.running = False
300 utils.catch_runtime_error(self.wait)
302 def run(self):
303 text = ''
304 items = []
305 self.running = True
306 # Loop when the matched text changes between the start and end time.
307 # This happens when gather_matches() takes too long and the
308 # model's match_text changes in-between.
309 while self.running and text != self.model.match_text:
310 text = self.model.match_text
311 items = self.model.gather_matches(self.case_sensitive)
313 if self.running and text is not None:
314 self.items_gathered.emit(items)
317 class HighlightDelegate(QtWidgets.QStyledItemDelegate):
318 """A delegate used for auto-completion to give formatted completion"""
320 def __init__(self, parent):
321 QtWidgets.QStyledItemDelegate.__init__(self, parent)
322 self.widget = parent
323 self.highlight_text = ''
324 self.case_sensitive = False
326 self.doc = QtGui.QTextDocument()
327 # older PyQt4 does not have setDocumentMargin
328 if hasattr(self.doc, 'setDocumentMargin'):
329 self.doc.setDocumentMargin(0)
331 def set_highlight_text(self, text, case_sensitive):
332 """Sets the text that will be made bold when displayed"""
333 self.highlight_text = text
334 self.case_sensitive = case_sensitive
336 def paint(self, painter, option, index):
337 """Overloaded Qt method for custom painting of a model index"""
338 if not self.highlight_text:
339 QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
340 return
341 text = index.data()
342 if self.case_sensitive:
343 html = text.replace(
344 self.highlight_text, '<strong>%s</strong>' % self.highlight_text
346 else:
347 match = re.match(
348 r'(.*)(%s)(.*)' % re.escape(self.highlight_text), text, re.IGNORECASE
350 if match:
351 start = match.group(1) or ''
352 middle = match.group(2) or ''
353 end = match.group(3) or ''
354 html = start + ('<strong>%s</strong>' % middle) + end
355 else:
356 html = text
357 self.doc.setHtml(html)
359 # Painting item without text, Text Document will paint the text
360 params = QtWidgets.QStyleOptionViewItem(option)
361 self.initStyleOption(params, index)
362 params.text = ''
364 style = QtWidgets.QApplication.style()
365 style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, params, painter)
366 ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
368 # Highlighting text if item is selected
369 if params.state & QtWidgets.QStyle.State_Selected:
370 color = params.palette.color(
371 QtGui.QPalette.Active, QtGui.QPalette.HighlightedText
373 ctx.palette.setColor(QtGui.QPalette.Text, color)
375 # translate the painter to where the text is drawn
376 item_text = QtWidgets.QStyle.SE_ItemViewItemText
377 rect = style.subElementRect(item_text, params, self.widget)
378 painter.save()
380 start = rect.topLeft() + QtCore.QPoint(defs.margin, 0)
381 painter.translate(start)
383 # tell the text document to draw the html for us
384 self.doc.documentLayout().draw(painter, ctx)
385 painter.restore()
388 def ref_sort_key(ref):
389 """Sort key function that causes shorter refs to sort first, but
390 alphabetizes refs of equal length (in order to make local branches sort
391 before remote ones)."""
392 return len(ref), ref
395 class CompletionModel(QtGui.QStandardItemModel):
397 updated = Signal()
398 items_gathered = Signal(object)
399 model_updated = Signal()
401 def __init__(self, context, parent):
402 QtGui.QStandardItemModel.__init__(self, parent)
403 self.context = context
404 self.match_text = ''
405 self.full_text = ''
406 self.case_sensitive = False
408 self.update_thread = GatherCompletionsThread(self)
409 self.update_thread.items_gathered.connect(
410 self.apply_matches, type=Qt.QueuedConnection
413 def update(self):
414 case_sensitive = self.update_thread.case_sensitive
415 self.update_matches(case_sensitive)
417 def set_match_text(self, full_text, match_text, case_sensitive):
418 self.full_text = full_text
419 self.match_text = match_text
420 self.update_matches(case_sensitive)
422 def update_matches(self, case_sensitive):
423 self.case_sensitive = case_sensitive
424 self.update_thread.case_sensitive = case_sensitive
425 if not self.update_thread.isRunning():
426 self.update_thread.start()
428 # pylint: disable=unused-argument,no-self-use
429 def gather_matches(self, case_sensitive):
430 return ((), (), set())
432 def apply_matches(self, match_tuple):
433 """Build widgets for all of the matching items"""
434 if not match_tuple:
435 # Results from background tasks can arrive after the widget has been destroyed.
436 utils.catch_runtime_error(self.set_items, [])
437 return
438 matched_refs, matched_paths, dirs = match_tuple
439 QStandardItem = QtGui.QStandardItem
441 dir_icon = icons.directory()
442 git_icon = icons.cola()
444 items = []
445 for ref in matched_refs:
446 item = QStandardItem()
447 item.setText(ref)
448 item.setIcon(git_icon)
449 items.append(item)
451 from_filename = icons.from_filename
452 for match in matched_paths:
453 item = QStandardItem()
454 item.setText(match)
455 if match in dirs:
456 item.setIcon(dir_icon)
457 else:
458 item.setIcon(from_filename(match))
459 items.append(item)
461 # Results from background tasks can arrive after the widget has been destroyed.
462 utils.catch_runtime_error(self.set_items, items)
464 def set_items(self, items):
465 """Clear the widget and add items to the model"""
466 self.clear()
467 self.invisibleRootItem().appendRows(items)
468 self.updated.emit()
470 def dispose(self):
471 self.update_thread.dispose()
474 def _identity(x):
475 return x
478 def _lower(x):
479 return x.lower()
482 def filter_matches(match_text, candidates, case_sensitive, sort_key=None):
483 """Filter candidates and return the matches"""
484 if case_sensitive:
485 case_transform = _identity
486 else:
487 case_transform = _lower
489 if match_text:
490 match_text = case_transform(match_text)
491 matches = [r for r in candidates if match_text in case_transform(r)]
492 else:
493 matches = list(candidates)
495 if case_sensitive:
496 if sort_key is None:
497 matches.sort()
498 else:
499 matches.sort(key=sort_key)
500 else:
501 if sort_key is None:
502 matches.sort(key=_lower)
503 else:
504 matches.sort(key=lambda x: sort_key(_lower(x)))
505 return matches
508 def filter_path_matches(match_text, file_list, case_sensitive):
509 """Return matching completions from a list of candidate files"""
510 files = set(file_list)
511 files_and_dirs = utils.add_parents(files)
512 dirs = files_and_dirs.difference(files)
514 paths = filter_matches(match_text, files_and_dirs, case_sensitive)
515 return (paths, dirs)
518 class Completer(QtWidgets.QCompleter):
519 def __init__(self, model, parent):
520 QtWidgets.QCompleter.__init__(self, parent)
521 self._model = model
522 self.setCompletionMode(QtWidgets.QCompleter.UnfilteredPopupCompletion)
523 self.setCaseSensitivity(Qt.CaseInsensitive)
525 model.model_updated.connect(self.update, type=Qt.QueuedConnection)
526 self.setModel(model)
528 def update(self):
529 self._model.update()
531 def dispose(self):
532 self._model.dispose()
534 def set_match_text(self, full_text, match_text, case_sensitive):
535 self._model.set_match_text(full_text, match_text, case_sensitive)
538 class GitCompletionModel(CompletionModel):
539 def __init__(self, context, parent):
540 CompletionModel.__init__(self, context, parent)
541 self.context = context
542 context.model.updated.connect(self.model_updated, type=Qt.QueuedConnection)
544 def gather_matches(self, case_sensitive):
545 refs = filter_matches(
546 self.match_text, self.matches(), case_sensitive, sort_key=ref_sort_key
548 return (refs, (), set())
550 # pylint: disable=no-self-use
551 def matches(self):
552 return []
555 class GitRefCompletionModel(GitCompletionModel):
556 """Completer for branches and tags"""
558 def __init__(self, context, parent):
559 GitCompletionModel.__init__(self, context, parent)
560 context.model.refs_updated.connect(self.model_updated, type=Qt.QueuedConnection)
562 def matches(self):
563 model = self.context.model
564 return model.local_branches + model.remote_branches + model.tags
567 def find_potential_branches(model):
568 remotes = model.remotes
569 remote_branches = model.remote_branches
571 ambiguous = set()
572 allnames = set(model.local_branches)
573 potential = []
575 for remote_branch in remote_branches:
576 branch = gitcmds.strip_remote(remotes, remote_branch)
577 if branch in allnames or branch == remote_branch:
578 ambiguous.add(branch)
579 continue
580 potential.append(branch)
581 allnames.add(branch)
583 potential_branches = [p for p in potential if p not in ambiguous]
584 return potential_branches
587 class GitCreateBranchCompletionModel(GitCompletionModel):
588 """Completer for naming new branches"""
590 def matches(self):
591 model = self.context.model
592 potential_branches = find_potential_branches(model)
593 return model.local_branches + potential_branches + model.tags
596 class GitCheckoutBranchCompletionModel(GitCompletionModel):
597 """Completer for git checkout <branch>"""
599 def matches(self):
600 model = self.context.model
601 potential_branches = find_potential_branches(model)
602 return (
603 model.local_branches
604 + potential_branches
605 + model.remote_branches
606 + model.tags
610 class GitBranchCompletionModel(GitCompletionModel):
611 """Completer for local branches"""
613 def __init__(self, context, parent):
614 GitCompletionModel.__init__(self, context, parent)
616 def matches(self):
617 model = self.context.model
618 return model.local_branches
621 class GitRemoteBranchCompletionModel(GitCompletionModel):
622 """Completer for remote branches"""
624 def __init__(self, context, parent):
625 GitCompletionModel.__init__(self, context, parent)
627 def matches(self):
628 model = self.context.model
629 return model.remote_branches
632 class GitPathCompletionModel(GitCompletionModel):
633 """Base class for path completion"""
635 def __init__(self, context, parent):
636 GitCompletionModel.__init__(self, context, parent)
638 # pylint: disable=no-self-use
639 def candidate_paths(self):
640 return []
642 def gather_matches(self, case_sensitive):
643 paths, dirs = filter_path_matches(
644 self.match_text, self.candidate_paths(), case_sensitive
646 return ((), paths, dirs)
649 class GitStatusFilterCompletionModel(GitPathCompletionModel):
650 """Completer for modified files and folders for status filtering"""
652 def __init__(self, context, parent):
653 GitPathCompletionModel.__init__(self, context, parent)
655 def candidate_paths(self):
656 model = self.context.model
657 return model.staged + model.unmerged + model.modified + model.untracked
660 class GitTrackedCompletionModel(GitPathCompletionModel):
661 """Completer for tracked files and folders"""
663 def __init__(self, context, parent):
664 GitPathCompletionModel.__init__(self, context, parent)
665 self.model_updated.connect(self.gather_paths, type=Qt.QueuedConnection)
666 self._paths = []
668 def gather_paths(self):
669 context = self.context
670 self._paths = gitcmds.tracked_files(context)
672 def gather_matches(self, case_sensitive):
673 if not self._paths:
674 self.gather_paths()
676 refs = []
677 paths, dirs = filter_path_matches(self.match_text, self._paths, case_sensitive)
678 return (refs, paths, dirs)
681 class GitLogCompletionModel(GitRefCompletionModel):
682 """Completer for arguments suitable for git-log like commands"""
684 def __init__(self, context, parent):
685 GitRefCompletionModel.__init__(self, context, parent)
686 self.model_updated.connect(self.gather_paths, type=Qt.QueuedConnection)
687 self._paths = []
688 self._model = context.model
690 def gather_paths(self):
691 """Gather paths and store them in the model"""
692 if not self._model.cfg.get(prefs.AUTOCOMPLETE_PATHS, True):
693 self._paths = []
694 return
695 context = self.context
696 self._paths = gitcmds.tracked_files(context)
698 def gather_matches(self, case_sensitive):
699 """Filter paths and refs to find matching entries"""
700 if not self._paths:
701 self.gather_paths()
702 refs = filter_matches(
703 self.match_text, self.matches(), case_sensitive, sort_key=ref_sort_key
705 paths, dirs = filter_path_matches(self.match_text, self._paths, case_sensitive)
706 has_doubledash = (
707 self.match_text == '--'
708 or self.full_text.startswith('-- ')
709 or ' -- ' in self.full_text
711 if has_doubledash:
712 refs = []
713 elif refs and paths:
714 paths.insert(0, '--')
716 return (refs, paths, dirs)
719 def bind_lineedit(model, hint=''):
720 """Create a line edit bound against a specific model"""
722 class BoundLineEdit(CompletionLineEdit):
723 def __init__(self, context, hint=hint, parent=None):
724 CompletionLineEdit.__init__(self, context, model, hint=hint, parent=parent)
725 self.context = context
727 return BoundLineEdit
730 # Concrete classes
731 GitLogLineEdit = bind_lineedit(GitLogCompletionModel, hint='<ref>')
732 GitRefLineEdit = bind_lineedit(GitRefCompletionModel, hint='<ref>')
733 GitCheckoutBranchLineEdit = bind_lineedit(
734 GitCheckoutBranchCompletionModel, hint='<branch>'
736 GitCreateBranchLineEdit = bind_lineedit(GitCreateBranchCompletionModel, hint='<branch>')
737 GitBranchLineEdit = bind_lineedit(GitBranchCompletionModel, hint='<branch>')
738 GitRemoteBranchLineEdit = bind_lineedit(
739 GitRemoteBranchCompletionModel, hint='<remote-branch>'
741 GitStatusFilterLineEdit = bind_lineedit(GitStatusFilterCompletionModel, hint='<path>')
742 GitTrackedLineEdit = bind_lineedit(GitTrackedCompletionModel, hint='<path>')
745 class GitDialog(QtWidgets.QDialog):
746 def __init__(self, lineedit, context, title, text, parent, icon=None):
747 QtWidgets.QDialog.__init__(self, parent)
748 self.context = context
749 self.setWindowTitle(title)
750 self.setWindowModality(Qt.WindowModal)
751 self.setMinimumWidth(333)
753 self.label = QtWidgets.QLabel()
754 self.label.setText(title)
755 self.lineedit = lineedit(context)
756 self.ok_button = qtutils.ok_button(text, icon=icon, enabled=False)
757 self.close_button = qtutils.close_button()
759 self.button_layout = qtutils.hbox(
760 defs.no_margin,
761 defs.button_spacing,
762 qtutils.STRETCH,
763 self.close_button,
764 self.ok_button,
767 self.main_layout = qtutils.vbox(
768 defs.margin, defs.spacing, self.label, self.lineedit, self.button_layout
770 self.setLayout(self.main_layout)
772 self.lineedit.textChanged.connect(self.text_changed)
773 self.lineedit.enter.connect(self.accept)
774 qtutils.connect_button(self.ok_button, self.accept)
775 qtutils.connect_button(self.close_button, self.reject)
777 self.setFocusProxy(self.lineedit)
778 self.lineedit.setFocus()
780 def text(self):
781 return self.lineedit.text()
783 def text_changed(self, _txt):
784 self.ok_button.setEnabled(bool(self.text()))
786 def set_text(self, ref):
787 self.lineedit.setText(ref)
789 @classmethod
790 def get(cls, context, title, text, parent, default=None, icon=None):
791 dlg = cls(context, title, text, parent, icon=icon)
792 if default:
793 dlg.set_text(default)
795 dlg.show()
797 def show_popup():
798 x = dlg.lineedit.x()
799 y = dlg.lineedit.y() + dlg.lineedit.height()
800 point = QtCore.QPoint(x, y)
801 mapped = dlg.mapToGlobal(point)
802 dlg.lineedit.popup().move(mapped.x(), mapped.y())
803 dlg.lineedit.popup().show()
804 dlg.lineedit.refresh()
805 dlg.lineedit.setFocus()
807 QtCore.QTimer().singleShot(100, show_popup)
809 if dlg.exec_() == cls.Accepted:
810 return dlg.text()
811 return None
814 class GitRefDialog(GitDialog):
815 def __init__(self, context, title, text, parent, icon=None):
816 GitDialog.__init__(
817 self, GitRefLineEdit, context, title, text, parent, icon=icon
821 class GitCheckoutBranchDialog(GitDialog):
822 def __init__(self, context, title, text, parent, icon=None):
823 GitDialog.__init__(
824 self, GitCheckoutBranchLineEdit, context, title, text, parent, icon=icon
828 class GitBranchDialog(GitDialog):
829 def __init__(self, context, title, text, parent, icon=None):
830 GitDialog.__init__(
831 self, GitBranchLineEdit, context, title, text, parent, icon=icon
835 class GitRemoteBranchDialog(GitDialog):
836 def __init__(self, context, title, text, parent, icon=None):
837 GitDialog.__init__(
838 self, GitRemoteBranchLineEdit, context, title, text, parent, icon=icon