Plugins/VcsPlugins/vcsGit/GitDiffHighlighter.py

changeset 6020
baf6da1ae288
child 6048
82ad8ec9548c
equal deleted inserted replaced
6019:58ecdaf0b789 6020:baf6da1ae288
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2015 - 2017 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a syntax highlighter for diff outputs.
8 """
9
10 from __future__ import unicode_literals
11
12 import re
13
14 from PyQt5.QtGui import QSyntaxHighlighter, QColor, QTextCharFormat, QFont
15
16 import Preferences
17
18 try:
19 from E5Gui.E5GenericDiffHighlighter import TERMINAL, \
20 E5GenericDiffHighlighter
21 except ImportError:
22 def TERMINAL(pattern):
23 """
24 Function to mark a pattern as the final one to search for.
25
26 @param pattern pattern to be marked (string)
27 @return marked pattern (string)
28 """
29 return "__TERMINAL__:{0}".format(pattern)
30
31 # Cache the results of re.compile for performance reasons
32 _REGEX_CACHE = {}
33
34 class E5GenericDiffHighlighter(QSyntaxHighlighter):
35 """
36 Class implementing a generic diff highlighter.
37 """
38 def __init__(self, doc):
39 """
40 Constructor
41
42 @param doc reference to the text document (QTextDocument)
43 """
44 super(E5GenericDiffHighlighter, self).__init__(doc)
45
46 self.textColor = QColor(0, 0, 0)
47 self.addedColor = QColor(190, 237, 190)
48 self.removedColor = QColor(237, 190, 190)
49 self.replacedColor = QColor(190, 190, 237)
50 self.contextColor = QColor(255, 220, 168)
51 self.headerColor = QColor(237, 237, 190)
52
53 self.normalFormat = self.makeFormat()
54
55 self._rules = []
56 self.generateRules()
57
58 def generateRules(self):
59 """
60 Public method to generate the rule set.
61
62 Note: This method must me implemented by derived syntax
63 highlighters.
64 """
65 pass
66
67 def createRules(self, *rules):
68 """
69 Public method to create the highlighting rules.
70
71 @param rules set of highlighting rules (list of tuples of rule
72 pattern (string) and highlighting format (QTextCharFormat))
73 """
74 for idx, ruleFormat in enumerate(rules):
75 rule, formats = ruleFormat
76 terminal = rule.startswith(TERMINAL(''))
77 if terminal:
78 rule = rule[len(TERMINAL('')):]
79 try:
80 regex = _REGEX_CACHE[rule]
81 except KeyError:
82 regex = _REGEX_CACHE[rule] = re.compile(rule)
83 self._rules.append((regex, formats, terminal))
84
85 def formats(self, line):
86 """
87 Public method to determine the highlighting formats for a line of
88 text.
89
90 @param line text line to be highlighted (string)
91 @return list of matched highlighting rules (list of tuples of match
92 object and format (QTextCharFormat))
93 """
94 matched = []
95 for rx, formats, terminal in self._rules:
96 match = rx.match(line)
97 if not match:
98 continue
99 matched.append([match, formats])
100 if terminal:
101 return matched
102
103 return matched
104
105 def makeFormat(self, fg=None, bg=None, bold=False):
106 """
107 Public method to generate a format definition.
108
109 @param fg foreground color (QColor)
110 @param bg background color (QColor)
111 @param bold flag indicating bold text (boolean)
112 @return format definiton (QTextCharFormat)
113 """
114 font = Preferences.getEditorOtherFonts("MonospacedFont")
115 charFormat = QTextCharFormat()
116 charFormat.setFontFamily(font.family())
117 charFormat.setFontPointSize(font.pointSize())
118
119 if fg:
120 charFormat.setForeground(fg)
121
122 if bg:
123 charFormat.setBackground(bg)
124
125 if bold:
126 charFormat.setFontWeight(QFont.Bold)
127
128 return charFormat
129
130 def highlightBlock(self, text):
131 """
132 Public method to highlight a block of text.
133
134 @param text text to be highlighted (string)
135 """
136 formats = self.formats(text)
137 if not formats:
138 # nothing matched
139 self.setFormat(0, len(text), self.normalFormat)
140 return
141
142 for match, charFormat in formats:
143 start = match.start()
144 groups = match.groups()
145
146 # No groups in the regex, assume this is a single rule
147 # that spans the entire line
148 if not groups:
149 self.setFormat(0, len(text), charFormat)
150 continue
151
152 # Groups exist, rule is a tuple corresponding to group
153 for groupIndex, group in enumerate(groups):
154 if not group:
155 # empty match
156 continue
157
158 # allow None as a no-op format
159 length = len(group)
160 if charFormat[groupIndex]:
161 self.setFormat(start, start + length,
162 charFormat[groupIndex])
163 start += length
164
165
166 class GitDiffHighlighter(E5GenericDiffHighlighter):
167 """
168 Class implementing a diff highlighter for Git.
169 """
170 def __init__(self, doc, whitespace=True):
171 """
172 Constructor
173
174 @param doc reference to the text document (QTextDocument)
175 @param whitespace flag indicating to highlight whitespace
176 at the end of a line (boolean)
177 """
178 self.whitespace = whitespace
179
180 super(GitDiffHighlighter, self).__init__(doc)
181
182 def generateRules(self):
183 """
184 Public method to generate the rule set.
185 """
186 diffHeader = self.makeFormat(fg=self.textColor,
187 bg=self.headerColor)
188 diffHeaderBold = self.makeFormat(fg=self.textColor,
189 bg=self.headerColor,
190 bold=True)
191 diffContext = self.makeFormat(fg=self.textColor,
192 bg=self.contextColor)
193
194 diffAdded = self.makeFormat(fg=self.textColor,
195 bg=self.addedColor)
196 diffRemoved = self.makeFormat(fg=self.textColor,
197 bg=self.removedColor)
198
199 if self.whitespace:
200 try:
201 badWhitespace = self.makeFormat(fg=self.textColor,
202 bg=self.whitespaceColor)
203 except AttributeError:
204 badWhitespace = self.makeFormat(fg=self.textColor,
205 bg=QColor(255, 0, 0, 192))
206
207 # We specify the whitespace rule last so that it is
208 # applied after the diff addition/removal rules.
209 diffOldRegex = TERMINAL(r'^--- ')
210 diffNewRegex = TERMINAL(r'^\+\+\+ ')
211 diffContextRegex = TERMINAL(r'^@@ ')
212
213 diffHeader1Regex = TERMINAL(r'^diff --git a/.*b/.*')
214 diffHeader2Regex = TERMINAL(r'^index \S+\.\.\S+')
215 diffHeader3Regex = TERMINAL(r'^new file mode')
216 diffHeader4Regex = TERMINAL(r'^deleted file mode')
217
218 diffAddedRegex = TERMINAL(r'^\+')
219 diffRemovedRegex = TERMINAL(r'^-')
220 diffBarRegex = TERMINAL(r'^([ ]+.*)(\|[ ]+\d+[ ]+[+-]+)$')
221 diffStsRegex = (r'(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
222 diffSummaryRegex = (r'(\s+\d+ files changed[^\d]*)'
223 r'(:?\d+ insertions[^\d]*)'
224 r'(:?\d+ deletions.*)$')
225
226 if self.whitespace:
227 self.createRules((r'(..*?)(\s+)$', (None, badWhitespace)))
228 self.createRules((diffOldRegex, diffRemoved),
229 (diffNewRegex, diffAdded),
230 (diffContextRegex, diffContext),
231 (diffBarRegex, (diffHeaderBold, diffHeader)),
232 (diffHeader1Regex, diffHeader),
233 (diffHeader2Regex, diffHeader),
234 (diffHeader3Regex, diffHeader),
235 (diffHeader4Regex, diffHeader),
236 (diffAddedRegex, diffAdded),
237 (diffRemovedRegex, diffRemoved),
238 (diffStsRegex, (None, diffHeader,
239 None, diffHeader,
240 diffHeader)),
241 (diffSummaryRegex, (diffHeader,
242 diffHeader,
243 diffHeader))
244 )

eric ide

mercurial