@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 upstream/main 164 lines 4.2 kB view raw
1<?php 2 3final class PhabricatorDiffScopeEngine 4 extends Phobject { 5 6 private $lineTextMap; 7 private $lineDepthMap; 8 9 public function setLineTextMap(array $map) { 10 if (array_key_exists(0, $map)) { 11 throw new Exception( 12 pht('ScopeEngine text map must be a 1-based map of lines.')); 13 } 14 15 $expect = 1; 16 foreach ($map as $key => $value) { 17 if ($key === $expect) { 18 $expect++; 19 continue; 20 } 21 22 throw new Exception( 23 pht( 24 'ScopeEngine text map must be a contiguous map of '. 25 'lines, but is not: found key "%s" where key "%s" was expected.', 26 $key, 27 $expect)); 28 } 29 30 $this->lineTextMap = $map; 31 32 return $this; 33 } 34 35 public function getLineTextMap() { 36 if ($this->lineTextMap === null) { 37 throw new PhutilInvalidStateException('setLineTextMap'); 38 } 39 return $this->lineTextMap; 40 } 41 42 public function getScopeStart($line) { 43 $text_map = $this->getLineTextMap(); 44 $depth_map = $this->getLineDepthMap(); 45 $length = count($text_map); 46 47 // Figure out the effective depth of the line we're getting scope for. 48 // If the line is just whitespace, it may have no depth on its own. In 49 // this case, we look for the next line. 50 $line_depth = null; 51 for ($ii = $line; $ii <= $length; $ii++) { 52 if ($depth_map[$ii] !== null) { 53 $line_depth = $depth_map[$ii]; 54 break; 55 } 56 } 57 58 // If we can't find a line depth for the target line, just bail. 59 if ($line_depth === null) { 60 return null; 61 } 62 63 // Limit the maximum number of lines we'll examine. If a user has a 64 // million-line diff of nonsense, scanning the whole thing is a waste 65 // of time. 66 $search_range = 1000; 67 $search_until = max(0, $ii - $search_range); 68 69 for ($ii = $line - 1; $ii > $search_until; $ii--) { 70 $line_text = $text_map[$ii]; 71 72 // This line is in missing context: the diff was diffed with partial 73 // context, and we ran out of context before finding a good scope line. 74 // Bail out, we don't want to jump across missing context blocks. 75 if ($line_text === null) { 76 return null; 77 } 78 79 $depth = $depth_map[$ii]; 80 81 // This line is all whitespace. This isn't a possible match. 82 if ($depth === null) { 83 continue; 84 } 85 86 // Don't match context lines which are too deeply indented, since they 87 // are very unlikely to be symbol definitions. 88 if ($depth > 2) { 89 continue; 90 } 91 92 // The depth is the same as (or greater than) the depth we started with, 93 // so this isn't a possible match. 94 if ($depth >= $line_depth) { 95 continue; 96 } 97 98 // Reject lines which begin with "}" or "{". These lines are probably 99 // never good matches. 100 if (preg_match('/^\s*[{}]/i', $line_text)) { 101 continue; 102 } 103 104 return $ii; 105 } 106 107 return null; 108 } 109 110 private function getLineDepthMap() { 111 if (!$this->lineDepthMap) { 112 $this->lineDepthMap = $this->newLineDepthMap(); 113 } 114 115 return $this->lineDepthMap; 116 } 117 118 private function getTabWidth() { 119 // TODO: This should be configurable once we handle tab widths better. 120 return 2; 121 } 122 123 private function newLineDepthMap() { 124 $text_map = $this->getLineTextMap(); 125 $tab_width = $this->getTabWidth(); 126 127 $depth_map = array(); 128 foreach ($text_map as $line_number => $line_text) { 129 if ($line_text === null) { 130 $depth_map[$line_number] = null; 131 continue; 132 } 133 134 $len = strlen($line_text); 135 136 // If the line has no actual text, don't assign it a depth. 137 if (!$len || !strlen(trim($line_text))) { 138 $depth_map[$line_number] = null; 139 continue; 140 } 141 142 $count = 0; 143 for ($ii = 0; $ii < $len; $ii++) { 144 $c = $line_text[$ii]; 145 if ($c == ' ') { 146 $count++; 147 } else if ($c == "\t") { 148 $count += $tab_width; 149 } else { 150 break; 151 } 152 } 153 154 // Round down to cheat our way through the " *" parts of docblock 155 // comments. 156 $depth = (int)floor($count / $tab_width); 157 158 $depth_map[$line_number] = $depth; 159 } 160 161 return $depth_map; 162 } 163 164}