Path: blob/master/src/infrastructure/diff/PhabricatorDiffScopeEngine.php
12241 views
<?php12final class PhabricatorDiffScopeEngine3extends Phobject {45private $lineTextMap;6private $lineDepthMap;78public function setLineTextMap(array $map) {9if (array_key_exists(0, $map)) {10throw new Exception(11pht('ScopeEngine text map must be a 1-based map of lines.'));12}1314$expect = 1;15foreach ($map as $key => $value) {16if ($key === $expect) {17$expect++;18continue;19}2021throw new Exception(22pht(23'ScopeEngine text map must be a contiguous map of '.24'lines, but is not: found key "%s" where key "%s" was expected.',25$key,26$expect));27}2829$this->lineTextMap = $map;3031return $this;32}3334public function getLineTextMap() {35if ($this->lineTextMap === null) {36throw new PhutilInvalidStateException('setLineTextMap');37}38return $this->lineTextMap;39}4041public function getScopeStart($line) {42$text_map = $this->getLineTextMap();43$depth_map = $this->getLineDepthMap();44$length = count($text_map);4546// Figure out the effective depth of the line we're getting scope for.47// If the line is just whitespace, it may have no depth on its own. In48// this case, we look for the next line.49$line_depth = null;50for ($ii = $line; $ii <= $length; $ii++) {51if ($depth_map[$ii] !== null) {52$line_depth = $depth_map[$ii];53break;54}55}5657// If we can't find a line depth for the target line, just bail.58if ($line_depth === null) {59return null;60}6162// Limit the maximum number of lines we'll examine. If a user has a63// million-line diff of nonsense, scanning the whole thing is a waste64// of time.65$search_range = 1000;66$search_until = max(0, $ii - $search_range);6768for ($ii = $line - 1; $ii > $search_until; $ii--) {69$line_text = $text_map[$ii];7071// This line is in missing context: the diff was diffed with partial72// context, and we ran out of context before finding a good scope line.73// Bail out, we don't want to jump across missing context blocks.74if ($line_text === null) {75return null;76}7778$depth = $depth_map[$ii];7980// This line is all whitespace. This isn't a possible match.81if ($depth === null) {82continue;83}8485// Don't match context lines which are too deeply indented, since they86// are very unlikely to be symbol definitions.87if ($depth > 2) {88continue;89}9091// The depth is the same as (or greater than) the depth we started with,92// so this isn't a possible match.93if ($depth >= $line_depth) {94continue;95}9697// Reject lines which begin with "}" or "{". These lines are probably98// never good matches.99if (preg_match('/^\s*[{}]/i', $line_text)) {100continue;101}102103return $ii;104}105106return null;107}108109private function getLineDepthMap() {110if (!$this->lineDepthMap) {111$this->lineDepthMap = $this->newLineDepthMap();112}113114return $this->lineDepthMap;115}116117private function getTabWidth() {118// TODO: This should be configurable once we handle tab widths better.119return 2;120}121122private function newLineDepthMap() {123$text_map = $this->getLineTextMap();124$tab_width = $this->getTabWidth();125126$depth_map = array();127foreach ($text_map as $line_number => $line_text) {128if ($line_text === null) {129$depth_map[$line_number] = null;130continue;131}132133$len = strlen($line_text);134135// If the line has no actual text, don't assign it a depth.136if (!$len || !strlen(trim($line_text))) {137$depth_map[$line_number] = null;138continue;139}140141$count = 0;142for ($ii = 0; $ii < $len; $ii++) {143$c = $line_text[$ii];144if ($c == ' ') {145$count++;146} else if ($c == "\t") {147$count += $tab_width;148} else {149break;150}151}152153// Round down to cheat our way through the " *" parts of docblock154// comments.155$depth = (int)floor($count / $tab_width);156157$depth_map[$line_number] = $depth;158}159160return $depth_map;161}162163}164165166