widgets: pylint tweaks, docstrings and use queued connections
[git-cola.git] / cola / widgets / completion.py
blob50233ef0aafb5ebf79e3c5bbab53da96a20cd472
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 matched_refs, matched_paths, dirs = match_tuple
434 QStandardItem = QtGui.QStandardItem
436 dir_icon = icons.directory()
437 git_icon = icons.cola()
439 items = []
440 for ref in matched_refs:
441 item = QStandardItem()
442 item.setText(ref)
443 item.setIcon(git_icon)
444 items.append(item)
446 from_filename = icons.from_filename
447 for match in matched_paths:
448 item = QStandardItem()
449 item.setText(match)
450 if match in dirs:
451 item.setIcon(dir_icon)
452 else:
453 item.setIcon(from_filename(match))
454 items.append(item)
456 # Results from background tasks can arrive after the widget has been destroyed.
457 utils.catch_runtime_error(self.set_items, items)
459 def set_items(self, items):
460 """Clear the widget and add items to the model"""
461 self.clear()
462 self.invisibleRootItem().appendRows(items)
463 self.updated.emit()
465 def dispose(self):
466 self.update_thread.dispose()
469 def _identity(x):
470 return x
473 def _lower(x):
474 return x.lower()
477 def filter_matches(match_text, candidates, case_sensitive, sort_key=lambda x: x):
478 """Filter candidates and return the matches"""
479 if case_sensitive:
480 case_transform = _identity
481 else:
482 case_transform = _lower
484 if match_text:
485 match_text = case_transform(match_text)
486 matches = [r for r in candidates if match_text in case_transform(r)]
487 else:
488 matches = list(candidates)
490 matches.sort(key=lambda x: sort_key(case_transform(x)))
491 return matches
494 def filter_path_matches(match_text, file_list, case_sensitive):
495 """Return matching completions from a list of candidate files"""
496 files = set(file_list)
497 files_and_dirs = utils.add_parents(files)
498 dirs = files_and_dirs.difference(files)
500 paths = filter_matches(match_text, files_and_dirs, case_sensitive)
501 return (paths, dirs)
504 class Completer(QtWidgets.QCompleter):
505 def __init__(self, model, parent):
506 QtWidgets.QCompleter.__init__(self, parent)
507 self._model = model
508 self.setCompletionMode(QtWidgets.QCompleter.UnfilteredPopupCompletion)
509 self.setCaseSensitivity(Qt.CaseInsensitive)
511 model.model_updated.connect(self.update, type=Qt.QueuedConnection)
512 self.setModel(model)
514 def update(self):
515 self._model.update()
517 def dispose(self):
518 self._model.dispose()
520 def set_match_text(self, full_text, match_text, case_sensitive):
521 self._model.set_match_text(full_text, match_text, case_sensitive)
524 class GitCompletionModel(CompletionModel):
525 def __init__(self, context, parent):
526 CompletionModel.__init__(self, context, parent)
527 self.context = context
528 context.model.updated.connect(self.model_updated, type=Qt.QueuedConnection)
530 def gather_matches(self, case_sensitive):
531 refs = filter_matches(
532 self.match_text, self.matches(), case_sensitive, sort_key=ref_sort_key
534 return (refs, (), set())
536 # pylint: disable=no-self-use
537 def matches(self):
538 return []
541 class GitRefCompletionModel(GitCompletionModel):
542 """Completer for branches and tags"""
544 def __init__(self, context, parent):
545 GitCompletionModel.__init__(self, context, parent)
546 context.model.refs_updated.connect(self.model_updated, type=Qt.QueuedConnection)
548 def matches(self):
549 model = self.context.model
550 return model.local_branches + model.remote_branches + model.tags
553 def find_potential_branches(model):
554 remotes = model.remotes
555 remote_branches = model.remote_branches
557 ambiguous = set()
558 allnames = set(model.local_branches)
559 potential = []
561 for remote_branch in remote_branches:
562 branch = gitcmds.strip_remote(remotes, remote_branch)
563 if branch in allnames or branch == remote_branch:
564 ambiguous.add(branch)
565 continue
566 potential.append(branch)
567 allnames.add(branch)
569 potential_branches = [p for p in potential if p not in ambiguous]
570 return potential_branches
573 class GitCreateBranchCompletionModel(GitCompletionModel):
574 """Completer for naming new branches"""
576 def matches(self):
577 model = self.context.model
578 potential_branches = find_potential_branches(model)
579 return model.local_branches + potential_branches + model.tags
582 class GitCheckoutBranchCompletionModel(GitCompletionModel):
583 """Completer for git checkout <branch>"""
585 def matches(self):
586 model = self.context.model
587 potential_branches = find_potential_branches(model)
588 return (
589 model.local_branches
590 + potential_branches
591 + model.remote_branches
592 + model.tags
596 class GitBranchCompletionModel(GitCompletionModel):
597 """Completer for local branches"""
599 def __init__(self, context, parent):
600 GitCompletionModel.__init__(self, context, parent)
602 def matches(self):
603 model = self.context.model
604 return model.local_branches
607 class GitRemoteBranchCompletionModel(GitCompletionModel):
608 """Completer for remote branches"""
610 def __init__(self, context, parent):
611 GitCompletionModel.__init__(self, context, parent)
613 def matches(self):
614 model = self.context.model
615 return model.remote_branches
618 class GitPathCompletionModel(GitCompletionModel):
619 """Base class for path completion"""
621 def __init__(self, context, parent):
622 GitCompletionModel.__init__(self, context, parent)
624 # pylint: disable=no-self-use
625 def candidate_paths(self):
626 return []
628 def gather_matches(self, case_sensitive):
629 paths, dirs = filter_path_matches(
630 self.match_text, self.candidate_paths(), case_sensitive
632 return ((), paths, dirs)
635 class GitStatusFilterCompletionModel(GitPathCompletionModel):
636 """Completer for modified files and folders for status filtering"""
638 def __init__(self, context, parent):
639 GitPathCompletionModel.__init__(self, context, parent)
641 def candidate_paths(self):
642 model = self.context.model
643 return model.staged + model.unmerged + model.modified + model.untracked
646 class GitTrackedCompletionModel(GitPathCompletionModel):
647 """Completer for tracked files and folders"""
649 def __init__(self, context, parent):
650 GitPathCompletionModel.__init__(self, context, parent)
651 self.model_updated.connect(self.gather_paths, type=Qt.QueuedConnection)
652 self._paths = []
654 def gather_paths(self):
655 context = self.context
656 self._paths = gitcmds.tracked_files(context)
658 def gather_matches(self, case_sensitive):
659 if not self._paths:
660 self.gather_paths()
662 refs = []
663 paths, dirs = filter_path_matches(self.match_text, self._paths, case_sensitive)
664 return (refs, paths, dirs)
667 class GitLogCompletionModel(GitRefCompletionModel):
668 """Completer for arguments suitable for git-log like commands"""
670 def __init__(self, context, parent):
671 GitRefCompletionModel.__init__(self, context, parent)
672 self.model_updated.connect(self.gather_paths, type=Qt.QueuedConnection)
673 self._paths = []
674 self._model = context.model
676 def gather_paths(self):
677 """Gather paths and store them in the model"""
678 if not self._model.cfg.get(prefs.AUTOCOMPLETE_PATHS, True):
679 self._paths = []
680 return
681 context = self.context
682 self._paths = gitcmds.tracked_files(context)
684 def gather_matches(self, case_sensitive):
685 """Filter paths and refs to find matching entries"""
686 if not self._paths:
687 self.gather_paths()
688 refs = filter_matches(
689 self.match_text, self.matches(), case_sensitive, sort_key=ref_sort_key
691 paths, dirs = filter_path_matches(self.match_text, self._paths, case_sensitive)
692 has_doubledash = (
693 self.match_text == '--'
694 or self.full_text.startswith('-- ')
695 or ' -- ' in self.full_text
697 if has_doubledash:
698 refs = []
699 elif refs and paths:
700 paths.insert(0, '--')
702 return (refs, paths, dirs)
705 def bind_lineedit(model, hint=''):
706 """Create a line edit bound against a specific model"""
708 class BoundLineEdit(CompletionLineEdit):
709 def __init__(self, context, hint=hint, parent=None):
710 CompletionLineEdit.__init__(self, context, model, hint=hint, parent=parent)
711 self.context = context
713 return BoundLineEdit
716 # Concrete classes
717 GitLogLineEdit = bind_lineedit(GitLogCompletionModel, hint='<ref>')
718 GitRefLineEdit = bind_lineedit(GitRefCompletionModel, hint='<ref>')
719 GitCheckoutBranchLineEdit = bind_lineedit(
720 GitCheckoutBranchCompletionModel, hint='<branch>'
722 GitCreateBranchLineEdit = bind_lineedit(GitCreateBranchCompletionModel, hint='<branch>')
723 GitBranchLineEdit = bind_lineedit(GitBranchCompletionModel, hint='<branch>')
724 GitRemoteBranchLineEdit = bind_lineedit(
725 GitRemoteBranchCompletionModel, hint='<remote-branch>'
727 GitStatusFilterLineEdit = bind_lineedit(GitStatusFilterCompletionModel, hint='<path>')
728 GitTrackedLineEdit = bind_lineedit(GitTrackedCompletionModel, hint='<path>')
731 class GitDialog(QtWidgets.QDialog):
732 def __init__(self, lineedit, context, title, text, parent, icon=None):
733 QtWidgets.QDialog.__init__(self, parent)
734 self.context = context
735 self.setWindowTitle(title)
736 self.setWindowModality(Qt.WindowModal)
737 self.setMinimumWidth(333)
739 self.label = QtWidgets.QLabel()
740 self.label.setText(title)
741 self.lineedit = lineedit(context)
742 self.ok_button = qtutils.ok_button(text, icon=icon, enabled=False)
743 self.close_button = qtutils.close_button()
745 self.button_layout = qtutils.hbox(
746 defs.no_margin,
747 defs.button_spacing,
748 qtutils.STRETCH,
749 self.close_button,
750 self.ok_button,
753 self.main_layout = qtutils.vbox(
754 defs.margin, defs.spacing, self.label, self.lineedit, self.button_layout
756 self.setLayout(self.main_layout)
758 self.lineedit.textChanged.connect(self.text_changed)
759 self.lineedit.enter.connect(self.accept)
760 qtutils.connect_button(self.ok_button, self.accept)
761 qtutils.connect_button(self.close_button, self.reject)
763 self.setFocusProxy(self.lineedit)
764 self.lineedit.setFocus()
766 def text(self):
767 return self.lineedit.text()
769 def text_changed(self, _txt):
770 self.ok_button.setEnabled(bool(self.text()))
772 def set_text(self, ref):
773 self.lineedit.setText(ref)
775 @classmethod
776 def get(cls, context, title, text, parent, default=None, icon=None):
777 dlg = cls(context, title, text, parent, icon=icon)
778 if default:
779 dlg.set_text(default)
781 dlg.show()
783 def show_popup():
784 x = dlg.lineedit.x()
785 y = dlg.lineedit.y() + dlg.lineedit.height()
786 point = QtCore.QPoint(x, y)
787 mapped = dlg.mapToGlobal(point)
788 dlg.lineedit.popup().move(mapped.x(), mapped.y())
789 dlg.lineedit.popup().show()
790 dlg.lineedit.refresh()
791 dlg.lineedit.setFocus()
793 QtCore.QTimer().singleShot(100, show_popup)
795 if dlg.exec_() == cls.Accepted:
796 return dlg.text()
797 return None
800 class GitRefDialog(GitDialog):
801 def __init__(self, context, title, text, parent, icon=None):
802 GitDialog.__init__(
803 self, GitRefLineEdit, context, title, text, parent, icon=icon
807 class GitCheckoutBranchDialog(GitDialog):
808 def __init__(self, context, title, text, parent, icon=None):
809 GitDialog.__init__(
810 self, GitCheckoutBranchLineEdit, context, title, text, parent, icon=icon
814 class GitBranchDialog(GitDialog):
815 def __init__(self, context, title, text, parent, icon=None):
816 GitDialog.__init__(
817 self, GitBranchLineEdit, context, title, text, parent, icon=icon
821 class GitRemoteBranchDialog(GitDialog):
822 def __init__(self, context, title, text, parent, icon=None):
823 GitDialog.__init__(
824 self, GitRemoteBranchLineEdit, context, title, text, parent, icon=icon