##// END OF EJS Templates
annotations: fixed UI problems in annotation view for newer browsers.
marcink -
r1427:a6bc3e0e stable
parent child Browse files
Show More
@@ -1,703 +1,703 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import difflib
22 import difflib
23 from itertools import groupby
23 from itertools import groupby
24
24
25 from pygments import lex
25 from pygments import lex
26 from pygments.formatters.html import _get_ttype_class as pygment_token_class
26 from pygments.formatters.html import _get_ttype_class as pygment_token_class
27 from rhodecode.lib.helpers import (
27 from rhodecode.lib.helpers import (
28 get_lexer_for_filenode, html_escape)
28 get_lexer_for_filenode, html_escape)
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.utils2 import AttributeDict
30 from rhodecode.lib.vcs.nodes import FileNode
30 from rhodecode.lib.vcs.nodes import FileNode
31 from rhodecode.lib.diff_match_patch import diff_match_patch
31 from rhodecode.lib.diff_match_patch import diff_match_patch
32 from rhodecode.lib.diffs import LimitedDiffContainer
32 from rhodecode.lib.diffs import LimitedDiffContainer
33 from pygments.lexers import get_lexer_by_name
33 from pygments.lexers import get_lexer_by_name
34
34
35 plain_text_lexer = get_lexer_by_name(
35 plain_text_lexer = get_lexer_by_name(
36 'text', stripall=False, stripnl=False, ensurenl=False)
36 'text', stripall=False, stripnl=False, ensurenl=False)
37
37
38
38
39 log = logging.getLogger()
39 log = logging.getLogger()
40
40
41
41
42 def filenode_as_lines_tokens(filenode, lexer=None):
42 def filenode_as_lines_tokens(filenode, lexer=None):
43 org_lexer = lexer
43 org_lexer = lexer
44 lexer = lexer or get_lexer_for_filenode(filenode)
44 lexer = lexer or get_lexer_for_filenode(filenode)
45 log.debug('Generating file node pygment tokens for %s, %s, org_lexer:%s',
45 log.debug('Generating file node pygment tokens for %s, %s, org_lexer:%s',
46 lexer, filenode, org_lexer)
46 lexer, filenode, org_lexer)
47 tokens = tokenize_string(filenode.content, lexer)
47 tokens = tokenize_string(filenode.content, lexer)
48 lines = split_token_stream(tokens, split_string='\n')
48 lines = split_token_stream(tokens, split_string='\n')
49 rv = list(lines)
49 rv = list(lines)
50 return rv
50 return rv
51
51
52
52
53 def tokenize_string(content, lexer):
53 def tokenize_string(content, lexer):
54 """
54 """
55 Use pygments to tokenize some content based on a lexer
55 Use pygments to tokenize some content based on a lexer
56 ensuring all original new lines and whitespace is preserved
56 ensuring all original new lines and whitespace is preserved
57 """
57 """
58
58
59 lexer.stripall = False
59 lexer.stripall = False
60 lexer.stripnl = False
60 lexer.stripnl = False
61 lexer.ensurenl = False
61 lexer.ensurenl = False
62 for token_type, token_text in lex(content, lexer):
62 for token_type, token_text in lex(content, lexer):
63 yield pygment_token_class(token_type), token_text
63 yield pygment_token_class(token_type), token_text
64
64
65
65
66 def split_token_stream(tokens, split_string=u'\n'):
66 def split_token_stream(tokens, split_string=u'\n'):
67 """
67 """
68 Take a list of (TokenType, text) tuples and split them by a string
68 Take a list of (TokenType, text) tuples and split them by a string
69
69
70 >>> split_token_stream([(TEXT, 'some\ntext'), (TEXT, 'more\n')])
70 >>> split_token_stream([(TEXT, 'some\ntext'), (TEXT, 'more\n')])
71 [(TEXT, 'some'), (TEXT, 'text'),
71 [(TEXT, 'some'), (TEXT, 'text'),
72 (TEXT, 'more'), (TEXT, 'text')]
72 (TEXT, 'more'), (TEXT, 'text')]
73 """
73 """
74
74
75 buffer = []
75 buffer = []
76 for token_class, token_text in tokens:
76 for token_class, token_text in tokens:
77 parts = token_text.split(split_string)
77 parts = token_text.split(split_string)
78 for part in parts[:-1]:
78 for part in parts[:-1]:
79 buffer.append((token_class, part))
79 buffer.append((token_class, part))
80 yield buffer
80 yield buffer
81 buffer = []
81 buffer = []
82
82
83 buffer.append((token_class, parts[-1]))
83 buffer.append((token_class, parts[-1]))
84
84
85 if buffer:
85 if buffer:
86 yield buffer
86 yield buffer
87
87
88
88
89 def filenode_as_annotated_lines_tokens(filenode):
89 def filenode_as_annotated_lines_tokens(filenode):
90 """
90 """
91 Take a file node and return a list of annotations => lines, if no annotation
91 Take a file node and return a list of annotations => lines, if no annotation
92 is found, it will be None.
92 is found, it will be None.
93
93
94 eg:
94 eg:
95
95
96 [
96 [
97 (annotation1, [
97 (annotation1, [
98 (1, line1_tokens_list),
98 (1, line1_tokens_list),
99 (2, line2_tokens_list),
99 (2, line2_tokens_list),
100 ]),
100 ]),
101 (annotation2, [
101 (annotation2, [
102 (3, line1_tokens_list),
102 (3, line1_tokens_list),
103 ]),
103 ]),
104 (None, [
104 (None, [
105 (4, line1_tokens_list),
105 (4, line1_tokens_list),
106 ]),
106 ]),
107 (annotation1, [
107 (annotation1, [
108 (5, line1_tokens_list),
108 (5, line1_tokens_list),
109 (6, line2_tokens_list),
109 (6, line2_tokens_list),
110 ])
110 ])
111 ]
111 ]
112 """
112 """
113
113
114 commit_cache = {} # cache commit_getter lookups
114 commit_cache = {} # cache commit_getter lookups
115
115
116 def _get_annotation(commit_id, commit_getter):
116 def _get_annotation(commit_id, commit_getter):
117 if commit_id not in commit_cache:
117 if commit_id not in commit_cache:
118 commit_cache[commit_id] = commit_getter()
118 commit_cache[commit_id] = commit_getter()
119 return commit_cache[commit_id]
119 return commit_cache[commit_id]
120
120
121 annotation_lookup = {
121 annotation_lookup = {
122 line_no: _get_annotation(commit_id, commit_getter)
122 line_no: _get_annotation(commit_id, commit_getter)
123 for line_no, commit_id, commit_getter, line_content
123 for line_no, commit_id, commit_getter, line_content
124 in filenode.annotate
124 in filenode.annotate
125 }
125 }
126
126
127 annotations_lines = ((annotation_lookup.get(line_no), line_no, tokens)
127 annotations_lines = ((annotation_lookup.get(line_no), line_no, tokens)
128 for line_no, tokens
128 for line_no, tokens
129 in enumerate(filenode_as_lines_tokens(filenode), 1))
129 in enumerate(filenode_as_lines_tokens(filenode), 1))
130
130
131 grouped_annotations_lines = groupby(annotations_lines, lambda x: x[0])
131 grouped_annotations_lines = groupby(annotations_lines, lambda x: x[0])
132
132
133 for annotation, group in grouped_annotations_lines:
133 for annotation, group in grouped_annotations_lines:
134 yield (
134 yield (
135 annotation, [(line_no, tokens)
135 annotation, [(line_no, tokens)
136 for (_, line_no, tokens) in group]
136 for (_, line_no, tokens) in group]
137 )
137 )
138
138
139
139
140 def render_tokenstream(tokenstream):
140 def render_tokenstream(tokenstream):
141 result = []
141 result = []
142 for token_class, token_ops_texts in rollup_tokenstream(tokenstream):
142 for token_class, token_ops_texts in rollup_tokenstream(tokenstream):
143
143
144 if token_class:
144 if token_class:
145 result.append(u'<span class="%s">' % token_class)
145 result.append(u'<span class="%s">' % token_class)
146 else:
146 else:
147 result.append(u'<span>')
147 result.append(u'<span>')
148
148
149 for op_tag, token_text in token_ops_texts:
149 for op_tag, token_text in token_ops_texts:
150
150
151 if op_tag:
151 if op_tag:
152 result.append(u'<%s>' % op_tag)
152 result.append(u'<%s>' % op_tag)
153
153
154 escaped_text = html_escape(token_text)
154 escaped_text = html_escape(token_text)
155
155
156 # TODO: dan: investigate showing hidden characters like space/nl/tab
156 # TODO: dan: investigate showing hidden characters like space/nl/tab
157 # escaped_text = escaped_text.replace(' ', '<sp> </sp>')
157 # escaped_text = escaped_text.replace(' ', '<sp> </sp>')
158 # escaped_text = escaped_text.replace('\n', '<nl>\n</nl>')
158 # escaped_text = escaped_text.replace('\n', '<nl>\n</nl>')
159 # escaped_text = escaped_text.replace('\t', '<tab>\t</tab>')
159 # escaped_text = escaped_text.replace('\t', '<tab>\t</tab>')
160
160
161 result.append(escaped_text)
161 result.append(escaped_text)
162
162
163 if op_tag:
163 if op_tag:
164 result.append(u'</%s>' % op_tag)
164 result.append(u'</%s>' % op_tag)
165
165
166 result.append(u'</span>')
166 result.append(u'</span>')
167
167
168 html = ''.join(result)
168 html = ''.join(result)
169 return html
169 return html
170
170
171
171
172 def rollup_tokenstream(tokenstream):
172 def rollup_tokenstream(tokenstream):
173 """
173 """
174 Group a token stream of the format:
174 Group a token stream of the format:
175
175
176 ('class', 'op', 'text')
176 ('class', 'op', 'text')
177 or
177 or
178 ('class', 'text')
178 ('class', 'text')
179
179
180 into
180 into
181
181
182 [('class1',
182 [('class1',
183 [('op1', 'text'),
183 [('op1', 'text'),
184 ('op2', 'text')]),
184 ('op2', 'text')]),
185 ('class2',
185 ('class2',
186 [('op3', 'text')])]
186 [('op3', 'text')])]
187
187
188 This is used to get the minimal tags necessary when
188 This is used to get the minimal tags necessary when
189 rendering to html eg for a token stream ie.
189 rendering to html eg for a token stream ie.
190
190
191 <span class="A"><ins>he</ins>llo</span>
191 <span class="A"><ins>he</ins>llo</span>
192 vs
192 vs
193 <span class="A"><ins>he</ins></span><span class="A">llo</span>
193 <span class="A"><ins>he</ins></span><span class="A">llo</span>
194
194
195 If a 2 tuple is passed in, the output op will be an empty string.
195 If a 2 tuple is passed in, the output op will be an empty string.
196
196
197 eg:
197 eg:
198
198
199 >>> rollup_tokenstream([('classA', '', 'h'),
199 >>> rollup_tokenstream([('classA', '', 'h'),
200 ('classA', 'del', 'ell'),
200 ('classA', 'del', 'ell'),
201 ('classA', '', 'o'),
201 ('classA', '', 'o'),
202 ('classB', '', ' '),
202 ('classB', '', ' '),
203 ('classA', '', 'the'),
203 ('classA', '', 'the'),
204 ('classA', '', 're'),
204 ('classA', '', 're'),
205 ])
205 ])
206
206
207 [('classA', [('', 'h'), ('del', 'ell'), ('', 'o')],
207 [('classA', [('', 'h'), ('del', 'ell'), ('', 'o')],
208 ('classB', [('', ' ')],
208 ('classB', [('', ' ')],
209 ('classA', [('', 'there')]]
209 ('classA', [('', 'there')]]
210
210
211 """
211 """
212 if tokenstream and len(tokenstream[0]) == 2:
212 if tokenstream and len(tokenstream[0]) == 2:
213 tokenstream = ((t[0], '', t[1]) for t in tokenstream)
213 tokenstream = ((t[0], '', t[1]) for t in tokenstream)
214
214
215 result = []
215 result = []
216 for token_class, op_list in groupby(tokenstream, lambda t: t[0]):
216 for token_class, op_list in groupby(tokenstream, lambda t: t[0]):
217 ops = []
217 ops = []
218 for token_op, token_text_list in groupby(op_list, lambda o: o[1]):
218 for token_op, token_text_list in groupby(op_list, lambda o: o[1]):
219 text_buffer = []
219 text_buffer = []
220 for t_class, t_op, t_text in token_text_list:
220 for t_class, t_op, t_text in token_text_list:
221 text_buffer.append(t_text)
221 text_buffer.append(t_text)
222 ops.append((token_op, ''.join(text_buffer)))
222 ops.append((token_op, ''.join(text_buffer)))
223 result.append((token_class, ops))
223 result.append((token_class, ops))
224 return result
224 return result
225
225
226
226
227 def tokens_diff(old_tokens, new_tokens, use_diff_match_patch=True):
227 def tokens_diff(old_tokens, new_tokens, use_diff_match_patch=True):
228 """
228 """
229 Converts a list of (token_class, token_text) tuples to a list of
229 Converts a list of (token_class, token_text) tuples to a list of
230 (token_class, token_op, token_text) tuples where token_op is one of
230 (token_class, token_op, token_text) tuples where token_op is one of
231 ('ins', 'del', '')
231 ('ins', 'del', '')
232
232
233 :param old_tokens: list of (token_class, token_text) tuples of old line
233 :param old_tokens: list of (token_class, token_text) tuples of old line
234 :param new_tokens: list of (token_class, token_text) tuples of new line
234 :param new_tokens: list of (token_class, token_text) tuples of new line
235 :param use_diff_match_patch: boolean, will use google's diff match patch
235 :param use_diff_match_patch: boolean, will use google's diff match patch
236 library which has options to 'smooth' out the character by character
236 library which has options to 'smooth' out the character by character
237 differences making nicer ins/del blocks
237 differences making nicer ins/del blocks
238 """
238 """
239
239
240 old_tokens_result = []
240 old_tokens_result = []
241 new_tokens_result = []
241 new_tokens_result = []
242
242
243 similarity = difflib.SequenceMatcher(None,
243 similarity = difflib.SequenceMatcher(None,
244 ''.join(token_text for token_class, token_text in old_tokens),
244 ''.join(token_text for token_class, token_text in old_tokens),
245 ''.join(token_text for token_class, token_text in new_tokens)
245 ''.join(token_text for token_class, token_text in new_tokens)
246 ).ratio()
246 ).ratio()
247
247
248 if similarity < 0.6: # return, the blocks are too different
248 if similarity < 0.6: # return, the blocks are too different
249 for token_class, token_text in old_tokens:
249 for token_class, token_text in old_tokens:
250 old_tokens_result.append((token_class, '', token_text))
250 old_tokens_result.append((token_class, '', token_text))
251 for token_class, token_text in new_tokens:
251 for token_class, token_text in new_tokens:
252 new_tokens_result.append((token_class, '', token_text))
252 new_tokens_result.append((token_class, '', token_text))
253 return old_tokens_result, new_tokens_result, similarity
253 return old_tokens_result, new_tokens_result, similarity
254
254
255 token_sequence_matcher = difflib.SequenceMatcher(None,
255 token_sequence_matcher = difflib.SequenceMatcher(None,
256 [x[1] for x in old_tokens],
256 [x[1] for x in old_tokens],
257 [x[1] for x in new_tokens])
257 [x[1] for x in new_tokens])
258
258
259 for tag, o1, o2, n1, n2 in token_sequence_matcher.get_opcodes():
259 for tag, o1, o2, n1, n2 in token_sequence_matcher.get_opcodes():
260 # check the differences by token block types first to give a more
260 # check the differences by token block types first to give a more
261 # nicer "block" level replacement vs character diffs
261 # nicer "block" level replacement vs character diffs
262
262
263 if tag == 'equal':
263 if tag == 'equal':
264 for token_class, token_text in old_tokens[o1:o2]:
264 for token_class, token_text in old_tokens[o1:o2]:
265 old_tokens_result.append((token_class, '', token_text))
265 old_tokens_result.append((token_class, '', token_text))
266 for token_class, token_text in new_tokens[n1:n2]:
266 for token_class, token_text in new_tokens[n1:n2]:
267 new_tokens_result.append((token_class, '', token_text))
267 new_tokens_result.append((token_class, '', token_text))
268 elif tag == 'delete':
268 elif tag == 'delete':
269 for token_class, token_text in old_tokens[o1:o2]:
269 for token_class, token_text in old_tokens[o1:o2]:
270 old_tokens_result.append((token_class, 'del', token_text))
270 old_tokens_result.append((token_class, 'del', token_text))
271 elif tag == 'insert':
271 elif tag == 'insert':
272 for token_class, token_text in new_tokens[n1:n2]:
272 for token_class, token_text in new_tokens[n1:n2]:
273 new_tokens_result.append((token_class, 'ins', token_text))
273 new_tokens_result.append((token_class, 'ins', token_text))
274 elif tag == 'replace':
274 elif tag == 'replace':
275 # if same type token blocks must be replaced, do a diff on the
275 # if same type token blocks must be replaced, do a diff on the
276 # characters in the token blocks to show individual changes
276 # characters in the token blocks to show individual changes
277
277
278 old_char_tokens = []
278 old_char_tokens = []
279 new_char_tokens = []
279 new_char_tokens = []
280 for token_class, token_text in old_tokens[o1:o2]:
280 for token_class, token_text in old_tokens[o1:o2]:
281 for char in token_text:
281 for char in token_text:
282 old_char_tokens.append((token_class, char))
282 old_char_tokens.append((token_class, char))
283
283
284 for token_class, token_text in new_tokens[n1:n2]:
284 for token_class, token_text in new_tokens[n1:n2]:
285 for char in token_text:
285 for char in token_text:
286 new_char_tokens.append((token_class, char))
286 new_char_tokens.append((token_class, char))
287
287
288 old_string = ''.join([token_text for
288 old_string = ''.join([token_text for
289 token_class, token_text in old_char_tokens])
289 token_class, token_text in old_char_tokens])
290 new_string = ''.join([token_text for
290 new_string = ''.join([token_text for
291 token_class, token_text in new_char_tokens])
291 token_class, token_text in new_char_tokens])
292
292
293 char_sequence = difflib.SequenceMatcher(
293 char_sequence = difflib.SequenceMatcher(
294 None, old_string, new_string)
294 None, old_string, new_string)
295 copcodes = char_sequence.get_opcodes()
295 copcodes = char_sequence.get_opcodes()
296 obuffer, nbuffer = [], []
296 obuffer, nbuffer = [], []
297
297
298 if use_diff_match_patch:
298 if use_diff_match_patch:
299 dmp = diff_match_patch()
299 dmp = diff_match_patch()
300 dmp.Diff_EditCost = 11 # TODO: dan: extract this to a setting
300 dmp.Diff_EditCost = 11 # TODO: dan: extract this to a setting
301 reps = dmp.diff_main(old_string, new_string)
301 reps = dmp.diff_main(old_string, new_string)
302 dmp.diff_cleanupEfficiency(reps)
302 dmp.diff_cleanupEfficiency(reps)
303
303
304 a, b = 0, 0
304 a, b = 0, 0
305 for op, rep in reps:
305 for op, rep in reps:
306 l = len(rep)
306 l = len(rep)
307 if op == 0:
307 if op == 0:
308 for i, c in enumerate(rep):
308 for i, c in enumerate(rep):
309 obuffer.append((old_char_tokens[a+i][0], '', c))
309 obuffer.append((old_char_tokens[a+i][0], '', c))
310 nbuffer.append((new_char_tokens[b+i][0], '', c))
310 nbuffer.append((new_char_tokens[b+i][0], '', c))
311 a += l
311 a += l
312 b += l
312 b += l
313 elif op == -1:
313 elif op == -1:
314 for i, c in enumerate(rep):
314 for i, c in enumerate(rep):
315 obuffer.append((old_char_tokens[a+i][0], 'del', c))
315 obuffer.append((old_char_tokens[a+i][0], 'del', c))
316 a += l
316 a += l
317 elif op == 1:
317 elif op == 1:
318 for i, c in enumerate(rep):
318 for i, c in enumerate(rep):
319 nbuffer.append((new_char_tokens[b+i][0], 'ins', c))
319 nbuffer.append((new_char_tokens[b+i][0], 'ins', c))
320 b += l
320 b += l
321 else:
321 else:
322 for ctag, co1, co2, cn1, cn2 in copcodes:
322 for ctag, co1, co2, cn1, cn2 in copcodes:
323 if ctag == 'equal':
323 if ctag == 'equal':
324 for token_class, token_text in old_char_tokens[co1:co2]:
324 for token_class, token_text in old_char_tokens[co1:co2]:
325 obuffer.append((token_class, '', token_text))
325 obuffer.append((token_class, '', token_text))
326 for token_class, token_text in new_char_tokens[cn1:cn2]:
326 for token_class, token_text in new_char_tokens[cn1:cn2]:
327 nbuffer.append((token_class, '', token_text))
327 nbuffer.append((token_class, '', token_text))
328 elif ctag == 'delete':
328 elif ctag == 'delete':
329 for token_class, token_text in old_char_tokens[co1:co2]:
329 for token_class, token_text in old_char_tokens[co1:co2]:
330 obuffer.append((token_class, 'del', token_text))
330 obuffer.append((token_class, 'del', token_text))
331 elif ctag == 'insert':
331 elif ctag == 'insert':
332 for token_class, token_text in new_char_tokens[cn1:cn2]:
332 for token_class, token_text in new_char_tokens[cn1:cn2]:
333 nbuffer.append((token_class, 'ins', token_text))
333 nbuffer.append((token_class, 'ins', token_text))
334 elif ctag == 'replace':
334 elif ctag == 'replace':
335 for token_class, token_text in old_char_tokens[co1:co2]:
335 for token_class, token_text in old_char_tokens[co1:co2]:
336 obuffer.append((token_class, 'del', token_text))
336 obuffer.append((token_class, 'del', token_text))
337 for token_class, token_text in new_char_tokens[cn1:cn2]:
337 for token_class, token_text in new_char_tokens[cn1:cn2]:
338 nbuffer.append((token_class, 'ins', token_text))
338 nbuffer.append((token_class, 'ins', token_text))
339
339
340 old_tokens_result.extend(obuffer)
340 old_tokens_result.extend(obuffer)
341 new_tokens_result.extend(nbuffer)
341 new_tokens_result.extend(nbuffer)
342
342
343 return old_tokens_result, new_tokens_result, similarity
343 return old_tokens_result, new_tokens_result, similarity
344
344
345
345
346 class DiffSet(object):
346 class DiffSet(object):
347 """
347 """
348 An object for parsing the diff result from diffs.DiffProcessor and
348 An object for parsing the diff result from diffs.DiffProcessor and
349 adding highlighting, side by side/unified renderings and line diffs
349 adding highlighting, side by side/unified renderings and line diffs
350 """
350 """
351
351
352 HL_REAL = 'REAL' # highlights using original file, slow
352 HL_REAL = 'REAL' # highlights using original file, slow
353 HL_FAST = 'FAST' # highlights using just the line, fast but not correct
353 HL_FAST = 'FAST' # highlights using just the line, fast but not correct
354 # in the case of multiline code
354 # in the case of multiline code
355 HL_NONE = 'NONE' # no highlighting, fastest
355 HL_NONE = 'NONE' # no highlighting, fastest
356
356
357 def __init__(self, highlight_mode=HL_REAL, repo_name=None,
357 def __init__(self, highlight_mode=HL_REAL, repo_name=None,
358 source_repo_name=None,
358 source_repo_name=None,
359 source_node_getter=lambda filename: None,
359 source_node_getter=lambda filename: None,
360 target_node_getter=lambda filename: None,
360 target_node_getter=lambda filename: None,
361 source_nodes=None, target_nodes=None,
361 source_nodes=None, target_nodes=None,
362 max_file_size_limit=150 * 1024, # files over this size will
362 max_file_size_limit=150 * 1024, # files over this size will
363 # use fast highlighting
363 # use fast highlighting
364 comments=None,
364 comments=None,
365 ):
365 ):
366
366
367 self.highlight_mode = highlight_mode
367 self.highlight_mode = highlight_mode
368 self.highlighted_filenodes = {}
368 self.highlighted_filenodes = {}
369 self.source_node_getter = source_node_getter
369 self.source_node_getter = source_node_getter
370 self.target_node_getter = target_node_getter
370 self.target_node_getter = target_node_getter
371 self.source_nodes = source_nodes or {}
371 self.source_nodes = source_nodes or {}
372 self.target_nodes = target_nodes or {}
372 self.target_nodes = target_nodes or {}
373 self.repo_name = repo_name
373 self.repo_name = repo_name
374 self.source_repo_name = source_repo_name or repo_name
374 self.source_repo_name = source_repo_name or repo_name
375 self.comments = comments or {}
375 self.comments = comments or {}
376 self.comments_store = self.comments.copy()
376 self.comments_store = self.comments.copy()
377 self.max_file_size_limit = max_file_size_limit
377 self.max_file_size_limit = max_file_size_limit
378
378
379 def render_patchset(self, patchset, source_ref=None, target_ref=None):
379 def render_patchset(self, patchset, source_ref=None, target_ref=None):
380 diffset = AttributeDict(dict(
380 diffset = AttributeDict(dict(
381 lines_added=0,
381 lines_added=0,
382 lines_deleted=0,
382 lines_deleted=0,
383 changed_files=0,
383 changed_files=0,
384 files=[],
384 files=[],
385 file_stats={},
385 file_stats={},
386 limited_diff=isinstance(patchset, LimitedDiffContainer),
386 limited_diff=isinstance(patchset, LimitedDiffContainer),
387 repo_name=self.repo_name,
387 repo_name=self.repo_name,
388 source_repo_name=self.source_repo_name,
388 source_repo_name=self.source_repo_name,
389 source_ref=source_ref,
389 source_ref=source_ref,
390 target_ref=target_ref,
390 target_ref=target_ref,
391 ))
391 ))
392 for patch in patchset:
392 for patch in patchset:
393 diffset.file_stats[patch['filename']] = patch['stats']
393 diffset.file_stats[patch['filename']] = patch['stats']
394 filediff = self.render_patch(patch)
394 filediff = self.render_patch(patch)
395 filediff.diffset = diffset
395 filediff.diffset = diffset
396 diffset.files.append(filediff)
396 diffset.files.append(filediff)
397 diffset.changed_files += 1
397 diffset.changed_files += 1
398 if not patch['stats']['binary']:
398 if not patch['stats']['binary']:
399 diffset.lines_added += patch['stats']['added']
399 diffset.lines_added += patch['stats']['added']
400 diffset.lines_deleted += patch['stats']['deleted']
400 diffset.lines_deleted += patch['stats']['deleted']
401
401
402 return diffset
402 return diffset
403
403
404 _lexer_cache = {}
404 _lexer_cache = {}
405 def _get_lexer_for_filename(self, filename, filenode=None):
405 def _get_lexer_for_filename(self, filename, filenode=None):
406 # cached because we might need to call it twice for source/target
406 # cached because we might need to call it twice for source/target
407 if filename not in self._lexer_cache:
407 if filename not in self._lexer_cache:
408 if filenode:
408 if filenode:
409 lexer = filenode.lexer
409 lexer = filenode.lexer
410 else:
410 else:
411 lexer = FileNode.get_lexer(filename=filename)
411 lexer = FileNode.get_lexer(filename=filename)
412 self._lexer_cache[filename] = lexer
412 self._lexer_cache[filename] = lexer
413 return self._lexer_cache[filename]
413 return self._lexer_cache[filename]
414
414
415 def render_patch(self, patch):
415 def render_patch(self, patch):
416 log.debug('rendering diff for %r' % patch['filename'])
416 log.debug('rendering diff for %r' % patch['filename'])
417
417
418 source_filename = patch['original_filename']
418 source_filename = patch['original_filename']
419 target_filename = patch['filename']
419 target_filename = patch['filename']
420
420
421 source_lexer = plain_text_lexer
421 source_lexer = plain_text_lexer
422 target_lexer = plain_text_lexer
422 target_lexer = plain_text_lexer
423
423
424 if not patch['stats']['binary']:
424 if not patch['stats']['binary']:
425 if self.highlight_mode == self.HL_REAL:
425 if self.highlight_mode == self.HL_REAL:
426 if (source_filename and patch['operation'] in ('D', 'M')
426 if (source_filename and patch['operation'] in ('D', 'M')
427 and source_filename not in self.source_nodes):
427 and source_filename not in self.source_nodes):
428 self.source_nodes[source_filename] = (
428 self.source_nodes[source_filename] = (
429 self.source_node_getter(source_filename))
429 self.source_node_getter(source_filename))
430
430
431 if (target_filename and patch['operation'] in ('A', 'M')
431 if (target_filename and patch['operation'] in ('A', 'M')
432 and target_filename not in self.target_nodes):
432 and target_filename not in self.target_nodes):
433 self.target_nodes[target_filename] = (
433 self.target_nodes[target_filename] = (
434 self.target_node_getter(target_filename))
434 self.target_node_getter(target_filename))
435
435
436 elif self.highlight_mode == self.HL_FAST:
436 elif self.highlight_mode == self.HL_FAST:
437 source_lexer = self._get_lexer_for_filename(source_filename)
437 source_lexer = self._get_lexer_for_filename(source_filename)
438 target_lexer = self._get_lexer_for_filename(target_filename)
438 target_lexer = self._get_lexer_for_filename(target_filename)
439
439
440 source_file = self.source_nodes.get(source_filename, source_filename)
440 source_file = self.source_nodes.get(source_filename, source_filename)
441 target_file = self.target_nodes.get(target_filename, target_filename)
441 target_file = self.target_nodes.get(target_filename, target_filename)
442
442
443 source_filenode, target_filenode = None, None
443 source_filenode, target_filenode = None, None
444
444
445 # TODO: dan: FileNode.lexer works on the content of the file - which
445 # TODO: dan: FileNode.lexer works on the content of the file - which
446 # can be slow - issue #4289 explains a lexer clean up - which once
446 # can be slow - issue #4289 explains a lexer clean up - which once
447 # done can allow caching a lexer for a filenode to avoid the file lookup
447 # done can allow caching a lexer for a filenode to avoid the file lookup
448 if isinstance(source_file, FileNode):
448 if isinstance(source_file, FileNode):
449 source_filenode = source_file
449 source_filenode = source_file
450 #source_lexer = source_file.lexer
450 #source_lexer = source_file.lexer
451 source_lexer = self._get_lexer_for_filename(source_filename)
451 source_lexer = self._get_lexer_for_filename(source_filename)
452 source_file.lexer = source_lexer
452 source_file.lexer = source_lexer
453
453
454 if isinstance(target_file, FileNode):
454 if isinstance(target_file, FileNode):
455 target_filenode = target_file
455 target_filenode = target_file
456 #target_lexer = target_file.lexer
456 #target_lexer = target_file.lexer
457 target_lexer = self._get_lexer_for_filename(target_filename)
457 target_lexer = self._get_lexer_for_filename(target_filename)
458 target_file.lexer = target_lexer
458 target_file.lexer = target_lexer
459
459
460 source_file_path, target_file_path = None, None
460 source_file_path, target_file_path = None, None
461
461
462 if source_filename != '/dev/null':
462 if source_filename != '/dev/null':
463 source_file_path = source_filename
463 source_file_path = source_filename
464 if target_filename != '/dev/null':
464 if target_filename != '/dev/null':
465 target_file_path = target_filename
465 target_file_path = target_filename
466
466
467 source_file_type = source_lexer.name
467 source_file_type = source_lexer.name
468 target_file_type = target_lexer.name
468 target_file_type = target_lexer.name
469
469
470 op_hunks = patch['chunks'][0]
470 op_hunks = patch['chunks'][0]
471 hunks = patch['chunks'][1:]
471 hunks = patch['chunks'][1:]
472
472
473 filediff = AttributeDict({
473 filediff = AttributeDict({
474 'source_file_path': source_file_path,
474 'source_file_path': source_file_path,
475 'target_file_path': target_file_path,
475 'target_file_path': target_file_path,
476 'source_filenode': source_filenode,
476 'source_filenode': source_filenode,
477 'target_filenode': target_filenode,
477 'target_filenode': target_filenode,
478 'hunks': [],
478 'hunks': [],
479 'source_file_type': target_file_type,
479 'source_file_type': target_file_type,
480 'target_file_type': source_file_type,
480 'target_file_type': source_file_type,
481 'patch': patch,
481 'patch': patch,
482 'source_mode': patch['stats']['old_mode'],
482 'source_mode': patch['stats']['old_mode'],
483 'target_mode': patch['stats']['new_mode'],
483 'target_mode': patch['stats']['new_mode'],
484 'limited_diff': isinstance(patch, LimitedDiffContainer),
484 'limited_diff': isinstance(patch, LimitedDiffContainer),
485 'diffset': self,
485 'diffset': self,
486 })
486 })
487
487
488 for hunk in hunks:
488 for hunk in hunks:
489 hunkbit = self.parse_hunk(hunk, source_file, target_file)
489 hunkbit = self.parse_hunk(hunk, source_file, target_file)
490 hunkbit.filediff = filediff
490 hunkbit.filediff = filediff
491 filediff.hunks.append(hunkbit)
491 filediff.hunks.append(hunkbit)
492
492
493 left_comments = {}
493 left_comments = {}
494
494
495 if source_file_path in self.comments_store:
495 if source_file_path in self.comments_store:
496 for lineno, comments in self.comments_store[source_file_path].items():
496 for lineno, comments in self.comments_store[source_file_path].items():
497 left_comments[lineno] = comments
497 left_comments[lineno] = comments
498
498
499 if target_file_path in self.comments_store:
499 if target_file_path in self.comments_store:
500 for lineno, comments in self.comments_store[target_file_path].items():
500 for lineno, comments in self.comments_store[target_file_path].items():
501 left_comments[lineno] = comments
501 left_comments[lineno] = comments
502
502
503 filediff.left_comments = left_comments
503 filediff.left_comments = left_comments
504 return filediff
504 return filediff
505
505
506 def parse_hunk(self, hunk, source_file, target_file):
506 def parse_hunk(self, hunk, source_file, target_file):
507 result = AttributeDict(dict(
507 result = AttributeDict(dict(
508 source_start=hunk['source_start'],
508 source_start=hunk['source_start'],
509 source_length=hunk['source_length'],
509 source_length=hunk['source_length'],
510 target_start=hunk['target_start'],
510 target_start=hunk['target_start'],
511 target_length=hunk['target_length'],
511 target_length=hunk['target_length'],
512 section_header=hunk['section_header'],
512 section_header=hunk['section_header'],
513 lines=[],
513 lines=[],
514 ))
514 ))
515 before, after = [], []
515 before, after = [], []
516
516
517 for line in hunk['lines']:
517 for line in hunk['lines']:
518 if line['action'] == 'unmod':
518 if line['action'] == 'unmod':
519 result.lines.extend(
519 result.lines.extend(
520 self.parse_lines(before, after, source_file, target_file))
520 self.parse_lines(before, after, source_file, target_file))
521 after.append(line)
521 after.append(line)
522 before.append(line)
522 before.append(line)
523 elif line['action'] == 'add':
523 elif line['action'] == 'add':
524 after.append(line)
524 after.append(line)
525 elif line['action'] == 'del':
525 elif line['action'] == 'del':
526 before.append(line)
526 before.append(line)
527 elif line['action'] == 'old-no-nl':
527 elif line['action'] == 'old-no-nl':
528 before.append(line)
528 before.append(line)
529 elif line['action'] == 'new-no-nl':
529 elif line['action'] == 'new-no-nl':
530 after.append(line)
530 after.append(line)
531
531
532 result.lines.extend(
532 result.lines.extend(
533 self.parse_lines(before, after, source_file, target_file))
533 self.parse_lines(before, after, source_file, target_file))
534 result.unified = self.as_unified(result.lines)
534 result.unified = self.as_unified(result.lines)
535 result.sideside = result.lines
535 result.sideside = result.lines
536
536
537 return result
537 return result
538
538
539 def parse_lines(self, before_lines, after_lines, source_file, target_file):
539 def parse_lines(self, before_lines, after_lines, source_file, target_file):
540 # TODO: dan: investigate doing the diff comparison and fast highlighting
540 # TODO: dan: investigate doing the diff comparison and fast highlighting
541 # on the entire before and after buffered block lines rather than by
541 # on the entire before and after buffered block lines rather than by
542 # line, this means we can get better 'fast' highlighting if the context
542 # line, this means we can get better 'fast' highlighting if the context
543 # allows it - eg.
543 # allows it - eg.
544 # line 4: """
544 # line 4: """
545 # line 5: this gets highlighted as a string
545 # line 5: this gets highlighted as a string
546 # line 6: """
546 # line 6: """
547
547
548 lines = []
548 lines = []
549 while before_lines or after_lines:
549 while before_lines or after_lines:
550 before, after = None, None
550 before, after = None, None
551 before_tokens, after_tokens = None, None
551 before_tokens, after_tokens = None, None
552
552
553 if before_lines:
553 if before_lines:
554 before = before_lines.pop(0)
554 before = before_lines.pop(0)
555 if after_lines:
555 if after_lines:
556 after = after_lines.pop(0)
556 after = after_lines.pop(0)
557
557
558 original = AttributeDict()
558 original = AttributeDict()
559 modified = AttributeDict()
559 modified = AttributeDict()
560
560
561 if before:
561 if before:
562 if before['action'] == 'old-no-nl':
562 if before['action'] == 'old-no-nl':
563 before_tokens = [('nonl', before['line'])]
563 before_tokens = [('nonl', before['line'])]
564 else:
564 else:
565 before_tokens = self.get_line_tokens(
565 before_tokens = self.get_line_tokens(
566 line_text=before['line'], line_number=before['old_lineno'],
566 line_text=before['line'], line_number=before['old_lineno'],
567 file=source_file)
567 file=source_file)
568 original.lineno = before['old_lineno']
568 original.lineno = before['old_lineno']
569 original.content = before['line']
569 original.content = before['line']
570 original.action = self.action_to_op(before['action'])
570 original.action = self.action_to_op(before['action'])
571 original.comments = self.get_comments_for('old',
571 original.comments = self.get_comments_for('old',
572 source_file, before['old_lineno'])
572 source_file, before['old_lineno'])
573
573
574 if after:
574 if after:
575 if after['action'] == 'new-no-nl':
575 if after['action'] == 'new-no-nl':
576 after_tokens = [('nonl', after['line'])]
576 after_tokens = [('nonl', after['line'])]
577 else:
577 else:
578 after_tokens = self.get_line_tokens(
578 after_tokens = self.get_line_tokens(
579 line_text=after['line'], line_number=after['new_lineno'],
579 line_text=after['line'], line_number=after['new_lineno'],
580 file=target_file)
580 file=target_file)
581 modified.lineno = after['new_lineno']
581 modified.lineno = after['new_lineno']
582 modified.content = after['line']
582 modified.content = after['line']
583 modified.action = self.action_to_op(after['action'])
583 modified.action = self.action_to_op(after['action'])
584 modified.comments = self.get_comments_for('new',
584 modified.comments = self.get_comments_for('new',
585 target_file, after['new_lineno'])
585 target_file, after['new_lineno'])
586
586
587 # diff the lines
587 # diff the lines
588 if before_tokens and after_tokens:
588 if before_tokens and after_tokens:
589 o_tokens, m_tokens, similarity = tokens_diff(
589 o_tokens, m_tokens, similarity = tokens_diff(
590 before_tokens, after_tokens)
590 before_tokens, after_tokens)
591 original.content = render_tokenstream(o_tokens)
591 original.content = render_tokenstream(o_tokens)
592 modified.content = render_tokenstream(m_tokens)
592 modified.content = render_tokenstream(m_tokens)
593 elif before_tokens:
593 elif before_tokens:
594 original.content = render_tokenstream(
594 original.content = render_tokenstream(
595 [(x[0], '', x[1]) for x in before_tokens])
595 [(x[0], '', x[1]) for x in before_tokens])
596 elif after_tokens:
596 elif after_tokens:
597 modified.content = render_tokenstream(
597 modified.content = render_tokenstream(
598 [(x[0], '', x[1]) for x in after_tokens])
598 [(x[0], '', x[1]) for x in after_tokens])
599
599
600 lines.append(AttributeDict({
600 lines.append(AttributeDict({
601 'original': original,
601 'original': original,
602 'modified': modified,
602 'modified': modified,
603 }))
603 }))
604
604
605 return lines
605 return lines
606
606
607 def get_comments_for(self, version, file, line_number):
607 def get_comments_for(self, version, file, line_number):
608 if hasattr(file, 'unicode_path'):
608 if hasattr(file, 'unicode_path'):
609 file = file.unicode_path
609 file = file.unicode_path
610
610
611 if not isinstance(file, basestring):
611 if not isinstance(file, basestring):
612 return None
612 return None
613
613
614 line_key = {
614 line_key = {
615 'old': 'o',
615 'old': 'o',
616 'new': 'n',
616 'new': 'n',
617 }[version] + str(line_number)
617 }[version] + str(line_number)
618
618
619 if file in self.comments_store:
619 if file in self.comments_store:
620 file_comments = self.comments_store[file]
620 file_comments = self.comments_store[file]
621 if line_key in file_comments:
621 if line_key in file_comments:
622 return file_comments.pop(line_key)
622 return file_comments.pop(line_key)
623
623
624 def get_line_tokens(self, line_text, line_number, file=None):
624 def get_line_tokens(self, line_text, line_number, file=None):
625 filenode = None
625 filenode = None
626 filename = None
626 filename = None
627
627
628 if isinstance(file, basestring):
628 if isinstance(file, basestring):
629 filename = file
629 filename = file
630 elif isinstance(file, FileNode):
630 elif isinstance(file, FileNode):
631 filenode = file
631 filenode = file
632 filename = file.unicode_path
632 filename = file.unicode_path
633
633
634 if self.highlight_mode == self.HL_REAL and filenode:
634 if self.highlight_mode == self.HL_REAL and filenode:
635 lexer = self._get_lexer_for_filename(filename)
635 lexer = self._get_lexer_for_filename(filename)
636 file_size_allowed = file.size < self.max_file_size_limit
636 file_size_allowed = file.size < self.max_file_size_limit
637 if line_number and file_size_allowed:
637 if line_number and file_size_allowed:
638 return self.get_tokenized_filenode_line(
638 return self.get_tokenized_filenode_line(
639 file, line_number, lexer)
639 file, line_number, lexer)
640
640
641 if self.highlight_mode in (self.HL_REAL, self.HL_FAST) and filename:
641 if self.highlight_mode in (self.HL_REAL, self.HL_FAST) and filename:
642 lexer = self._get_lexer_for_filename(filename)
642 lexer = self._get_lexer_for_filename(filename)
643 return list(tokenize_string(line_text, lexer))
643 return list(tokenize_string(line_text, lexer))
644
644
645 return list(tokenize_string(line_text, plain_text_lexer))
645 return list(tokenize_string(line_text, plain_text_lexer))
646
646
647 def get_tokenized_filenode_line(self, filenode, line_number, lexer=None):
647 def get_tokenized_filenode_line(self, filenode, line_number, lexer=None):
648
648
649 if filenode not in self.highlighted_filenodes:
649 if filenode not in self.highlighted_filenodes:
650 tokenized_lines = filenode_as_lines_tokens(filenode, lexer)
650 tokenized_lines = filenode_as_lines_tokens(filenode, lexer)
651 self.highlighted_filenodes[filenode] = tokenized_lines
651 self.highlighted_filenodes[filenode] = tokenized_lines
652 return self.highlighted_filenodes[filenode][line_number - 1]
652 return self.highlighted_filenodes[filenode][line_number - 1]
653
653
654 def action_to_op(self, action):
654 def action_to_op(self, action):
655 return {
655 return {
656 'add': '+',
656 'add': '+',
657 'del': '-',
657 'del': '-',
658 'unmod': ' ',
658 'unmod': ' ',
659 'old-no-nl': ' ',
659 'old-no-nl': ' ',
660 'new-no-nl': ' ',
660 'new-no-nl': ' ',
661 }.get(action, action)
661 }.get(action, action)
662
662
663 def as_unified(self, lines):
663 def as_unified(self, lines):
664 """
664 """
665 Return a generator that yields the lines of a diff in unified order
665 Return a generator that yields the lines of a diff in unified order
666 """
666 """
667 def generator():
667 def generator():
668 buf = []
668 buf = []
669 for line in lines:
669 for line in lines:
670
670
671 if buf and not line.original or line.original.action == ' ':
671 if buf and not line.original or line.original.action == ' ':
672 for b in buf:
672 for b in buf:
673 yield b
673 yield b
674 buf = []
674 buf = []
675
675
676 if line.original:
676 if line.original:
677 if line.original.action == ' ':
677 if line.original.action == ' ':
678 yield (line.original.lineno, line.modified.lineno,
678 yield (line.original.lineno, line.modified.lineno,
679 line.original.action, line.original.content,
679 line.original.action, line.original.content,
680 line.original.comments)
680 line.original.comments)
681 continue
681 continue
682
682
683 if line.original.action == '-':
683 if line.original.action == '-':
684 yield (line.original.lineno, None,
684 yield (line.original.lineno, None,
685 line.original.action, line.original.content,
685 line.original.action, line.original.content,
686 line.original.comments)
686 line.original.comments)
687
687
688 if line.modified.action == '+':
688 if line.modified.action == '+':
689 buf.append((
689 buf.append((
690 None, line.modified.lineno,
690 None, line.modified.lineno,
691 line.modified.action, line.modified.content,
691 line.modified.action, line.modified.content,
692 line.modified.comments))
692 line.modified.comments))
693 continue
693 continue
694
694
695 if line.modified:
695 if line.modified:
696 yield (None, line.modified.lineno,
696 yield (None, line.modified.lineno,
697 line.modified.action, line.modified.content,
697 line.modified.action, line.modified.content,
698 line.modified.comments)
698 line.modified.comments)
699
699
700 for b in buf:
700 for b in buf:
701 yield b
701 yield b
702
702
703 return generator()
703 return generator()
@@ -1,1143 +1,1135 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21 .compare_view_files {
21 .compare_view_files {
22
22
23 .diff-container {
23 .diff-container {
24
24
25 .diffblock {
25 .diffblock {
26 margin-bottom: 0;
26 margin-bottom: 0;
27 }
27 }
28 }
28 }
29 }
29 }
30
30
31 div.diffblock .sidebyside {
31 div.diffblock .sidebyside {
32 background: #ffffff;
32 background: #ffffff;
33 }
33 }
34
34
35 div.diffblock {
35 div.diffblock {
36 overflow-x: auto;
36 overflow-x: auto;
37 overflow-y: hidden;
37 overflow-y: hidden;
38 clear: both;
38 clear: both;
39 padding: 0px;
39 padding: 0px;
40 background: @grey6;
40 background: @grey6;
41 border: @border-thickness solid @grey5;
41 border: @border-thickness solid @grey5;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
44
44
45
45
46 .comments-number {
46 .comments-number {
47 float: right;
47 float: right;
48 }
48 }
49
49
50 // BEGIN CODE-HEADER STYLES
50 // BEGIN CODE-HEADER STYLES
51
51
52 .code-header {
52 .code-header {
53 background: @grey6;
53 background: @grey6;
54 padding: 10px 0 10px 0;
54 padding: 10px 0 10px 0;
55 height: auto;
55 height: auto;
56 width: 100%;
56 width: 100%;
57
57
58 .hash {
58 .hash {
59 float: left;
59 float: left;
60 padding: 2px 0 0 2px;
60 padding: 2px 0 0 2px;
61 }
61 }
62
62
63 .date {
63 .date {
64 float: left;
64 float: left;
65 text-transform: uppercase;
65 text-transform: uppercase;
66 padding: 4px 0px 0px 2px;
66 padding: 4px 0px 0px 2px;
67 }
67 }
68
68
69 div {
69 div {
70 margin-left: 4px;
70 margin-left: 4px;
71 }
71 }
72
72
73 div.compare_header {
73 div.compare_header {
74 min-height: 40px;
74 min-height: 40px;
75 margin: 0;
75 margin: 0;
76 padding: 0 @padding;
76 padding: 0 @padding;
77
77
78 .drop-menu {
78 .drop-menu {
79 float:left;
79 float:left;
80 display: block;
80 display: block;
81 margin:0 0 @padding 0;
81 margin:0 0 @padding 0;
82 }
82 }
83
83
84 .compare-label {
84 .compare-label {
85 float: left;
85 float: left;
86 clear: both;
86 clear: both;
87 display: inline-block;
87 display: inline-block;
88 min-width: 5em;
88 min-width: 5em;
89 margin: 0;
89 margin: 0;
90 padding: @button-padding @button-padding @button-padding 0;
90 padding: @button-padding @button-padding @button-padding 0;
91 font-family: @text-semibold;
91 font-family: @text-semibold;
92 }
92 }
93
93
94 .compare-buttons {
94 .compare-buttons {
95 float: left;
95 float: left;
96 margin: 0;
96 margin: 0;
97 padding: 0 0 @padding;
97 padding: 0 0 @padding;
98
98
99 .btn {
99 .btn {
100 margin: 0 @padding 0 0;
100 margin: 0 @padding 0 0;
101 }
101 }
102 }
102 }
103 }
103 }
104
104
105 }
105 }
106
106
107 .parents {
107 .parents {
108 float: left;
108 float: left;
109 width: 100px;
109 width: 100px;
110 font-weight: 400;
110 font-weight: 400;
111 vertical-align: middle;
111 vertical-align: middle;
112 padding: 0px 2px 0px 2px;
112 padding: 0px 2px 0px 2px;
113 background-color: @grey6;
113 background-color: @grey6;
114
114
115 #parent_link {
115 #parent_link {
116 margin: 00px 2px;
116 margin: 00px 2px;
117
117
118 &.double {
118 &.double {
119 margin: 0px 2px;
119 margin: 0px 2px;
120 }
120 }
121
121
122 &.disabled{
122 &.disabled{
123 margin-right: @padding;
123 margin-right: @padding;
124 }
124 }
125 }
125 }
126 }
126 }
127
127
128 .children {
128 .children {
129 float: right;
129 float: right;
130 width: 100px;
130 width: 100px;
131 font-weight: 400;
131 font-weight: 400;
132 vertical-align: middle;
132 vertical-align: middle;
133 text-align: right;
133 text-align: right;
134 padding: 0px 2px 0px 2px;
134 padding: 0px 2px 0px 2px;
135 background-color: @grey6;
135 background-color: @grey6;
136
136
137 #child_link {
137 #child_link {
138 margin: 0px 2px;
138 margin: 0px 2px;
139
139
140 &.double {
140 &.double {
141 margin: 0px 2px;
141 margin: 0px 2px;
142 }
142 }
143
143
144 &.disabled{
144 &.disabled{
145 margin-right: @padding;
145 margin-right: @padding;
146 }
146 }
147 }
147 }
148 }
148 }
149
149
150 .changeset_header {
150 .changeset_header {
151 height: 16px;
151 height: 16px;
152
152
153 & > div{
153 & > div{
154 margin-right: @padding;
154 margin-right: @padding;
155 }
155 }
156 }
156 }
157
157
158 .changeset_file {
158 .changeset_file {
159 text-align: left;
159 text-align: left;
160 float: left;
160 float: left;
161 padding: 0;
161 padding: 0;
162
162
163 a{
163 a{
164 display: inline-block;
164 display: inline-block;
165 margin-right: 0.5em;
165 margin-right: 0.5em;
166 }
166 }
167
167
168 #selected_mode{
168 #selected_mode{
169 margin-left: 0;
169 margin-left: 0;
170 }
170 }
171 }
171 }
172
172
173 .diff-menu-wrapper {
173 .diff-menu-wrapper {
174 float: left;
174 float: left;
175 }
175 }
176
176
177 .diff-menu {
177 .diff-menu {
178 position: absolute;
178 position: absolute;
179 background: none repeat scroll 0 0 #FFFFFF;
179 background: none repeat scroll 0 0 #FFFFFF;
180 border-color: #003367 @grey3 @grey3;
180 border-color: #003367 @grey3 @grey3;
181 border-right: 1px solid @grey3;
181 border-right: 1px solid @grey3;
182 border-style: solid solid solid;
182 border-style: solid solid solid;
183 border-width: @border-thickness;
183 border-width: @border-thickness;
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
185 margin-top: 5px;
185 margin-top: 5px;
186 margin-left: 1px;
186 margin-left: 1px;
187 }
187 }
188
188
189 .diff-actions, .editor-actions {
189 .diff-actions, .editor-actions {
190 float: left;
190 float: left;
191
191
192 input{
192 input{
193 margin: 0 0.5em 0 0;
193 margin: 0 0.5em 0 0;
194 }
194 }
195 }
195 }
196
196
197 // END CODE-HEADER STYLES
197 // END CODE-HEADER STYLES
198
198
199 // BEGIN CODE-BODY STYLES
199 // BEGIN CODE-BODY STYLES
200
200
201 .code-body {
201 .code-body {
202 background: white;
202 background: white;
203 padding: 0;
203 padding: 0;
204 background-color: #ffffff;
204 background-color: #ffffff;
205 position: relative;
205 position: relative;
206 max-width: none;
206 max-width: none;
207 box-sizing: border-box;
207 box-sizing: border-box;
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
209 // to have the intended size and to scroll. Should be simplified.
209 // to have the intended size and to scroll. Should be simplified.
210 width: 100%;
210 width: 100%;
211 overflow-x: auto;
211 overflow-x: auto;
212 }
212 }
213
213
214 pre.raw {
214 pre.raw {
215 background: white;
215 background: white;
216 color: @grey1;
216 color: @grey1;
217 }
217 }
218 // END CODE-BODY STYLES
218 // END CODE-BODY STYLES
219
219
220 }
220 }
221
221
222
222
223 table.code-difftable {
223 table.code-difftable {
224 border-collapse: collapse;
224 border-collapse: collapse;
225 width: 99%;
225 width: 99%;
226 border-radius: 0px !important;
226 border-radius: 0px !important;
227
227
228 td {
228 td {
229 padding: 0 !important;
229 padding: 0 !important;
230 background: none !important;
230 background: none !important;
231 border: 0 !important;
231 border: 0 !important;
232 }
232 }
233
233
234 .context {
234 .context {
235 background: none repeat scroll 0 0 #DDE7EF;
235 background: none repeat scroll 0 0 #DDE7EF;
236 }
236 }
237
237
238 .add {
238 .add {
239 background: none repeat scroll 0 0 #DDFFDD;
239 background: none repeat scroll 0 0 #DDFFDD;
240
240
241 ins {
241 ins {
242 background: none repeat scroll 0 0 #AAFFAA;
242 background: none repeat scroll 0 0 #AAFFAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 .del {
247 .del {
248 background: none repeat scroll 0 0 #FFDDDD;
248 background: none repeat scroll 0 0 #FFDDDD;
249
249
250 del {
250 del {
251 background: none repeat scroll 0 0 #FFAAAA;
251 background: none repeat scroll 0 0 #FFAAAA;
252 text-decoration: none;
252 text-decoration: none;
253 }
253 }
254 }
254 }
255
255
256 /** LINE NUMBERS **/
256 /** LINE NUMBERS **/
257 .lineno {
257 .lineno {
258 padding-left: 2px !important;
258 padding-left: 2px !important;
259 padding-right: 2px;
259 padding-right: 2px;
260 text-align: right;
260 text-align: right;
261 width: 32px;
261 width: 32px;
262 -moz-user-select: none;
262 -moz-user-select: none;
263 -webkit-user-select: none;
263 -webkit-user-select: none;
264 border-right: @border-thickness solid @grey5 !important;
264 border-right: @border-thickness solid @grey5 !important;
265 border-left: 0px solid #CCC !important;
265 border-left: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
267 border-bottom: none !important;
267 border-bottom: none !important;
268
268
269 a {
269 a {
270 &:extend(pre);
270 &:extend(pre);
271 text-align: right;
271 text-align: right;
272 padding-right: 2px;
272 padding-right: 2px;
273 cursor: pointer;
273 cursor: pointer;
274 display: block;
274 display: block;
275 width: 32px;
275 width: 32px;
276 }
276 }
277 }
277 }
278
278
279 .context {
279 .context {
280 cursor: auto;
280 cursor: auto;
281 &:extend(pre);
281 &:extend(pre);
282 }
282 }
283
283
284 .lineno-inline {
284 .lineno-inline {
285 background: none repeat scroll 0 0 #FFF !important;
285 background: none repeat scroll 0 0 #FFF !important;
286 padding-left: 2px;
286 padding-left: 2px;
287 padding-right: 2px;
287 padding-right: 2px;
288 text-align: right;
288 text-align: right;
289 width: 30px;
289 width: 30px;
290 -moz-user-select: none;
290 -moz-user-select: none;
291 -webkit-user-select: none;
291 -webkit-user-select: none;
292 }
292 }
293
293
294 /** CODE **/
294 /** CODE **/
295 .code {
295 .code {
296 display: block;
296 display: block;
297 width: 100%;
297 width: 100%;
298
298
299 td {
299 td {
300 margin: 0;
300 margin: 0;
301 padding: 0;
301 padding: 0;
302 }
302 }
303
303
304 pre {
304 pre {
305 margin: 0;
305 margin: 0;
306 padding: 0;
306 padding: 0;
307 margin-left: .5em;
307 margin-left: .5em;
308 }
308 }
309 }
309 }
310 }
310 }
311
311
312
312
313 // Comments
313 // Comments
314
314
315 div.comment:target {
315 div.comment:target {
316 border-left: 6px solid @comment-highlight-color !important;
316 border-left: 6px solid @comment-highlight-color !important;
317 padding-left: 3px;
317 padding-left: 3px;
318 margin-left: -9px;
318 margin-left: -9px;
319 }
319 }
320
320
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
322 //current values that might change. But to make it clear I put as a calculation
322 //current values that might change. But to make it clear I put as a calculation
323 @comment-max-width: 1065px;
323 @comment-max-width: 1065px;
324 @pr-extra-margin: 34px;
324 @pr-extra-margin: 34px;
325 @pr-border-spacing: 4px;
325 @pr-border-spacing: 4px;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
327
327
328 // Pull Request
328 // Pull Request
329 .cs_files .code-difftable {
329 .cs_files .code-difftable {
330 border: @border-thickness solid @grey5; //borders only on PRs
330 border: @border-thickness solid @grey5; //borders only on PRs
331
331
332 .comment-inline-form,
332 .comment-inline-form,
333 div.comment {
333 div.comment {
334 width: @pr-comment-width;
334 width: @pr-comment-width;
335 }
335 }
336 }
336 }
337
337
338 // Changeset
338 // Changeset
339 .code-difftable {
339 .code-difftable {
340 .comment-inline-form,
340 .comment-inline-form,
341 div.comment {
341 div.comment {
342 width: @comment-max-width;
342 width: @comment-max-width;
343 }
343 }
344 }
344 }
345
345
346 //Style page
346 //Style page
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
348 #style-page .code-difftable{
348 #style-page .code-difftable{
349 .comment-inline-form,
349 .comment-inline-form,
350 div.comment {
350 div.comment {
351 width: @comment-max-width - @style-extra-margin;
351 width: @comment-max-width - @style-extra-margin;
352 }
352 }
353 }
353 }
354
354
355 #context-bar > h2 {
355 #context-bar > h2 {
356 font-size: 20px;
356 font-size: 20px;
357 }
357 }
358
358
359 #context-bar > h2> a {
359 #context-bar > h2> a {
360 font-size: 20px;
360 font-size: 20px;
361 }
361 }
362 // end of defaults
362 // end of defaults
363
363
364 .file_diff_buttons {
364 .file_diff_buttons {
365 padding: 0 0 @padding;
365 padding: 0 0 @padding;
366
366
367 .drop-menu {
367 .drop-menu {
368 float: left;
368 float: left;
369 margin: 0 @padding 0 0;
369 margin: 0 @padding 0 0;
370 }
370 }
371 .btn {
371 .btn {
372 margin: 0 @padding 0 0;
372 margin: 0 @padding 0 0;
373 }
373 }
374 }
374 }
375
375
376 .code-body.textarea.editor {
376 .code-body.textarea.editor {
377 max-width: none;
377 max-width: none;
378 padding: 15px;
378 padding: 15px;
379 }
379 }
380
380
381 td.injected_diff{
381 td.injected_diff{
382 max-width: 1178px;
382 max-width: 1178px;
383 overflow-x: auto;
383 overflow-x: auto;
384 overflow-y: hidden;
384 overflow-y: hidden;
385
385
386 div.diff-container,
386 div.diff-container,
387 div.diffblock{
387 div.diffblock{
388 max-width: 100%;
388 max-width: 100%;
389 }
389 }
390
390
391 div.code-body {
391 div.code-body {
392 max-width: 1124px;
392 max-width: 1124px;
393 overflow-x: auto;
393 overflow-x: auto;
394 overflow-y: hidden;
394 overflow-y: hidden;
395 padding: 0;
395 padding: 0;
396 }
396 }
397 div.diffblock {
397 div.diffblock {
398 border: none;
398 border: none;
399 }
399 }
400
400
401 &.inline-form {
401 &.inline-form {
402 width: 99%
402 width: 99%
403 }
403 }
404 }
404 }
405
405
406
406
407 table.code-difftable {
407 table.code-difftable {
408 width: 100%;
408 width: 100%;
409 }
409 }
410
410
411 /** PYGMENTS COLORING **/
411 /** PYGMENTS COLORING **/
412 div.codeblock {
412 div.codeblock {
413
413
414 // TODO: johbo: Added interim to get rid of the margin around
414 // TODO: johbo: Added interim to get rid of the margin around
415 // Select2 widgets. This needs further cleanup.
415 // Select2 widgets. This needs further cleanup.
416 margin-top: @padding;
416 margin-top: @padding;
417
417
418 overflow: auto;
418 overflow: auto;
419 padding: 0px;
419 padding: 0px;
420 border: @border-thickness solid @grey5;
420 border: @border-thickness solid @grey5;
421 background: @grey6;
421 background: @grey6;
422 .border-radius(@border-radius);
422 .border-radius(@border-radius);
423
423
424 #remove_gist {
424 #remove_gist {
425 float: right;
425 float: right;
426 }
426 }
427
427
428 .author {
428 .author {
429 clear: both;
429 clear: both;
430 vertical-align: middle;
430 vertical-align: middle;
431 font-family: @text-bold;
431 font-family: @text-bold;
432 }
432 }
433
433
434 .btn-mini {
434 .btn-mini {
435 float: left;
435 float: left;
436 margin: 0 5px 0 0;
436 margin: 0 5px 0 0;
437 }
437 }
438
438
439 .code-header {
439 .code-header {
440 padding: @padding;
440 padding: @padding;
441 border-bottom: @border-thickness solid @grey5;
441 border-bottom: @border-thickness solid @grey5;
442
442
443 .rc-user {
443 .rc-user {
444 min-width: 0;
444 min-width: 0;
445 margin-right: .5em;
445 margin-right: .5em;
446 }
446 }
447
447
448 .stats {
448 .stats {
449 clear: both;
449 clear: both;
450 margin: 0 0 @padding 0;
450 margin: 0 0 @padding 0;
451 padding: 0;
451 padding: 0;
452 .left {
452 .left {
453 float: left;
453 float: left;
454 clear: left;
454 clear: left;
455 max-width: 75%;
455 max-width: 75%;
456 margin: 0 0 @padding 0;
456 margin: 0 0 @padding 0;
457
457
458 &.item {
458 &.item {
459 margin-right: @padding;
459 margin-right: @padding;
460 &.last { border-right: none; }
460 &.last { border-right: none; }
461 }
461 }
462 }
462 }
463 .buttons { float: right; }
463 .buttons { float: right; }
464 .author {
464 .author {
465 height: 25px; margin-left: 15px; font-weight: bold;
465 height: 25px; margin-left: 15px; font-weight: bold;
466 }
466 }
467 }
467 }
468
468
469 .commit {
469 .commit {
470 margin: 5px 0 0 26px;
470 margin: 5px 0 0 26px;
471 font-weight: normal;
471 font-weight: normal;
472 white-space: pre-wrap;
472 white-space: pre-wrap;
473 }
473 }
474 }
474 }
475
475
476 .message {
476 .message {
477 position: relative;
477 position: relative;
478 margin: @padding;
478 margin: @padding;
479
479
480 .codeblock-label {
480 .codeblock-label {
481 margin: 0 0 1em 0;
481 margin: 0 0 1em 0;
482 }
482 }
483 }
483 }
484
484
485 .code-body {
485 .code-body {
486 padding: @padding;
486 padding: @padding;
487 background-color: #ffffff;
487 background-color: #ffffff;
488 min-width: 100%;
488 min-width: 100%;
489 box-sizing: border-box;
489 box-sizing: border-box;
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
491 // to have the intended size and to scroll. Should be simplified.
491 // to have the intended size and to scroll. Should be simplified.
492 width: 100%;
492 width: 100%;
493 overflow-x: auto;
493 overflow-x: auto;
494 }
494 }
495 }
495 }
496
496
497 .code-highlighttable,
497 .code-highlighttable,
498 div.codeblock {
498 div.codeblock {
499
499
500 &.readme {
500 &.readme {
501 background-color: white;
501 background-color: white;
502 }
502 }
503
503
504 .markdown-block table {
504 .markdown-block table {
505 border-collapse: collapse;
505 border-collapse: collapse;
506
506
507 th,
507 th,
508 td {
508 td {
509 padding: .5em;
509 padding: .5em;
510 border: @border-thickness solid @border-default-color;
510 border: @border-thickness solid @border-default-color;
511 }
511 }
512 }
512 }
513
513
514 table {
514 table {
515 border: 0px;
515 border: 0px;
516 margin: 0;
516 margin: 0;
517 letter-spacing: normal;
517 letter-spacing: normal;
518
518
519
519
520 td {
520 td {
521 border: 0px;
521 border: 0px;
522 vertical-align: top;
522 vertical-align: top;
523 }
523 }
524 }
524 }
525 }
525 }
526
526
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
528 div.search-code-body {
528 div.search-code-body {
529 background-color: #ffffff; padding: 5px 0 5px 10px;
529 background-color: #ffffff; padding: 5px 0 5px 10px;
530 pre {
530 pre {
531 .match { background-color: #faffa6;}
531 .match { background-color: #faffa6;}
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
533 }
533 }
534 .code-highlighttable {
534 .code-highlighttable {
535 border-collapse: collapse;
535 border-collapse: collapse;
536
536
537 tr:hover {
537 tr:hover {
538 background: #fafafa;
538 background: #fafafa;
539 }
539 }
540 td.code {
540 td.code {
541 padding-left: 10px;
541 padding-left: 10px;
542 }
542 }
543 td.line {
543 td.line {
544 border-right: 1px solid #ccc !important;
544 border-right: 1px solid #ccc !important;
545 padding-right: 10px;
545 padding-right: 10px;
546 text-align: right;
546 text-align: right;
547 font-family: "Lucida Console",Monaco,monospace;
547 font-family: "Lucida Console",Monaco,monospace;
548 span {
548 span {
549 white-space: pre-wrap;
549 white-space: pre-wrap;
550 color: #666666;
550 color: #666666;
551 }
551 }
552 }
552 }
553 }
553 }
554 }
554 }
555
555
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
557 .code-highlight {
557 .code-highlight {
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
560 pre div:target {background-color: @comment-highlight-color !important;}
560 pre div:target {background-color: @comment-highlight-color !important;}
561 }
561 }
562
562
563 .linenos a { text-decoration: none; }
563 .linenos a { text-decoration: none; }
564
564
565 .CodeMirror-selected { background: @rchighlightblue; }
565 .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
569
569
570 .code { display: block; border:0px !important; }
570 .code { display: block; border:0px !important; }
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
572 .codehilite {
572 .codehilite {
573 .hll { background-color: #ffffcc }
573 .hll { background-color: #ffffcc }
574 .c { color: #408080; font-style: italic } /* Comment */
574 .c { color: #408080; font-style: italic } /* Comment */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
576 .k { color: #008000; font-weight: bold } /* Keyword */
576 .k { color: #008000; font-weight: bold } /* Keyword */
577 .o { color: #666666 } /* Operator */
577 .o { color: #666666 } /* Operator */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
582 .gd { color: #A00000 } /* Generic.Deleted */
582 .gd { color: #A00000 } /* Generic.Deleted */
583 .ge { font-style: italic } /* Generic.Emph */
583 .ge { font-style: italic } /* Generic.Emph */
584 .gr { color: #FF0000 } /* Generic.Error */
584 .gr { color: #FF0000 } /* Generic.Error */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
586 .gi { color: #00A000 } /* Generic.Inserted */
586 .gi { color: #00A000 } /* Generic.Inserted */
587 .go { color: #808080 } /* Generic.Output */
587 .go { color: #808080 } /* Generic.Output */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
589 .gs { font-weight: bold } /* Generic.Strong */
589 .gs { font-weight: bold } /* Generic.Strong */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
591 .gt { color: #0040D0 } /* Generic.Traceback */
591 .gt { color: #0040D0 } /* Generic.Traceback */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
595 .kp { color: #008000 } /* Keyword.Pseudo */
595 .kp { color: #008000 } /* Keyword.Pseudo */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
597 .kt { color: #B00040 } /* Keyword.Type */
597 .kt { color: #B00040 } /* Keyword.Type */
598 .m { color: #666666 } /* Literal.Number */
598 .m { color: #666666 } /* Literal.Number */
599 .s { color: #BA2121 } /* Literal.String */
599 .s { color: #BA2121 } /* Literal.String */
600 .na { color: #7D9029 } /* Name.Attribute */
600 .na { color: #7D9029 } /* Name.Attribute */
601 .nb { color: #008000 } /* Name.Builtin */
601 .nb { color: #008000 } /* Name.Builtin */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
603 .no { color: #880000 } /* Name.Constant */
603 .no { color: #880000 } /* Name.Constant */
604 .nd { color: #AA22FF } /* Name.Decorator */
604 .nd { color: #AA22FF } /* Name.Decorator */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
607 .nf { color: #0000FF } /* Name.Function */
607 .nf { color: #0000FF } /* Name.Function */
608 .nl { color: #A0A000 } /* Name.Label */
608 .nl { color: #A0A000 } /* Name.Label */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
611 .nv { color: #19177C } /* Name.Variable */
611 .nv { color: #19177C } /* Name.Variable */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
613 .w { color: #bbbbbb } /* Text.Whitespace */
613 .w { color: #bbbbbb } /* Text.Whitespace */
614 .mf { color: #666666 } /* Literal.Number.Float */
614 .mf { color: #666666 } /* Literal.Number.Float */
615 .mh { color: #666666 } /* Literal.Number.Hex */
615 .mh { color: #666666 } /* Literal.Number.Hex */
616 .mi { color: #666666 } /* Literal.Number.Integer */
616 .mi { color: #666666 } /* Literal.Number.Integer */
617 .mo { color: #666666 } /* Literal.Number.Oct */
617 .mo { color: #666666 } /* Literal.Number.Oct */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
619 .sc { color: #BA2121 } /* Literal.String.Char */
619 .sc { color: #BA2121 } /* Literal.String.Char */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
625 .sx { color: #008000 } /* Literal.String.Other */
625 .sx { color: #008000 } /* Literal.String.Other */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
628 .ss { color: #19177C } /* Literal.String.Symbol */
628 .ss { color: #19177C } /* Literal.String.Symbol */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
630 .vc { color: #19177C } /* Name.Variable.Class */
630 .vc { color: #19177C } /* Name.Variable.Class */
631 .vg { color: #19177C } /* Name.Variable.Global */
631 .vg { color: #19177C } /* Name.Variable.Global */
632 .vi { color: #19177C } /* Name.Variable.Instance */
632 .vi { color: #19177C } /* Name.Variable.Instance */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
634 }
634 }
635
635
636 /* customized pre blocks for markdown/rst */
636 /* customized pre blocks for markdown/rst */
637 pre.literal-block, .codehilite pre{
637 pre.literal-block, .codehilite pre{
638 padding: @padding;
638 padding: @padding;
639 border: 1px solid @grey6;
639 border: 1px solid @grey6;
640 .border-radius(@border-radius);
640 .border-radius(@border-radius);
641 background-color: @grey7;
641 background-color: @grey7;
642 }
642 }
643
643
644
644
645 /* START NEW CODE BLOCK CSS */
645 /* START NEW CODE BLOCK CSS */
646
646
647 @cb-line-height: 18px;
647 @cb-line-height: 18px;
648 @cb-line-code-padding: 10px;
648 @cb-line-code-padding: 10px;
649 @cb-text-padding: 5px;
649 @cb-text-padding: 5px;
650
650
651 @pill-padding: 2px 7px;
651 @pill-padding: 2px 7px;
652
652
653 input.filediff-collapse-state {
653 input.filediff-collapse-state {
654 display: none;
654 display: none;
655
655
656 &:checked + .filediff { /* file diff is collapsed */
656 &:checked + .filediff { /* file diff is collapsed */
657 .cb {
657 .cb {
658 display: none
658 display: none
659 }
659 }
660 .filediff-collapse-indicator {
660 .filediff-collapse-indicator {
661 width: 0;
661 width: 0;
662 height: 0;
662 height: 0;
663 border-style: solid;
663 border-style: solid;
664 border-width: 6.5px 0 6.5px 11.3px;
664 border-width: 6.5px 0 6.5px 11.3px;
665 border-color: transparent transparent transparent #ccc;
665 border-color: transparent transparent transparent #ccc;
666 }
666 }
667 .filediff-menu {
667 .filediff-menu {
668 display: none;
668 display: none;
669 }
669 }
670 margin: 10px 0 0 0;
670 margin: 10px 0 0 0;
671 }
671 }
672
672
673 &+ .filediff { /* file diff is expanded */
673 &+ .filediff { /* file diff is expanded */
674 .filediff-collapse-indicator {
674 .filediff-collapse-indicator {
675 width: 0;
675 width: 0;
676 height: 0;
676 height: 0;
677 border-style: solid;
677 border-style: solid;
678 border-width: 11.3px 6.5px 0 6.5px;
678 border-width: 11.3px 6.5px 0 6.5px;
679 border-color: #ccc transparent transparent transparent;
679 border-color: #ccc transparent transparent transparent;
680 }
680 }
681 .filediff-menu {
681 .filediff-menu {
682 display: block;
682 display: block;
683 }
683 }
684 margin: 10px 0;
684 margin: 10px 0;
685 &:nth-child(2) {
685 &:nth-child(2) {
686 margin: 0;
686 margin: 0;
687 }
687 }
688 }
688 }
689 }
689 }
690 .cs_files {
690 .cs_files {
691 clear: both;
691 clear: both;
692 }
692 }
693
693
694 .diffset-menu {
694 .diffset-menu {
695 margin-bottom: 20px;
695 margin-bottom: 20px;
696 }
696 }
697 .diffset {
697 .diffset {
698 margin: 20px auto;
698 margin: 20px auto;
699 .diffset-heading {
699 .diffset-heading {
700 border: 1px solid @grey5;
700 border: 1px solid @grey5;
701 margin-bottom: -1px;
701 margin-bottom: -1px;
702 // margin-top: 20px;
702 // margin-top: 20px;
703 h2 {
703 h2 {
704 margin: 0;
704 margin: 0;
705 line-height: 38px;
705 line-height: 38px;
706 padding-left: 10px;
706 padding-left: 10px;
707 }
707 }
708 .btn {
708 .btn {
709 margin: 0;
709 margin: 0;
710 }
710 }
711 background: @grey6;
711 background: @grey6;
712 display: block;
712 display: block;
713 padding: 5px;
713 padding: 5px;
714 }
714 }
715 .diffset-heading-warning {
715 .diffset-heading-warning {
716 background: @alert3-inner;
716 background: @alert3-inner;
717 border: 1px solid @alert3;
717 border: 1px solid @alert3;
718 }
718 }
719 &.diffset-comments-disabled {
719 &.diffset-comments-disabled {
720 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
720 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
721 display: none !important;
721 display: none !important;
722 }
722 }
723 }
723 }
724 }
724 }
725
725
726 .pill {
726 .pill {
727 display: block;
727 display: block;
728 float: left;
728 float: left;
729 padding: @pill-padding;
729 padding: @pill-padding;
730 }
730 }
731 .pill-group {
731 .pill-group {
732 .pill {
732 .pill {
733 opacity: .8;
733 opacity: .8;
734 &:first-child {
734 &:first-child {
735 border-radius: @border-radius 0 0 @border-radius;
735 border-radius: @border-radius 0 0 @border-radius;
736 }
736 }
737 &:last-child {
737 &:last-child {
738 border-radius: 0 @border-radius @border-radius 0;
738 border-radius: 0 @border-radius @border-radius 0;
739 }
739 }
740 &:only-child {
740 &:only-child {
741 border-radius: @border-radius;
741 border-radius: @border-radius;
742 }
742 }
743 }
743 }
744 }
744 }
745
745
746 /* Main comments*/
746 /* Main comments*/
747 #comments {
747 #comments {
748 .comment-selected {
748 .comment-selected {
749 border-left: 6px solid @comment-highlight-color;
749 border-left: 6px solid @comment-highlight-color;
750 padding-left: 3px;
750 padding-left: 3px;
751 margin-left: -9px;
751 margin-left: -9px;
752 }
752 }
753 }
753 }
754
754
755 .filediff {
755 .filediff {
756 border: 1px solid @grey5;
756 border: 1px solid @grey5;
757
757
758 /* START OVERRIDES */
758 /* START OVERRIDES */
759 .code-highlight {
759 .code-highlight {
760 border: none; // TODO: remove this border from the global
760 border: none; // TODO: remove this border from the global
761 // .code-highlight, it doesn't belong there
761 // .code-highlight, it doesn't belong there
762 }
762 }
763 label {
763 label {
764 margin: 0; // TODO: remove this margin definition from global label
764 margin: 0; // TODO: remove this margin definition from global label
765 // it doesn't belong there - if margin on labels
765 // it doesn't belong there - if margin on labels
766 // are needed for a form they should be defined
766 // are needed for a form they should be defined
767 // in the form's class
767 // in the form's class
768 }
768 }
769 /* END OVERRIDES */
769 /* END OVERRIDES */
770
770
771 * {
771 * {
772 box-sizing: border-box;
772 box-sizing: border-box;
773 }
773 }
774 .filediff-anchor {
774 .filediff-anchor {
775 visibility: hidden;
775 visibility: hidden;
776 }
776 }
777 &:hover {
777 &:hover {
778 .filediff-anchor {
778 .filediff-anchor {
779 visibility: visible;
779 visibility: visible;
780 }
780 }
781 }
781 }
782
782
783 .filediff-collapse-indicator {
783 .filediff-collapse-indicator {
784 border-style: solid;
784 border-style: solid;
785 float: left;
785 float: left;
786 margin: 4px 0px 0 0;
786 margin: 4px 0px 0 0;
787 cursor: pointer;
787 cursor: pointer;
788 }
788 }
789
789
790 .filediff-heading {
790 .filediff-heading {
791 background: @grey7;
791 background: @grey7;
792 cursor: pointer;
792 cursor: pointer;
793 display: block;
793 display: block;
794 padding: 5px 10px;
794 padding: 5px 10px;
795 }
795 }
796 .filediff-heading:after {
796 .filediff-heading:after {
797 content: "";
797 content: "";
798 display: table;
798 display: table;
799 clear: both;
799 clear: both;
800 }
800 }
801 .filediff-heading:hover {
801 .filediff-heading:hover {
802 background: #e1e9f4 !important;
802 background: #e1e9f4 !important;
803 }
803 }
804
804
805 .filediff-menu {
805 .filediff-menu {
806 float: right;
806 float: right;
807 text-align: right;
807 text-align: right;
808 padding: 5px 5px 5px 0px;
808 padding: 5px 5px 5px 0px;
809
809
810 &> a,
810 &> a,
811 &> span {
811 &> span {
812 padding: 1px;
812 padding: 1px;
813 }
813 }
814 }
814 }
815
815
816 .pill {
816 .pill {
817 &[op="name"] {
817 &[op="name"] {
818 background: none;
818 background: none;
819 color: @grey2;
819 color: @grey2;
820 opacity: 1;
820 opacity: 1;
821 color: white;
821 color: white;
822 }
822 }
823 &[op="limited"] {
823 &[op="limited"] {
824 background: @grey2;
824 background: @grey2;
825 color: white;
825 color: white;
826 }
826 }
827 &[op="binary"] {
827 &[op="binary"] {
828 background: @color7;
828 background: @color7;
829 color: white;
829 color: white;
830 }
830 }
831 &[op="modified"] {
831 &[op="modified"] {
832 background: @alert1;
832 background: @alert1;
833 color: white;
833 color: white;
834 }
834 }
835 &[op="renamed"] {
835 &[op="renamed"] {
836 background: @color4;
836 background: @color4;
837 color: white;
837 color: white;
838 }
838 }
839 &[op="mode"] {
839 &[op="mode"] {
840 background: @grey3;
840 background: @grey3;
841 color: white;
841 color: white;
842 }
842 }
843 &[op="symlink"] {
843 &[op="symlink"] {
844 background: @color8;
844 background: @color8;
845 color: white;
845 color: white;
846 }
846 }
847
847
848 &[op="added"] { /* added lines */
848 &[op="added"] { /* added lines */
849 background: @alert1;
849 background: @alert1;
850 color: white;
850 color: white;
851 }
851 }
852 &[op="deleted"] { /* deleted lines */
852 &[op="deleted"] { /* deleted lines */
853 background: @alert2;
853 background: @alert2;
854 color: white;
854 color: white;
855 }
855 }
856
856
857 &[op="created"] { /* created file */
857 &[op="created"] { /* created file */
858 background: @alert1;
858 background: @alert1;
859 color: white;
859 color: white;
860 }
860 }
861 &[op="removed"] { /* deleted file */
861 &[op="removed"] { /* deleted file */
862 background: @color5;
862 background: @color5;
863 color: white;
863 color: white;
864 }
864 }
865 }
865 }
866
866
867 .filediff-collapse-button, .filediff-expand-button {
867 .filediff-collapse-button, .filediff-expand-button {
868 cursor: pointer;
868 cursor: pointer;
869 }
869 }
870 .filediff-collapse-button {
870 .filediff-collapse-button {
871 display: inline;
871 display: inline;
872 }
872 }
873 .filediff-expand-button {
873 .filediff-expand-button {
874 display: none;
874 display: none;
875 }
875 }
876 .filediff-collapsed .filediff-collapse-button {
876 .filediff-collapsed .filediff-collapse-button {
877 display: none;
877 display: none;
878 }
878 }
879 .filediff-collapsed .filediff-expand-button {
879 .filediff-collapsed .filediff-expand-button {
880 display: inline;
880 display: inline;
881 }
881 }
882
882
883 /**** COMMENTS ****/
883 /**** COMMENTS ****/
884
884
885 .filediff-menu {
885 .filediff-menu {
886 .show-comment-button {
886 .show-comment-button {
887 display: none;
887 display: none;
888 }
888 }
889 }
889 }
890 &.hide-comments {
890 &.hide-comments {
891 .inline-comments {
891 .inline-comments {
892 display: none;
892 display: none;
893 }
893 }
894 .filediff-menu {
894 .filediff-menu {
895 .show-comment-button {
895 .show-comment-button {
896 display: inline;
896 display: inline;
897 }
897 }
898 .hide-comment-button {
898 .hide-comment-button {
899 display: none;
899 display: none;
900 }
900 }
901 }
901 }
902 }
902 }
903
903
904 .hide-line-comments {
904 .hide-line-comments {
905 .inline-comments {
905 .inline-comments {
906 display: none;
906 display: none;
907 }
907 }
908 }
908 }
909
909
910 /**** END COMMENTS ****/
910 /**** END COMMENTS ****/
911
911
912 }
912 }
913
913
914 .filediff-outdated {
914 .filediff-outdated {
915 padding: 8px 0;
915 padding: 8px 0;
916
916
917 .filediff-heading {
917 .filediff-heading {
918 opacity: .5;
918 opacity: .5;
919 }
919 }
920 }
920 }
921
921
922 table.cb {
922 table.cb {
923 width: 100%;
923 width: 100%;
924 border-collapse: collapse;
924 border-collapse: collapse;
925
925
926 .cb-text {
926 .cb-text {
927 padding: @cb-text-padding;
927 padding: @cb-text-padding;
928 }
928 }
929 .cb-hunk {
929 .cb-hunk {
930 padding: @cb-text-padding;
930 padding: @cb-text-padding;
931 }
931 }
932 .cb-expand {
932 .cb-expand {
933 display: none;
933 display: none;
934 }
934 }
935 .cb-collapse {
935 .cb-collapse {
936 display: inline;
936 display: inline;
937 }
937 }
938 &.cb-collapsed {
938 &.cb-collapsed {
939 .cb-line {
939 .cb-line {
940 display: none;
940 display: none;
941 }
941 }
942 .cb-expand {
942 .cb-expand {
943 display: inline;
943 display: inline;
944 }
944 }
945 .cb-collapse {
945 .cb-collapse {
946 display: none;
946 display: none;
947 }
947 }
948 }
948 }
949
949
950 /* intentionally general selector since .cb-line-selected must override it
950 /* intentionally general selector since .cb-line-selected must override it
951 and they both use !important since the td itself may have a random color
951 and they both use !important since the td itself may have a random color
952 generated by annotation blocks. TLDR: if you change it, make sure
952 generated by annotation blocks. TLDR: if you change it, make sure
953 annotated block selection and line selection in file view still work */
953 annotated block selection and line selection in file view still work */
954 .cb-line-fresh .cb-content {
954 .cb-line-fresh .cb-content {
955 background: white !important;
955 background: white !important;
956 }
956 }
957 .cb-warning {
957 .cb-warning {
958 background: #fff4dd;
958 background: #fff4dd;
959 }
959 }
960
960
961 &.cb-diff-sideside {
961 &.cb-diff-sideside {
962 td {
962 td {
963 &.cb-content {
963 &.cb-content {
964 width: 50%;
964 width: 50%;
965 }
965 }
966 }
966 }
967 }
967 }
968
968
969 tr {
969 tr {
970 &.cb-annotate {
970 &.cb-annotate {
971 border-top: 1px solid #eee;
971 border-top: 1px solid #eee;
972
973 &+ .cb-line {
974 border-top: 1px solid #eee;
975 }
976
977 &:first-child {
978 border-top: none;
979 &+ .cb-line {
980 border-top: none;
981 }
982 }
983 }
972 }
984
973
985 &.cb-hunk {
974 &.cb-hunk {
986 font-family: @font-family-monospace;
975 font-family: @font-family-monospace;
987 color: rgba(0, 0, 0, 0.3);
976 color: rgba(0, 0, 0, 0.3);
988
977
989 td {
978 td {
990 &:first-child {
979 &:first-child {
991 background: #edf2f9;
980 background: #edf2f9;
992 }
981 }
993 &:last-child {
982 &:last-child {
994 background: #f4f7fb;
983 background: #f4f7fb;
995 }
984 }
996 }
985 }
997 }
986 }
998 }
987 }
999
988
1000
989
1001 td {
990 td {
1002 vertical-align: top;
991 vertical-align: top;
1003 padding: 0;
992 padding: 0;
1004
993
1005 &.cb-content {
994 &.cb-content {
1006 font-size: 12.35px;
995 font-size: 12.35px;
1007
996
1008 &.cb-line-selected .cb-code {
997 &.cb-line-selected .cb-code {
1009 background: @comment-highlight-color !important;
998 background: @comment-highlight-color !important;
1010 }
999 }
1011
1000
1012 span.cb-code {
1001 span.cb-code {
1013 line-height: @cb-line-height;
1002 line-height: @cb-line-height;
1014 padding-left: @cb-line-code-padding;
1003 padding-left: @cb-line-code-padding;
1015 padding-right: @cb-line-code-padding;
1004 padding-right: @cb-line-code-padding;
1016 display: block;
1005 display: block;
1017 white-space: pre-wrap;
1006 white-space: pre-wrap;
1018 font-family: @font-family-monospace;
1007 font-family: @font-family-monospace;
1019 word-break: break-word;
1008 word-break: break-word;
1020 .nonl {
1009 .nonl {
1021 color: @color5;
1010 color: @color5;
1022 }
1011 }
1023 }
1012 }
1024
1013
1025 &> button.cb-comment-box-opener {
1014 &> button.cb-comment-box-opener {
1026
1015
1027 padding: 2px 2px 1px 3px;
1016 padding: 2px 2px 1px 3px;
1028 margin-left: -6px;
1017 margin-left: -6px;
1029 margin-top: -1px;
1018 margin-top: -1px;
1030
1019
1031 border-radius: @border-radius;
1020 border-radius: @border-radius;
1032 position: absolute;
1021 position: absolute;
1033 display: none;
1022 display: none;
1034 }
1023 }
1035 .cb-comment {
1024 .cb-comment {
1036 margin-top: 10px;
1025 margin-top: 10px;
1037 white-space: normal;
1026 white-space: normal;
1038 }
1027 }
1039 }
1028 }
1040 &:hover {
1029 &:hover {
1041 button.cb-comment-box-opener {
1030 button.cb-comment-box-opener {
1042 display: block;
1031 display: block;
1043 }
1032 }
1044 &+ td button.cb-comment-box-opener {
1033 &+ td button.cb-comment-box-opener {
1045 display: block
1034 display: block
1046 }
1035 }
1047 }
1036 }
1048
1037
1049 &.cb-data {
1038 &.cb-data {
1050 text-align: right;
1039 text-align: right;
1051 width: 30px;
1040 width: 30px;
1052 font-family: @font-family-monospace;
1041 font-family: @font-family-monospace;
1053
1042
1054 .icon-comment {
1043 .icon-comment {
1055 cursor: pointer;
1044 cursor: pointer;
1056 }
1045 }
1057 &.cb-line-selected > div {
1046 &.cb-line-selected > div {
1058 display: block;
1047 display: block;
1059 background: @comment-highlight-color !important;
1048 background: @comment-highlight-color !important;
1060 line-height: @cb-line-height;
1049 line-height: @cb-line-height;
1061 color: rgba(0, 0, 0, 0.3);
1050 color: rgba(0, 0, 0, 0.3);
1062 }
1051 }
1063 }
1052 }
1064
1053
1065 &.cb-lineno {
1054 &.cb-lineno {
1066 padding: 0;
1055 padding: 0;
1067 width: 50px;
1056 width: 50px;
1068 color: rgba(0, 0, 0, 0.3);
1057 color: rgba(0, 0, 0, 0.3);
1069 text-align: right;
1058 text-align: right;
1070 border-right: 1px solid #eee;
1059 border-right: 1px solid #eee;
1071 font-family: @font-family-monospace;
1060 font-family: @font-family-monospace;
1072
1061
1073 a::before {
1062 a::before {
1074 content: attr(data-line-no);
1063 content: attr(data-line-no);
1075 }
1064 }
1076 &.cb-line-selected a {
1065 &.cb-line-selected a {
1077 background: @comment-highlight-color !important;
1066 background: @comment-highlight-color !important;
1078 }
1067 }
1079
1068
1080 a {
1069 a {
1081 display: block;
1070 display: block;
1082 padding-right: @cb-line-code-padding;
1071 padding-right: @cb-line-code-padding;
1083 padding-left: @cb-line-code-padding;
1072 padding-left: @cb-line-code-padding;
1084 line-height: @cb-line-height;
1073 line-height: @cb-line-height;
1085 color: rgba(0, 0, 0, 0.3);
1074 color: rgba(0, 0, 0, 0.3);
1086 }
1075 }
1087 }
1076 }
1088
1077
1089 &.cb-empty {
1078 &.cb-empty {
1090 background: @grey7;
1079 background: @grey7;
1091 }
1080 }
1092
1081
1093 ins {
1082 ins {
1094 color: black;
1083 color: black;
1095 background: #a6f3a6;
1084 background: #a6f3a6;
1096 text-decoration: none;
1085 text-decoration: none;
1097 }
1086 }
1098 del {
1087 del {
1099 color: black;
1088 color: black;
1100 background: #f8cbcb;
1089 background: #f8cbcb;
1101 text-decoration: none;
1090 text-decoration: none;
1102 }
1091 }
1103 &.cb-addition {
1092 &.cb-addition {
1104 background: #ecffec;
1093 background: #ecffec;
1105
1094
1106 &.blob-lineno {
1095 &.blob-lineno {
1107 background: #ddffdd;
1096 background: #ddffdd;
1108 }
1097 }
1109 }
1098 }
1110 &.cb-deletion {
1099 &.cb-deletion {
1111 background: #ffecec;
1100 background: #ffecec;
1112
1101
1113 &.blob-lineno {
1102 &.blob-lineno {
1114 background: #ffdddd;
1103 background: #ffdddd;
1115 }
1104 }
1116 }
1105 }
1117
1106 &.cb-annotate-message-spacer {
1107 width:8px;
1108 }
1118 &.cb-annotate-info {
1109 &.cb-annotate-info {
1119 width: 320px;
1110 width: 320px;
1120 min-width: 320px;
1111 min-width: 320px;
1121 max-width: 320px;
1112 max-width: 320px;
1122 padding: 5px 2px;
1113 padding: 5px 2px;
1123 font-size: 13px;
1114 font-size: 13px;
1124
1115
1125 strong.cb-annotate-message {
1116 .cb-annotate-message {
1126 padding: 5px 0;
1117 padding: 2px 0px 0px 0px;
1127 white-space: pre-line;
1118 white-space: pre-line;
1128 display: inline-block;
1119 overflow: hidden;
1129 }
1120 }
1130 .rc-user {
1121 .rc-user {
1131 float: none;
1122 float: none;
1132 padding: 0 6px 0 17px;
1123 padding: 0 6px 0 17px;
1133 min-width: auto;
1124 min-width: unset;
1134 min-height: auto;
1125 min-height: unset;
1135 }
1126 }
1136 }
1127 }
1137
1128
1138 &.cb-annotate-revision {
1129 &.cb-annotate-revision {
1139 cursor: pointer;
1130 cursor: pointer;
1140 text-align: right;
1131 text-align: right;
1132 padding: 1px 3px 0px 3px;
1141 }
1133 }
1142 }
1134 }
1143 }
1135 }
@@ -1,66 +1,71 b''
1 <%def name="render_line(line_num, tokens,
1 <%def name="render_line(line_num, tokens,
2 annotation=None,
2 annotation=None,
3 bgcolor=None)">
3 bgcolor=None, show_annotation=None)">
4 <%
4 <%
5 from rhodecode.lib.codeblocks import render_tokenstream
5 from rhodecode.lib.codeblocks import render_tokenstream
6 # avoid module lookup for performance
6 # avoid module lookup for performance
7 html_escape = h.html_escape
7 html_escape = h.html_escape
8 %>
8 %>
9 <tr class="cb-line cb-line-fresh"
9 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
10 %if annotation:
10 %if annotation:
11 data-revision="${annotation.revision}"
11 data-revision="${annotation.revision}"
12 %endif
12 %endif
13 >
13 >
14
15 % if annotation:
16 % if show_annotation:
17 <td class="cb-annotate-info tooltip"
18 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
19 >
20 ${h.gravatar_with_user(annotation.author, 16) | n}
21 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 </td>
23 <td class="cb-annotate-message-spacer"></td>
24 <td
25 class="cb-annotate-revision"
26 data-revision="${annotation.revision}"
27 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
28 style="background: ${bgcolor}">
29 <a class="cb-annotate" href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
30 r${annotation.revision}
31 </a>
32 </td>
33 % else:
34 <td></td>
35 <td class="cb-annotate-message-spacer"></td>
36 <td
37 class="cb-annotate-revision"
38 data-revision="${annotation.revision}"
39 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
40 style="background: ${bgcolor}">
41 </td>
42 % endif
43 % else:
44 <td colspan="3"></td>
45 % endif
46
47
14 <td class="cb-lineno" id="L${line_num}">
48 <td class="cb-lineno" id="L${line_num}">
15 <a data-line-no="${line_num}" href="#L${line_num}"></a>
49 <a data-line-no="${line_num}" href="#L${line_num}"></a>
16 </td>
50 </td>
17 <td class="cb-content cb-content-fresh"
51 <td class="cb-content cb-content-fresh"
18 %if bgcolor:
52 %if bgcolor:
19 style="background: ${bgcolor}"
53 style="background: ${bgcolor}"
20 %endif
54 %endif
21 >
55 >
22 ## newline at end is necessary for highlight to work when line is empty
56 ## newline at end is necessary for highlight to work when line is empty
23 ## and for copy pasting code to work as expected
57 ## and for copy pasting code to work as expected
24 <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span>
58 <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span>
25 </td>
59 </td>
26 </tr>
60 </tr>
27 </%def>
61 </%def>
28
62
29 <%def name="render_annotation_lines(annotation, lines, color_hasher)">
63 <%def name="render_annotation_lines(annotation, lines, color_hasher)">
30 <%
64 % for line_num, tokens in lines:
31 rowspan = len(lines) + 1 # span the line's <tr> and annotation <tr>
32 %>
33 %if not annotation:
34 <tr class="cb-annotate">
35 <td class="cb-annotate-message" rowspan="${rowspan}"></td>
36 <td class="cb-annotate-revision" rowspan="${rowspan}"></td>
37 </tr>
38 %else:
39 <tr class="cb-annotate">
40 <td class="cb-annotate-info tooltip"
41 rowspan="${rowspan}"
42 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
43 >
44 ${h.gravatar_with_user(annotation.author, 16) | n}
45 <strong class="cb-annotate-message">${h.truncate(annotation.message, len(lines) * 30)}</strong>
46 </td>
47 <td
48 class="cb-annotate-revision"
49 rowspan="${rowspan}"
50 data-revision="${annotation.revision}"
51 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
52 style="background: ${color_hasher(annotation.raw_id)}">
53 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
54 r${annotation.revision}
55 </a>
56 </td>
57 </tr>
58 %endif
59
60 %for line_num, tokens in lines:
61 ${render_line(line_num, tokens,
65 ${render_line(line_num, tokens,
62 bgcolor=color_hasher(annotation and annotation.raw_id or ''),
66 bgcolor=color_hasher(annotation and annotation.raw_id or ''),
63 annotation=annotation,
67 annotation=annotation, show_annotation=loop.first
64 )}
68 )}
65 %endfor
69 % endfor
70
66 </%def>
71 </%def>
General Comments 0
You need to be logged in to leave comments. Login now