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