@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
at recaptime-dev/main 255 lines 6.3 kB view raw
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}