Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/render/DifferentialChangesetRenderer.php
12256 views
1
<?php
2
3
abstract class DifferentialChangesetRenderer extends Phobject {
4
5
private $user;
6
private $changeset;
7
private $renderingReference;
8
private $renderPropertyChangeHeader;
9
private $isTopLevel;
10
private $isUndershield;
11
private $hunkStartLines;
12
private $oldLines;
13
private $newLines;
14
private $oldComments;
15
private $newComments;
16
private $oldChangesetID;
17
private $newChangesetID;
18
private $oldAttachesToNewFile;
19
private $newAttachesToNewFile;
20
private $highlightOld = array();
21
private $highlightNew = array();
22
private $codeCoverage;
23
private $handles;
24
private $markupEngine;
25
private $oldRender;
26
private $newRender;
27
private $originalOld;
28
private $originalNew;
29
private $gaps;
30
private $mask;
31
private $originalCharacterEncoding;
32
private $showEditAndReplyLinks;
33
private $canMarkDone;
34
private $objectOwnerPHID;
35
private $highlightingDisabled;
36
private $scopeEngine = false;
37
private $depthOnlyLines;
38
39
private $documentEngine;
40
private $documentEngineBlocks;
41
42
private $oldFile = false;
43
private $newFile = false;
44
45
abstract public function getRendererKey();
46
47
public function setShowEditAndReplyLinks($bool) {
48
$this->showEditAndReplyLinks = $bool;
49
return $this;
50
}
51
52
public function getShowEditAndReplyLinks() {
53
return $this->showEditAndReplyLinks;
54
}
55
56
public function setHighlightingDisabled($highlighting_disabled) {
57
$this->highlightingDisabled = $highlighting_disabled;
58
return $this;
59
}
60
61
public function getHighlightingDisabled() {
62
return $this->highlightingDisabled;
63
}
64
65
public function setOriginalCharacterEncoding($original_character_encoding) {
66
$this->originalCharacterEncoding = $original_character_encoding;
67
return $this;
68
}
69
70
public function getOriginalCharacterEncoding() {
71
return $this->originalCharacterEncoding;
72
}
73
74
public function setIsUndershield($is_undershield) {
75
$this->isUndershield = $is_undershield;
76
return $this;
77
}
78
79
public function getIsUndershield() {
80
return $this->isUndershield;
81
}
82
83
public function setMask($mask) {
84
$this->mask = $mask;
85
return $this;
86
}
87
protected function getMask() {
88
return $this->mask;
89
}
90
91
public function setGaps($gaps) {
92
$this->gaps = $gaps;
93
return $this;
94
}
95
protected function getGaps() {
96
return $this->gaps;
97
}
98
99
public function setDepthOnlyLines(array $lines) {
100
$this->depthOnlyLines = $lines;
101
return $this;
102
}
103
104
public function getDepthOnlyLines() {
105
return $this->depthOnlyLines;
106
}
107
108
public function attachOldFile(PhabricatorFile $old = null) {
109
$this->oldFile = $old;
110
return $this;
111
}
112
113
public function getOldFile() {
114
if ($this->oldFile === false) {
115
throw new PhabricatorDataNotAttachedException($this);
116
}
117
return $this->oldFile;
118
}
119
120
public function hasOldFile() {
121
return (bool)$this->oldFile;
122
}
123
124
public function attachNewFile(PhabricatorFile $new = null) {
125
$this->newFile = $new;
126
return $this;
127
}
128
129
public function getNewFile() {
130
if ($this->newFile === false) {
131
throw new PhabricatorDataNotAttachedException($this);
132
}
133
return $this->newFile;
134
}
135
136
public function hasNewFile() {
137
return (bool)$this->newFile;
138
}
139
140
public function setOriginalNew($original_new) {
141
$this->originalNew = $original_new;
142
return $this;
143
}
144
protected function getOriginalNew() {
145
return $this->originalNew;
146
}
147
148
public function setOriginalOld($original_old) {
149
$this->originalOld = $original_old;
150
return $this;
151
}
152
protected function getOriginalOld() {
153
return $this->originalOld;
154
}
155
156
public function setNewRender($new_render) {
157
$this->newRender = $new_render;
158
return $this;
159
}
160
protected function getNewRender() {
161
return $this->newRender;
162
}
163
164
public function setOldRender($old_render) {
165
$this->oldRender = $old_render;
166
return $this;
167
}
168
protected function getOldRender() {
169
return $this->oldRender;
170
}
171
172
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
173
$this->markupEngine = $markup_engine;
174
return $this;
175
}
176
public function getMarkupEngine() {
177
return $this->markupEngine;
178
}
179
180
public function setHandles(array $handles) {
181
assert_instances_of($handles, 'PhabricatorObjectHandle');
182
$this->handles = $handles;
183
return $this;
184
}
185
protected function getHandles() {
186
return $this->handles;
187
}
188
189
public function setCodeCoverage($code_coverage) {
190
$this->codeCoverage = $code_coverage;
191
return $this;
192
}
193
protected function getCodeCoverage() {
194
return $this->codeCoverage;
195
}
196
197
public function setHighlightNew($highlight_new) {
198
$this->highlightNew = $highlight_new;
199
return $this;
200
}
201
protected function getHighlightNew() {
202
return $this->highlightNew;
203
}
204
205
public function setHighlightOld($highlight_old) {
206
$this->highlightOld = $highlight_old;
207
return $this;
208
}
209
protected function getHighlightOld() {
210
return $this->highlightOld;
211
}
212
213
public function setNewAttachesToNewFile($attaches) {
214
$this->newAttachesToNewFile = $attaches;
215
return $this;
216
}
217
protected function getNewAttachesToNewFile() {
218
return $this->newAttachesToNewFile;
219
}
220
221
public function setOldAttachesToNewFile($attaches) {
222
$this->oldAttachesToNewFile = $attaches;
223
return $this;
224
}
225
protected function getOldAttachesToNewFile() {
226
return $this->oldAttachesToNewFile;
227
}
228
229
public function setNewChangesetID($new_changeset_id) {
230
$this->newChangesetID = $new_changeset_id;
231
return $this;
232
}
233
protected function getNewChangesetID() {
234
return $this->newChangesetID;
235
}
236
237
public function setOldChangesetID($old_changeset_id) {
238
$this->oldChangesetID = $old_changeset_id;
239
return $this;
240
}
241
protected function getOldChangesetID() {
242
return $this->oldChangesetID;
243
}
244
245
public function setDocumentEngine(PhabricatorDocumentEngine $engine) {
246
$this->documentEngine = $engine;
247
return $this;
248
}
249
250
public function getDocumentEngine() {
251
return $this->documentEngine;
252
}
253
254
public function setDocumentEngineBlocks(
255
PhabricatorDocumentEngineBlocks $blocks) {
256
$this->documentEngineBlocks = $blocks;
257
return $this;
258
}
259
260
public function getDocumentEngineBlocks() {
261
return $this->documentEngineBlocks;
262
}
263
264
public function setNewComments(array $new_comments) {
265
foreach ($new_comments as $line_number => $comments) {
266
assert_instances_of($comments, 'PhabricatorInlineComment');
267
}
268
$this->newComments = $new_comments;
269
return $this;
270
}
271
protected function getNewComments() {
272
return $this->newComments;
273
}
274
275
public function setOldComments(array $old_comments) {
276
foreach ($old_comments as $line_number => $comments) {
277
assert_instances_of($comments, 'PhabricatorInlineComment');
278
}
279
$this->oldComments = $old_comments;
280
return $this;
281
}
282
protected function getOldComments() {
283
return $this->oldComments;
284
}
285
286
public function setNewLines(array $new_lines) {
287
$this->newLines = $new_lines;
288
return $this;
289
}
290
protected function getNewLines() {
291
return $this->newLines;
292
}
293
294
public function setOldLines(array $old_lines) {
295
$this->oldLines = $old_lines;
296
return $this;
297
}
298
protected function getOldLines() {
299
return $this->oldLines;
300
}
301
302
public function setHunkStartLines(array $hunk_start_lines) {
303
$this->hunkStartLines = $hunk_start_lines;
304
return $this;
305
}
306
307
protected function getHunkStartLines() {
308
return $this->hunkStartLines;
309
}
310
311
public function setUser(PhabricatorUser $user) {
312
$this->user = $user;
313
return $this;
314
}
315
protected function getUser() {
316
return $this->user;
317
}
318
319
public function setChangeset(DifferentialChangeset $changeset) {
320
$this->changeset = $changeset;
321
return $this;
322
}
323
protected function getChangeset() {
324
return $this->changeset;
325
}
326
327
public function setRenderingReference($rendering_reference) {
328
$this->renderingReference = $rendering_reference;
329
return $this;
330
}
331
protected function getRenderingReference() {
332
return $this->renderingReference;
333
}
334
335
public function setRenderPropertyChangeHeader($should_render) {
336
$this->renderPropertyChangeHeader = $should_render;
337
return $this;
338
}
339
340
private function shouldRenderPropertyChangeHeader() {
341
return $this->renderPropertyChangeHeader;
342
}
343
344
public function setIsTopLevel($is) {
345
$this->isTopLevel = $is;
346
return $this;
347
}
348
349
private function getIsTopLevel() {
350
return $this->isTopLevel;
351
}
352
353
public function setCanMarkDone($can_mark_done) {
354
$this->canMarkDone = $can_mark_done;
355
return $this;
356
}
357
358
public function getCanMarkDone() {
359
return $this->canMarkDone;
360
}
361
362
public function setObjectOwnerPHID($phid) {
363
$this->objectOwnerPHID = $phid;
364
return $this;
365
}
366
367
public function getObjectOwnerPHID() {
368
return $this->objectOwnerPHID;
369
}
370
371
final public function renderChangesetTable($content) {
372
$props = null;
373
if ($this->shouldRenderPropertyChangeHeader()) {
374
$props = $this->renderPropertyChangeHeader();
375
}
376
377
$notice = null;
378
if ($this->getIsTopLevel()) {
379
$force = (!$content && !$props);
380
381
// If we have DocumentEngine messages about the blocks, assume they
382
// explain why there's no content.
383
$blocks = $this->getDocumentEngineBlocks();
384
if ($blocks) {
385
if ($blocks->getMessages()) {
386
$force = false;
387
}
388
}
389
390
$notice = $this->renderChangeTypeHeader($force);
391
}
392
393
$undershield = null;
394
if ($this->getIsUndershield()) {
395
$undershield = $this->renderUndershieldHeader();
396
}
397
398
$result = array(
399
$notice,
400
$props,
401
$undershield,
402
$content,
403
);
404
405
return hsprintf('%s', $result);
406
}
407
408
abstract public function isOneUpRenderer();
409
abstract public function renderTextChange(
410
$range_start,
411
$range_len,
412
$rows);
413
414
public function renderDocumentEngineBlocks(
415
PhabricatorDocumentEngineBlocks $blocks,
416
$old_changeset_key,
417
$new_changeset_key) {
418
return null;
419
}
420
421
abstract protected function renderChangeTypeHeader($force);
422
abstract protected function renderUndershieldHeader();
423
424
protected function didRenderChangesetTableContents($contents) {
425
return $contents;
426
}
427
428
/**
429
* Render a "shield" over the diff, with a message like "This file is
430
* generated and does not need to be reviewed." or "This file was completely
431
* deleted." This UI element hides unimportant text so the reviewer doesn't
432
* need to scroll past it.
433
*
434
* The shield includes a link to view the underlying content. This link
435
* may force certain rendering modes when the link is clicked:
436
*
437
* - `"default"`: Render the diff normally, as though it was not
438
* shielded. This is the default and appropriate if the underlying
439
* diff is a normal change, but was hidden for reasons of not being
440
* important (e.g., generated code).
441
* - `"text"`: Force the text to be shown. This is probably only relevant
442
* when a file is not changed.
443
* - `"none"`: Don't show the link (e.g., text not available).
444
*
445
* @param string Message explaining why the diff is hidden.
446
* @param string|null Force mode, see above.
447
* @return string Shield markup.
448
*/
449
abstract public function renderShield($message, $force = 'default');
450
451
abstract protected function renderPropertyChangeHeader();
452
453
protected function buildPrimitives($range_start, $range_len) {
454
$primitives = array();
455
456
$hunk_starts = $this->getHunkStartLines();
457
458
$mask = $this->getMask();
459
$gaps = $this->getGaps();
460
461
$old = $this->getOldLines();
462
$new = $this->getNewLines();
463
$old_render = $this->getOldRender();
464
$new_render = $this->getNewRender();
465
$old_comments = $this->getOldComments();
466
$new_comments = $this->getNewComments();
467
468
$size = count($old);
469
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
470
if (empty($mask[$ii])) {
471
list($top, $len) = array_pop($gaps);
472
$primitives[] = array(
473
'type' => 'context',
474
'top' => $top,
475
'len' => $len,
476
);
477
478
$ii += ($len - 1);
479
continue;
480
}
481
482
$ospec = array(
483
'type' => 'old',
484
'htype' => null,
485
'cursor' => $ii,
486
'line' => null,
487
'oline' => null,
488
'render' => null,
489
);
490
491
$nspec = array(
492
'type' => 'new',
493
'htype' => null,
494
'cursor' => $ii,
495
'line' => null,
496
'oline' => null,
497
'render' => null,
498
'copy' => null,
499
'coverage' => null,
500
);
501
502
if (isset($old[$ii])) {
503
$ospec['line'] = (int)$old[$ii]['line'];
504
$nspec['oline'] = (int)$old[$ii]['line'];
505
$ospec['htype'] = $old[$ii]['type'];
506
if (isset($old_render[$ii])) {
507
$ospec['render'] = $old_render[$ii];
508
} else if ($ospec['htype'] === '\\') {
509
$ospec['render'] = $old[$ii]['text'];
510
}
511
}
512
513
if (isset($new[$ii])) {
514
$nspec['line'] = (int)$new[$ii]['line'];
515
$ospec['oline'] = (int)$new[$ii]['line'];
516
$nspec['htype'] = $new[$ii]['type'];
517
if (isset($new_render[$ii])) {
518
$nspec['render'] = $new_render[$ii];
519
} else if ($nspec['htype'] === '\\') {
520
$nspec['render'] = $new[$ii]['text'];
521
}
522
}
523
524
if (isset($hunk_starts[$ospec['line']])) {
525
$primitives[] = array(
526
'type' => 'no-context',
527
);
528
}
529
530
$primitives[] = $ospec;
531
$primitives[] = $nspec;
532
533
if ($ospec['line'] !== null && isset($old_comments[$ospec['line']])) {
534
foreach ($old_comments[$ospec['line']] as $comment) {
535
$primitives[] = array(
536
'type' => 'inline',
537
'comment' => $comment,
538
'right' => false,
539
);
540
}
541
}
542
543
if ($nspec['line'] !== null && isset($new_comments[$nspec['line']])) {
544
foreach ($new_comments[$nspec['line']] as $comment) {
545
$primitives[] = array(
546
'type' => 'inline',
547
'comment' => $comment,
548
'right' => true,
549
);
550
}
551
}
552
553
if ($hunk_starts && ($ii == $size - 1)) {
554
$primitives[] = array(
555
'type' => 'no-context',
556
);
557
}
558
}
559
560
if ($this->isOneUpRenderer()) {
561
$primitives = $this->processPrimitivesForOneUp($primitives);
562
}
563
564
return $primitives;
565
}
566
567
private function processPrimitivesForOneUp(array $primitives) {
568
// Primitives come out of buildPrimitives() in two-up format, because it
569
// is the most general, flexible format. To put them into one-up format,
570
// we need to filter and reorder them. In particular:
571
//
572
// - We discard unchanged lines in the old file; in one-up format, we
573
// render them only once.
574
// - We group contiguous blocks of old-modified and new-modified lines, so
575
// they render in "block of old, block of new" order instead of
576
// alternating old and new lines.
577
578
$out = array();
579
580
$old_buf = array();
581
$new_buf = array();
582
foreach ($primitives as $primitive) {
583
$type = $primitive['type'];
584
585
if ($type == 'old') {
586
if (!$primitive['htype']) {
587
// This is a line which appears in both the old file and the new
588
// file, or the spacer corresponding to a line added in the new file.
589
// Ignore it when rendering a one-up diff.
590
continue;
591
}
592
$old_buf[] = $primitive;
593
} else if ($type == 'new') {
594
if ($primitive['line'] === null) {
595
// This is an empty spacer corresponding to a line removed from the
596
// old file. Ignore it when rendering a one-up diff.
597
continue;
598
}
599
if (!$primitive['htype']) {
600
// If this line is the same in both versions of the file, put it in
601
// the old line buffer. This makes sure inlines on old, unchanged
602
// lines end up in the right place.
603
604
// First, we need to flush the line buffers if they're not empty.
605
if ($old_buf) {
606
$out[] = $old_buf;
607
$old_buf = array();
608
}
609
if ($new_buf) {
610
$out[] = $new_buf;
611
$new_buf = array();
612
}
613
$old_buf[] = $primitive;
614
} else {
615
$new_buf[] = $primitive;
616
}
617
} else if ($type == 'context' || $type == 'no-context') {
618
$out[] = $old_buf;
619
$out[] = $new_buf;
620
$old_buf = array();
621
$new_buf = array();
622
$out[] = array($primitive);
623
} else if ($type == 'inline') {
624
625
// If this inline is on the left side, put it after the old lines.
626
if (!$primitive['right']) {
627
$out[] = $old_buf;
628
$out[] = array($primitive);
629
$old_buf = array();
630
} else {
631
$out[] = $old_buf;
632
$out[] = $new_buf;
633
$out[] = array($primitive);
634
$old_buf = array();
635
$new_buf = array();
636
}
637
638
} else {
639
throw new Exception(pht("Unknown primitive type '%s'!", $primitive));
640
}
641
}
642
643
$out[] = $old_buf;
644
$out[] = $new_buf;
645
$out = array_mergev($out);
646
647
return $out;
648
}
649
650
protected function getChangesetProperties($changeset) {
651
$old = $changeset->getOldProperties();
652
$new = $changeset->getNewProperties();
653
654
// If a property has been changed, but is not present on one side of the
655
// change and has an uninteresting default value on the other, remove it.
656
// This most commonly happens when a change adds or removes a file: the
657
// side of the change with the file has a "100644" filemode in Git.
658
659
$defaults = array(
660
'unix:filemode' => '100644',
661
);
662
663
foreach ($defaults as $default_key => $default_value) {
664
$old_value = idx($old, $default_key, $default_value);
665
$new_value = idx($new, $default_key, $default_value);
666
667
$old_default = ($old_value === $default_value);
668
$new_default = ($new_value === $default_value);
669
670
if ($old_default && $new_default) {
671
unset($old[$default_key]);
672
unset($new[$default_key]);
673
}
674
}
675
676
$metadata = $changeset->getMetadata();
677
678
if ($this->hasOldFile()) {
679
$file = $this->getOldFile();
680
if ($file->getImageWidth()) {
681
$dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
682
$old['file:dimensions'] = $dimensions;
683
}
684
$old['file:mimetype'] = $file->getMimeType();
685
$old['file:size'] = phutil_format_bytes($file->getByteSize());
686
} else {
687
$old['file:mimetype'] = idx($metadata, 'old:file:mime-type');
688
$size = idx($metadata, 'old:file:size');
689
if ($size !== null) {
690
$old['file:size'] = phutil_format_bytes($size);
691
}
692
}
693
694
if ($this->hasNewFile()) {
695
$file = $this->getNewFile();
696
if ($file->getImageWidth()) {
697
$dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
698
$new['file:dimensions'] = $dimensions;
699
}
700
$new['file:mimetype'] = $file->getMimeType();
701
$new['file:size'] = phutil_format_bytes($file->getByteSize());
702
} else {
703
$new['file:mimetype'] = idx($metadata, 'new:file:mime-type');
704
$size = idx($metadata, 'new:file:size');
705
if ($size !== null) {
706
$new['file:size'] = phutil_format_bytes($size);
707
}
708
}
709
710
return array($old, $new);
711
}
712
713
public function renderUndoTemplates() {
714
$views = array(
715
'l' => id(new PHUIDiffInlineCommentUndoView())->setIsOnRight(false),
716
'r' => id(new PHUIDiffInlineCommentUndoView())->setIsOnRight(true),
717
);
718
719
foreach ($views as $key => $view) {
720
$scaffold = $this->getRowScaffoldForInline($view);
721
722
$scaffold->setIsUndoTemplate(true);
723
724
$views[$key] = id(new PHUIDiffInlineCommentTableScaffold())
725
->addRowScaffold($scaffold);
726
}
727
728
return $views;
729
}
730
731
final protected function getScopeEngine() {
732
if ($this->scopeEngine === false) {
733
$hunk_starts = $this->getHunkStartLines();
734
735
// If this change is missing context, don't try to identify scopes, since
736
// we won't really be able to get anywhere.
737
$has_multiple_hunks = (count($hunk_starts) > 1);
738
739
$has_offset_hunks = false;
740
if ($hunk_starts) {
741
$has_offset_hunks = (head_key($hunk_starts) != 1);
742
}
743
744
$missing_context = ($has_multiple_hunks || $has_offset_hunks);
745
746
if ($missing_context) {
747
$scope_engine = null;
748
} else {
749
$line_map = $this->getNewLineTextMap();
750
$scope_engine = id(new PhabricatorDiffScopeEngine())
751
->setLineTextMap($line_map);
752
}
753
754
$this->scopeEngine = $scope_engine;
755
}
756
757
return $this->scopeEngine;
758
}
759
760
private function getNewLineTextMap() {
761
$new = $this->getNewLines();
762
763
$text_map = array();
764
foreach ($new as $new_line) {
765
if (!isset($new_line['line'])) {
766
continue;
767
}
768
$text_map[$new_line['line']] = $new_line['text'];
769
}
770
771
return $text_map;
772
}
773
774
}
775
776