Restore highlighting when jumping to transactions using URI anchors
[phabricator/blender.git] / src / view / layout / PhabricatorSourceCodeView.php
blobae20fe250194b40d2bf1d25639fa408fa68df1bb
1 <?php
3 final class PhabricatorSourceCodeView extends AphrontView {
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();
15 public function setLines(array $lines) {
16 $this->lines = $lines;
17 return $this;
20 public function setURI(PhutilURI $uri) {
21 $this->uri = $uri;
22 return $this;
25 public function setHighlights(array $array) {
26 $this->highlights = array_fuse($array);
27 return $this;
30 public function disableHighlightOnClick() {
31 $this->canClickHighlight = false;
32 return $this;
35 public function setTruncatedFirstBytes($truncated_first_bytes) {
36 $this->truncatedFirstBytes = $truncated_first_bytes;
37 return $this;
40 public function setTruncatedFirstLines($truncated_first_lines) {
41 $this->truncatedFirstLines = $truncated_first_lines;
42 return $this;
45 public function setSymbolMetadata(array $symbol_metadata) {
46 $this->symbolMetadata = $symbol_metadata;
47 return $this;
50 public function getSymbolMetadata() {
51 return $this->symbolMetadata;
54 public function setBlameMap(array $map) {
55 $this->blameMap = $map;
56 return $this;
59 public function getBlameMap() {
60 return $this->blameMap;
63 public function setCoverage(array $coverage) {
64 $this->coverage = $coverage;
65 return $this;
68 public function getCoverage() {
69 return $this->coverage;
72 public function render() {
73 $blame_map = $this->getBlameMap();
74 $has_blame = ($blame_map !== null);
76 require_celerity_resource('phabricator-source-code-view-css');
77 require_celerity_resource('syntax-highlighting-css');
79 if ($this->canClickHighlight) {
80 Javelin::initBehavior('phabricator-line-linker');
83 $line_number = 1;
85 $rows = array();
87 $lines = $this->lines;
88 if ($this->truncatedFirstLines) {
89 $lines[] = phutil_tag(
90 'span',
91 array(
92 'class' => 'c',
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',
105 pht('...')));
108 $base_uri = (string)$this->uri;
109 $wrote_anchor = false;
111 $coverage = $this->getCoverage();
112 $coverage_count = count($coverage);
113 $coverage_data = ipull($coverage, 'data');
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;',
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;
133 if ($this->canClickHighlight) {
134 if ($base_uri) {
135 $line_href = $base_uri.'$'.$line_number;
136 } else {
137 $line_href = null;
140 $tag_number = phutil_tag(
141 'a',
142 array(
143 'href' => $line_href,
144 'data-n' => $line_number,
146 } else {
147 $tag_number = phutil_tag(
148 'span',
149 array(),
150 $line_number);
153 if ($has_blame) {
154 $lines = idx($blame_map, $line_number);
156 if ($lines) {
157 $skip_blame = 'skip';
158 $info_blame = 'info';
159 } else {
160 $skip_blame = null;
161 $info_blame = null;
164 $blame_cells = array(
165 phutil_tag(
166 'th',
167 array(
168 'class' => 'phabricator-source-blame-skip',
169 'data-blame' => $skip_blame,
171 phutil_tag(
172 'th',
173 array(
174 'class' => 'phabricator-source-blame-info',
175 'data-blame' => $info_blame,
176 'data-blame-lines' => $lines,
179 } else {
180 $blame_cells = null;
183 $coverage_cells = array();
184 foreach ($coverage as $coverage_idx => $coverage_spec) {
185 if (isset($coverage_spec['data'][$line_number - 1])) {
186 $coverage_char = $coverage_spec['data'][$line_number - 1];
187 } else {
188 $coverage_char = null;
191 $coverage_style = idx($coverage_map, $coverage_char, null);
193 $coverage_cells[] = phutil_tag(
194 'th',
195 array(
196 'class' => 'phabricator-source-coverage',
197 'style' => $coverage_style,
198 'data-coverage' => $coverage_idx.'/'.$coverage_char,
202 $rows[] = phutil_tag(
203 'tr',
204 $row_attributes,
205 array(
206 $blame_cells,
207 phutil_tag(
208 'th',
209 array(
210 'class' => 'phabricator-source-line',
212 $tag_number),
213 phutil_tag(
214 'td',
215 array(
216 'class' => 'phabricator-source-code',
218 $line),
219 $coverage_cells,
222 $line_number++;
225 $classes = array();
226 $classes[] = 'phabricator-source-code-view';
227 $classes[] = 'remarkup-code';
228 $classes[] = 'PhabricatorMonospaced';
230 $symbol_metadata = $this->getSymbolMetadata();
232 $sigils = array();
233 $sigils[] = 'phabricator-source';
234 $sigils[] = 'has-symbols';
236 Javelin::initBehavior('repository-crossreference');
238 return phutil_tag_div(
239 'phabricator-source-code-container',
240 javelin_tag(
241 'table',
242 array(
243 'class' => implode(' ', $classes),
244 'sigil' => implode(' ', $sigils),
245 'meta' => array(
246 'uri' => (string)$this->uri,
247 'symbols' => $symbol_metadata,
250 phutil_implode_html('', $rows)));