diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -154,6 +154,8 @@ 'ArcanistDiffParserTestCase' => 'parser/__tests__/ArcanistDiffParserTestCase.php', 'ArcanistDiffUtils' => 'difference/ArcanistDiffUtils.php', 'ArcanistDiffUtilsTestCase' => 'difference/__tests__/ArcanistDiffUtilsTestCase.php', + 'ArcanistDiffVectorNode' => 'difference/ArcanistDiffVectorNode.php', + 'ArcanistDiffVectorTree' => 'difference/ArcanistDiffVectorTree.php', 'ArcanistDiffWorkflow' => 'workflow/ArcanistDiffWorkflow.php', 'ArcanistDifferentialCommitMessage' => 'differential/ArcanistDifferentialCommitMessage.php', 'ArcanistDifferentialCommitMessageParserException' => 'differential/ArcanistDifferentialCommitMessageParserException.php', @@ -691,7 +693,6 @@ 'PhutilExecutionEnvironment' => 'utils/PhutilExecutionEnvironment.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', - 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilFrenchLocale' => 'internationalization/locales/PhutilFrenchLocale.php', 'PhutilGermanLocale' => 'internationalization/locales/PhutilGermanLocale.php', 'PhutilGitBinaryAnalyzer' => 'filesystem/binary/PhutilGitBinaryAnalyzer.php', @@ -1134,6 +1135,8 @@ 'ArcanistDiffParserTestCase' => 'PhutilTestCase', 'ArcanistDiffUtils' => 'Phobject', 'ArcanistDiffUtilsTestCase' => 'PhutilTestCase', + 'ArcanistDiffVectorNode' => 'Phobject', + 'ArcanistDiffVectorTree' => 'Phobject', 'ArcanistDiffWorkflow' => 'ArcanistWorkflow', 'ArcanistDifferentialCommitMessage' => 'Phobject', 'ArcanistDifferentialCommitMessageParserException' => 'Exception', @@ -1702,7 +1705,6 @@ 'PhutilExecutionEnvironment' => 'Phobject', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', - 'PhutilFileTree' => 'Phobject', 'PhutilFrenchLocale' => 'PhutilLocale', 'PhutilGermanLocale' => 'PhutilLocale', 'PhutilGitBinaryAnalyzer' => 'PhutilBinaryAnalyzer', diff --git a/src/difference/ArcanistDiffVectorNode.php b/src/difference/ArcanistDiffVectorNode.php new file mode 100644 --- /dev/null +++ b/src/difference/ArcanistDiffVectorNode.php @@ -0,0 +1,113 @@ +vector = $vector; + return $this; + } + + public function getVector() { + return $this->vector; + } + + public function getChildren() { + return $this->children; + } + + public function setParentNode(ArcanistDiffVectorNode $parent) { + $this->parentNode = $parent; + return $this; + } + + public function getParentNode() { + return $this->parentNode; + } + + public function addChild(array $vector, $length, $idx) { + $is_node = ($idx === ($length - 1)); + $element = $vector[$idx]; + + if (!isset($this->children[$element])) { + $this->children[$element] = id(new self()) + ->setParentNode($this) + ->setVector(array_slice($vector, 0, $idx + 1)); + } + + $child = $this->children[$element]; + + if ($is_node) { + $child->setValueNode($child); + return; + } + + $child->addChild($vector, $length, $idx + 1); + } + + public function getDisplayVector() { + return $this->displayVector; + } + + public function appendDisplayElement($element) { + if ($this->displayVector === null) { + $this->displayVector = array(); + } + + $this->displayVector[] = $element; + + return $this; + } + + public function setDisplayNode(ArcanistDiffVectorNode $display_node) { + $this->displayNode = $display_node; + return $this; + } + + public function getDisplayNode() { + return $this->displayNode; + } + + public function setDisplayDepth($display_depth) { + $this->displayDepth = $display_depth; + return $this; + } + + public function getDisplayDepth() { + return $this->displayDepth; + } + + public function setValueNode($value_node) { + $this->valueNode = $value_node; + return $this; + } + + public function getValueNode() { + return $this->valueNode; + } + + public function setAncestralAttribute($key, $value) { + $this->attributes[$key] = $value; + + $parent = $this->getParentNode(); + if ($parent) { + $parent->setAncestralAttribute($key, $value); + } + + return $this; + } + + public function getAttribute($key, $default = null) { + return idx($this->attributes, $key, $default); + } + +} diff --git a/src/difference/ArcanistDiffVectorTree.php b/src/difference/ArcanistDiffVectorTree.php new file mode 100644 --- /dev/null +++ b/src/difference/ArcanistDiffVectorTree.php @@ -0,0 +1,95 @@ +vectors[] = $vector; + return $this; + } + + public function newDisplayList() { + $root = new ArcanistDiffVectorNode(); + + foreach ($this->vectors as $vector) { + $root->addChild($vector, count($vector), 0); + } + + foreach ($root->getChildren() as $child) { + $this->compressTree($child); + } + + $root->setDisplayDepth(-1); + foreach ($root->getChildren() as $child) { + $this->updateDisplayDepth($child); + } + + return $this->getDisplayList($root); + } + + private function compressTree(ArcanistDiffVectorNode $node) { + $display_node = $node; + + $children = $node->getChildren(); + if ($children) { + $parent = $node->getParentNode(); + if ($parent) { + $siblings = $parent->getChildren(); + if (count($siblings) === 1) { + if (!$parent->getValueNode()) { + $parent_display = $parent->getDisplayNode(); + if ($parent_display) { + $display_node = $parent_display; + if ($node->getValueNode()) { + $parent->setValueNode($node->getValueNode()); + } + } + } + } + } + } + + $node->setDisplayNode($display_node); + + $display_element = last($node->getVector()); + $display_node->appendDisplayElement($display_element); + + foreach ($children as $child) { + $this->compressTree($child); + } + } + + private function updateDisplayDepth(ArcanistDiffVectorNode $node) { + $parent_depth = $node->getParentNode()->getDisplayDepth(); + + if ($node->getDisplayVector() === null) { + $display_depth = $parent_depth; + } else { + $display_depth = $parent_depth + 1; + } + + $node->setDisplayDepth($display_depth); + + foreach ($node->getChildren() as $child) { + $this->updateDisplayDepth($child); + } + } + + private function getDisplayList(ArcanistDiffVectorNode $node) { + $result = array(); + + foreach ($node->getChildren() as $child) { + if ($child->getDisplayVector() !== null) { + $result[] = $child; + } + foreach ($this->getDisplayList($child) as $item) { + $result[] = $item; + } + } + + return $result; + } + +} diff --git a/src/filesystem/PhutilFileTree.php b/src/filesystem/PhutilFileTree.php deleted file mode 100644 --- a/src/filesystem/PhutilFileTree.php +++ /dev/null @@ -1,112 +0,0 @@ -splitPath($path); - $parts = array_reverse($parts); - $this->insertPath($parts, $data); - return $this; - } - - public function destroy() { - $this->parentNode = null; - foreach ($this->children as $child) { - $child->destroy(); - } - $this->children = array(); - return $this; - } - - /** - * Get the next node, iterating in depth-first order. - */ - public function getNextNode() { - if ($this->children) { - return head($this->children); - } - $cursor = $this; - while ($cursor) { - if ($cursor->getNextSibling()) { - return $cursor->getNextSibling(); - } - $cursor = $cursor->parentNode; - } - return null; - } - - public function getName() { - return $this->name; - } - - public function getFullPath() { - return $this->fullPath; - } - - public function getDepth() { - return $this->depth; - } - - public function getData() { - return $this->data; - } - - protected function insertPath(array $parts, $data) { - $part = array_pop($parts); - if ($part === null) { - if ($this->data) { - $full_path = $this->getFullPath(); - throw new Exception( - pht("Duplicate insertion for path '%s'.", $full_path)); - } - $this->data = $data; - return; - } - - if (empty($this->children[$part])) { - $node = new PhutilFileTree(); - $node->parentNode = $this; - $node->depth = $this->depth + 1; - $node->name = $part; - $node->fullPath = $this->parentNode ? ($this->fullPath.'/'.$part) : $part; - $this->children[$part] = $node; - } - - $this->children[$part]->insertPath($parts, $data); - } - - protected function splitPath($path) { - $path = trim($path, '/'); - $parts = preg_split('@/+@', $path); - return $parts; - } - - protected function getNextSibling() { - if (!$this->parentNode) { - return null; - } - - $found = false; - foreach ($this->parentNode->children as $node) { - if ($found) { - return $node; - } - if ($this->name === $node->name) { - $found = true; - } - } - - return null; - } - -}