4 from PyQt4
import QtGui
5 from PyQt4
import QtCore
6 from PyQt4
.QtCore
import Qt
7 from PyQt4
.QtCore
import SIGNAL
8 from PyQt4
.QtGui
import QFont
9 from PyQt4
.QtGui
import QSyntaxHighlighter
10 from PyQt4
.QtGui
import QTextCharFormat
11 from PyQt4
.QtGui
import QColor
14 from cola
import utils
15 from cola
import qtutils
16 from cola
.compat
import set
17 from cola
.qtutils
import tr
18 from cola
.widgets
import defs
21 def create_button(text
='', layout
=None, tooltip
=None, icon
=None):
22 """Create a button, set its title, and add it to the parent."""
23 button
= QtGui
.QPushButton()
25 button
.setText(tr(text
))
28 if tooltip
is not None:
29 button
.setToolTip(tooltip
)
30 if layout
is not None:
31 layout
.addWidget(button
)
35 class DockTitleBarWidget(QtGui
.QWidget
):
36 def __init__(self
, parent
, title
):
37 QtGui
.QWidget
.__init
__(self
, parent
)
38 self
.label
= label
= QtGui
.QLabel()
40 font
.setCapitalization(QtGui
.QFont
.SmallCaps
)
44 self
.close_button
= QtGui
.QPushButton()
45 self
.close_button
.setFlat(True)
46 self
.close_button
.setFixedSize(QtCore
.QSize(16, 16))
47 self
.close_button
.setIcon(qtutils
.titlebar_close_icon())
49 self
.toggle_button
= QtGui
.QPushButton()
50 self
.toggle_button
.setFlat(True)
51 self
.toggle_button
.setFixedSize(QtCore
.QSize(16, 16))
52 self
.toggle_button
.setIcon(qtutils
.titlebar_normal_icon())
54 self
.corner_layout
= QtGui
.QHBoxLayout()
55 self
.corner_layout
.setMargin(0)
56 self
.corner_layout
.setSpacing(defs
.spacing
)
58 layout
= QtGui
.QHBoxLayout()
60 layout
.setSpacing(defs
.spacing
)
61 layout
.addWidget(label
)
63 layout
.addLayout(self
.corner_layout
)
64 layout
.addWidget(self
.toggle_button
)
65 layout
.addWidget(self
.close_button
)
66 self
.setLayout(layout
)
68 self
.connect(self
.toggle_button
, SIGNAL('clicked()'),
71 self
.connect(self
.close_button
, SIGNAL('clicked()'),
72 self
.parent().toggleViewAction().trigger
)
74 def toggle_floating(self
):
75 self
.parent().setFloating(not self
.parent().isFloating())
77 def set_title(self
, title
):
78 self
.label
.setText(title
)
80 def add_corner_widget(self
, widget
):
81 self
.corner_layout
.addWidget(widget
)
84 def create_dock(title
, parent
):
85 """Create a dock widget and set it up accordingly."""
86 dock
= QtGui
.QDockWidget(parent
)
87 dock
.setWindowTitle(tr(title
))
88 dock
.setObjectName(title
)
89 titlebar
= DockTitleBarWidget(dock
, title
)
90 dock
.setTitleBarWidget(titlebar
)
94 def create_menu(title
, parent
):
95 """Create a menu and set its title."""
96 qmenu
= QtGui
.QMenu(parent
)
97 qmenu
.setTitle(tr(title
))
101 def create_toolbutton(text
=None, layout
=None, tooltip
=None, icon
=None):
102 button
= QtGui
.QToolButton()
103 button
.setAutoRaise(True)
104 button
.setAutoFillBackground(True)
108 button
.setText(tr(text
))
109 button
.setToolButtonStyle(Qt
.ToolButtonTextBesideIcon
)
111 button
.setToolTip(tr(tooltip
))
112 if layout
is not None:
113 layout
.addWidget(button
)
117 class QFlowLayoutWidget(QtGui
.QWidget
):
119 _horizontal
= QtGui
.QBoxLayout
.LeftToRight
120 _vertical
= QtGui
.QBoxLayout
.TopToBottom
122 def __init__(self
, parent
):
123 QtGui
.QWidget
.__init
__(self
, parent
)
124 self
._direction
= self
._vertical
125 self
._layout
= layout
= QtGui
.QBoxLayout(self
._direction
)
126 layout
.setSpacing(defs
.spacing
)
127 layout
.setMargin(defs
.margin
)
128 self
.setLayout(layout
)
129 policy
= QtGui
.QSizePolicy(QtGui
.QSizePolicy
.Minimum
,
130 QtGui
.QSizePolicy
.Minimum
)
131 self
.setSizePolicy(policy
)
132 self
.setMinimumSize(QtCore
.QSize(1, 1))
134 def resizeEvent(self
, event
):
136 if size
.width() * 0.8 < size
.height():
139 dxn
= self
._horizontal
141 if dxn
!= self
._direction
:
142 self
._direction
= dxn
143 self
.layout().setDirection(dxn
)
146 class ExpandableGroupBox(QtGui
.QGroupBox
):
147 def __init__(self
, parent
=None):
148 QtGui
.QGroupBox
.__init
__(self
, parent
)
151 self
.click_pos
= None
152 self
.arrow_icon_size
= 16
154 def set_expanded(self
, expanded
):
155 if expanded
== self
.expanded
:
156 self
.emit(SIGNAL('expanded(bool)'), expanded
)
158 self
.expanded
= expanded
159 for widget
in self
.findChildren(QtGui
.QWidget
):
160 widget
.setHidden(not expanded
)
161 self
.emit(SIGNAL('expanded(bool)'), expanded
)
163 def mousePressEvent(self
, event
):
164 if event
.button() == QtCore
.Qt
.LeftButton
:
165 option
= QtGui
.QStyleOptionGroupBox()
166 self
.initStyleOption(option
)
167 icon_size
= self
.arrow_icon_size
168 button_area
= QtCore
.QRect(0, 0, icon_size
, icon_size
)
169 offset
= self
.arrow_icon_size
+ defs
.spacing
170 adjusted
= option
.rect
.adjusted(0, 0, -offset
, 0)
171 top_left
= adjusted
.topLeft()
172 button_area
.moveTopLeft(QtCore
.QPoint(top_left
))
173 self
.click_pos
= event
.pos()
174 QtGui
.QGroupBox
.mousePressEvent(self
, event
)
176 def mouseReleaseEvent(self
, event
):
177 if (event
.button() == QtCore
.Qt
.LeftButton
and
178 self
.click_pos
== event
.pos()):
179 self
.set_expanded(not self
.expanded
)
180 QtGui
.QGroupBox
.mouseReleaseEvent(self
, event
)
182 def paintEvent(self
, event
):
183 painter
= QtGui
.QStylePainter(self
)
184 option
= QtGui
.QStyleOptionGroupBox()
185 self
.initStyleOption(option
)
187 painter
.translate(self
.arrow_icon_size
+ defs
.spacing
, 0)
188 painter
.drawText(option
.rect
, QtCore
.Qt
.AlignLeft
, self
.title())
192 point
= option
.rect
.adjusted(0, -4, 0, 0).topLeft()
193 icon_size
= self
.arrow_icon_size
194 option
.rect
= QtCore
.QRect(point
.x(), point
.y(), icon_size
, icon_size
)
196 painter
.drawPrimitive(style
.PE_IndicatorArrowDown
, option
)
198 painter
.drawPrimitive(style
.PE_IndicatorArrowRight
, option
)
201 class GitRefCompleter(QtGui
.QCompleter
):
202 """Provides completion for branches and tags"""
203 def __init__(self
, parent
, provider
=None):
204 QtGui
.QCompleter
.__init
__(self
, parent
)
205 self
._model
= GitRefModel(parent
, provider
=provider
)
206 self
.setModel(self
._model
)
207 self
.setCompletionMode(self
.UnfilteredPopupCompletion
)
208 self
.setCaseSensitivity(QtCore
.Qt
.CaseInsensitive
)
214 self
._model
.dispose()
217 class GitRefLineEdit(QtGui
.QLineEdit
):
218 def __init__(self
, parent
=None, provider
=None):
219 QtGui
.QLineEdit
.__init
__(self
, parent
)
220 self
.refcompleter
= GitRefCompleter(self
, provider
=provider
)
221 self
.setCompleter(self
.refcompleter
)
222 self
.refcompleter
.popup().installEventFilter(self
)
224 def eventFilter(self
, obj
, event
):
225 """Fix an annoyance on OS X
227 The completer popup steals focus. Work around it.
228 This affects dialogs without QtCore.Qt.WindowModal modality.
231 if obj
== self
.refcompleter
.popup():
232 if event
.type() == QtCore
.QEvent
.FocusIn
:
236 def mouseReleaseEvent(self
, event
):
237 super(GitRefLineEdit
, self
).mouseReleaseEvent(event
)
238 self
.refcompleter
.complete()
241 class GitRefDialog(QtGui
.QDialog
):
242 def __init__(self
, title
, button_text
, parent
, provider
=None):
243 super(GitRefDialog
, self
).__init
__(parent
)
244 self
.setWindowTitle(title
)
246 self
.label
= QtGui
.QLabel()
247 self
.label
.setText(title
)
249 self
.lineedit
= GitRefLineEdit(self
, provider
=provider
)
250 self
.setFocusProxy(self
.lineedit
)
252 self
.ok_button
= QtGui
.QPushButton()
253 self
.ok_button
.setText(self
.tr(button_text
))
254 self
.ok_button
.setIcon(qtutils
.apply_icon())
256 self
.close_button
= QtGui
.QPushButton()
257 self
.close_button
.setText(self
.tr('Close'))
259 self
.button_layout
= QtGui
.QHBoxLayout()
260 self
.button_layout
.setMargin(0)
261 self
.button_layout
.setSpacing(defs
.button_spacing
)
262 self
.button_layout
.addStretch()
263 self
.button_layout
.addWidget(self
.ok_button
)
264 self
.button_layout
.addWidget(self
.close_button
)
266 self
.main_layout
= QtGui
.QVBoxLayout()
267 self
.main_layout
.setMargin(defs
.margin
)
268 self
.main_layout
.setSpacing(defs
.spacing
)
270 self
.main_layout
.addWidget(self
.label
)
271 self
.main_layout
.addWidget(self
.lineedit
)
272 self
.main_layout
.addLayout(self
.button_layout
)
273 self
.setLayout(self
.main_layout
)
275 qtutils
.connect_button(self
.ok_button
, self
.accept
)
276 qtutils
.connect_button(self
.close_button
, self
.reject
)
278 self
.connect(self
.lineedit
, SIGNAL('textChanged(QString)'),
281 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
282 self
.ok_button
.setEnabled(False)
285 return unicode(self
.lineedit
.text())
287 def text_changed(self
, txt
):
288 self
.ok_button
.setEnabled(bool(self
.text()))
291 def ref(title
, button_text
, parent
, provider
=None):
292 dlg
= GitRefDialog(title
, button_text
, parent
, provider
=provider
)
296 if dlg
.exec_() == GitRefDialog
.Accepted
:
302 class GitRefProvider(QtCore
.QObject
):
303 def __init__(self
, pre
=None):
304 super(GitRefProvider
, self
).__init
__()
309 self
.model
= model
= cola
.model()
310 msg
= model
.message_updated
311 model
.add_observer(msg
, self
.emit_updated
)
313 def emit_updated(self
):
314 self
.emit(SIGNAL('updated()'))
318 return self
.pre
+ model
.local_branches
+ model
.remote_branches
+ model
.tags
321 self
.model
.remove_observer(self
.emit_updated
)
324 class GitRefModel(QtGui
.QStandardItemModel
):
325 def __init__(self
, parent
, provider
=None):
326 QtGui
.QStandardItemModel
.__init
__(self
, parent
)
329 provider
= GitRefProvider()
330 self
.provider
= provider
331 self
.update_matches()
333 self
.connect(self
.provider
, SIGNAL('updated()'),
337 self
.provider
.dispose()
339 def update_matches(self
):
340 QStandardItem
= QtGui
.QStandardItem
342 for match
in self
.provider
.matches():
343 item
= QStandardItem()
344 item
.setIcon(qtutils
.git_icon())
349 class UpdateGitLogCompletionModelThread(QtCore
.QThread
):
350 def __init__(self
, model
):
351 QtCore
.QThread
.__init
__(self
)
353 self
.case_insensitive
= False
357 # Loop when the matched text changes between the start and end time.
358 # This happens when gather_matches() takes too long and the
359 # model's matched_text changes in-between.
360 while text
!= self
.model
.matched_text
:
361 text
= self
.model
.matched_text
362 items
= self
.model
.gather_matches(self
.case_insensitive
)
363 self
.emit(SIGNAL('items_gathered'), *items
)
366 class GitLogCompletionModel(QtGui
.QStandardItemModel
):
367 def __init__(self
, parent
):
368 self
.matched_text
= None
369 QtGui
.QStandardItemModel
.__init
__(self
, parent
)
370 self
.cmodel
= cola
.model()
371 self
.update_thread
= UpdateGitLogCompletionModelThread(self
)
372 self
.connect(self
.update_thread
, SIGNAL('items_gathered'),
375 def lower_cmp(self
, a
, b
):
376 return cmp(a
.replace('.','').lower(), b
.replace('.','').lower())
378 def update_matches(self
, case_insensitive
):
379 self
.update_thread
.case_insensitive
= case_insensitive
380 if not self
.update_thread
.isRunning():
381 self
.update_thread
.start()
383 def gather_matches(self
, case_sensitive
):
384 file_list
= self
.cmodel
.everything()
385 files
= set(file_list
)
386 files_and_dirs
= utils
.add_parents(set(files
))
388 dirs
= files_and_dirs
.difference(files
)
391 refs
= model
.local_branches
+ model
.remote_branches
+ model
.tags
392 matched_text
= self
.matched_text
396 matched_refs
= [r
for r
in refs
if matched_text
in r
]
398 matched_refs
= [r
for r
in refs
399 if matched_text
.lower() in r
.lower()]
403 matched_refs
.sort(cmp=self
.lower_cmp
)
407 matched_paths
= [f
for f
in files_and_dirs
408 if matched_text
in f
]
410 matched_paths
= [f
for f
in files_and_dirs
411 if matched_text
.lower() in f
.lower()]
413 matched_paths
= list(files_and_dirs
)
415 matched_paths
.sort(cmp=self
.lower_cmp
)
417 return (matched_refs
, matched_paths
, dirs
)
420 def apply_matches(self
, matched_refs
, matched_paths
, dirs
):
421 QStandardItem
= QtGui
.QStandardItem
422 file_icon
= qtutils
.file_icon()
423 dir_icon
= qtutils
.dir_icon()
424 git_icon
= qtutils
.git_icon()
426 matched_text
= self
.matched_text
428 for ref
in matched_refs
:
429 item
= QStandardItem()
431 item
.setIcon(git_icon
)
434 if matched_paths
and (not matched_text
or matched_text
in '--'):
435 item
= QStandardItem()
437 item
.setIcon(file_icon
)
440 for match
in matched_paths
:
441 item
= QStandardItem()
444 item
.setIcon(dir_icon
)
446 item
.setIcon(file_icon
)
450 self
.invisibleRootItem().appendRows(items
)
452 def set_match_text(self
, text
, case_sensitive
):
453 self
.matched_text
= text
454 self
.update_matches(case_sensitive
)
457 class GitLogLineEdit(QtGui
.QLineEdit
):
458 def __init__(self
, parent
=None):
459 QtGui
.QLineEdit
.__init
__(self
, parent
)
460 # used to hide the completion popup after a drag-select
463 self
._model
= GitLogCompletionModel(self
)
464 self
._delegate
= HighlightCompletionDelegate(self
)
466 self
._completer
= QtGui
.QCompleter(self
)
467 self
._completer
.setWidget(self
)
468 self
._completer
.setModel(self
._model
)
469 self
._completer
.setCompletionMode(
470 QtGui
.QCompleter
.UnfilteredPopupCompletion
)
471 self
._completer
.popup().setItemDelegate(self
._delegate
)
473 self
.connect(self
._completer
, SIGNAL('activated(QString)'),
475 self
.connect(self
, SIGNAL('textChanged(QString)'), self
._text
_changed
)
476 self
._keys
_to
_ignore
= set([QtCore
.Qt
.Key_Enter
,
477 QtCore
.Qt
.Key_Return
,
478 QtCore
.Qt
.Key_Escape
])
480 def is_case_sensitive(self
, text
):
481 return bool([char
for char
in text
if char
.isupper()])
483 def _text_changed(self
, text
):
484 text
= self
.last_word()
485 case_sensitive
= self
.is_case_sensitive(text
)
487 self
._completer
.setCaseSensitivity(QtCore
.Qt
.CaseSensitive
)
489 self
._completer
.setCaseSensitivity(QtCore
.Qt
.CaseInsensitive
)
490 self
._delegate
.set_highlight_text(text
, case_sensitive
)
491 self
._model
.set_match_text(text
, case_sensitive
)
493 def update_matches(self
):
494 text
= self
.last_word()
495 case_sensitive
= self
.is_case_sensitive(text
)
496 self
._model
.update_matches(case_sensitive
)
498 def _complete(self
, completion
):
500 This is the event handler for the QCompleter.activated(QString) signal,
501 it is called when the user selects an item in the completer popup.
508 words
.append(unicode(completion
))
509 self
.setText(subprocess
.list2cmdline(words
))
510 self
.emit(SIGNAL('ref_changed'))
513 return utils
.shell_usplit(unicode(self
.text()))
518 return unicode(self
.text())
523 def event(self
, event
):
524 if event
.type() == QtCore
.QEvent
.KeyPress
:
525 if (event
.key() == QtCore
.Qt
.Key_Tab
and
526 self
._completer
.popup().isVisible()):
529 return QtGui
.QLineEdit
.event(self
, event
)
531 def do_completion(self
):
532 self
._completer
.popup().setCurrentIndex(
533 self
._completer
.completionModel().index(0,0))
534 self
._completer
.complete()
536 def keyPressEvent(self
, event
):
537 if self
._completer
.popup().isVisible():
538 if event
.key() in self
._keys
_to
_ignore
:
540 self
._complete
(self
.last_word())
543 elif (event
.key() == QtCore
.Qt
.Key_Down
and
544 self
._completer
.completionCount() > 0):
549 QtGui
.QLineEdit
.keyPressEvent(self
, event
)
551 prefix
= self
.last_word()
552 if prefix
!= unicode(self
._completer
.completionPrefix()):
553 self
._update
_popup
_items
(prefix
)
554 if len(event
.text()) > 0 and len(prefix
) > 0:
555 self
._completer
.complete()
557 self
._completer
.popup().hide()
559 #: _drag: 0 - unclicked, 1 - clicked, 2 - dragged
560 def mousePressEvent(self
, event
):
562 return QtGui
.QLineEdit
.mousePressEvent(self
, event
)
564 def mouseMoveEvent(self
, event
):
567 return QtGui
.QLineEdit
.mouseMoveEvent(self
, event
)
569 def mouseReleaseEvent(self
, event
):
570 if self
._drag
!= 2 and event
.buttons() != QtCore
.Qt
.RightButton
:
573 return QtGui
.QLineEdit
.mouseReleaseEvent(self
, event
)
575 def close_popup(self
):
576 self
._completer
.popup().close()
578 def _update_popup_items(self
, prefix
):
580 Filters the completer's popup items to only show items
581 with the given prefix.
583 self
._completer
.setCompletionPrefix(prefix
)
584 self
._completer
.popup().setCurrentIndex(
585 self
._completer
.completionModel().index(0,0))
588 class HighlightCompletionDelegate(QtGui
.QStyledItemDelegate
):
589 """A delegate used for auto-completion to give formatted completion"""
590 def __init__(self
, parent
=None): # model, parent=None):
591 QtGui
.QStyledItemDelegate
.__init
__(self
, parent
)
592 self
.highlight_text
= ''
593 self
.case_sensitive
= False
595 self
.doc
= QtGui
.QTextDocument()
596 self
.doc
.setDocumentMargin(0)
598 def set_highlight_text(self
, text
, case_sensitive
):
599 """Sets the text that will be made bold in the term name when displayed"""
600 self
.highlight_text
= text
601 self
.case_sensitive
= case_sensitive
603 def paint(self
, painter
, option
, index
):
604 """Overloaded Qt method for custom painting of a model index"""
605 if not self
.highlight_text
:
606 return QtGui
.QStyledItemDelegate
.paint(self
, painter
, option
, index
)
608 text
= unicode(index
.data().toPyObject())
609 if self
.case_sensitive
:
610 html
= text
.replace(self
.highlight_text
,
611 '<strong>%s</strong>' % self
.highlight_text
)
613 match
= re
.match('(.*)(' + self
.highlight_text
+ ')(.*)',
616 start
= match
.group(1) or ''
617 middle
= match
.group(2) or ''
618 end
= match
.group(3) or ''
619 html
= (start
+ ('<strong>%s</strong>' % middle
) + end
)
622 self
.doc
.setHtml(html
)
624 # Painting item without text, Text Document will paint the text
625 optionV4
= QtGui
.QStyleOptionViewItemV4(option
)
626 self
.initStyleOption(optionV4
, index
)
627 optionV4
.text
= QtCore
.QString()
629 style
= QtGui
.QApplication
.style()
630 style
.drawControl(QtGui
.QStyle
.CE_ItemViewItem
, optionV4
, painter
)
631 ctx
= QtGui
.QAbstractTextDocumentLayout
.PaintContext()
633 # Highlighting text if item is selected
634 if (optionV4
.state
& QtGui
.QStyle
.State_Selected
):
635 ctx
.palette
.setColor(QtGui
.QPalette
.Text
, optionV4
.palette
.color(QtGui
.QPalette
.Active
, QtGui
.QPalette
.HighlightedText
))
637 # translate the painter to where the text is drawn
638 textRect
= style
.subElementRect(QtGui
.QStyle
.SE_ItemViewItemText
, optionV4
)
641 start
= textRect
.topLeft() + QtCore
.QPoint(3, 0)
642 painter
.translate(start
)
643 painter
.setClipRect(textRect
.translated(-start
))
645 # tell the text document to draw the html for us
646 self
.doc
.documentLayout().draw(painter
, ctx
)
649 # Syntax highlighting
651 def TERMINAL(pattern
):
653 Denotes that a pattern is the final pattern that should
654 be matched. If this pattern matches no other formats
655 will be applied, even if they would have matched.
657 return '__TERMINAL__:%s' % pattern
659 # Cache the results of re.compile so that we don't keep
660 # rebuilding the same regexes whenever stylesheets change
663 def rgba(r
, g
, b
, a
=255):
670 'color_text': rgba(0x00, 0x00, 0x00),
671 'color_add': rgba(0xcd, 0xff, 0xe0),
672 'color_remove': rgba(0xff, 0xd0, 0xd0),
673 'color_header': rgba(0xbb, 0xbb, 0xbb),
677 class GenericSyntaxHighligher(QSyntaxHighlighter
):
678 def __init__(self
, doc
, *args
, **kwargs
):
679 QSyntaxHighlighter
.__init
__(self
, doc
)
680 for attr
, val
in default_colors
.items():
681 setattr(self
, attr
, val
)
683 self
.generate_rules()
685 def generate_rules(self
):
688 def create_rules(self
, *rules
):
690 raise Exception('create_rules requires an even '
691 'number of arguments.')
692 for idx
, rule
in enumerate(rules
):
695 formats
= rules
[idx
+1]
696 terminal
= rule
.startswith(TERMINAL(''))
698 rule
= rule
[len(TERMINAL('')):]
700 regex
= _RGX_CACHE
[rule
]
702 regex
= _RGX_CACHE
[rule
] = re
.compile(rule
)
703 self
._rules
.append((regex
, formats
, terminal
,))
705 def formats(self
, line
):
707 for regex
, fmts
, terminal
in self
._rules
:
708 match
= regex
.match(line
)
711 matched
.append([match
, fmts
])
716 def mkformat(self
, fg
=None, bg
=None, bold
=False):
717 fmt
= QTextCharFormat()
719 fmt
.setForeground(fg
)
721 fmt
.setBackground(bg
)
723 fmt
.setFontWeight(QFont
.Bold
)
726 def highlightBlock(self
, qstr
):
727 ascii
= unicode(qstr
)
730 formats
= self
.formats(ascii
)
733 for match
, fmts
in formats
:
734 start
= match
.start()
735 groups
= match
.groups()
737 # No groups in the regex, assume this is a single rule
738 # that spans the entire line
740 self
.setFormat(0, len(ascii
), fmts
)
743 # Groups exist, rule is a tuple corresponding to group
744 for grpidx
, group
in enumerate(groups
):
745 # allow empty matches
748 # allow None as a no-op format
751 self
.setFormat(start
, start
+length
,
755 def set_colors(self
, colordict
):
756 for attr
, val
in colordict
.items():
757 setattr(self
, attr
, val
)
760 class DiffSyntaxHighlighter(GenericSyntaxHighligher
):
761 """Implements the diff syntax highlighting
763 This class is used by widgets that display diffs.
766 def __init__(self
, doc
, whitespace
=True):
767 self
.whitespace
= whitespace
768 GenericSyntaxHighligher
.__init
__(self
, doc
)
770 def generate_rules(self
):
771 diff_head
= self
.mkformat(fg
=self
.color_header
)
772 diff_head_bold
= self
.mkformat(fg
=self
.color_header
, bold
=True)
774 diff_add
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_add
)
775 diff_remove
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_remove
)
778 bad_ws
= self
.mkformat(fg
=Qt
.black
, bg
=Qt
.red
)
780 # We specify the whitespace rule last so that it is
781 # applied after the diff addition/removal rules.
782 # The rules for the header
783 diff_old_rgx
= TERMINAL(r
'^--- ')
784 diff_new_rgx
= TERMINAL(r
'^\+\+\+ ')
785 diff_ctx_rgx
= TERMINAL(r
'^@@ ')
787 diff_hd1_rgx
= TERMINAL(r
'^diff --git a/.*b/.*')
788 diff_hd2_rgx
= TERMINAL(r
'^index \S+\.\.\S+')
789 diff_hd3_rgx
= TERMINAL(r
'^new file mode')
790 diff_hd4_rgx
= TERMINAL(r
'^deleted file mode')
791 diff_add_rgx
= TERMINAL(r
'^\+')
792 diff_rmv_rgx
= TERMINAL(r
'^-')
793 diff_bar_rgx
= TERMINAL(r
'^([ ]+.*)(\|[ ]+\d+[ ]+[+-]+)$')
794 diff_sts_rgx
= (r
'(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
795 diff_sum_rgx
= (r
'(\s+\d+ files changed[^\d]*)'
796 r
'(:?\d+ insertions[^\d]*)'
797 r
'(:?\d+ deletions.*)$')
799 self
.create_rules(diff_old_rgx
, diff_head
,
800 diff_new_rgx
, diff_head
,
801 diff_ctx_rgx
, diff_head_bold
,
802 diff_bar_rgx
, (diff_head_bold
, diff_head
),
803 diff_hd1_rgx
, diff_head
,
804 diff_hd2_rgx
, diff_head
,
805 diff_hd3_rgx
, diff_head
,
806 diff_hd4_rgx
, diff_head
,
807 diff_add_rgx
, diff_add
,
808 diff_rmv_rgx
, diff_remove
,
809 diff_sts_rgx
, (None, diff_head
,
812 diff_sum_rgx
, (diff_head
,
816 self
.create_rules('(..*?)(\s+)$', (None, bad_ws
))
819 if __name__
== '__main__':
821 class SyntaxTestDialog(QtGui
.QDialog
):
822 def __init__(self
, parent
):
823 QtGui
.QDialog
.__init
__(self
, parent
)
824 self
.resize(720, 512)
825 self
.vboxlayout
= QtGui
.QVBoxLayout(self
)
826 self
.vboxlayout
.setObjectName('vboxlayout')
827 self
.output_text
= QtGui
.QTextEdit(self
)
829 if utils
.is_darwin():
833 font
.setFamily(family
)
834 font
.setPointSize(12)
835 self
.output_text
.setFont(font
)
836 self
.output_text
.setAcceptDrops(False)
837 self
.vboxlayout
.addWidget(self
.output_text
)
838 self
.syntax
= DiffSyntaxHighlighter(self
.output_text
.document())
840 app
= QtGui
.QApplication(sys
.argv
)
841 dialog
= SyntaxTestDialog(qtutils
.active_window())