2 # Copyright (c) 2008 David Aguilar
4 from PyQt4
.QtCore
import Qt
5 from PyQt4
.QtCore
import pyqtProperty
6 from PyQt4
.QtCore
import QVariant
7 from PyQt4
.QtGui
import QFont
8 from PyQt4
.QtGui
import QSyntaxHighlighter
9 from PyQt4
.QtGui
import QTextCharFormat
10 from PyQt4
.QtGui
import QColor
12 def TERMINAL(pattern
):
14 Denotes that a pattern is the final pattern that should
15 be matched. If this pattern matches no other formats
16 will be applied, even if they would have matched.
18 return '__TERMINAL__:%s' % pattern
20 # Cache the results of re.compile so that we don't keep
21 # rebuilding the same regexes whenever stylesheets change
25 def _install_default_colors():
30 default_colors
.update({
31 'color_add': color(Qt
.green
, 128),
32 'color_remove': color(Qt
.red
, 128),
33 'color_begin': color(Qt
.darkCyan
),
34 'color_header': color(Qt
.darkYellow
),
35 'color_stat_add': color(QColor(32, 255, 32)),
36 'color_stat_info': color(QColor(32, 32, 255)),
37 'color_stat_remove': color(QColor(255, 32, 32)),
38 'color_emphasis': color(Qt
.black
),
39 'color_info': color(Qt
.blue
),
40 'color_date': color(Qt
.darkCyan
),
42 _install_default_colors()
44 class GenericSyntaxHighligher(QSyntaxHighlighter
):
45 def __init__(self
, doc
, *args
, **kwargs
):
46 QSyntaxHighlighter
.__init
__(self
, doc
)
47 for attr
, val
in default_colors
.items():
48 setattr(self
, attr
, val
)
49 self
.init(doc
, *args
, **kwargs
)
52 def init(self
, *args
, **kwargs
):
59 def generate_rules(self
):
62 def create_rules(self
, *rules
):
64 raise Exception('create_rules requires an even '
65 'number of arguments.')
66 for idx
, rule
in enumerate(rules
):
69 formats
= rules
[idx
+1]
70 terminal
= rule
.startswith(TERMINAL(''))
72 rule
= rule
[len(TERMINAL('')):]
73 if rule
in _RGX_CACHE
:
74 regex
= _RGX_CACHE
[rule
]
76 regex
= re
.compile(rule
)
77 _RGX_CACHE
[rule
] = regex
78 self
._rules
.append((regex
, formats
, terminal
,))
80 def get_formats(self
, line
):
82 for regex
, fmts
, terminal
in self
._rules
:
83 match
= regex
.match(line
)
85 matched
.append([match
, fmts
])
90 def mkformat(self
, fg
=None, bg
=None, bold
=False):
91 format
= QTextCharFormat()
92 if fg
: format
.setForeground(fg
)
93 if bg
: format
.setBackground(bg
)
94 if bold
: format
.setFontWeight(QFont
.Bold
)
97 def highlightBlock(self
, qstr
):
98 ascii
= qstr
.toAscii().data()
100 formats
= self
.get_formats(ascii
)
101 if not formats
: return
102 for match
, fmts
in formats
:
103 start
= match
.start()
105 groups
= match
.groups()
107 # No groups in the regex, assume this is a single rule
108 # that spans the entire line
110 self
.setFormat(0, len(ascii
), fmts
)
113 # Groups exist, rule is a tuple corresponding to group
114 for grpidx
, group
in enumerate(groups
):
115 # allow empty matches
116 if not group
: continue
117 # allow None as a no-op format
120 self
.setFormat(start
, start
+length
,
124 def set_colors(self
, colordict
):
125 for attr
, val
in colordict
.items():
126 setattr(self
, attr
, val
)
128 class DiffSyntaxHighlighter(GenericSyntaxHighligher
):
129 def init(self
, doc
, whitespace
=True):
130 self
.whitespace
= whitespace
131 GenericSyntaxHighligher
.init(self
, doc
)
133 def generate_rules(self
):
134 diff_begin
= self
.mkformat(self
.color_begin
, bold
=True)
135 diff_head
= self
.mkformat(self
.color_header
)
136 diff_add
= self
.mkformat(bg
=self
.color_add
)
137 diff_remove
= self
.mkformat(bg
=self
.color_remove
)
139 diffstat_info
= self
.mkformat(self
.color_stat_info
, bold
=True)
140 diffstat_add
= self
.mkformat(self
.color_stat_add
, bold
=True)
141 diffstat_remove
= self
.mkformat(self
.color_stat_remove
, bold
=True)
144 bad_ws
= self
.mkformat(Qt
.black
, Qt
.red
)
146 # We specify the whitespace rule last so that it is
147 # applied after the diff addition/removal rules.
148 # The rules for the header
149 diff_bgn_rgx
= TERMINAL('^@@|^\+\+\+|^---')
150 diff_hd1_rgx
= TERMINAL('^diff --git')
151 diff_hd2_rgx
= TERMINAL('^index \S+\.\.\S+')
152 diff_hd3_rgx
= TERMINAL('^new file mode')
153 diff_add_rgx
= TERMINAL('^\+')
154 diff_rmv_rgx
= TERMINAL('^-')
155 diff_sts_rgx
= ('(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
156 diff_sum_rgx
= ('(\s+\d+ files changed[^\d]*)'
157 '(:?\d+ insertions[^\d]*)'
158 '(:?\d+ deletions.*)$')
160 self
.create_rules(diff_bgn_rgx
, diff_begin
,
161 diff_hd1_rgx
, diff_head
,
162 diff_hd2_rgx
, diff_head
,
163 diff_hd3_rgx
, diff_head
,
164 diff_add_rgx
, diff_add
,
165 diff_rmv_rgx
, diff_remove
,
166 diff_sts_rgx
, (None, diffstat_info
,
169 diff_sum_rgx
, (diffstat_info
,
173 self
.create_rules('(..*?)(\s+)$', (None, bad_ws
))
175 class LogSyntaxHighlighter(GenericSyntaxHighligher
):
176 def generate_rules(self
):
177 info
= self
.mkformat(self
.color_info
, bold
=True)
178 emphasis
= self
.mkformat(self
.color_emphasis
, bold
=True)
179 date
= self
.mkformat(self
.color_date
, bold
=True)
181 info_rgx
= '^([^:]+:)(.*)$'
182 date_rgx
= TERMINAL('^\w{3}\W+\w{3}\W+\d+\W+[:0-9]+\W+\d{4}$')
184 self
.create_rules(date_rgx
, date
,
185 info_rgx
, (info
, emphasis
))
187 # This is used as a mixin to generate property callbacks
189 private_attr
= '_'+attr
191 if private_attr
in self
.__dict
__:
192 return self
.__dict
__[private_attr
]
195 def setter(self
, value
):
196 self
.__dict
__[private_attr
] = value
198 return (getter
, setter
)
200 def install_theme_properties(cls
):
201 # Diff GUI colors -- this is controllable via the style sheet
202 for name
in default_colors
:
203 setattr(cls
, name
, pyqtProperty('QColor', *accessors(name
)))
205 def set_theme_properties(widget
):
206 for name
, color
in default_colors
.items():
207 widget
.setProperty(name
, QVariant(color
))
210 if __name__
== '__main__':
212 from PyQt4
import QtCore
, QtGui
213 class SyntaxTestDialog(QtGui
.QDialog
):
214 def __init__(self
, parent
):
215 QtGui
.QDialog
.__init
__(self
, parent
)
217 def setupUi(self
, dialog
):
218 dialog
.resize(QtCore
.QSize(QtCore
.QRect(0,0,720,512).size()).expandedTo(dialog
.minimumSizeHint()))
219 self
.vboxlayout
= QtGui
.QVBoxLayout(dialog
)
220 self
.vboxlayout
.setObjectName('vboxlayout')
221 self
.output_text
= QtGui
.QTextEdit(dialog
)
223 font
.setFamily('Monospace')
224 font
.setPointSize(13)
225 self
.output_text
.setFont(font
)
226 self
.output_text
.setAcceptDrops(False)
227 self
.vboxlayout
.addWidget(self
.output_text
)
228 self
.syntax
= DiffSyntaxHighlighter(self
.output_text
.document())
230 app
= QtGui
.QApplication(sys
.argv
)
231 dialog
= SyntaxTestDialog(app
.activeWindow())