@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 PhabricatorSourceCodeView extends AphrontView {
4
5 private $lines;
6 private $uri;
7 private $highlights = array();
8 private $canClickHighlight = true;
9 private $truncatedFirstBytes = false;
10 private $truncatedFirstLines = false;
11 private $symbolMetadata;
12 private $blameMap;
13 private $coverage = array();
14
15 public function setLines(array $lines) {
16 $this->lines = $lines;
17 return $this;
18 }
19
20 public function setURI(PhutilURI $uri) {
21 $this->uri = $uri;
22 return $this;
23 }
24
25 public function setHighlights(array $array) {
26 $this->highlights = array_fuse($array);
27 return $this;
28 }
29
30 public function disableHighlightOnClick() {
31 $this->canClickHighlight = false;
32 return $this;
33 }
34
35 public function setTruncatedFirstBytes($truncated_first_bytes) {
36 $this->truncatedFirstBytes = $truncated_first_bytes;
37 return $this;
38 }
39
40 public function setTruncatedFirstLines($truncated_first_lines) {
41 $this->truncatedFirstLines = $truncated_first_lines;
42 return $this;
43 }
44
45 public function setSymbolMetadata(array $symbol_metadata) {
46 $this->symbolMetadata = $symbol_metadata;
47 return $this;
48 }
49
50 public function getSymbolMetadata() {
51 return $this->symbolMetadata;
52 }
53
54 public function setBlameMap(array $map) {
55 $this->blameMap = $map;
56 return $this;
57 }
58
59 public function getBlameMap() {
60 return $this->blameMap;
61 }
62
63 public function setCoverage(array $coverage) {
64 $this->coverage = $coverage;
65 return $this;
66 }
67
68 public function getCoverage() {
69 return $this->coverage;
70 }
71
72 public function render() {
73 $blame_map = $this->getBlameMap();
74 $has_blame = ($blame_map !== null);
75
76 require_celerity_resource('phabricator-source-code-view-css');
77 require_celerity_resource('syntax-highlighting-css');
78
79 if ($this->canClickHighlight) {
80 Javelin::initBehavior('phabricator-line-linker');
81 }
82
83 $line_number = 1;
84
85 $rows = array();
86
87 $lines = $this->lines;
88 if ($this->truncatedFirstLines) {
89 $lines[] = phutil_tag(
90 'span',
91 array(
92 'class' => 'c',
93 ),
94 pht('...'));
95 } else if ($this->truncatedFirstBytes) {
96 $last_key = last_key($lines);
97 $lines[$last_key] = hsprintf(
98 '%s%s',
99 $lines[$last_key],
100 phutil_tag(
101 'span',
102 array(
103 'class' => 'c',
104 ),
105 pht('...')));
106 }
107
108 $base_uri = (string)$this->uri;
109 $wrote_anchor = false;
110
111 $coverage = $this->getCoverage();
112 $coverage_count = count($coverage);
113 $coverage_data = ipull($coverage, 'data');
114
115 // TODO: Modularize this properly, see T13125.
116 $coverage_map = array(
117 'C' => 'background: #66bbff;',
118 'U' => 'background: #dd8866;',
119 'N' => 'background: #ddeeff;',
120 'X' => 'background: #aa00aa;',
121 );
122
123 foreach ($lines as $line) {
124 $row_attributes = array();
125 if (isset($this->highlights[$line_number])) {
126 $row_attributes['class'] = 'phabricator-source-highlight';
127 if (!$wrote_anchor) {
128 $row_attributes['id'] = 'phabricator-line-linker-anchor';
129 $wrote_anchor = true;
130 }
131 }
132
133 if ($this->canClickHighlight) {
134 $line_id = 'L'.$line_number;
135 if ($base_uri) {
136 $line_href = $base_uri.'#'.$line_id;
137 } else {
138 $line_href = null;
139 }
140
141 $tag_number = phutil_tag(
142 'a',
143 array(
144 'href' => $line_href,
145 'data-n' => $line_number,
146 'id' => $line_id,
147 ));
148 } else {
149 $tag_number = phutil_tag(
150 'span',
151 array(),
152 $line_number);
153 }
154
155 if ($has_blame) {
156 $lines = idx($blame_map, $line_number);
157
158 if ($lines) {
159 $skip_blame = 'skip';
160 $info_blame = 'info';
161 } else {
162 $skip_blame = null;
163 $info_blame = null;
164 }
165
166 $blame_cells = array(
167 phutil_tag(
168 'th',
169 array(
170 'class' => 'phabricator-source-blame-skip',
171 'data-blame' => $skip_blame,
172 )),
173 phutil_tag(
174 'th',
175 array(
176 'class' => 'phabricator-source-blame-info',
177 'data-blame' => $info_blame,
178 'data-blame-lines' => $lines,
179 )),
180 );
181 } else {
182 $blame_cells = null;
183 }
184
185 $coverage_cells = array();
186 foreach ($coverage as $coverage_idx => $coverage_spec) {
187 if (isset($coverage_spec['data'][$line_number - 1])) {
188 $coverage_char = $coverage_spec['data'][$line_number - 1];
189 } else {
190 $coverage_char = null;
191 }
192
193 $coverage_style = idx($coverage_map, $coverage_char, null);
194
195 $coverage_cells[] = phutil_tag(
196 'th',
197 array(
198 'class' => 'phabricator-source-coverage',
199 'style' => $coverage_style,
200 'data-coverage' => $coverage_idx.'/'.$coverage_char,
201 ));
202 }
203
204 $rows[] = phutil_tag(
205 'tr',
206 $row_attributes,
207 array(
208 $blame_cells,
209 phutil_tag(
210 'th',
211 array(
212 'class' => 'phabricator-source-line',
213 ),
214 $tag_number),
215 phutil_tag(
216 'td',
217 array(
218 'class' => 'phabricator-source-code',
219 ),
220 $line),
221 $coverage_cells,
222 ));
223
224 $line_number++;
225 }
226
227 $classes = array();
228 $classes[] = 'phabricator-source-code-view';
229 $classes[] = 'remarkup-code';
230 $classes[] = 'PhabricatorMonospaced';
231
232 $symbol_metadata = $this->getSymbolMetadata();
233
234 $sigils = array();
235 $sigils[] = 'phabricator-source';
236 $sigils[] = 'has-symbols';
237
238 Javelin::initBehavior('repository-crossreference');
239
240 return phutil_tag_div(
241 'phabricator-source-code-container',
242 javelin_tag(
243 'table',
244 array(
245 'class' => implode(' ', $classes),
246 'sigil' => implode(' ', $sigils),
247 'meta' => array(
248 'uri' => (string)$this->uri,
249 'symbols' => $symbol_metadata,
250 ),
251 ),
252 phutil_implode_html('', $rows)));
253 }
254
255}