@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DifferentialChangesetTwoUpRenderer
4 extends DifferentialChangesetHTMLRenderer {
5
6 private $newOffsetMap;
7
8 public function isOneUpRenderer() {
9 return false;
10 }
11
12 protected function getRendererTableClass() {
13 return 'diff-2up';
14 }
15
16 public function getRendererKey() {
17 return '2up';
18 }
19
20 protected function renderColgroup() {
21 return phutil_tag('colgroup', array(), array(
22 phutil_tag('col', array('class' => 'num')),
23 phutil_tag('col', array('class' => 'left')),
24 phutil_tag('col', array('class' => 'num')),
25 phutil_tag('col', array('class' => 'copy')),
26 phutil_tag('col', array('class' => 'right')),
27 phutil_tag('col', array('class' => 'cov')),
28 ));
29 }
30
31 public function renderTextChange(
32 $range_start,
33 $range_len,
34 $rows) {
35
36 $hunk_starts = $this->getHunkStartLines();
37
38 $context_not_available = null;
39 if ($hunk_starts) {
40 $context_not_available = javelin_tag(
41 'tr',
42 array(
43 'sigil' => 'context-target',
44 ),
45 phutil_tag(
46 'td',
47 array(
48 'colspan' => 6,
49 'class' => 'show-more',
50 ),
51 pht('Context not available.')));
52 }
53
54 $html = array();
55
56 $old_lines = $this->getOldLines();
57 $new_lines = $this->getNewLines();
58 $gaps = $this->getGaps();
59 $reference = $this->getRenderingReference();
60
61 list($left_prefix, $right_prefix) = $this->getLineIDPrefixes();
62
63 $changeset = $this->getChangeset();
64 $copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
65 $highlight_old = $this->getHighlightOld();
66 $highlight_new = $this->getHighlightNew();
67 $old_render = $this->getOldRender();
68 $new_render = $this->getNewRender();
69 $original_left = $this->getOriginalOld();
70 $original_right = $this->getOriginalNew();
71 $mask = $this->getMask();
72
73 $scope_engine = $this->getScopeEngine();
74 $offset_map = null;
75 $depth_only = $this->getDepthOnlyLines();
76
77 for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
78 if (empty($mask[$ii])) {
79 // If we aren't going to show this line, we've just entered a gap.
80 // Pop information about the next gap off the $gaps stack and render
81 // an appropriate "Show more context" element. This branch eventually
82 // increments $ii by the entire size of the gap and then continues
83 // the loop.
84 $gap = array_pop($gaps);
85 $top = $gap[0];
86 $len = $gap[1];
87
88 $contents = $this->renderShowContextLinks($top, $len, $rows);
89
90 $is_last_block = false;
91 if ($ii + $len >= $rows) {
92 $is_last_block = true;
93 }
94
95 $context_text = null;
96 $context_line = null;
97 if (!$is_last_block && $scope_engine) {
98 $target_line = $new_lines[$ii + $len]['line'];
99 $context_line = $scope_engine->getScopeStart($target_line);
100 if ($context_line !== null) {
101 // The scope engine returns a line number in the file. We need
102 // to map that back to a display offset in the diff.
103 if (!$offset_map) {
104 $offset_map = $this->getNewLineToOffsetMap();
105 }
106 $offset = $offset_map[$context_line];
107 $context_text = $new_render[$offset];
108 }
109 }
110
111 $container = javelin_tag(
112 'tr',
113 array(
114 'sigil' => 'context-target',
115 ),
116 array(
117 phutil_tag(
118 'td',
119 array(
120 'class' => 'show-context-line n left-context',
121 )),
122 phutil_tag(
123 'td',
124 array(
125 'class' => 'show-more',
126 ),
127 $contents),
128 phutil_tag(
129 'td',
130 array(
131 'class' => 'show-context-line n',
132 'data-n' => $context_line,
133 )),
134 phutil_tag(
135 'td',
136 array(
137 'colspan' => 3,
138 'class' => 'show-context',
139 ),
140 // TODO: [HTML] Escaping model here isn't ideal.
141 phutil_safe_html($context_text)),
142 ));
143
144 $html[] = $container;
145
146 $ii += ($len - 1);
147 continue;
148 }
149
150 $o_num = null;
151 $o_classes = '';
152 $o_text = null;
153 if (isset($old_lines[$ii])) {
154 $o_num = $old_lines[$ii]['line'];
155 $o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
156 if ($old_lines[$ii]['type']) {
157 if ($old_lines[$ii]['type'] == '\\') {
158 $o_text = $old_lines[$ii]['text'];
159 $o_class = 'comment';
160 } else if ($original_left && !isset($highlight_old[$o_num])) {
161 $o_class = 'old-rebase';
162 } else if (empty($new_lines[$ii])) {
163 $o_class = 'old old-full';
164 } else {
165 if (isset($depth_only[$ii])) {
166 if ($depth_only[$ii] == '>') {
167 // When a line has depth-only change, we only highlight the
168 // left side of the diff if the depth is decreasing. When the
169 // depth is increasing, the ">>" marker on the right hand side
170 // of the diff generally provides enough visibility on its own.
171
172 $o_class = '';
173 } else {
174 $o_class = 'old';
175 }
176 } else {
177 $o_class = 'old';
178 }
179 }
180 $o_classes = $o_class;
181 }
182 }
183
184 $n_copy = hsprintf('<td class="copy" />');
185 $n_cov = null;
186 $n_colspan = 2;
187 $n_classes = '';
188 $n_num = null;
189 $n_text = null;
190
191 if (isset($new_lines[$ii])) {
192 $n_num = $new_lines[$ii]['line'];
193 $n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
194 $coverage = $this->getCodeCoverage();
195
196 if ($coverage !== null) {
197 if (empty($coverage[$n_num - 1])) {
198 $cov_class = 'N';
199 } else {
200 $cov_class = $coverage[$n_num - 1];
201 }
202 $cov_class = 'cov-'.$cov_class;
203 $n_cov = phutil_tag('td', array('class' => "cov {$cov_class}"));
204 $n_colspan--;
205 }
206
207 if ($new_lines[$ii]['type']) {
208 if ($new_lines[$ii]['type'] == '\\') {
209 $n_text = $new_lines[$ii]['text'];
210 $n_class = 'comment';
211 } else if ($original_right && !isset($highlight_new[$n_num])) {
212 $n_class = 'new-rebase';
213 } else if (empty($old_lines[$ii])) {
214 $n_class = 'new new-full';
215 } else {
216 // When a line has a depth-only change, never highlight it on
217 // the right side. The ">>" marker generally provides enough
218 // visibility on its own for indent depth increases, and the left
219 // side is still highlighted for indent depth decreases.
220
221 if (isset($depth_only[$ii])) {
222 $n_class = '';
223 } else {
224 $n_class = 'new';
225 }
226 }
227 $n_classes = $n_class;
228
229 $not_copied =
230 // If this line only changed depth, copy markers are pointless.
231 (!isset($copy_lines[$n_num])) ||
232 (isset($depth_only[$ii])) ||
233 ($new_lines[$ii]['type'] == '\\');
234
235 if ($not_copied) {
236 $n_copy = phutil_tag('td', array('class' => 'copy'));
237 } else {
238 list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
239 $title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
240 if ($orig_file == '') {
241 $title .= "line {$orig_line}";
242 } else {
243 $title .=
244 basename($orig_file).
245 ":{$orig_line} in dir ".
246 dirname('/'.$orig_file);
247 }
248 $class = ($orig_type == '-' ? 'new-move' : 'new-copy');
249 $n_copy = javelin_tag(
250 'td',
251 array(
252 'meta' => array(
253 'msg' => $title,
254 ),
255 'class' => 'copy '.$class,
256 ));
257 }
258 }
259 }
260
261 if ($o_num && isset($hunk_starts[$o_num])) {
262 $html[] = $context_not_available;
263 }
264
265 if ($o_num && $left_prefix) {
266 $o_id = $left_prefix.$o_num;
267 } else {
268 $o_id = null;
269 }
270
271 if ($n_num && $right_prefix) {
272 $n_id = $right_prefix.$n_num;
273 } else {
274 $n_id = null;
275 }
276
277 $old_comments = $this->getOldComments();
278 $new_comments = $this->getNewComments();
279 $scaffolds = array();
280
281 if ($o_num && isset($old_comments[$o_num])) {
282 foreach ($old_comments[$o_num] as $comment) {
283 $inline = $this->buildInlineComment(
284 $comment,
285 $on_right = false);
286 $scaffold = $this->getRowScaffoldForInline($inline);
287
288 if ($n_num && isset($new_comments[$n_num])) {
289 foreach ($new_comments[$n_num] as $key => $new_comment) {
290 if ($comment->isCompatible($new_comment)) {
291 $companion = $this->buildInlineComment(
292 $new_comment,
293 $on_right = true);
294
295 $scaffold->addInlineView($companion);
296 unset($new_comments[$n_num][$key]);
297 break;
298 }
299 }
300 }
301
302
303 $scaffolds[] = $scaffold;
304 }
305 }
306
307 if ($n_num && isset($new_comments[$n_num])) {
308 foreach ($new_comments[$n_num] as $comment) {
309 $inline = $this->buildInlineComment(
310 $comment,
311 $on_right = true);
312
313 $scaffolds[] = $this->getRowScaffoldForInline($inline);
314 }
315 }
316
317 $old_number = phutil_tag(
318 'td',
319 array(
320 'id' => $o_id,
321 'class' => $o_classes.' n',
322 'data-n' => $o_num,
323 ));
324
325 $new_number = phutil_tag(
326 'td',
327 array(
328 'id' => $n_id,
329 'class' => $n_classes.' n',
330 'data-n' => $n_num,
331 ));
332
333 $html[] = phutil_tag('tr', array(), array(
334 $old_number,
335 phutil_tag(
336 'td',
337 array(
338 'class' => $o_classes,
339 'data-copy-mode' => 'copy-l',
340 ),
341 $o_text),
342 $new_number,
343 $n_copy,
344 phutil_tag(
345 'td',
346 array(
347 'class' => $n_classes,
348 'colspan' => $n_colspan,
349 'data-copy-mode' => 'copy-r',
350 ),
351 $n_text),
352 $n_cov,
353 ));
354
355 if ($context_not_available && ($ii == $rows - 1)) {
356 $html[] = $context_not_available;
357 }
358
359 foreach ($scaffolds as $scaffold) {
360 $html[] = $scaffold;
361 }
362 }
363
364 return $this->wrapChangeInTable(phutil_implode_html('', $html));
365 }
366
367 public function renderDocumentEngineBlocks(
368 PhabricatorDocumentEngineBlocks $block_list,
369 $old_changeset_key,
370 $new_changeset_key) {
371
372 $engine = $this->getDocumentEngine();
373
374 $old_ref = null;
375 $new_ref = null;
376 $refs = $block_list->getDocumentRefs();
377 if ($refs) {
378 list($old_ref, $new_ref) = $refs;
379 }
380
381 $old_comments = $this->getOldComments();
382 $new_comments = $this->getNewComments();
383
384 $rows = array();
385 $gap = array();
386 $in_gap = false;
387
388 // NOTE: The generated layout is affected by range constraints, and may
389 // represent only a slice of the document.
390
391 $layout = $block_list->newTwoUpLayout();
392 $available_count = $block_list->getLayoutAvailableRowCount();
393
394 foreach ($layout as $idx => $row) {
395 list($old, $new) = $row;
396
397 if ($old) {
398 $old_key = $old->getBlockKey();
399 $is_visible = $old->getIsVisible();
400 } else {
401 $old_key = null;
402 }
403
404 if ($new) {
405 $new_key = $new->getBlockKey();
406 $is_visible = $new->getIsVisible();
407 } else {
408 $new_key = null;
409 }
410
411 if (!$is_visible) {
412 if (!$in_gap) {
413 $in_gap = true;
414 }
415 $gap[$idx] = $row;
416 continue;
417 }
418
419 if ($in_gap) {
420 $in_gap = false;
421 $rows[] = $this->renderDocumentEngineGap(
422 $gap,
423 $available_count);
424 $gap = array();
425 }
426
427 if ($old) {
428 $is_rem = ($old->getDifferenceType() === '-');
429 } else {
430 $is_rem = false;
431 }
432
433 if ($new) {
434 $is_add = ($new->getDifferenceType() === '+');
435 } else {
436 $is_add = false;
437 }
438
439 if ($is_rem && $is_add) {
440 $block_diff = $engine->newBlockDiffViews(
441 $old_ref,
442 $old,
443 $new_ref,
444 $new);
445
446 $old_content = $block_diff->getOldContent();
447 $new_content = $block_diff->getNewContent();
448
449 $old_classes = $block_diff->getOldClasses();
450 $new_classes = $block_diff->getNewClasses();
451 } else {
452 $old_classes = array();
453 $new_classes = array();
454
455 if ($old) {
456 $old_content = $engine->newBlockContentView(
457 $old_ref,
458 $old);
459
460 if ($is_rem) {
461 $old_classes[] = 'old';
462 $old_classes[] = 'old-full';
463 }
464 } else {
465 $old_content = null;
466 }
467
468 if ($new) {
469 $new_content = $engine->newBlockContentView(
470 $new_ref,
471 $new);
472
473 if ($is_add) {
474 $new_classes[] = 'new';
475 $new_classes[] = 'new-full';
476 }
477 } else {
478 $new_content = null;
479 }
480 }
481
482 $old_classes[] = 'diff-flush';
483 $old_classes = implode(' ', $old_classes);
484
485 $new_classes[] = 'diff-flush';
486 $new_classes = implode(' ', $new_classes);
487
488 $old_inline_rows = array();
489 if ($old_key !== null) {
490 $old_inlines = idx($old_comments, $old_key, array());
491 foreach ($old_inlines as $inline) {
492 $inline = $this->buildInlineComment(
493 $inline,
494 $on_right = false);
495 $old_inline_rows[] = $this->getRowScaffoldForInline($inline);
496 }
497 }
498
499 $new_inline_rows = array();
500 if ($new_key !== null) {
501 $new_inlines = idx($new_comments, $new_key, array());
502 foreach ($new_inlines as $inline) {
503 $inline = $this->buildInlineComment(
504 $inline,
505 $on_right = true);
506 $new_inline_rows[] = $this->getRowScaffoldForInline($inline);
507 }
508 }
509
510 if ($old_content === null) {
511 $old_id = null;
512 } else {
513 $old_id = "C{$old_changeset_key}OL{$old_key}";
514 }
515
516 $old_line_cell = phutil_tag(
517 'td',
518 array(
519 'id' => $old_id,
520 'data-n' => $old_key,
521 'class' => 'n',
522 ));
523
524 $old_content_cell = phutil_tag(
525 'td',
526 array(
527 'class' => $old_classes,
528 'data-copy-mode' => 'copy-l',
529 ),
530 $old_content);
531
532 if ($new_content === null) {
533 $new_id = null;
534 } else {
535 $new_id = "C{$new_changeset_key}NL{$new_key}";
536 }
537
538 $new_line_cell = phutil_tag(
539 'td',
540 array(
541 'id' => $new_id,
542 'data-n' => $new_key,
543 'class' => 'n',
544 ));
545
546 $copy_gutter = phutil_tag(
547 'td',
548 array(
549 'class' => 'copy',
550 ));
551
552 $new_content_cell = phutil_tag(
553 'td',
554 array(
555 'class' => $new_classes,
556 'colspan' => '2',
557 'data-copy-mode' => 'copy-r',
558 ),
559 $new_content);
560
561 $row_view = phutil_tag(
562 'tr',
563 array(),
564 array(
565 $old_line_cell,
566 $old_content_cell,
567 $new_line_cell,
568 $copy_gutter,
569 $new_content_cell,
570 ));
571
572 $rows[] = array(
573 $row_view,
574 $old_inline_rows,
575 $new_inline_rows,
576 );
577 }
578
579 if ($in_gap) {
580 $rows[] = $this->renderDocumentEngineGap(
581 $gap,
582 $available_count);
583 }
584
585 $output = $this->wrapChangeInTable($rows);
586
587 return $this->renderChangesetTable($output);
588 }
589
590 public function getRowScaffoldForInline(PHUIDiffInlineCommentView $view) {
591 return id(new PHUIDiffTwoUpInlineCommentRowScaffold())
592 ->addInlineView($view);
593 }
594
595 private function getNewLineToOffsetMap() {
596 if ($this->newOffsetMap === null) {
597 $new = $this->getNewLines();
598
599 $map = array();
600 foreach ($new as $offset => $new_line) {
601 if ($new_line === null) {
602 continue;
603 }
604
605 if ($new_line['line'] === null) {
606 continue;
607 }
608
609 $map[$new_line['line']] = $offset;
610 }
611
612 $this->newOffsetMap = $map;
613 }
614
615 return $this->newOffsetMap;
616 }
617
618 protected function getTableSigils() {
619 return array(
620 'intercept-copy',
621 );
622 }
623
624 private function renderDocumentEngineGap(array $gap, $available_count) {
625 $content = $this->renderShowContextLinks(
626 head_key($gap),
627 count($gap),
628 $available_count,
629 $is_blocks = true);
630
631 return javelin_tag(
632 'tr',
633 array(
634 'sigil' => 'context-target',
635 ),
636 phutil_tag(
637 'td',
638 array(
639 'colspan' => 6,
640 'class' => 'show-more',
641 ),
642 $content));
643 }
644
645}