Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php
12242 views
1
<?php
2
3
final class PHUIDiffTableOfContentsListView extends AphrontView {
4
5
private $items = array();
6
private $authorityPackages;
7
private $header;
8
private $infoView;
9
private $background;
10
private $bare;
11
12
private $components = array();
13
14
public function addItem(PHUIDiffTableOfContentsItemView $item) {
15
$this->items[] = $item;
16
return $this;
17
}
18
19
public function setAuthorityPackages(array $authority_packages) {
20
assert_instances_of($authority_packages, 'PhabricatorOwnersPackage');
21
$this->authorityPackages = $authority_packages;
22
return $this;
23
}
24
25
public function getAuthorityPackages() {
26
return $this->authorityPackages;
27
}
28
29
public function setBackground($background) {
30
$this->background = $background;
31
return $this;
32
}
33
34
public function setHeader(PHUIHeaderView $header) {
35
$this->header = $header;
36
return $this;
37
}
38
39
public function setInfoView(PHUIInfoView $infoview) {
40
$this->infoView = $infoview;
41
return $this;
42
}
43
44
public function setBare($bare) {
45
$this->bare = $bare;
46
return $this;
47
}
48
49
public function getBare() {
50
return $this->bare;
51
}
52
53
public function render() {
54
$this->requireResource('differential-core-view-css');
55
$this->requireResource('differential-table-of-contents-css');
56
57
Javelin::initBehavior('phabricator-tooltips');
58
59
if ($this->getAuthorityPackages()) {
60
$authority = mpull($this->getAuthorityPackages(), null, 'getPHID');
61
} else {
62
$authority = array();
63
}
64
65
$items = $this->items;
66
$viewer = $this->getViewer();
67
68
$item_map = array();
69
70
$vector_tree = new ArcanistDiffVectorTree();
71
foreach ($items as $item) {
72
$item->setViewer($viewer);
73
74
$changeset = $item->getChangeset();
75
76
$old_vector = $changeset->getOldStatePathVector();
77
$new_vector = $changeset->getNewStatePathVector();
78
79
$tree_vector = $this->newTreeVector($old_vector, $new_vector);
80
81
$item_map[implode("\n", $tree_vector)] = $item;
82
83
$vector_tree->addVector($tree_vector);
84
}
85
$node_list = $vector_tree->newDisplayList();
86
87
$node_map = array();
88
foreach ($node_list as $node) {
89
$path_vector = $node->getVector();
90
$path_vector = implode("\n", $path_vector);
91
$node_map[$path_vector] = $node;
92
}
93
94
// Mark all nodes which contain at least one path which exists in the new
95
// state. Nodes we don't mark contain only deleted or moved files, so they
96
// can be rendered with a less-prominent style.
97
98
foreach ($node_map as $node_key => $node) {
99
$item = idx($item_map, $node_key);
100
101
if (!$item) {
102
continue;
103
}
104
105
$changeset = $item->getChangeset();
106
if (!$changeset->getIsLowImportanceChangeset()) {
107
$node->setAncestralAttribute('important', true);
108
}
109
}
110
111
$any_packages = false;
112
$any_coverage = false;
113
$any_context = false;
114
115
$rows = array();
116
$rowc = array();
117
foreach ($node_map as $node_key => $node) {
118
$display_vector = $node->getDisplayVector();
119
$item = idx($item_map, $node_key);
120
121
if ($item) {
122
$changeset = $item->getChangeset();
123
$icon = $changeset->newFileTreeIcon();
124
} else {
125
$changeset = null;
126
$icon = id(new PHUIIconView())
127
->setIcon('fa-folder-open-o grey');
128
}
129
130
if ($node->getChildren()) {
131
$old_dir = true;
132
$new_dir = true;
133
} else {
134
// TODO: When properties are set on a directory in SVN directly, this
135
// might be incorrect.
136
$old_dir = false;
137
$new_dir = false;
138
}
139
140
$display_view = $this->newComponentView(
141
$icon,
142
$display_vector,
143
$old_dir,
144
$new_dir,
145
$item);
146
147
$depth = $node->getDisplayDepth();
148
149
$style = sprintf('padding-left: %dpx;', $depth * 16);
150
151
if ($item) {
152
$packages = $item->renderPackages();
153
} else {
154
$packages = null;
155
}
156
157
if ($packages) {
158
$any_packages = true;
159
}
160
161
if ($item) {
162
if ($item->getCoverage()) {
163
$any_coverage = true;
164
}
165
$coverage = $item->renderCoverage();
166
$modified_coverage = $item->renderModifiedCoverage();
167
} else {
168
$coverage = null;
169
$modified_coverage = null;
170
}
171
172
if ($item) {
173
$context = $item->getContext();
174
if ($context) {
175
$any_context = true;
176
}
177
} else {
178
$context = null;
179
}
180
181
if ($item) {
182
$lines = $item->renderChangesetLines();
183
} else {
184
$lines = null;
185
}
186
187
$rows[] = array(
188
$context,
189
phutil_tag(
190
'div',
191
array(
192
'style' => $style,
193
),
194
$display_view),
195
$lines,
196
$coverage,
197
$modified_coverage,
198
$packages,
199
);
200
201
$classes = array();
202
203
$have_authority = false;
204
205
if ($item) {
206
$packages = $item->getPackages();
207
if ($packages) {
208
if (array_intersect_key($packages, $authority)) {
209
$have_authority = true;
210
}
211
}
212
}
213
214
if ($have_authority) {
215
$classes[] = 'highlighted';
216
}
217
218
if (!$node->getAttribute('important')) {
219
$classes[] = 'diff-toc-low-importance-row';
220
}
221
222
if ($changeset) {
223
$classes[] = 'diff-toc-changeset-row';
224
} else {
225
$classes[] = 'diff-toc-no-changeset-row';
226
}
227
228
$rowc[] = implode(' ', $classes);
229
}
230
231
$table = id(new AphrontTableView($rows))
232
->setRowClasses($rowc)
233
->setClassName('aphront-table-view-compact')
234
->setHeaders(
235
array(
236
null,
237
pht('Path'),
238
pht('Size'),
239
pht('Coverage (All)'),
240
pht('Coverage (Touched)'),
241
pht('Packages'),
242
))
243
->setColumnClasses(
244
array(
245
null,
246
'diff-toc-path wide',
247
'right',
248
'differential-toc-cov',
249
'differential-toc-cov',
250
null,
251
))
252
->setColumnVisibility(
253
array(
254
$any_context,
255
true,
256
true,
257
$any_coverage,
258
$any_coverage,
259
$any_packages,
260
))
261
->setDeviceVisibility(
262
array(
263
true,
264
true,
265
false,
266
false,
267
false,
268
true,
269
));
270
271
$anchor = id(new PhabricatorAnchorView())
272
->setAnchorName('toc')
273
->setNavigationMarker(true);
274
275
if ($this->bare) {
276
return $table;
277
}
278
279
$header = id(new PHUIHeaderView())
280
->setHeader(pht('Table of Contents'));
281
282
if ($this->header) {
283
$header = $this->header;
284
}
285
286
$box = id(new PHUIObjectBoxView())
287
->setHeader($header)
288
->setBackground($this->background)
289
->setTable($table)
290
->appendChild($anchor);
291
292
if ($this->infoView) {
293
$box->setInfoView($this->infoView);
294
}
295
296
return $box;
297
}
298
299
private function newTreeVector($old, $new) {
300
if ($old === null && $new === null) {
301
throw new Exception(pht('Changeset has no path vectors!'));
302
}
303
304
$vector = null;
305
if ($old === null) {
306
$vector = $new;
307
} else if ($new === null) {
308
$vector = $old;
309
} else if ($old === $new) {
310
$vector = $new;
311
}
312
313
if ($vector) {
314
foreach ($vector as $k => $v) {
315
$vector[$k] = $this->newScalarComponent($v);
316
}
317
return $vector;
318
}
319
320
$matrix = id(new PhutilEditDistanceMatrix())
321
->setSequences($old, $new)
322
->setComputeString(true);
323
$edits = $matrix->getEditString();
324
325
// If the edit sequence contains deletions followed by edits, move
326
// the deletions to the end to left-align the new path.
327
$edits = preg_replace('/(d+)(x+)/', '\2\1', $edits);
328
329
$vector = array();
330
$length = strlen($edits);
331
332
$old_cursor = 0;
333
$new_cursor = 0;
334
335
for ($ii = 0; $ii < strlen($edits); $ii++) {
336
$c = $edits[$ii];
337
switch ($c) {
338
case 'i':
339
$vector[] = $this->newPairComponent(null, $new[$new_cursor]);
340
$new_cursor++;
341
break;
342
case 'd':
343
$vector[] = $this->newPairComponent($old[$old_cursor], null);
344
$old_cursor++;
345
break;
346
case 's':
347
case 'x':
348
case 't':
349
$vector[] = $this->newPairComponent(
350
$old[$old_cursor],
351
$new[$new_cursor]);
352
$old_cursor++;
353
$new_cursor++;
354
break;
355
default:
356
throw new Exception(pht('Unknown edit string "%s"!', $c));
357
}
358
}
359
360
return $vector;
361
}
362
363
private function newScalarComponent($v) {
364
$key = sprintf('path(%s)', $v);
365
366
if (!isset($this->components[$key])) {
367
$this->components[$key] = $v;
368
}
369
370
return $key;
371
}
372
373
private function newPairComponent($u, $v) {
374
if ($u === $v) {
375
return $this->newScalarComponent($u);
376
}
377
378
$key = sprintf('pair(%s > %s)', $u, $v);
379
380
if (!isset($this->components[$key])) {
381
$this->components[$key] = array($u, $v);
382
}
383
384
return $key;
385
}
386
387
private function newComponentView(
388
$icon,
389
array $keys,
390
$old_dir,
391
$new_dir,
392
$item) {
393
394
$is_simple = true;
395
396
$items = array();
397
foreach ($keys as $key) {
398
$component = $this->components[$key];
399
400
if (is_array($component)) {
401
$is_simple = false;
402
} else {
403
$component = array(
404
$component,
405
$component,
406
);
407
}
408
409
$items[] = $component;
410
}
411
412
$move_icon = id(new PHUIIconView())
413
->setIcon('fa-angle-double-right pink');
414
415
$old_row = array(
416
phutil_tag('td', array(), $move_icon),
417
);
418
$new_row = array(
419
phutil_tag('td', array(), $icon),
420
);
421
422
$last_old_key = null;
423
$last_new_key = null;
424
425
foreach ($items as $key => $component) {
426
if (!is_array($component)) {
427
$last_old_key = $key;
428
$last_new_key = $key;
429
} else {
430
if ($component[0] !== null) {
431
$last_old_key = $key;
432
}
433
if ($component[1] !== null) {
434
$last_new_key = $key;
435
}
436
}
437
}
438
439
foreach ($items as $key => $component) {
440
if (!is_array($component)) {
441
$old = $component;
442
$new = $component;
443
} else {
444
$old = $component[0];
445
$new = $component[1];
446
}
447
448
$old_classes = array();
449
$new_classes = array();
450
451
if ($old === $new) {
452
// Do nothing.
453
} else if ($old === null) {
454
$new_classes[] = 'diff-path-component-new';
455
} else if ($new === null) {
456
$old_classes[] = 'diff-path-component-old';
457
} else {
458
$old_classes[] = 'diff-path-component-old';
459
$new_classes[] = 'diff-path-component-new';
460
}
461
462
if ($old !== null) {
463
if (($key === $last_old_key) && !$old_dir) {
464
// Do nothing.
465
} else {
466
$old = $old.'/';
467
}
468
}
469
470
if ($new !== null) {
471
if (($key === $last_new_key) && $item) {
472
$new = $item->newLink();
473
} else if (($key === $last_new_key) && !$new_dir) {
474
// Do nothing.
475
} else {
476
$new = $new.'/';
477
}
478
}
479
480
$old_row[] = phutil_tag(
481
'td',
482
array(),
483
phutil_tag(
484
'div',
485
array(
486
'class' => implode(' ', $old_classes),
487
),
488
$old));
489
$new_row[] = phutil_tag(
490
'td',
491
array(),
492
phutil_tag(
493
'div',
494
array(
495
'class' => implode(' ', $new_classes),
496
),
497
$new));
498
}
499
500
$old_row = phutil_tag(
501
'tr',
502
array(
503
'class' => 'diff-path-old',
504
),
505
$old_row);
506
507
$new_row = phutil_tag(
508
'tr',
509
array(
510
'class' => 'diff-path-new',
511
),
512
$new_row);
513
514
$rows = array();
515
$rows[] = $new_row;
516
if (!$is_simple) {
517
$rows[] = $old_row;
518
}
519
520
$body = phutil_tag('tbody', array(), $rows);
521
522
$table = phutil_tag(
523
'table',
524
array(
525
),
526
$body);
527
528
return $table;
529
}
530
531
}
532
533