@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor DifferentialChangesetParser -- pass 1 of N

Summary:
basically did my darnedest to pull out a TwoUp rendering view. Made a base class for the rendering views with "old" and "new" terminology rather than "left" and "right.

Future revisions will finish cleaning up the terminology within the DifferentialChangesetParser itself and more of the ideas within T2009.

Test Plan: been playing with differential all day

Reviewers: epriestley

Reviewed By: epriestley

CC: vrana, chad, aran, Korvin

Maniphest Tasks: T2009

Differential Revision: https://secure.phabricator.com/D4117

+1287 -961
+3
src/__phutil_library_map__.php
··· 227 227 'DifferentialChangesetListView' => 'applications/differential/view/DifferentialChangesetListView.php', 228 228 'DifferentialChangesetParser' => 'applications/differential/parser/DifferentialChangesetParser.php', 229 229 'DifferentialChangesetParserTestCase' => 'applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php', 230 + 'DifferentialChangesetRenderer' => 'applications/differential/render/DifferentialChangesetRenderer.php', 231 + 'DifferentialChangesetTwoUpRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpRenderer.php', 230 232 'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php', 231 233 'DifferentialComment' => 'applications/differential/storage/DifferentialComment.php', 232 234 'DifferentialCommentEditor' => 'applications/differential/editor/DifferentialCommentEditor.php', ··· 1510 1512 'DifferentialChangesetDetailView' => 'AphrontView', 1511 1513 'DifferentialChangesetListView' => 'AphrontView', 1512 1514 'DifferentialChangesetParserTestCase' => 'ArcanistPhutilTestCase', 1515 + 'DifferentialChangesetTwoUpRenderer' => 'DifferentialChangesetRenderer', 1513 1516 'DifferentialChangesetViewController' => 'DifferentialController', 1514 1517 'DifferentialComment' => 1515 1518 array(
+145 -961
src/applications/differential/parser/DifferentialChangesetParser.php
··· 188 188 return $this->renderCacheKey; 189 189 } 190 190 191 - public function setChangeset($changeset) { 191 + public function setChangeset(DifferentialChangeset $changeset) { 192 192 $this->changeset = $changeset; 193 193 194 194 $this->setFilename($changeset->getFilename()); ··· 204 204 public function setRenderingReference($ref) { 205 205 $this->renderingReference = $ref; 206 206 return $this; 207 + } 208 + 209 + private function getRenderingReference() { 210 + return $this->renderingReference; 207 211 } 208 212 209 213 public function getChangeset() { ··· 775 779 return idx($this->specialAttributes, self::ATTR_WHITELINES, false); 776 780 } 777 781 778 - public function getLength() { 779 - return max(count($this->old), count($this->new)); 780 - } 781 - 782 782 protected function applyIntraline(&$render, $intra, $corpus) { 783 783 784 784 foreach ($render as $key => $text) { ··· 928 928 } 929 929 } 930 930 931 + private function shouldRenderPropertyChangeHeader($changeset) { 932 + if (!$this->isTopLevel) { 933 + // We render properties only at top level; otherwise we get multiple 934 + // copies of them when a user clicks "Show More". 935 + return false; 936 + } 937 + 938 + $old = $changeset->getOldProperties(); 939 + $new = $changeset->getNewProperties(); 940 + 941 + if ($old === $new) { 942 + return false; 943 + } 944 + 945 + if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD && 946 + $new == array('unix:filemode' => '100644')) { 947 + return false; 948 + } 949 + 950 + if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE && 951 + $old == array('unix:filemode' => '100644')) { 952 + return false; 953 + } 954 + 955 + return true; 956 + } 957 + 931 958 public function render( 932 959 $range_start = null, 933 960 $range_len = null, ··· 938 965 // generate property changes and "shield" UI elements only for toplevel 939 966 // requests. 940 967 $this->isTopLevel = (($range_start === null) && ($range_len === null)); 941 - 942 968 $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine(); 943 - 944 969 $this->tryCacheStuff(); 970 + $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset); 971 + 972 + $renderer = id(new DifferentialChangesetTwoUpRenderer()) 973 + ->setChangeset($this->changeset) 974 + ->setRenderPropertyChangeHeader($render_pch) 975 + ->setOldLines($this->old) 976 + ->setNewLines($this->new) 977 + ->setOldRender($this->oldRender) 978 + ->setNewRender($this->newRender) 979 + ->setMissingOldLines($this->missingOld) 980 + ->setMissingNewLines($this->missingNew) 981 + ->setVisibleLines($this->visible) 982 + ->setOldChangesetID($this->leftSideChangesetID) 983 + ->setNewChangesetID($this->rightSideChangesetID) 984 + ->setOldAttachesToNewFile($this->leftSideAttachesToNewFile) 985 + ->setNewAttachesToNewFile($this->rightSideAttachesToNewFile) 986 + ->setLinesOfContext(self::LINES_CONTEXT) 987 + ->setCodeCoverage($this->coverage) 988 + ->setRenderingReference($this->getRenderingReference()) 989 + ->setMarkupEngine($this->markupEngine) 990 + ->setHandles($this->handles); 945 991 946 992 $shield = null; 947 993 if ($this->isTopLevel && !$this->comments) { 948 994 if ($this->isGenerated()) { 949 - $shield = $this->renderShield( 950 - "This file contains generated code, which does not normally need ". 951 - "to be reviewed.", 995 + $shield = $renderer->renderShield( 996 + pht( 997 + 'This file contains generated code, which does not normally '. 998 + 'need to be reviewed.'), 952 999 true); 953 1000 } else if ($this->isUnchanged()) { 954 1001 if ($this->isWhitespaceOnly()) { 955 - $shield = $this->renderShield( 956 - "This file was changed only by adding or removing trailing ". 957 - "whitespace.", 1002 + $shield = $renderer->renderShield( 1003 + pht( 1004 + 'This file was changed only by adding or removing trailing '. 1005 + 'whitespace.'), 958 1006 false); 959 1007 } else { 960 - $shield = $this->renderShield( 961 - "The contents of this file were not changed.", 1008 + $shield = $renderer->renderShield( 1009 + pht("The contents of this file were not changed."), 962 1010 false); 963 1011 } 964 1012 } else if ($this->isDeleted()) { 965 - $shield = $this->renderShield( 966 - "This file was completely deleted.", 1013 + $shield = $renderer->renderShield( 1014 + pht("This file was completely deleted."), 967 1015 true); 968 1016 } else if ($this->changeset->getAffectedLineCount() > 2500) { 969 1017 $lines = number_format($this->changeset->getAffectedLineCount()); 970 - $shield = $this->renderShield( 971 - "This file has a very large number of changes ({$lines} lines).", 1018 + $shield = $renderer->renderShield( 1019 + pht( 1020 + 'This file has a very large number of changes ({%s} lines).', 1021 + $lines), 972 1022 true); 973 1023 } 974 1024 } 975 1025 976 1026 if ($shield) { 977 - return $this->renderChangesetTable($this->changeset, $shield); 1027 + return $renderer->renderChangesetTable($shield); 978 1028 } 979 1029 1030 + $old_comments = array(); 1031 + $new_comments = array(); 1032 + $old_mask = array(); 1033 + $new_mask = array(); 980 1034 $feedback_mask = array(); 981 1035 1036 + if ($this->comments) { 1037 + foreach ($this->comments as $comment) { 1038 + $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0); 1039 + $end = $comment->getLineNumber() + 1040 + $comment->getLineLength() + 1041 + self::LINES_CONTEXT; 1042 + $new_side = $this->isCommentOnRightSideWhenDisplayed($comment); 1043 + for ($ii = $start; $ii <= $end; $ii++) { 1044 + if ($new_side) { 1045 + $new_mask[$ii] = true; 1046 + } else { 1047 + $old_mask[$ii] = true; 1048 + } 1049 + } 1050 + } 1051 + 1052 + foreach ($this->old as $ii => $old) { 1053 + if (isset($old['line']) && isset($old_mask[$old['line']])) { 1054 + $feedback_mask[$ii] = true; 1055 + } 1056 + } 1057 + 1058 + foreach ($this->new as $ii => $new) { 1059 + if (isset($new['line']) && isset($new_mask[$new['line']])) { 1060 + $feedback_mask[$ii] = true; 1061 + } 1062 + } 1063 + $this->comments = msort($this->comments, 'getID'); 1064 + foreach ($this->comments as $comment) { 1065 + $final = $comment->getLineNumber() + 1066 + $comment->getLineLength(); 1067 + $final = max(1, $final); 1068 + if ($this->isCommentOnRightSideWhenDisplayed($comment)) { 1069 + $new_comments[$final][] = $comment; 1070 + } else { 1071 + $old_comments[$final][] = $comment; 1072 + } 1073 + } 1074 + } 1075 + $renderer 1076 + ->setOldComments($old_comments) 1077 + ->setNewComments($new_comments); 1078 + 982 1079 switch ($this->changeset->getFileType()) { 983 1080 case DifferentialChangeType::FILE_IMAGE: 984 1081 $old = null; 985 1082 $cur = null; 986 1083 // TODO: Improve the architectural issue as discussed in D955 987 1084 // https://secure.phabricator.com/D955 988 - $reference = $this->renderingReference; 1085 + $reference = $this->getRenderingReference(); 989 1086 $parts = explode('/', $reference); 990 1087 if (count($parts) == 2) { 991 1088 list($id, $vs) = $parts; ··· 1013 1110 } 1014 1111 1015 1112 if ($old_phid || $new_phid) { 1016 - 1017 1113 // grab the files, (micro) optimization for 1 query not 2 1018 1114 $file_phids = array(); 1019 1115 if ($old_phid) { ··· 1026 1122 $files = id(new PhabricatorFile())->loadAllWhere( 1027 1123 'phid IN (%Ls)', 1028 1124 $file_phids); 1029 - 1030 1125 foreach ($files as $file) { 1031 1126 if (empty($file)) { 1032 1127 continue; 1033 1128 } 1034 1129 if ($file->getPHID() == $old_phid) { 1035 - $old = phutil_render_tag( 1036 - 'div', 1037 - array( 1038 - 'class' => 'differential-image-stage' 1039 - ), 1040 - phutil_render_tag( 1041 - 'img', 1042 - array( 1043 - 'src' => $file->getBestURI(), 1044 - ) 1045 - ) 1046 - ); 1047 - } else { 1048 - $cur = phutil_render_tag( 1049 - 'div', 1050 - array( 1051 - 'class' => 'differential-image-stage' 1052 - ), 1053 - phutil_render_tag( 1054 - 'img', 1055 - array( 1056 - 'src' => $file->getBestURI(), 1057 - ) 1058 - ) 1059 - ); 1130 + $old = $file; 1131 + } else if ($file->getPHID() == $new_phid) { 1132 + $new = $file; 1060 1133 } 1061 1134 } 1062 1135 } 1063 - 1064 - $this->comments = msort($this->comments, 'getID'); 1065 - $old_comments = array(); 1066 - $new_comments = array(); 1067 - foreach ($this->comments as $comment) { 1068 - if ($this->isCommentOnRightSideWhenDisplayed($comment)) { 1069 - $new_comments[] = $comment; 1070 - } else { 1071 - $old_comments[] = $comment; 1072 - } 1073 - } 1074 - 1075 - $html_old = array(); 1076 - $html_new = array(); 1077 - foreach ($old_comments as $comment) { 1078 - $xhp = $this->renderInlineComment($comment); 1079 - $html_old[] = 1080 - '<tr class="inline">'. 1081 - '<th />'. 1082 - '<td class="left">'.$xhp.'</td>'. 1083 - '<th />'. 1084 - '<td class="right3" colspan="3" />'. 1085 - '</tr>'; 1086 - } 1087 - foreach ($new_comments as $comment) { 1088 - $xhp = $this->renderInlineComment($comment); 1089 - $html_new[] = 1090 - '<tr class="inline">'. 1091 - '<th />'. 1092 - '<td class="left" />'. 1093 - '<th />'. 1094 - '<td class="right3" colspan="3">'.$xhp.'</td>'. 1095 - '</tr>'; 1096 - } 1097 - 1098 - if (!$old) { 1099 - $th_old = '<th></th>'; 1100 - } else { 1101 - $th_old = '<th id="C'.$vs.'OL1">1</th>'; 1102 - } 1103 - 1104 - if (!$cur) { 1105 - $th_new = '<th></th>'; 1106 - } else { 1107 - $th_new = '<th id="C'.$id.'NL1">1</th>'; 1108 - } 1109 - 1110 - $output = $this->renderChangesetTable( 1111 - $this->changeset, 1112 - '<tr class="differential-image-diff">'. 1113 - $th_old. 1114 - '<td class="left differential-old-image">'.$old.'</td>'. 1115 - $th_new. 1116 - '<td class="right3 differential-new-image" colspan="3">'. 1117 - $cur. 1118 - '</td>'. 1119 - '</tr>'. 1120 - implode('', $html_old). 1121 - implode('', $html_new)); 1122 - 1123 - return $output; 1136 + return $renderer->renderFileChange($old, $new, $id, $vs); 1124 1137 case DifferentialChangeType::FILE_DIRECTORY: 1125 1138 case DifferentialChangeType::FILE_BINARY: 1126 - $output = $this->renderChangesetTable($this->changeset, null); 1139 + $output = $renderer->renderChangesetTable(null); 1127 1140 return $output; 1128 1141 } 1129 1142 1130 - $old_comments = array(); 1131 - $new_comments = array(); 1132 - 1133 - $old_mask = array(); 1134 - $new_mask = array(); 1135 - $feedback_mask = array(); 1136 - 1137 - if ($this->comments) { 1138 - foreach ($this->comments as $comment) { 1139 - $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0); 1140 - $end = $comment->getLineNumber() + 1141 - $comment->getLineLength() + 1142 - self::LINES_CONTEXT; 1143 - $new = $this->isCommentOnRightSideWhenDisplayed($comment); 1144 - for ($ii = $start; $ii <= $end; $ii++) { 1145 - if ($new) { 1146 - $new_mask[$ii] = true; 1147 - } else { 1148 - $old_mask[$ii] = true; 1149 - } 1150 - } 1151 - } 1152 - 1153 - foreach ($this->old as $ii => $old) { 1154 - if (isset($old['line']) && isset($old_mask[$old['line']])) { 1155 - $feedback_mask[$ii] = true; 1156 - } 1157 - } 1158 - 1159 - foreach ($this->new as $ii => $new) { 1160 - if (isset($new['line']) && isset($new_mask[$new['line']])) { 1161 - $feedback_mask[$ii] = true; 1162 - } 1163 - } 1164 - $this->comments = msort($this->comments, 'getID'); 1165 - foreach ($this->comments as $comment) { 1166 - $final = $comment->getLineNumber() + 1167 - $comment->getLineLength(); 1168 - $final = max(1, $final); 1169 - if ($this->isCommentOnRightSideWhenDisplayed($comment)) { 1170 - $new_comments[$final][] = $comment; 1171 - } else { 1172 - $old_comments[$final][] = $comment; 1173 - } 1174 - } 1143 + if ($this->originalLeft && $this->originalRight()) { 1144 + list($highlight_old, $highlight_new) = $this->diffOriginals(); 1145 + $highlight_old = array_flip($highlight_old); 1146 + $highlight_new = array_flip($highlight_new); 1147 + $renderer 1148 + ->setHighlightOld($highlight_old) 1149 + ->setHighlightNew($highlight_new); 1175 1150 } 1151 + $renderer 1152 + ->setOriginalOld($this->originalLeft) 1153 + ->setOriginalNew($this->originalRight); 1176 1154 1177 - $html = $this->renderTextChange( 1155 + $html = $renderer->renderTextChange( 1178 1156 $range_start, 1179 1157 $range_len, 1180 1158 $mask_force, 1181 - $feedback_mask, 1182 - $old_comments, 1183 - $new_comments); 1159 + $feedback_mask 1160 + ); 1184 1161 1185 - return $this->renderChangesetTable($this->changeset, $html); 1162 + return $renderer->renderChangesetTable($html); 1186 1163 } 1187 1164 1188 1165 /** ··· 1196 1173 private function isCommentVisibleOnRenderedDiff( 1197 1174 PhabricatorInlineCommentInterface $comment) { 1198 1175 1199 - $changeset_id = $comment->getChangesetID(); 1200 - $is_new = $comment->getIsNewFile(); 1176 + $changeset_id = $comment->getChangesetID(); 1177 + $is_new = $comment->getIsNewFile(); 1201 1178 1202 - if ($changeset_id == $this->rightSideChangesetID && 1179 + if ($changeset_id == $this->rightSideChangesetID && 1203 1180 $is_new == $this->rightSideAttachesToNewFile) { 1204 - return true; 1205 - } 1181 + return true; 1182 + } 1206 1183 1207 - if ($changeset_id == $this->leftSideChangesetID && 1184 + if ($changeset_id == $this->leftSideChangesetID && 1208 1185 $is_new == $this->leftSideAttachesToNewFile) { 1209 - return true; 1210 - } 1186 + return true; 1187 + } 1211 1188 1212 1189 return false; 1213 1190 } ··· 1238 1215 } 1239 1216 1240 1217 return false; 1241 - } 1242 - 1243 - protected function renderShield($message, $more) { 1244 - 1245 - if ($more) { 1246 - $end = $this->getLength(); 1247 - $reference = $this->renderingReference; 1248 - $more = 1249 - ' '. 1250 - javelin_render_tag( 1251 - 'a', 1252 - array( 1253 - 'mustcapture' => true, 1254 - 'sigil' => 'show-more', 1255 - 'class' => 'complete', 1256 - 'href' => '#', 1257 - 'meta' => array( 1258 - 'ref' => $reference, 1259 - 'range' => "0-{$end}", 1260 - ), 1261 - ), 1262 - 'Show File Contents'); 1263 - } else { 1264 - $more = null; 1265 - } 1266 - 1267 - return javelin_render_tag( 1268 - 'tr', 1269 - array( 1270 - 'sigil' => 'context-target', 1271 - ), 1272 - '<td class="differential-shield" colspan="6">'. 1273 - phutil_escape_html($message). 1274 - $more. 1275 - '</td>'); 1276 - } 1277 - 1278 - protected function renderTextChange( 1279 - $range_start, 1280 - $range_len, 1281 - $mask_force, 1282 - $feedback_mask, 1283 - array $old_comments, 1284 - array $new_comments) { 1285 - foreach (array_merge($old_comments, $new_comments) as $comments) { 1286 - assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 1287 - } 1288 - 1289 - $context_not_available = null; 1290 - if ($this->missingOld || $this->missingNew) { 1291 - $context_not_available = javelin_render_tag( 1292 - 'tr', 1293 - array( 1294 - 'sigil' => 'context-target', 1295 - ), 1296 - phutil_render_tag( 1297 - 'td', 1298 - array( 1299 - 'colspan' => 6, 1300 - 'class' => 'show-more' 1301 - ), 1302 - pht('Context not available.') 1303 - ) 1304 - ); 1305 - } 1306 - 1307 - $html = array(); 1308 - 1309 - $rows = max( 1310 - count($this->old), 1311 - count($this->new)); 1312 - 1313 - if ($range_start === null) { 1314 - $range_start = 0; 1315 - } 1316 - 1317 - if ($range_len === null) { 1318 - $range_len = $rows; 1319 - } 1320 - 1321 - $range_len = min($range_len, $rows - $range_start); 1322 - 1323 - // Gaps - compute gaps in the visible display diff, where we will render 1324 - // "Show more context" spacers. This builds an aggregate $mask of all the 1325 - // lines we must show (because they are near changed lines, near inline 1326 - // comments, or the request has explicitly asked for them, i.e. resulting 1327 - // from the user clicking "show more") and then finds all the gaps between 1328 - // visible lines. If a gap is smaller than the context size, we just 1329 - // display it. Otherwise, we record it into $gaps and will render a 1330 - // "show more context" element instead of diff text below. 1331 - 1332 - $gaps = array(); 1333 - $gap_start = 0; 1334 - $in_gap = false; 1335 - $mask = $this->visible + $mask_force + $feedback_mask; 1336 - $mask[$range_start + $range_len] = true; 1337 - for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) { 1338 - if (isset($mask[$ii])) { 1339 - if ($in_gap) { 1340 - $gap_length = $ii - $gap_start; 1341 - if ($gap_length <= self::LINES_CONTEXT) { 1342 - for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) { 1343 - $mask[$jj] = true; 1344 - } 1345 - } else { 1346 - $gaps[] = array($gap_start, $gap_length); 1347 - } 1348 - $in_gap = false; 1349 - } 1350 - } else { 1351 - if (!$in_gap) { 1352 - $gap_start = $ii; 1353 - $in_gap = true; 1354 - } 1355 - } 1356 - } 1357 - 1358 - $gaps = array_reverse($gaps); 1359 - 1360 - $reference = $this->renderingReference; 1361 - 1362 - $left_id = $this->leftSideChangesetID; 1363 - $right_id = $this->rightSideChangesetID; 1364 - 1365 - // "N" stands for 'new' and means the comment should attach to the new file 1366 - // when stored, i.e. DifferentialInlineComment->setIsNewFile(). 1367 - // "O" stands for 'old' and means the comment should attach to the old file. 1368 - 1369 - $left_char = $this->leftSideAttachesToNewFile 1370 - ? 'N' 1371 - : 'O'; 1372 - $right_char = $this->rightSideAttachesToNewFile 1373 - ? 'N' 1374 - : 'O'; 1375 - 1376 - $copy_lines = idx($this->changeset->getMetadata(), 'copy:lines', array()); 1377 - 1378 - if ($this->originalLeft && $this->originalRight) { 1379 - list($highlight_old, $highlight_new) = $this->diffOriginals(); 1380 - $highlight_old = array_flip($highlight_old); 1381 - $highlight_new = array_flip($highlight_new); 1382 - } 1383 - 1384 - // We need to go backwards to properly indent whitespace in this code: 1385 - // 1386 - // 0: class C { 1387 - // 1: 1388 - // 1: function f() { 1389 - // 2: 1390 - // 2: return; 1391 - // 1392 - $depths = array(); 1393 - $last_depth = 0; 1394 - $range_end = $range_start + $range_len; 1395 - if (!isset($this->new[$range_end])) { 1396 - $range_end--; 1397 - } 1398 - for ($ii = $range_end; $ii >= $range_start; $ii--) { 1399 - // We need to expand tabs to process mixed indenting and to round 1400 - // correctly later. 1401 - $line = str_replace("\t", " ", $this->new[$ii]['text']); 1402 - $trimmed = ltrim($line); 1403 - if ($trimmed != '') { 1404 - // We round down to flatten "/**" and " *". 1405 - $last_depth = floor((strlen($line) - strlen($trimmed)) / 2); 1406 - } 1407 - $depths[$ii] = $last_depth; 1408 - } 1409 - 1410 - for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { 1411 - if (empty($mask[$ii])) { 1412 - // If we aren't going to show this line, we've just entered a gap. 1413 - // Pop information about the next gap off the $gaps stack and render 1414 - // an appropriate "Show more context" element. This branch eventually 1415 - // increments $ii by the entire size of the gap and then continues 1416 - // the loop. 1417 - $gap = array_pop($gaps); 1418 - $top = $gap[0]; 1419 - $len = $gap[1]; 1420 - 1421 - $end = $top + $len - 20; 1422 - 1423 - $contents = array(); 1424 - 1425 - if ($len > 40) { 1426 - $is_first_block = false; 1427 - if ($ii == 0) { 1428 - $is_first_block = true; 1429 - } 1430 - 1431 - $contents[] = javelin_render_tag( 1432 - 'a', 1433 - array( 1434 - 'href' => '#', 1435 - 'mustcapture' => true, 1436 - 'sigil' => 'show-more', 1437 - 'meta' => array( 1438 - 'ref' => $reference, 1439 - 'range' => "{$top}-{$len}/{$top}-20", 1440 - ), 1441 - ), 1442 - $is_first_block 1443 - ? "Show First 20 Lines" 1444 - : "\xE2\x96\xB2 Show 20 Lines"); 1445 - } 1446 - 1447 - $contents[] = javelin_render_tag( 1448 - 'a', 1449 - array( 1450 - 'href' => '#', 1451 - 'mustcapture' => true, 1452 - 'sigil' => 'show-more', 1453 - 'meta' => array( 1454 - 'type' => 'all', 1455 - 'ref' => $reference, 1456 - 'range' => "{$top}-{$len}/{$top}-{$len}", 1457 - ), 1458 - ), 1459 - 'Show All '.$len.' Lines'); 1460 - 1461 - $is_last_block = false; 1462 - if ($ii + $len >= $rows) { 1463 - $is_last_block = true; 1464 - } 1465 - 1466 - if ($len > 40) { 1467 - $contents[] = javelin_render_tag( 1468 - 'a', 1469 - array( 1470 - 'href' => '#', 1471 - 'mustcapture' => true, 1472 - 'sigil' => 'show-more', 1473 - 'meta' => array( 1474 - 'ref' => $reference, 1475 - 'range' => "{$top}-{$len}/{$end}-20", 1476 - ), 1477 - ), 1478 - $is_last_block 1479 - ? "Show Last 20 Lines" 1480 - : "\xE2\x96\xBC Show 20 Lines"); 1481 - } 1482 - 1483 - $context = null; 1484 - $context_line = null; 1485 - if (!$is_last_block && $depths[$ii + $len]) { 1486 - for ($l = $ii + $len - 1; $l >= $ii; $l--) { 1487 - $line = $this->new[$l]['text']; 1488 - if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') { 1489 - $context = $this->newRender[$l]; 1490 - $context_line = $this->new[$l]['line']; 1491 - break; 1492 - } 1493 - } 1494 - } 1495 - 1496 - $container = javelin_render_tag( 1497 - 'tr', 1498 - array( 1499 - 'sigil' => 'context-target', 1500 - ), 1501 - '<td colspan="2" class="show-more">'. 1502 - implode(' &bull; ', $contents). 1503 - '</td>'. 1504 - '<th class="show-context-line">'.$context_line.'</td>'. 1505 - '<td colspan="3" class="show-context">'.$context.'</td>'); 1506 - 1507 - $html[] = $container; 1508 - 1509 - $ii += ($len - 1); 1510 - continue; 1511 - } 1512 - 1513 - $o_num = null; 1514 - $o_classes = 'left'; 1515 - $o_text = null; 1516 - if (isset($this->old[$ii])) { 1517 - $o_num = $this->old[$ii]['line']; 1518 - $o_text = isset($this->oldRender[$ii]) ? $this->oldRender[$ii] : null; 1519 - if ($this->old[$ii]['type']) { 1520 - if ($this->old[$ii]['type'] == '\\') { 1521 - $o_text = $this->old[$ii]['text']; 1522 - $o_classes .= ' comment'; 1523 - } else if ($this->originalLeft && !isset($highlight_old[$o_num])) { 1524 - $o_classes .= ' old-rebase'; 1525 - } else if (empty($this->new[$ii])) { 1526 - $o_classes .= ' old old-full'; 1527 - } else { 1528 - $o_classes .= ' old'; 1529 - } 1530 - } 1531 - } 1532 - 1533 - $n_copy = '<td class="copy" />'; 1534 - $n_cov = null; 1535 - $n_colspan = 2; 1536 - $n_classes = ''; 1537 - $n_num = null; 1538 - $n_text = null; 1539 - 1540 - if (isset($this->new[$ii])) { 1541 - $n_num = $this->new[$ii]['line']; 1542 - $n_text = isset($this->newRender[$ii]) ? $this->newRender[$ii] : null; 1543 - 1544 - if ($this->coverage !== null) { 1545 - if (empty($this->coverage[$n_num - 1])) { 1546 - $cov_class = 'N'; 1547 - } else { 1548 - $cov_class = $this->coverage[$n_num - 1]; 1549 - } 1550 - $cov_class = 'cov-'.$cov_class; 1551 - $n_cov = '<td class="cov '.$cov_class.'"></td>'; 1552 - $n_colspan--; 1553 - } 1554 - 1555 - if ($this->new[$ii]['type']) { 1556 - if ($this->new[$ii]['type'] == '\\') { 1557 - $n_text = $this->new[$ii]['text']; 1558 - $n_class = 'comment'; 1559 - } else if ($this->originalRight && !isset($highlight_new[$n_num])) { 1560 - $n_class = 'new-rebase'; 1561 - } else if (empty($this->old[$ii])) { 1562 - $n_class = 'new new-full'; 1563 - } else { 1564 - $n_class = 'new'; 1565 - } 1566 - $n_classes = $n_class; 1567 - 1568 - if ($this->new[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) { 1569 - $n_copy = '<td class="copy '.$n_class.'"></td>'; 1570 - } else { 1571 - list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num]; 1572 - $title = ($orig_type == '-' ? 'Moved' : 'Copied').' from '; 1573 - if ($orig_file == '') { 1574 - $title .= "line {$orig_line}"; 1575 - } else { 1576 - $title .= 1577 - basename($orig_file). 1578 - ":{$orig_line} in dir ". 1579 - dirname('/'.$orig_file); 1580 - } 1581 - $class = ($orig_type == '-' ? 'new-move' : 'new-copy'); 1582 - $n_copy = javelin_render_tag( 1583 - 'td', 1584 - array( 1585 - 'meta' => array( 1586 - 'msg' => $title, 1587 - ), 1588 - 'class' => 'copy '.$class, 1589 - ), 1590 - ''); 1591 - } 1592 - } 1593 - } 1594 - $n_classes .= ' right'.$n_colspan; 1595 - 1596 - 1597 - if (($o_num && !empty($this->missingOld[$o_num])) || 1598 - ($n_num && !empty($this->missingNew[$n_num]))) { 1599 - $html[] = $context_not_available; 1600 - } 1601 - 1602 - if ($o_num && $left_id) { 1603 - $o_id = ' id="C'.$left_id.$left_char.'L'.$o_num.'"'; 1604 - } else { 1605 - $o_id = null; 1606 - } 1607 - 1608 - if ($n_num && $right_id) { 1609 - $n_id = ' id="C'.$right_id.$right_char.'L'.$n_num.'"'; 1610 - } else { 1611 - $n_id = null; 1612 - } 1613 - 1614 - // NOTE: The Javascript is sensitive to whitespace changes in this 1615 - // block! 1616 - 1617 - $html[] = 1618 - '<tr>'. 1619 - '<th'.$o_id.'>'.$o_num.'</th>'. 1620 - '<td class="'.$o_classes.'">'.$o_text.'</td>'. 1621 - '<th'.$n_id.'>'.$n_num.'</th>'. 1622 - $n_copy. 1623 - // NOTE: This is a unicode zero-width space, which we use as a hint 1624 - // when intercepting 'copy' events to make sure sensible text ends 1625 - // up on the clipboard. See the 'phabricator-oncopy' behavior. 1626 - '<td class="'.$n_classes.'" colspan="'.$n_colspan.'">'. 1627 - "\xE2\x80\x8B".$n_text. 1628 - '</td>'. 1629 - $n_cov. 1630 - '</tr>'; 1631 - 1632 - if ($context_not_available && ($ii == $rows - 1)) { 1633 - $html[] = $context_not_available; 1634 - } 1635 - 1636 - if ($o_num && isset($old_comments[$o_num])) { 1637 - foreach ($old_comments[$o_num] as $comment) { 1638 - $xhp = $this->renderInlineComment($comment); 1639 - $new = ''; 1640 - if ($n_num && isset($new_comments[$n_num])) { 1641 - foreach ($new_comments[$n_num] as $key => $new_comment) { 1642 - if ($comment->isCompatible($new_comment)) { 1643 - $new = $this->renderInlineComment($new_comment); 1644 - unset($new_comments[$n_num][$key]); 1645 - } 1646 - } 1647 - } 1648 - $html[] = 1649 - '<tr class="inline">'. 1650 - '<th />'. 1651 - '<td class="left">'.$xhp.'</td>'. 1652 - '<th />'. 1653 - '<td colspan="3" class="right3">'.$new.'</td>'. 1654 - '</tr>'; 1655 - } 1656 - } 1657 - if ($n_num && isset($new_comments[$n_num])) { 1658 - foreach ($new_comments[$n_num] as $comment) { 1659 - $xhp = $this->renderInlineComment($comment); 1660 - $html[] = 1661 - '<tr class="inline">'. 1662 - '<th />'. 1663 - '<td class="left" />'. 1664 - '<th />'. 1665 - '<td colspan="3" class="right3">'.$xhp.'</td>'. 1666 - '</tr>'; 1667 - } 1668 - } 1669 - } 1670 - 1671 - return implode('', $html); 1672 - } 1673 - 1674 - private function renderInlineComment( 1675 - PhabricatorInlineCommentInterface $comment) { 1676 - 1677 - $user = $this->user; 1678 - $edit = $user && 1679 - ($comment->getAuthorPHID() == $user->getPHID()) && 1680 - ($comment->isDraft()); 1681 - $allow_reply = (bool)$this->user; 1682 - 1683 - $on_right = $this->isCommentOnRightSideWhenDisplayed($comment); 1684 - 1685 - return id(new DifferentialInlineCommentView()) 1686 - ->setInlineComment($comment) 1687 - ->setOnRight($on_right) 1688 - ->setHandles($this->handles) 1689 - ->setMarkupEngine($this->markupEngine) 1690 - ->setEditable($edit) 1691 - ->setAllowReply($allow_reply) 1692 - ->render(); 1693 - } 1694 - 1695 - protected function renderPropertyChangeHeader($changeset) { 1696 - if (!$this->isTopLevel) { 1697 - // We render properties only at top level; otherwise we get multiple 1698 - // copies of them when a user clicks "Show More". 1699 - return null; 1700 - } 1701 - 1702 - $old = $changeset->getOldProperties(); 1703 - $new = $changeset->getNewProperties(); 1704 - 1705 - if ($old === $new) { 1706 - return null; 1707 - } 1708 - 1709 - if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD && 1710 - $new == array('unix:filemode' => '100644')) { 1711 - return null; 1712 - } 1713 - 1714 - if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE && 1715 - $old == array('unix:filemode' => '100644')) { 1716 - return null; 1717 - } 1718 - 1719 - $keys = array_keys($old + $new); 1720 - sort($keys); 1721 - 1722 - $rows = array(); 1723 - foreach ($keys as $key) { 1724 - $oval = idx($old, $key); 1725 - $nval = idx($new, $key); 1726 - if ($oval !== $nval) { 1727 - if ($oval === null) { 1728 - $oval = '<em>null</em>'; 1729 - } else { 1730 - $oval = nl2br(phutil_escape_html($oval)); 1731 - } 1732 - 1733 - if ($nval === null) { 1734 - $nval = '<em>null</em>'; 1735 - } else { 1736 - $nval = nl2br(phutil_escape_html($nval)); 1737 - } 1738 - 1739 - $rows[] = 1740 - '<tr>'. 1741 - '<th>'.phutil_escape_html($key).'</th>'. 1742 - '<td class="oval">'.$oval.'</td>'. 1743 - '<td class="nval">'.$nval.'</td>'. 1744 - '</tr>'; 1745 - } 1746 - } 1747 - 1748 - return 1749 - '<table class="differential-property-table">'. 1750 - '<tr class="property-table-header">'. 1751 - '<th>Property Changes</th>'. 1752 - '<td class="oval">Old Value</td>'. 1753 - '<td class="nval">New Value</td>'. 1754 - '</tr>'. 1755 - implode('', $rows). 1756 - '</table>'; 1757 - } 1758 - 1759 - protected function renderChangesetTable($changeset, $contents) { 1760 - $props = $this->renderPropertyChangeHeader($this->changeset); 1761 - $table = null; 1762 - if ($contents) { 1763 - $table = javelin_render_tag( 1764 - 'table', 1765 - array( 1766 - 'class' => 'differential-diff remarkup-code PhabricatorMonospaced', 1767 - 'sigil' => 'differential-diff', 1768 - ), 1769 - $contents); 1770 - } 1771 - 1772 - if (!$table && !$props) { 1773 - $notice = $this->renderChangeTypeHeader($this->changeset, true); 1774 - } else { 1775 - $notice = $this->renderChangeTypeHeader($this->changeset, false); 1776 - } 1777 - 1778 - $result = implode( 1779 - "\n", 1780 - array( 1781 - $notice, 1782 - $props, 1783 - $table, 1784 - )); 1785 - 1786 - // TODO: Let the user customize their tab width / display style. 1787 - $result = str_replace("\t", ' ', $result); 1788 - 1789 - // TODO: We should possibly post-process "\r" as well. 1790 - 1791 - return $result; 1792 - } 1793 - 1794 - protected function renderChangeTypeHeader($changeset, $force) { 1795 - $change = $changeset->getChangeType(); 1796 - $file = $changeset->getFileType(); 1797 - 1798 - $message = null; 1799 - if ($change == DifferentialChangeType::TYPE_CHANGE && 1800 - $file == DifferentialChangeType::FILE_TEXT) { 1801 - if ($force) { 1802 - // We have to force something to render because there were no changes 1803 - // of other kinds. 1804 - $message = pht('This file was not modified.'); 1805 - } else { 1806 - // Default case of changes to a text file, no metadata. 1807 - return null; 1808 - } 1809 - } else { 1810 - switch ($change) { 1811 - 1812 - case DifferentialChangeType::TYPE_ADD: 1813 - switch ($file) { 1814 - case DifferentialChangeType::FILE_TEXT: 1815 - $message = pht('This file was <strong>added</strong>.'); 1816 - break; 1817 - case DifferentialChangeType::FILE_IMAGE: 1818 - $message = pht('This image was <strong>added</strong>.'); 1819 - break; 1820 - case DifferentialChangeType::FILE_DIRECTORY: 1821 - $message = pht('This directory was <strong>added</strong>.'); 1822 - break; 1823 - case DifferentialChangeType::FILE_BINARY: 1824 - $message = pht('This binary file was <strong>added</strong>.'); 1825 - break; 1826 - case DifferentialChangeType::FILE_SYMLINK: 1827 - $message = pht('This symlink was <strong>added</strong>.'); 1828 - break; 1829 - case DifferentialChangeType::FILE_SUBMODULE: 1830 - $message = pht('This submodule was <strong>added</strong>.'); 1831 - break; 1832 - } 1833 - break; 1834 - 1835 - case DifferentialChangeType::TYPE_DELETE: 1836 - switch ($file) { 1837 - case DifferentialChangeType::FILE_TEXT: 1838 - $message = pht('This file was <strong>deleted</strong>.'); 1839 - break; 1840 - case DifferentialChangeType::FILE_IMAGE: 1841 - $message = pht('This image was <strong>deleted</strong>.'); 1842 - break; 1843 - case DifferentialChangeType::FILE_DIRECTORY: 1844 - $message = pht('This directory was <strong>deleted</strong>.'); 1845 - break; 1846 - case DifferentialChangeType::FILE_BINARY: 1847 - $message = pht('This binary file was <strong>deleted</strong>.'); 1848 - break; 1849 - case DifferentialChangeType::FILE_SYMLINK: 1850 - $message = pht('This symlink was <strong>deleted</strong>.'); 1851 - break; 1852 - case DifferentialChangeType::FILE_SUBMODULE: 1853 - $message = pht('This submodule was <strong>deleted</strong>.'); 1854 - break; 1855 - } 1856 - break; 1857 - 1858 - case DifferentialChangeType::TYPE_MOVE_HERE: 1859 - $from = 1860 - "<strong>". 1861 - phutil_escape_html($changeset->getOldFile()). 1862 - "</strong>"; 1863 - switch ($file) { 1864 - case DifferentialChangeType::FILE_TEXT: 1865 - $message = pht('This file was moved from %s.', $from); 1866 - break; 1867 - case DifferentialChangeType::FILE_IMAGE: 1868 - $message = pht('This image was moved from %s.', $from); 1869 - break; 1870 - case DifferentialChangeType::FILE_DIRECTORY: 1871 - $message = pht('This directory was moved from %s.', $from); 1872 - break; 1873 - case DifferentialChangeType::FILE_BINARY: 1874 - $message = pht('This binary file was moved from %s.', $from); 1875 - break; 1876 - case DifferentialChangeType::FILE_SYMLINK: 1877 - $message = pht('This symlink was moved from %s.', $from); 1878 - break; 1879 - case DifferentialChangeType::FILE_SUBMODULE: 1880 - $message = pht('This submodule was moved from %s.', $from); 1881 - break; 1882 - } 1883 - break; 1884 - 1885 - case DifferentialChangeType::TYPE_COPY_HERE: 1886 - $from = 1887 - "<strong>". 1888 - phutil_escape_html($changeset->getOldFile()). 1889 - "</strong>"; 1890 - switch ($file) { 1891 - case DifferentialChangeType::FILE_TEXT: 1892 - $message = pht('This file was copied from %s.', $from); 1893 - break; 1894 - case DifferentialChangeType::FILE_IMAGE: 1895 - $message = pht('This image was copied from %s.', $from); 1896 - break; 1897 - case DifferentialChangeType::FILE_DIRECTORY: 1898 - $message = pht('This directory was copied from %s.', $from); 1899 - break; 1900 - case DifferentialChangeType::FILE_BINARY: 1901 - $message = pht('This binary file was copied from %s.', $from); 1902 - break; 1903 - case DifferentialChangeType::FILE_SYMLINK: 1904 - $message = pht('This symlink was copied from %s.', $from); 1905 - break; 1906 - case DifferentialChangeType::FILE_SUBMODULE: 1907 - $message = pht('This submodule was copied from %s.', $from); 1908 - break; 1909 - } 1910 - break; 1911 - 1912 - case DifferentialChangeType::TYPE_MOVE_AWAY: 1913 - $paths = 1914 - "<strong>". 1915 - phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 1916 - "</strong>"; 1917 - switch ($file) { 1918 - case DifferentialChangeType::FILE_TEXT: 1919 - $message = pht('This file was moved to %s.', $paths); 1920 - break; 1921 - case DifferentialChangeType::FILE_IMAGE: 1922 - $message = pht('This image was moved to %s.', $paths); 1923 - break; 1924 - case DifferentialChangeType::FILE_DIRECTORY: 1925 - $message = pht('This directory was moved to %s.', $paths); 1926 - break; 1927 - case DifferentialChangeType::FILE_BINARY: 1928 - $message = pht('This binary file was moved to %s.', $paths); 1929 - break; 1930 - case DifferentialChangeType::FILE_SYMLINK: 1931 - $message = pht('This symlink was moved to %s.', $paths); 1932 - break; 1933 - case DifferentialChangeType::FILE_SUBMODULE: 1934 - $message = pht('This submodule was moved to %s.', $paths); 1935 - break; 1936 - } 1937 - break; 1938 - 1939 - case DifferentialChangeType::TYPE_COPY_AWAY: 1940 - $paths = 1941 - "<strong>". 1942 - phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 1943 - "</strong>"; 1944 - switch ($file) { 1945 - case DifferentialChangeType::FILE_TEXT: 1946 - $message = pht('This file was copied to %s.', $paths); 1947 - break; 1948 - case DifferentialChangeType::FILE_IMAGE: 1949 - $message = pht('This image was copied to %s.', $paths); 1950 - break; 1951 - case DifferentialChangeType::FILE_DIRECTORY: 1952 - $message = pht('This directory was copied to %s.', $paths); 1953 - break; 1954 - case DifferentialChangeType::FILE_BINARY: 1955 - $message = pht('This binary file was copied to %s.', $paths); 1956 - break; 1957 - case DifferentialChangeType::FILE_SYMLINK: 1958 - $message = pht('This symlink was copied to %s.', $paths); 1959 - break; 1960 - case DifferentialChangeType::FILE_SUBMODULE: 1961 - $message = pht('This submodule was copied to %s.', $paths); 1962 - break; 1963 - } 1964 - break; 1965 - 1966 - case DifferentialChangeType::TYPE_MULTICOPY: 1967 - $paths = 1968 - "<strong>". 1969 - phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 1970 - "</strong>"; 1971 - switch ($file) { 1972 - case DifferentialChangeType::FILE_TEXT: 1973 - $message = pht( 1974 - 'This file was deleted after being copied to %s.', 1975 - $paths); 1976 - break; 1977 - case DifferentialChangeType::FILE_IMAGE: 1978 - $message = pht( 1979 - 'This image was deleted after being copied to %s.', 1980 - $paths); 1981 - break; 1982 - case DifferentialChangeType::FILE_DIRECTORY: 1983 - $message = pht( 1984 - 'This directory was deleted after being copied to %s.', 1985 - $paths); 1986 - break; 1987 - case DifferentialChangeType::FILE_BINARY: 1988 - $message = pht( 1989 - 'This binary file was deleted after being copied to %s.', 1990 - $paths); 1991 - break; 1992 - case DifferentialChangeType::FILE_SYMLINK: 1993 - $message = pht( 1994 - 'This symlink was deleted after being copied to %s.', 1995 - $paths); 1996 - break; 1997 - case DifferentialChangeType::FILE_SUBMODULE: 1998 - $message = pht( 1999 - 'This submodule was deleted after being copied to %s.', 2000 - $paths); 2001 - break; 2002 - } 2003 - break; 2004 - 2005 - default: 2006 - switch ($file) { 2007 - case DifferentialChangeType::FILE_TEXT: 2008 - $message = pht('This is a file.'); 2009 - break; 2010 - case DifferentialChangeType::FILE_IMAGE: 2011 - $message = pht('This is an image.'); 2012 - break; 2013 - case DifferentialChangeType::FILE_DIRECTORY: 2014 - $message = pht('This is a directory.'); 2015 - break; 2016 - case DifferentialChangeType::FILE_BINARY: 2017 - $message = pht('This is a binary file.'); 2018 - break; 2019 - case DifferentialChangeType::FILE_SYMLINK: 2020 - $message = pht('This is a symlink.'); 2021 - break; 2022 - case DifferentialChangeType::FILE_SUBMODULE: 2023 - $message = pht('This is a submodule.'); 2024 - break; 2025 - } 2026 - break; 2027 - } 2028 - } 2029 - 2030 - return 2031 - '<div class="differential-meta-notice">'. 2032 - $message. 2033 - '</div>'; 2034 1218 } 2035 1219 2036 1220 public function renderForEmail() {
+603
src/applications/differential/render/DifferentialChangesetRenderer.php
··· 1 + <?php 2 + 3 + abstract class DifferentialChangesetRenderer { 4 + 5 + private $user; 6 + private $changeset; 7 + private $renderingReference; 8 + private $renderPropertyChangeHeader; 9 + private $missingOldLines; 10 + private $missingNewLines; 11 + private $oldLines; 12 + private $newLines; 13 + private $visibleLines; 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 $linesOfContext; 23 + private $codeCoverage; 24 + private $handles; 25 + private $markupEngine; 26 + private $oldRender; 27 + private $newRender; 28 + private $originalOld; 29 + private $originalNew; 30 + 31 + public function setOriginalNew($original_new) { 32 + $this->originalNew = $original_new; 33 + return $this; 34 + } 35 + protected function getOriginalNew() { 36 + return $this->originalNew; 37 + } 38 + 39 + public function setOriginalOld($original_old) { 40 + $this->originalOld = $original_old; 41 + return $this; 42 + } 43 + protected function getOriginalOld() { 44 + return $this->originalOld; 45 + } 46 + 47 + public function setNewRender($new_render) { 48 + $this->newRender = $new_render; 49 + return $this; 50 + } 51 + protected function getNewRender() { 52 + return $this->newRender; 53 + } 54 + 55 + public function setOldRender($old_render) { 56 + $this->oldRender = $old_render; 57 + return $this; 58 + } 59 + protected function getOldRender() { 60 + return $this->oldRender; 61 + } 62 + 63 + public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) { 64 + $this->markupEngine = $markup_engine; 65 + return $this; 66 + } 67 + public function getMarkupEngine() { 68 + return $this->markupEngine; 69 + } 70 + 71 + public function setHandles(array $handles) { 72 + assert_instances_of($handles, 'PhabricatorObjectHandle'); 73 + $this->handles = $handles; 74 + return $this; 75 + } 76 + protected function getHandles() { 77 + return $this->handles; 78 + } 79 + 80 + public function setCodeCoverage($code_coverage) { 81 + $this->codeCoverage = $code_coverage; 82 + return $this; 83 + } 84 + protected function getCodeCoverage() { 85 + return $this->codeCoverage; 86 + } 87 + 88 + public function setLinesOfContext($lines_of_context) { 89 + $this->linesOfContext = $lines_of_context; 90 + return $this; 91 + } 92 + protected function getLinesOfContext() { 93 + return $this->linesOfContext; 94 + } 95 + 96 + public function setHighlightNew($highlight_new) { 97 + $this->highlightNew = $highlight_new; 98 + return $this; 99 + } 100 + protected function getHighlightNew() { 101 + return $this->highlightNew; 102 + } 103 + 104 + public function setHighlightOld($highlight_old) { 105 + $this->highlightOld = $highlight_old; 106 + return $this; 107 + } 108 + protected function getHighlightOld() { 109 + return $this->highlightOld; 110 + } 111 + 112 + public function setNewAttachesToNewFile($attaches) { 113 + $this->newAttachesToNewFile = $attaches; 114 + return $this; 115 + } 116 + protected function getNewAttachesToNewFile() { 117 + return $this->newAttachesToNewFile; 118 + } 119 + 120 + public function setOldAttachesToNewFile($attaches) { 121 + $this->oldAttachesToNewFile = $attaches; 122 + return $this; 123 + } 124 + protected function getOldAttachesToNewFile() { 125 + return $this->oldAttachesToNewFile; 126 + } 127 + 128 + public function setNewChangesetID($new_changeset_id) { 129 + $this->newChangesetID = $new_changeset_id; 130 + return $this; 131 + } 132 + protected function getNewChangesetID() { 133 + return $this->newChangesetID; 134 + } 135 + 136 + public function setOldChangesetID($old_changeset_id) { 137 + $this->oldChangesetID = $old_changeset_id; 138 + return $this; 139 + } 140 + protected function getOldChangesetID() { 141 + return $this->oldChangesetID; 142 + } 143 + 144 + public function setNewComments(array $new_comments) { 145 + foreach ($new_comments as $line_number => $comments) { 146 + assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 147 + } 148 + $this->newComments = $new_comments; 149 + return $this; 150 + } 151 + protected function getNewComments() { 152 + return $this->newComments; 153 + } 154 + 155 + public function setOldComments(array $old_comments) { 156 + foreach ($old_comments as $line_number => $comments) { 157 + assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 158 + } 159 + $this->oldComments = $old_comments; 160 + return $this; 161 + } 162 + protected function getOldComments() { 163 + return $this->oldComments; 164 + } 165 + 166 + public function setVisibleLines(array $visible_lines) { 167 + $this->visibleLines = $visible_lines; 168 + return $this; 169 + } 170 + protected function getVisibleLines() { 171 + return $this->visibleLines; 172 + } 173 + 174 + public function setNewLines(array $new_lines) { 175 + phlog(print_r($new_lines, true)); 176 + $this->newLines = $new_lines; 177 + return $this; 178 + } 179 + protected function getNewLines() { 180 + return $this->newLines; 181 + } 182 + 183 + public function setOldLines(array $old_lines) { 184 + phlog(print_r($old_lines, true)); 185 + $this->oldLines = $old_lines; 186 + return $this; 187 + } 188 + protected function getOldLines() { 189 + return $this->oldLines; 190 + } 191 + 192 + public function setMissingNewLines(array $missing_new_lines) { 193 + $this->missingNewLines = $missing_new_lines; 194 + return $this; 195 + } 196 + protected function getMissingNewLines() { 197 + return $this->missingNewLines; 198 + } 199 + 200 + public function setMissingOldLines(array $missing_old_lines) { 201 + $this->missingOldLines = $missing_old_lines; 202 + return $this; 203 + } 204 + protected function getMissingOldLines() { 205 + return $this->missingOldLines; 206 + } 207 + 208 + public function setUser(PhabricatorUser $user) { 209 + $this->user = $user; 210 + return $this; 211 + } 212 + protected function getUser() { 213 + return $this->user; 214 + } 215 + 216 + public function setChangeset(DifferentialChangeset $changeset) { 217 + $this->changeset = $changeset; 218 + return $this; 219 + } 220 + protected function getChangeset() { 221 + return $this->changeset; 222 + } 223 + 224 + public function setRenderingReference($rendering_reference) { 225 + $this->renderingReference = $rendering_reference; 226 + return $this; 227 + } 228 + protected function getRenderingReference() { 229 + return $this->renderingReference; 230 + } 231 + 232 + public function setRenderPropertyChangeHeader($should_render) { 233 + $this->renderPropertyChangeHeader = $should_render; 234 + return $this; 235 + } 236 + private function shouldRenderPropertyChangeHeader() { 237 + return $this->renderPropertyChangeHeader; 238 + } 239 + 240 + abstract public function renderChangesetTable($contents); 241 + abstract public function renderTextChange( 242 + $range_start, 243 + $range_len, 244 + $mask_force, 245 + $feedback_mask 246 + ); 247 + abstract public function renderFileChange( 248 + $old = null, 249 + $new = null, 250 + $id = 0, 251 + $vs = 0 252 + ); 253 + 254 + public function renderShield($message, $more) { 255 + 256 + if ($more) { 257 + $end = max( 258 + count($this->getOldLines()), 259 + count($this->getNewLines()) 260 + ); 261 + $reference = $this->getRenderingReference(); 262 + $more = 263 + ' '. 264 + javelin_render_tag( 265 + 'a', 266 + array( 267 + 'mustcapture' => true, 268 + 'sigil' => 'show-more', 269 + 'class' => 'complete', 270 + 'href' => '#', 271 + 'meta' => array( 272 + 'ref' => $reference, 273 + 'range' => "0-{$end}", 274 + ), 275 + ), 276 + 'Show File Contents'); 277 + } else { 278 + $more = null; 279 + } 280 + 281 + return javelin_render_tag( 282 + 'tr', 283 + array( 284 + 'sigil' => 'context-target', 285 + ), 286 + '<td class="differential-shield" colspan="6">'. 287 + phutil_escape_html($message). 288 + $more. 289 + '</td>'); 290 + } 291 + 292 + 293 + protected function renderPropertyChangeHeader($changeset) { 294 + if (!$this->shouldRenderPropertyChangeHeader()) { 295 + return null; 296 + } 297 + 298 + $old = $changeset->getOldProperties(); 299 + $new = $changeset->getNewProperties(); 300 + 301 + $keys = array_keys($old + $new); 302 + sort($keys); 303 + 304 + $rows = array(); 305 + foreach ($keys as $key) { 306 + $oval = idx($old, $key); 307 + $nval = idx($new, $key); 308 + if ($oval !== $nval) { 309 + if ($oval === null) { 310 + $oval = '<em>null</em>'; 311 + } else { 312 + $oval = nl2br(phutil_escape_html($oval)); 313 + } 314 + 315 + if ($nval === null) { 316 + $nval = '<em>null</em>'; 317 + } else { 318 + $nval = nl2br(phutil_escape_html($nval)); 319 + } 320 + 321 + $rows[] = 322 + '<tr>'. 323 + '<th>'.phutil_escape_html($key).'</th>'. 324 + '<td class="oval">'.$oval.'</td>'. 325 + '<td class="nval">'.$nval.'</td>'. 326 + '</tr>'; 327 + } 328 + } 329 + 330 + return 331 + '<table class="differential-property-table">'. 332 + '<tr class="property-table-header">'. 333 + '<th>Property Changes</th>'. 334 + '<td class="oval">Old Value</td>'. 335 + '<td class="nval">New Value</td>'. 336 + '</tr>'. 337 + implode('', $rows). 338 + '</table>'; 339 + } 340 + 341 + protected function renderChangeTypeHeader($changeset, $force) { 342 + $change = $changeset->getChangeType(); 343 + $file = $changeset->getFileType(); 344 + 345 + $message = null; 346 + if ($change == DifferentialChangeType::TYPE_CHANGE && 347 + $file == DifferentialChangeType::FILE_TEXT) { 348 + if ($force) { 349 + // We have to force something to render because there were no changes 350 + // of other kinds. 351 + $message = pht('This file was not modified.'); 352 + } else { 353 + // Default case of changes to a text file, no metadata. 354 + return null; 355 + } 356 + } else { 357 + switch ($change) { 358 + 359 + case DifferentialChangeType::TYPE_ADD: 360 + switch ($file) { 361 + case DifferentialChangeType::FILE_TEXT: 362 + $message = pht('This file was <strong>added</strong>.'); 363 + break; 364 + case DifferentialChangeType::FILE_IMAGE: 365 + $message = pht('This image was <strong>added</strong>.'); 366 + break; 367 + case DifferentialChangeType::FILE_DIRECTORY: 368 + $message = pht('This directory was <strong>added</strong>.'); 369 + break; 370 + case DifferentialChangeType::FILE_BINARY: 371 + $message = pht('This binary file was <strong>added</strong>.'); 372 + break; 373 + case DifferentialChangeType::FILE_SYMLINK: 374 + $message = pht('This symlink was <strong>added</strong>.'); 375 + break; 376 + case DifferentialChangeType::FILE_SUBMODULE: 377 + $message = pht('This submodule was <strong>added</strong>.'); 378 + break; 379 + } 380 + break; 381 + 382 + case DifferentialChangeType::TYPE_DELETE: 383 + switch ($file) { 384 + case DifferentialChangeType::FILE_TEXT: 385 + $message = pht('This file was <strong>deleted</strong>.'); 386 + break; 387 + case DifferentialChangeType::FILE_IMAGE: 388 + $message = pht('This image was <strong>deleted</strong>.'); 389 + break; 390 + case DifferentialChangeType::FILE_DIRECTORY: 391 + $message = pht('This directory was <strong>deleted</strong>.'); 392 + break; 393 + case DifferentialChangeType::FILE_BINARY: 394 + $message = pht('This binary file was <strong>deleted</strong>.'); 395 + break; 396 + case DifferentialChangeType::FILE_SYMLINK: 397 + $message = pht('This symlink was <strong>deleted</strong>.'); 398 + break; 399 + case DifferentialChangeType::FILE_SUBMODULE: 400 + $message = pht('This submodule was <strong>deleted</strong>.'); 401 + break; 402 + } 403 + break; 404 + 405 + case DifferentialChangeType::TYPE_MOVE_HERE: 406 + $from = 407 + "<strong>". 408 + phutil_escape_html($changeset->getOldFile()). 409 + "</strong>"; 410 + switch ($file) { 411 + case DifferentialChangeType::FILE_TEXT: 412 + $message = pht('This file was moved from %s.', $from); 413 + break; 414 + case DifferentialChangeType::FILE_IMAGE: 415 + $message = pht('This image was moved from %s.', $from); 416 + break; 417 + case DifferentialChangeType::FILE_DIRECTORY: 418 + $message = pht('This directory was moved from %s.', $from); 419 + break; 420 + case DifferentialChangeType::FILE_BINARY: 421 + $message = pht('This binary file was moved from %s.', $from); 422 + break; 423 + case DifferentialChangeType::FILE_SYMLINK: 424 + $message = pht('This symlink was moved from %s.', $from); 425 + break; 426 + case DifferentialChangeType::FILE_SUBMODULE: 427 + $message = pht('This submodule was moved from %s.', $from); 428 + break; 429 + } 430 + break; 431 + 432 + case DifferentialChangeType::TYPE_COPY_HERE: 433 + $from = 434 + "<strong>". 435 + phutil_escape_html($changeset->getOldFile()). 436 + "</strong>"; 437 + switch ($file) { 438 + case DifferentialChangeType::FILE_TEXT: 439 + $message = pht('This file was copied from %s.', $from); 440 + break; 441 + case DifferentialChangeType::FILE_IMAGE: 442 + $message = pht('This image was copied from %s.', $from); 443 + break; 444 + case DifferentialChangeType::FILE_DIRECTORY: 445 + $message = pht('This directory was copied from %s.', $from); 446 + break; 447 + case DifferentialChangeType::FILE_BINARY: 448 + $message = pht('This binary file was copied from %s.', $from); 449 + break; 450 + case DifferentialChangeType::FILE_SYMLINK: 451 + $message = pht('This symlink was copied from %s.', $from); 452 + break; 453 + case DifferentialChangeType::FILE_SUBMODULE: 454 + $message = pht('This submodule was copied from %s.', $from); 455 + break; 456 + } 457 + break; 458 + 459 + case DifferentialChangeType::TYPE_MOVE_AWAY: 460 + $paths = 461 + "<strong>". 462 + phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 463 + "</strong>"; 464 + switch ($file) { 465 + case DifferentialChangeType::FILE_TEXT: 466 + $message = pht('This file was moved to %s.', $paths); 467 + break; 468 + case DifferentialChangeType::FILE_IMAGE: 469 + $message = pht('This image was moved to %s.', $paths); 470 + break; 471 + case DifferentialChangeType::FILE_DIRECTORY: 472 + $message = pht('This directory was moved to %s.', $paths); 473 + break; 474 + case DifferentialChangeType::FILE_BINARY: 475 + $message = pht('This binary file was moved to %s.', $paths); 476 + break; 477 + case DifferentialChangeType::FILE_SYMLINK: 478 + $message = pht('This symlink was moved to %s.', $paths); 479 + break; 480 + case DifferentialChangeType::FILE_SUBMODULE: 481 + $message = pht('This submodule was moved to %s.', $paths); 482 + break; 483 + } 484 + break; 485 + 486 + case DifferentialChangeType::TYPE_COPY_AWAY: 487 + $paths = 488 + "<strong>". 489 + phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 490 + "</strong>"; 491 + switch ($file) { 492 + case DifferentialChangeType::FILE_TEXT: 493 + $message = pht('This file was copied to %s.', $paths); 494 + break; 495 + case DifferentialChangeType::FILE_IMAGE: 496 + $message = pht('This image was copied to %s.', $paths); 497 + break; 498 + case DifferentialChangeType::FILE_DIRECTORY: 499 + $message = pht('This directory was copied to %s.', $paths); 500 + break; 501 + case DifferentialChangeType::FILE_BINARY: 502 + $message = pht('This binary file was copied to %s.', $paths); 503 + break; 504 + case DifferentialChangeType::FILE_SYMLINK: 505 + $message = pht('This symlink was copied to %s.', $paths); 506 + break; 507 + case DifferentialChangeType::FILE_SUBMODULE: 508 + $message = pht('This submodule was copied to %s.', $paths); 509 + break; 510 + } 511 + break; 512 + 513 + case DifferentialChangeType::TYPE_MULTICOPY: 514 + $paths = 515 + "<strong>". 516 + phutil_escape_html(implode(', ', $changeset->getAwayPaths())). 517 + "</strong>"; 518 + switch ($file) { 519 + case DifferentialChangeType::FILE_TEXT: 520 + $message = pht( 521 + 'This file was deleted after being copied to %s.', 522 + $paths); 523 + break; 524 + case DifferentialChangeType::FILE_IMAGE: 525 + $message = pht( 526 + 'This image was deleted after being copied to %s.', 527 + $paths); 528 + break; 529 + case DifferentialChangeType::FILE_DIRECTORY: 530 + $message = pht( 531 + 'This directory was deleted after being copied to %s.', 532 + $paths); 533 + break; 534 + case DifferentialChangeType::FILE_BINARY: 535 + $message = pht( 536 + 'This binary file was deleted after being copied to %s.', 537 + $paths); 538 + break; 539 + case DifferentialChangeType::FILE_SYMLINK: 540 + $message = pht( 541 + 'This symlink was deleted after being copied to %s.', 542 + $paths); 543 + break; 544 + case DifferentialChangeType::FILE_SUBMODULE: 545 + $message = pht( 546 + 'This submodule was deleted after being copied to %s.', 547 + $paths); 548 + break; 549 + } 550 + break; 551 + 552 + default: 553 + switch ($file) { 554 + case DifferentialChangeType::FILE_TEXT: 555 + $message = pht('This is a file.'); 556 + break; 557 + case DifferentialChangeType::FILE_IMAGE: 558 + $message = pht('This is an image.'); 559 + break; 560 + case DifferentialChangeType::FILE_DIRECTORY: 561 + $message = pht('This is a directory.'); 562 + break; 563 + case DifferentialChangeType::FILE_BINARY: 564 + $message = pht('This is a binary file.'); 565 + break; 566 + case DifferentialChangeType::FILE_SYMLINK: 567 + $message = pht('This is a symlink.'); 568 + break; 569 + case DifferentialChangeType::FILE_SUBMODULE: 570 + $message = pht('This is a submodule.'); 571 + break; 572 + } 573 + break; 574 + } 575 + } 576 + 577 + return 578 + '<div class="differential-meta-notice">'. 579 + $message. 580 + '</div>'; 581 + } 582 + 583 + protected function renderInlineComment( 584 + PhabricatorInlineCommentInterface $comment, 585 + $on_right = false) { 586 + 587 + $user = $this->getUser(); 588 + $edit = $user && 589 + ($comment->getAuthorPHID() == $user->getPHID()) && 590 + ($comment->isDraft()); 591 + $allow_reply = (bool)$user; 592 + 593 + return id(new DifferentialInlineCommentView()) 594 + ->setInlineComment($comment) 595 + ->setOnRight($on_right) 596 + ->setHandles($this->getHandles()) 597 + ->setMarkupEngine($this->getMarkupEngine()) 598 + ->setEditable($edit) 599 + ->setAllowReply($allow_reply) 600 + ->render(); 601 + } 602 + 603 + }
+536
src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
··· 1 + <?php 2 + 3 + final class DifferentialChangesetTwoUpRenderer 4 + extends DifferentialChangesetRenderer { 5 + 6 + public function renderChangesetTable($contents) { 7 + $changeset = $this->getChangeset(); 8 + $props = $this->renderPropertyChangeHeader($changeset); 9 + $table = null; 10 + if ($contents) { 11 + $table = javelin_render_tag( 12 + 'table', 13 + array( 14 + 'class' => 'differential-diff remarkup-code PhabricatorMonospaced', 15 + 'sigil' => 'differential-diff', 16 + ), 17 + $contents); 18 + } 19 + 20 + if (!$table && !$props) { 21 + $notice = $this->renderChangeTypeHeader($changeset, true); 22 + } else { 23 + $notice = $this->renderChangeTypeHeader($changeset, false); 24 + } 25 + 26 + $result = implode( 27 + "\n", 28 + array( 29 + $notice, 30 + $props, 31 + $table, 32 + )); 33 + 34 + // TODO: Let the user customize their tab width / display style. 35 + $result = str_replace("\t", ' ', $result); 36 + 37 + // TODO: We should possibly post-process "\r" as well. 38 + 39 + return $result; 40 + } 41 + 42 + public function renderTextChange( 43 + $range_start, 44 + $range_len, 45 + $mask_force, 46 + $feedback_mask) { 47 + 48 + $missing_old = $this->getMissingOldLines(); 49 + $missing_new = $this->getMissingNewLines(); 50 + 51 + $context_not_available = null; 52 + if ($missing_old || $missing_new) { 53 + $context_not_available = javelin_render_tag( 54 + 'tr', 55 + array( 56 + 'sigil' => 'context-target', 57 + ), 58 + phutil_render_tag( 59 + 'td', 60 + array( 61 + 'colspan' => 6, 62 + 'class' => 'show-more' 63 + ), 64 + pht('Context not available.') 65 + ) 66 + ); 67 + } 68 + 69 + $html = array(); 70 + $old_lines = $this->getOldLines(); 71 + $new_lines = $this->getNewLines(); 72 + 73 + $rows = max( 74 + count($old_lines), 75 + count($new_lines)); 76 + 77 + phlog($rows); 78 + 79 + if ($range_start === null) { 80 + $range_start = 0; 81 + } 82 + 83 + if ($range_len === null) { 84 + $range_len = $rows; 85 + } 86 + 87 + $range_len = min($range_len, $rows - $range_start); 88 + 89 + // Gaps - compute gaps in the visible display diff, where we will render 90 + // "Show more context" spacers. This builds an aggregate $mask of all the 91 + // lines we must show (because they are near changed lines, near inline 92 + // comments, or the request has explicitly asked for them, i.e. resulting 93 + // from the user clicking "show more") and then finds all the gaps between 94 + // visible lines. If a gap is smaller than the context size, we just 95 + // display it. Otherwise, we record it into $gaps and will render a 96 + // "show more context" element instead of diff text below. 97 + 98 + $gaps = array(); 99 + $gap_start = 0; 100 + $in_gap = false; 101 + $lines_of_context = $this->getLinesOfContext(); 102 + $mask = $this->getVisibleLines() + $mask_force + $feedback_mask; 103 + $mask[$range_start + $range_len] = true; 104 + for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) { 105 + if (isset($mask[$ii])) { 106 + if ($in_gap) { 107 + $gap_length = $ii - $gap_start; 108 + if ($gap_length <= $lines_of_context) { 109 + for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) { 110 + $mask[$jj] = true; 111 + } 112 + } else { 113 + $gaps[] = array($gap_start, $gap_length); 114 + } 115 + $in_gap = false; 116 + } 117 + } else { 118 + if (!$in_gap) { 119 + $gap_start = $ii; 120 + $in_gap = true; 121 + } 122 + } 123 + } 124 + 125 + $gaps = array_reverse($gaps); 126 + 127 + $reference = $this->getRenderingReference(); 128 + 129 + $left_id = $this->getOldChangesetID(); 130 + $right_id = $this->getNewChangesetID(); 131 + 132 + // "N" stands for 'new' and means the comment should attach to the new file 133 + // when stored, i.e. DifferentialInlineComment->setIsNewFile(). 134 + // "O" stands for 'old' and means the comment should attach to the old file. 135 + 136 + $left_char = $this->getOldAttachesToNewFile() 137 + ? 'N' 138 + : 'O'; 139 + $right_char = $this->getNewAttachesToNewFile() 140 + ? 'N' 141 + : 'O'; 142 + 143 + $changeset = $this->getChangeset(); 144 + $copy_lines = idx($changeset->getMetadata(), 'copy:lines', array()); 145 + $highlight_old = $this->getHighlightOld(); 146 + $highlight_new = $this->getHighlightNew(); 147 + $old_render = $this->getOldRender(); 148 + $new_render = $this->getNewRender(); 149 + $original_left = $this->getOriginalOld(); 150 + $original_right = $this->getOriginalNew(); 151 + 152 + // We need to go backwards to properly indent whitespace in this code: 153 + // 154 + // 0: class C { 155 + // 1: 156 + // 1: function f() { 157 + // 2: 158 + // 2: return; 159 + // 3: 160 + // 3: } 161 + // 4: 162 + // 4: } 163 + // 164 + $depths = array(); 165 + $last_depth = 0; 166 + $range_end = $range_start + $range_len; 167 + if (!isset($new_lines[$range_end])) { 168 + $range_end--; 169 + } 170 + for ($ii = $range_end; $ii >= $range_start; $ii--) { 171 + // We need to expand tabs to process mixed indenting and to round 172 + // correctly later. 173 + $line = str_replace("\t", " ", $new_lines[$ii]['text']); 174 + $trimmed = ltrim($line); 175 + if ($trimmed != '') { 176 + // We round down to flatten "/**" and " *". 177 + $last_depth = floor((strlen($line) - strlen($trimmed)) / 2); 178 + } 179 + $depths[$ii] = $last_depth; 180 + } 181 + 182 + for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { 183 + if (empty($mask[$ii])) { 184 + // If we aren't going to show this line, we've just entered a gap. 185 + // Pop information about the next gap off the $gaps stack and render 186 + // an appropriate "Show more context" element. This branch eventually 187 + // increments $ii by the entire size of the gap and then continues 188 + // the loop. 189 + $gap = array_pop($gaps); 190 + $top = $gap[0]; 191 + $len = $gap[1]; 192 + 193 + $end = $top + $len - 20; 194 + 195 + $contents = array(); 196 + 197 + if ($len > 40) { 198 + $is_first_block = false; 199 + if ($ii == 0) { 200 + $is_first_block = true; 201 + } 202 + 203 + $contents[] = javelin_render_tag( 204 + 'a', 205 + array( 206 + 'href' => '#', 207 + 'mustcapture' => true, 208 + 'sigil' => 'show-more', 209 + 'meta' => array( 210 + 'ref' => $reference, 211 + 'range' => "{$top}-{$len}/{$top}-20", 212 + ), 213 + ), 214 + $is_first_block 215 + ? "Show First 20 Lines" 216 + : "\xE2\x96\xB2 Show 20 Lines"); 217 + } 218 + 219 + $contents[] = javelin_render_tag( 220 + 'a', 221 + array( 222 + 'href' => '#', 223 + 'mustcapture' => true, 224 + 'sigil' => 'show-more', 225 + 'meta' => array( 226 + 'type' => 'all', 227 + 'ref' => $reference, 228 + 'range' => "{$top}-{$len}/{$top}-{$len}", 229 + ), 230 + ), 231 + 'Show All '.$len.' Lines'); 232 + 233 + $is_last_block = false; 234 + if ($ii + $len >= $rows) { 235 + $is_last_block = true; 236 + } 237 + 238 + if ($len > 40) { 239 + $contents[] = javelin_render_tag( 240 + 'a', 241 + array( 242 + 'href' => '#', 243 + 'mustcapture' => true, 244 + 'sigil' => 'show-more', 245 + 'meta' => array( 246 + 'ref' => $reference, 247 + 'range' => "{$top}-{$len}/{$end}-20", 248 + ), 249 + ), 250 + $is_last_block 251 + ? "Show Last 20 Lines" 252 + : "\xE2\x96\xBC Show 20 Lines"); 253 + } 254 + 255 + $context = null; 256 + $context_line = null; 257 + if (!$is_last_block && $depths[$ii + $len]) { 258 + for ($l = $ii + $len - 1; $l >= $ii; $l--) { 259 + $line = $new_lines[$l]['text']; 260 + if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') { 261 + $context = $new_render[$l]; 262 + $context_line = $new_lines[$l]['line']; 263 + break; 264 + } 265 + } 266 + } 267 + 268 + $container = javelin_render_tag( 269 + 'tr', 270 + array( 271 + 'sigil' => 'context-target', 272 + ), 273 + '<td colspan="2" class="show-more">'. 274 + implode(' &bull; ', $contents). 275 + '</td>'. 276 + '<th class="show-context-line">'.$context_line.'</td>'. 277 + '<td colspan="3" class="show-context">'.$context.'</td>'); 278 + 279 + $html[] = $container; 280 + 281 + $ii += ($len - 1); 282 + continue; 283 + } 284 + 285 + $o_num = null; 286 + $o_classes = 'left'; 287 + $o_text = null; 288 + if (isset($old_lines[$ii])) { 289 + $o_num = $old_lines[$ii]['line']; 290 + $o_text = isset($old_render[$ii]) ? $old_render[$ii] : null; 291 + if ($old_lines[$ii]['type']) { 292 + if ($old_lines[$ii]['type'] == '\\') { 293 + $o_text = $old_lines[$ii]['text']; 294 + $o_classes .= ' comment'; 295 + } else if ($original_left && !isset($highlight_old[$o_num])) { 296 + $o_classes .= ' old-rebase'; 297 + } else if (empty($new_lines[$ii])) { 298 + $o_classes .= ' old old-full'; 299 + } else { 300 + $o_classes .= ' old'; 301 + } 302 + } 303 + } 304 + 305 + $n_copy = '<td class="copy" />'; 306 + $n_cov = null; 307 + $n_colspan = 2; 308 + $n_classes = ''; 309 + $n_num = null; 310 + $n_text = null; 311 + 312 + if (isset($new_lines[$ii])) { 313 + $n_num = $new_lines[$ii]['line']; 314 + $n_text = isset($new_render[$ii]) ? $new_render[$ii] : null; 315 + $coverage = $this->getCodeCoverage(); 316 + 317 + if ($coverage !== null) { 318 + if (empty($coverage[$n_num - 1])) { 319 + $cov_class = 'N'; 320 + } else { 321 + $cov_class = $coverage[$n_num - 1]; 322 + } 323 + $cov_class = 'cov-'.$cov_class; 324 + $n_cov = '<td class="cov '.$cov_class.'"></td>'; 325 + $n_colspan--; 326 + } 327 + 328 + if ($new_lines[$ii]['type']) { 329 + if ($new_lines[$ii]['type'] == '\\') { 330 + $n_text = $new_lines[$ii]['text']; 331 + $n_class = 'comment'; 332 + } else if ($original_right && !isset($highlight_new[$n_num])) { 333 + $n_class = 'new-rebase'; 334 + } else if (empty($old_lines[$ii])) { 335 + $n_class = 'new new-full'; 336 + } else { 337 + $n_class = 'new'; 338 + } 339 + $n_classes = $n_class; 340 + 341 + if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) { 342 + $n_copy = '<td class="copy '.$n_class.'"></td>'; 343 + } else { 344 + list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num]; 345 + $title = ($orig_type == '-' ? 'Moved' : 'Copied').' from '; 346 + if ($orig_file == '') { 347 + $title .= "line {$orig_line}"; 348 + } else { 349 + $title .= 350 + basename($orig_file). 351 + ":{$orig_line} in dir ". 352 + dirname('/'.$orig_file); 353 + } 354 + $class = ($orig_type == '-' ? 'new-move' : 'new-copy'); 355 + $n_copy = javelin_render_tag( 356 + 'td', 357 + array( 358 + 'meta' => array( 359 + 'msg' => $title, 360 + ), 361 + 'class' => 'copy '.$class, 362 + ), 363 + ''); 364 + } 365 + } 366 + } 367 + $n_classes .= ' right'.$n_colspan; 368 + 369 + if (($o_num && !empty($missing_old[$o_num])) || 370 + ($n_num && !empty($missing_new[$n_num]))) { 371 + $html[] = $context_not_available; 372 + } 373 + 374 + if ($o_num && $left_id) { 375 + $o_id = ' id="C'.$left_id.$left_char.'L'.$o_num.'"'; 376 + } else { 377 + $o_id = null; 378 + } 379 + 380 + if ($n_num && $right_id) { 381 + $n_id = ' id="C'.$right_id.$right_char.'L'.$n_num.'"'; 382 + } else { 383 + $n_id = null; 384 + } 385 + 386 + // NOTE: The Javascript is sensitive to whitespace changes in this 387 + // block! 388 + 389 + $html[] = 390 + '<tr>'. 391 + '<th'.$o_id.'>'.$o_num.'</th>'. 392 + '<td class="'.$o_classes.'">'.$o_text.'</td>'. 393 + '<th'.$n_id.'>'.$n_num.'</th>'. 394 + $n_copy. 395 + // NOTE: This is a unicode zero-width space, which we use as a hint 396 + // when intercepting 'copy' events to make sure sensible text ends 397 + // up on the clipboard. See the 'phabricator-oncopy' behavior. 398 + '<td class="'.$n_classes.'" colspan="'.$n_colspan.'">'. 399 + "\xE2\x80\x8B".$n_text. 400 + '</td>'. 401 + $n_cov. 402 + '</tr>'; 403 + 404 + if ($context_not_available && ($ii == $rows - 1)) { 405 + $html[] = $context_not_available; 406 + } 407 + 408 + $old_comments = $this->getOldComments(); 409 + $new_comments = $this->getNewComments(); 410 + 411 + if ($o_num && isset($old_comments[$o_num])) { 412 + foreach ($old_comments[$o_num] as $comment) { 413 + $xhp = $this->renderInlineComment($comment, $on_right = false); 414 + $new = ''; 415 + if ($n_num && isset($new_comments[$n_num])) { 416 + foreach ($new_comments[$n_num] as $key => $new_comment) { 417 + if ($comment->isCompatible($new_comment)) { 418 + $new = $this->renderInlineComment($new_comment, 419 + $on_right = true); 420 + unset($new_comments[$n_num][$key]); 421 + } 422 + } 423 + } 424 + $html[] = 425 + '<tr class="inline">'. 426 + '<th />'. 427 + '<td class="left">'.$xhp.'</td>'. 428 + '<th />'. 429 + '<td colspan="3" class="right3">'.$new.'</td>'. 430 + '</tr>'; 431 + } 432 + } 433 + if ($n_num && isset($new_comments[$n_num])) { 434 + foreach ($new_comments[$n_num] as $comment) { 435 + $xhp = $this->renderInlineComment($comment, $on_right = true); 436 + $html[] = 437 + '<tr class="inline">'. 438 + '<th />'. 439 + '<td class="left" />'. 440 + '<th />'. 441 + '<td colspan="3" class="right3">'.$xhp.'</td>'. 442 + '</tr>'; 443 + } 444 + } 445 + } 446 + 447 + return implode('', $html); 448 + } 449 + 450 + public function renderFileChange($old_file = null, 451 + $new_file = null, 452 + $id = 0, 453 + $vs = 0) { 454 + $old = null; 455 + if ($old_file) { 456 + $old = phutil_render_tag( 457 + 'div', 458 + array( 459 + 'class' => 'differential-image-stage' 460 + ), 461 + phutil_render_tag( 462 + 'img', 463 + array( 464 + 'src' => $old_file->getBestURI(), 465 + ) 466 + ) 467 + ); 468 + } 469 + 470 + $new = null; 471 + if ($new_file) { 472 + $new = phutil_render_tag( 473 + 'div', 474 + array( 475 + 'class' => 'differential-image-stage' 476 + ), 477 + phutil_render_tag( 478 + 'img', 479 + array( 480 + 'src' => $new_file->getBestURI(), 481 + ) 482 + ) 483 + ); 484 + } 485 + 486 + $html_old = array(); 487 + $html_new = array(); 488 + foreach ($this->getOldComments() as $comment) { 489 + $xhp = $this->renderInlineComment($comment, $on_right = false); 490 + $html_old[] = 491 + '<tr class="inline">'. 492 + '<th />'. 493 + '<td class="left">'.$xhp.'</td>'. 494 + '<th />'. 495 + '<td class="right3" colspan="3" />'. 496 + '</tr>'; 497 + } 498 + foreach ($this->getNewComments() as $comment) { 499 + $xhp = $this->renderInlineComment($comment, $on_right = true); 500 + $html_new[] = 501 + '<tr class="inline">'. 502 + '<th />'. 503 + '<td class="left" />'. 504 + '<th />'. 505 + '<td class="right3" colspan="3">'.$xhp.'</td>'. 506 + '</tr>'; 507 + } 508 + 509 + if (!$old) { 510 + $th_old = '<th></th>'; 511 + } else { 512 + $th_old = '<th id="C'.$vs.'OL1">1</th>'; 513 + } 514 + 515 + if (!$new) { 516 + $th_new = '<th></th>'; 517 + } else { 518 + $th_new = '<th id="C'.$id.'NL1">1</th>'; 519 + } 520 + 521 + $output = $this->renderChangesetTable( 522 + '<tr class="differential-image-diff">'. 523 + $th_old. 524 + '<td class="left differential-old-image">'.$old.'</td>'. 525 + $th_new. 526 + '<td class="right3 differential-new-image" colspan="3">'. 527 + $new. 528 + '</td>'. 529 + '</tr>'. 530 + implode('', $html_old). 531 + implode('', $html_new)); 532 + 533 + return $output; 534 + } 535 + 536 + }