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