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
):
204 QtGui
.QCompleter
.__init
__(self
, parent
)
205 self
._model
= GitRefModel(parent
)
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):
219 QtGui
.QLineEdit
.__init
__(self
, parent
)
220 self
.refcompleter
= GitRefCompleter(self
)
221 self
.setCompleter(self
.refcompleter
)
223 self
.refcompleter
.popup().installEventFilter(self
)
225 def eventFilter(self
, obj
, event
):
226 """Fix an annoyance on OS X
228 The completer popup steals focus. Work around it.
229 This affects dialogs without QtCore.Qt.WindowModal modality.
232 if obj
== self
.refcompleter
.popup():
233 if event
.type() == QtCore
.QEvent
.FocusIn
:
237 def mouseReleaseEvent(self
, event
):
238 super(GitRefLineEdit
, self
).mouseReleaseEvent(event
)
239 self
.refcompleter
.complete()
242 class GitRefDialog(QtGui
.QDialog
):
243 def __init__(self
, title
, button_text
, parent
):
244 super(GitRefDialog
, self
).__init
__(parent
)
245 self
.setWindowTitle(title
)
247 self
.label
= QtGui
.QLabel()
248 self
.label
.setText(title
)
250 self
.lineedit
= GitRefLineEdit(self
)
251 self
.setFocusProxy(self
.lineedit
)
253 self
.ok_button
= QtGui
.QPushButton()
254 self
.ok_button
.setText(self
.tr(button_text
))
255 self
.ok_button
.setIcon(qtutils
.apply_icon())
257 self
.close_button
= QtGui
.QPushButton()
258 self
.close_button
.setText(self
.tr('Close'))
260 self
.button_layout
= QtGui
.QHBoxLayout()
261 self
.button_layout
.setMargin(0)
262 self
.button_layout
.setSpacing(defs
.button_spacing
)
263 self
.button_layout
.addStretch()
264 self
.button_layout
.addWidget(self
.ok_button
)
265 self
.button_layout
.addWidget(self
.close_button
)
267 self
.main_layout
= QtGui
.QVBoxLayout()
268 self
.main_layout
.setMargin(defs
.margin
)
269 self
.main_layout
.setSpacing(defs
.spacing
)
271 self
.main_layout
.addWidget(self
.label
)
272 self
.main_layout
.addWidget(self
.lineedit
)
273 self
.main_layout
.addLayout(self
.button_layout
)
274 self
.setLayout(self
.main_layout
)
276 qtutils
.connect_button(self
.ok_button
, self
.accept
)
277 qtutils
.connect_button(self
.close_button
, self
.reject
)
279 self
.connect(self
.lineedit
, SIGNAL('textChanged(QString)'),
282 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
283 self
.ok_button
.setEnabled(False)
286 return unicode(self
.lineedit
.text())
288 def text_changed(self
, txt
):
289 self
.ok_button
.setEnabled(bool(self
.text()))
292 def ref(title
, button_text
, parent
):
293 dlg
= GitRefDialog(title
, button_text
, parent
)
297 if dlg
.exec_() == GitRefDialog
.Accepted
:
303 class GitRefModel(QtGui
.QStandardItemModel
):
304 def __init__(self
, parent
):
305 QtGui
.QStandardItemModel
.__init
__(self
, parent
)
306 self
.cmodel
= cola
.model()
307 msg
= self
.cmodel
.message_updated
308 self
.cmodel
.add_observer(msg
, self
.update_matches
)
309 self
.update_matches()
312 self
.cmodel
.remove_observer(self
.update_matches
)
314 def update_matches(self
):
316 matches
= model
.local_branches
+ model
.remote_branches
+ model
.tags
317 QStandardItem
= QtGui
.QStandardItem
319 for match
in matches
:
320 item
= QStandardItem()
321 item
.setIcon(qtutils
.git_icon())
326 class UpdateGitLogCompletionModelThread(QtCore
.QThread
):
327 def __init__(self
, model
):
328 QtCore
.QThread
.__init
__(self
)
330 self
.case_insensitive
= False
334 # Loop when the matched text changes between the start and end time.
335 # This happens when gather_matches() takes too long and the
336 # model's matched_text changes in-between.
337 while text
!= self
.model
.matched_text
:
338 text
= self
.model
.matched_text
339 items
= self
.model
.gather_matches(self
.case_insensitive
)
340 self
.emit(SIGNAL('items_gathered'), *items
)
343 class GitLogCompletionModel(QtGui
.QStandardItemModel
):
344 def __init__(self
, parent
):
345 self
.matched_text
= None
346 QtGui
.QStandardItemModel
.__init
__(self
, parent
)
347 self
.cmodel
= cola
.model()
348 self
.update_thread
= UpdateGitLogCompletionModelThread(self
)
349 self
.connect(self
.update_thread
, SIGNAL('items_gathered'),
352 def lower_cmp(self
, a
, b
):
353 return cmp(a
.replace('.','').lower(), b
.replace('.','').lower())
355 def update_matches(self
, case_insensitive
):
356 self
.update_thread
.case_insensitive
= case_insensitive
357 if not self
.update_thread
.isRunning():
358 self
.update_thread
.start()
360 def gather_matches(self
, case_sensitive
):
361 file_list
= self
.cmodel
.everything()
362 files
= set(file_list
)
363 files_and_dirs
= utils
.add_parents(set(files
))
365 dirs
= files_and_dirs
.difference(files
)
368 refs
= model
.local_branches
+ model
.remote_branches
+ model
.tags
369 matched_text
= self
.matched_text
373 matched_refs
= [r
for r
in refs
if matched_text
in r
]
375 matched_refs
= [r
for r
in refs
376 if matched_text
.lower() in r
.lower()]
380 matched_refs
.sort(cmp=self
.lower_cmp
)
384 matched_paths
= [f
for f
in files_and_dirs
385 if matched_text
in f
]
387 matched_paths
= [f
for f
in files_and_dirs
388 if matched_text
.lower() in f
.lower()]
390 matched_paths
= list(files_and_dirs
)
392 matched_paths
.sort(cmp=self
.lower_cmp
)
394 return (matched_refs
, matched_paths
, dirs
)
397 def apply_matches(self
, matched_refs
, matched_paths
, dirs
):
398 QStandardItem
= QtGui
.QStandardItem
399 file_icon
= qtutils
.file_icon()
400 dir_icon
= qtutils
.dir_icon()
401 git_icon
= qtutils
.git_icon()
403 matched_text
= self
.matched_text
405 for ref
in matched_refs
:
406 item
= QStandardItem()
408 item
.setIcon(git_icon
)
411 if matched_paths
and (not matched_text
or matched_text
in '--'):
412 item
= QStandardItem()
414 item
.setIcon(file_icon
)
417 for match
in matched_paths
:
418 item
= QStandardItem()
421 item
.setIcon(dir_icon
)
423 item
.setIcon(file_icon
)
427 self
.invisibleRootItem().appendRows(items
)
429 def set_match_text(self
, text
, case_sensitive
):
430 self
.matched_text
= text
431 self
.update_matches(case_sensitive
)
434 class GitLogLineEdit(QtGui
.QLineEdit
):
435 def __init__(self
, parent
=None):
436 QtGui
.QLineEdit
.__init
__(self
, parent
)
437 # used to hide the completion popup after a drag-select
440 self
._model
= GitLogCompletionModel(self
)
441 self
._delegate
= HighlightCompletionDelegate(self
)
443 self
._completer
= QtGui
.QCompleter(self
)
444 self
._completer
.setWidget(self
)
445 self
._completer
.setModel(self
._model
)
446 self
._completer
.setCompletionMode(
447 QtGui
.QCompleter
.UnfilteredPopupCompletion
)
448 self
._completer
.popup().setItemDelegate(self
._delegate
)
450 self
.connect(self
._completer
, SIGNAL('activated(QString)'),
452 self
.connect(self
, SIGNAL('textChanged(QString)'), self
._text
_changed
)
453 self
._keys
_to
_ignore
= set([QtCore
.Qt
.Key_Enter
,
454 QtCore
.Qt
.Key_Return
,
455 QtCore
.Qt
.Key_Escape
])
457 def is_case_sensitive(self
, text
):
458 return bool([char
for char
in text
if char
.isupper()])
460 def _text_changed(self
, text
):
461 text
= self
.last_word()
462 case_sensitive
= self
.is_case_sensitive(text
)
464 self
._completer
.setCaseSensitivity(QtCore
.Qt
.CaseSensitive
)
466 self
._completer
.setCaseSensitivity(QtCore
.Qt
.CaseInsensitive
)
467 self
._delegate
.set_highlight_text(text
, case_sensitive
)
468 self
._model
.set_match_text(text
, case_sensitive
)
470 def update_matches(self
):
471 text
= self
.last_word()
472 case_sensitive
= self
.is_case_sensitive(text
)
473 self
._model
.update_matches(case_sensitive
)
475 def _complete(self
, completion
):
477 This is the event handler for the QCompleter.activated(QString) signal,
478 it is called when the user selects an item in the completer popup.
485 words
.append(unicode(completion
))
486 self
.setText(subprocess
.list2cmdline(words
))
487 self
.emit(SIGNAL('ref_changed'))
490 return utils
.shell_usplit(unicode(self
.text()))
495 return unicode(self
.text())
500 def event(self
, event
):
501 if event
.type() == QtCore
.QEvent
.KeyPress
:
502 if (event
.key() == QtCore
.Qt
.Key_Tab
and
503 self
._completer
.popup().isVisible()):
506 return QtGui
.QLineEdit
.event(self
, event
)
508 def do_completion(self
):
509 self
._completer
.popup().setCurrentIndex(
510 self
._completer
.completionModel().index(0,0))
511 self
._completer
.complete()
513 def keyPressEvent(self
, event
):
514 if self
._completer
.popup().isVisible():
515 if event
.key() in self
._keys
_to
_ignore
:
517 self
._complete
(self
.last_word())
520 elif (event
.key() == QtCore
.Qt
.Key_Down
and
521 self
._completer
.completionCount() > 0):
526 QtGui
.QLineEdit
.keyPressEvent(self
, event
)
528 prefix
= self
.last_word()
529 if prefix
!= unicode(self
._completer
.completionPrefix()):
530 self
._update
_popup
_items
(prefix
)
531 if len(event
.text()) > 0 and len(prefix
) > 0:
532 self
._completer
.complete()
534 self
._completer
.popup().hide()
536 #: _drag: 0 - unclicked, 1 - clicked, 2 - dragged
537 def mousePressEvent(self
, event
):
539 return QtGui
.QLineEdit
.mousePressEvent(self
, event
)
541 def mouseMoveEvent(self
, event
):
544 return QtGui
.QLineEdit
.mouseMoveEvent(self
, event
)
546 def mouseReleaseEvent(self
, event
):
547 if self
._drag
!= 2 and event
.buttons() != QtCore
.Qt
.RightButton
:
550 return QtGui
.QLineEdit
.mouseReleaseEvent(self
, event
)
552 def close_popup(self
):
553 self
._completer
.popup().close()
555 def _update_popup_items(self
, prefix
):
557 Filters the completer's popup items to only show items
558 with the given prefix.
560 self
._completer
.setCompletionPrefix(prefix
)
561 self
._completer
.popup().setCurrentIndex(
562 self
._completer
.completionModel().index(0,0))
565 class HighlightCompletionDelegate(QtGui
.QStyledItemDelegate
):
566 """A delegate used for auto-completion to give formatted completion"""
567 def __init__(self
, parent
=None): # model, parent=None):
568 QtGui
.QStyledItemDelegate
.__init
__(self
, parent
)
569 self
.highlight_text
= ''
570 self
.case_sensitive
= False
572 self
.doc
= QtGui
.QTextDocument()
573 self
.doc
.setDocumentMargin(0)
575 def set_highlight_text(self
, text
, case_sensitive
):
576 """Sets the text that will be made bold in the term name when displayed"""
577 self
.highlight_text
= text
578 self
.case_sensitive
= case_sensitive
580 def paint(self
, painter
, option
, index
):
581 """Overloaded Qt method for custom painting of a model index"""
582 if not self
.highlight_text
:
583 return QtGui
.QStyledItemDelegate
.paint(self
, painter
, option
, index
)
585 text
= unicode(index
.data().toPyObject())
586 if self
.case_sensitive
:
587 html
= text
.replace(self
.highlight_text
,
588 '<strong>%s</strong>' % self
.highlight_text
)
590 match
= re
.match('(.*)(' + self
.highlight_text
+ ')(.*)',
593 start
= match
.group(1) or ''
594 middle
= match
.group(2) or ''
595 end
= match
.group(3) or ''
596 html
= (start
+ ('<strong>%s</strong>' % middle
) + end
)
599 self
.doc
.setHtml(html
)
601 # Painting item without text, Text Document will paint the text
602 optionV4
= QtGui
.QStyleOptionViewItemV4(option
)
603 self
.initStyleOption(optionV4
, index
)
604 optionV4
.text
= QtCore
.QString()
606 style
= QtGui
.QApplication
.style()
607 style
.drawControl(QtGui
.QStyle
.CE_ItemViewItem
, optionV4
, painter
)
608 ctx
= QtGui
.QAbstractTextDocumentLayout
.PaintContext()
610 # Highlighting text if item is selected
611 if (optionV4
.state
& QtGui
.QStyle
.State_Selected
):
612 ctx
.palette
.setColor(QtGui
.QPalette
.Text
, optionV4
.palette
.color(QtGui
.QPalette
.Active
, QtGui
.QPalette
.HighlightedText
))
614 # translate the painter to where the text is drawn
615 textRect
= style
.subElementRect(QtGui
.QStyle
.SE_ItemViewItemText
, optionV4
)
618 start
= textRect
.topLeft() + QtCore
.QPoint(3, 0)
619 painter
.translate(start
)
620 painter
.setClipRect(textRect
.translated(-start
))
622 # tell the text document to draw the html for us
623 self
.doc
.documentLayout().draw(painter
, ctx
)
626 # Syntax highlighting
628 def TERMINAL(pattern
):
630 Denotes that a pattern is the final pattern that should
631 be matched. If this pattern matches no other formats
632 will be applied, even if they would have matched.
634 return '__TERMINAL__:%s' % pattern
636 # Cache the results of re.compile so that we don't keep
637 # rebuilding the same regexes whenever stylesheets change
640 def rgba(r
, g
, b
, a
=255):
647 'color_text': rgba(0x00, 0x00, 0x00),
648 'color_add': rgba(0xcd, 0xff, 0xe0),
649 'color_remove': rgba(0xff, 0xd0, 0xd0),
650 'color_header': rgba(0xbb, 0xbb, 0xbb),
654 class GenericSyntaxHighligher(QSyntaxHighlighter
):
655 def __init__(self
, doc
, *args
, **kwargs
):
656 QSyntaxHighlighter
.__init
__(self
, doc
)
657 for attr
, val
in default_colors
.items():
658 setattr(self
, attr
, val
)
660 self
.generate_rules()
662 def generate_rules(self
):
665 def create_rules(self
, *rules
):
667 raise Exception('create_rules requires an even '
668 'number of arguments.')
669 for idx
, rule
in enumerate(rules
):
672 formats
= rules
[idx
+1]
673 terminal
= rule
.startswith(TERMINAL(''))
675 rule
= rule
[len(TERMINAL('')):]
677 regex
= _RGX_CACHE
[rule
]
679 regex
= _RGX_CACHE
[rule
] = re
.compile(rule
)
680 self
._rules
.append((regex
, formats
, terminal
,))
682 def formats(self
, line
):
684 for regex
, fmts
, terminal
in self
._rules
:
685 match
= regex
.match(line
)
688 matched
.append([match
, fmts
])
693 def mkformat(self
, fg
=None, bg
=None, bold
=False):
694 fmt
= QTextCharFormat()
696 fmt
.setForeground(fg
)
698 fmt
.setBackground(bg
)
700 fmt
.setFontWeight(QFont
.Bold
)
703 def highlightBlock(self
, qstr
):
704 ascii
= unicode(qstr
)
707 formats
= self
.formats(ascii
)
710 for match
, fmts
in formats
:
711 start
= match
.start()
712 groups
= match
.groups()
714 # No groups in the regex, assume this is a single rule
715 # that spans the entire line
717 self
.setFormat(0, len(ascii
), fmts
)
720 # Groups exist, rule is a tuple corresponding to group
721 for grpidx
, group
in enumerate(groups
):
722 # allow empty matches
725 # allow None as a no-op format
728 self
.setFormat(start
, start
+length
,
732 def set_colors(self
, colordict
):
733 for attr
, val
in colordict
.items():
734 setattr(self
, attr
, val
)
737 class DiffSyntaxHighlighter(GenericSyntaxHighligher
):
738 """Implements the diff syntax highlighting
740 This class is used by widgets that display diffs.
743 def __init__(self
, doc
, whitespace
=True):
744 self
.whitespace
= whitespace
745 GenericSyntaxHighligher
.__init
__(self
, doc
)
747 def generate_rules(self
):
748 diff_head
= self
.mkformat(fg
=self
.color_header
)
749 diff_head_bold
= self
.mkformat(fg
=self
.color_header
, bold
=True)
751 diff_add
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_add
)
752 diff_remove
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_remove
)
755 bad_ws
= self
.mkformat(fg
=Qt
.black
, bg
=Qt
.red
)
757 # We specify the whitespace rule last so that it is
758 # applied after the diff addition/removal rules.
759 # The rules for the header
760 diff_old_rgx
= TERMINAL(r
'^--- ')
761 diff_new_rgx
= TERMINAL(r
'^\+\+\+ ')
762 diff_ctx_rgx
= TERMINAL(r
'^@@ ')
764 diff_hd1_rgx
= TERMINAL(r
'^diff --git a/.*b/.*')
765 diff_hd2_rgx
= TERMINAL(r
'^index \S+\.\.\S+')
766 diff_hd3_rgx
= TERMINAL(r
'^new file mode')
767 diff_hd4_rgx
= TERMINAL(r
'^deleted file mode')
768 diff_add_rgx
= TERMINAL(r
'^\+')
769 diff_rmv_rgx
= TERMINAL(r
'^-')
770 diff_bar_rgx
= TERMINAL(r
'^([ ]+.*)(\|[ ]+\d+[ ]+[+-]+)$')
771 diff_sts_rgx
= (r
'(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
772 diff_sum_rgx
= (r
'(\s+\d+ files changed[^\d]*)'
773 r
'(:?\d+ insertions[^\d]*)'
774 r
'(:?\d+ deletions.*)$')
776 self
.create_rules(diff_old_rgx
, diff_head
,
777 diff_new_rgx
, diff_head
,
778 diff_ctx_rgx
, diff_head_bold
,
779 diff_bar_rgx
, (diff_head_bold
, diff_head
),
780 diff_hd1_rgx
, diff_head
,
781 diff_hd2_rgx
, diff_head
,
782 diff_hd3_rgx
, diff_head
,
783 diff_hd4_rgx
, diff_head
,
784 diff_add_rgx
, diff_add
,
785 diff_rmv_rgx
, diff_remove
,
786 diff_sts_rgx
, (None, diff_head
,
789 diff_sum_rgx
, (diff_head
,
793 self
.create_rules('(..*?)(\s+)$', (None, bad_ws
))
796 if __name__
== '__main__':
798 class SyntaxTestDialog(QtGui
.QDialog
):
799 def __init__(self
, parent
):
800 QtGui
.QDialog
.__init
__(self
, parent
)
801 self
.resize(720, 512)
802 self
.vboxlayout
= QtGui
.QVBoxLayout(self
)
803 self
.vboxlayout
.setObjectName('vboxlayout')
804 self
.output_text
= QtGui
.QTextEdit(self
)
806 if utils
.is_darwin():
810 font
.setFamily(family
)
811 font
.setPointSize(12)
812 self
.output_text
.setFont(font
)
813 self
.output_text
.setAcceptDrops(False)
814 self
.vboxlayout
.addWidget(self
.output_text
)
815 self
.syntax
= DiffSyntaxHighlighter(self
.output_text
.document())
817 app
= QtGui
.QApplication(sys
.argv
)
818 dialog
= SyntaxTestDialog(qtutils
.active_window())