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 @@ -507,6 +507,8 @@ 'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php', 'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php', 'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php', + 'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php', + 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', 'DiffusionFileContent' => 'applications/diffusion/data/DiffusionFileContent.php', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', @@ -565,11 +567,13 @@ 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php', 'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php', 'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php', + 'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php', 'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php', 'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php', 'DiffusionPushLogController' => 'applications/diffusion/controller/DiffusionPushLogController.php', 'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php', 'DiffusionPushLogListView' => 'applications/diffusion/view/DiffusionPushLogListView.php', + 'DiffusionPythonExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php', 'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php', 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', @@ -3803,11 +3807,13 @@ 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathTreeController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController', + 'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionPushEventViewController' => 'DiffusionPushLogController', 'DiffusionPushLogController' => 'DiffusionController', 'DiffusionPushLogListController' => 'DiffusionPushLogController', 'DiffusionPushLogListView' => 'AphrontView', + 'DiffusionPythonExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionQuery' => 'PhabricatorQuery', 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -12,7 +12,7 @@ ->setViewer($user) ->setName($this->name); - if ($request->getStr('context') !== null) { + if ($request->getStr('context')) { $query->setContext($request->getStr('context')); } @@ -47,63 +47,69 @@ $symbols = $query->execute(); - // For PHP builtins, jump to php.net documentation. - if ($request->getBool('jump') && count($symbols) == 0) { - if ($request->getStr('lang', 'php') == 'php') { - if ($request->getStr('type', 'function') == 'function') { - $functions = get_defined_functions(); - if (in_array($this->name, $functions['internal'])) { - return id(new AphrontRedirectResponse()) - ->setIsExternal(true) - ->setURI('http://www.php.net/function.'.$this->name); - } - } - if ($request->getStr('type', 'class') == 'class') { - if (class_exists($this->name, false) || - interface_exists($this->name, false)) { - if (id(new ReflectionClass($this->name))->isInternal()) { - return id(new AphrontRedirectResponse()) - ->setIsExternal(true) - ->setURI('http://www.php.net/class.'.$this->name); - } - } - } - } + + + $external_query = id(new DiffusionExternalSymbolQuery()) + ->withNames(array($this->name)); + + if ($request->getStr('context')) { + $external_query->withContexts(array($request->getStr('context'))); + } + + if ($request->getStr('type')) { + $external_query->withTypes(array($request->getStr('type'))); + } + + if ($request->getStr('lang')) { + $external_query->withLanguages(array($request->getStr('lang'))); + } + + $external_sources = id(new PhutilSymbolLoader()) + ->setAncestorClass('DiffusionExternalSymbolsSource') + ->loadObjects(); + $results = array($symbols); + foreach ($external_sources as $source) { + $results[] = $source->executeQuery($external_query); + } + $symbols = array_mergev($results); + + if ($request->getBool('jump') && count($symbols) == 1) { + // If this is a clickthrough from Differential, just jump them + // straight to the target if we got a single hit. + $symbol = head($symbols); + return id(new AphrontRedirectResponse()) + ->setIsExternal($symbol->isExternal()) + ->setURI($symbol->getURI()); } $rows = array(); foreach ($symbols as $symbol) { - $file = $symbol->getPath(); - $line = $symbol->getLineNumber(); + $href = $symbol->getURI(); - $repo = $symbol->getRepository(); - if ($repo) { - $href = $symbol->getURI(); - - if ($request->getBool('jump') && count($symbols) == 1) { - // If this is a clickthrough from Differential, just jump them - // straight to the target if we got a single hit. - return id(new AphrontRedirectResponse())->setURI($href); - } + if ($symbol->isExternal()) { + $source = $symbol->getSource(); + $location = $symbol->getLocation(); + } else { + $repo = $symbol->getRepository(); + $file = $symbol->getPath(); + $line = $symbol->getLineNumber(); - $location = phutil_tag( - 'a', - array( - 'href' => $href, - ), - $file.':'.$line); - } else if ($file) { + $source = $repo->getMonogram(); $location = $file.':'.$line; - } else { - $location = '?'; } + $location = phutil_tag( + 'a', + array( + 'href' => $href, + ), + $location); $rows[] = array( $symbol->getSymbolType(), $symbol->getSymbolContext(), $symbol->getSymbolName(), $symbol->getSymbolLanguage(), - $repo->getMonogram(), + $source, $location, ); } @@ -115,8 +121,8 @@ pht('Context'), pht('Name'), pht('Language'), - pht('Repository'), - pht('File'), + pht('Source'), + pht('Location'), )); $table->setColumnClasses( array( diff --git a/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php b/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionExternalSymbolQuery.php @@ -0,0 +1,46 @@ +languages = $languages; + return $this; + } + public function withTypes(array $types) { + $this->types = $types; + return $this; + } + public function withNames(array $names) { + $this->names = $names; + return $this; + } + public function withContexts(array $contexts) { + $this->contexts = $contexts; + return $this; + } + + + public function getLanguages() { + return $this->languages; + } + public function getTypes() { + return $this->types; + } + public function getNames() { + return $this->names; + } + public function getContexts() { + return $this->contexts; + } + + public function matchesAnyLanguage(array $languages) { + return (!$this->languages) || array_intersect($languages, $this->languages); + } + public function matchesAnyType(array $types) { + return (!$this->types) || array_intersect($types, $this->types); + } +} diff --git a/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionExternalSymbolsSource.php @@ -0,0 +1,15 @@ +setIsExternal(true) + ->makeEphemeral(); + } +} diff --git a/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php @@ -0,0 +1,49 @@ +matchesAnyLanguage(array('php'))) { + return $symbols; + } + + $names = $query->getNames(); + + if ($query->matchesAnyType(array('function'))) { + $functions = get_defined_functions(); + $functions = $functions['internal']; + + foreach ($names as $name) { + if (in_array($name, $functions)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('PHP')) + ->setLocation(pht('Manual at php.net')) + ->setSymbolLanguage('php') + ->setExternalURI('http://www.php.net/function.'.$name); + } + } + } + if ($query->matchesAnyType(array('class'))) { + foreach ($names as $name) { + if (class_exists($name, false) || interface_exists($name, false)) { + if (id(new ReflectionClass($name))->isInternal()) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('class') + ->setSource(pht('PHP')) + ->setLocation(pht('Manual at php.net')) + ->setSymbolLanguage('php') + ->setExternalURI('http://www.php.net/class.'.$name); + } + } + } + } + + return $symbols; + } +} diff --git a/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php b/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php @@ -0,0 +1,134 @@ +matchesAnyLanguage(array('py', 'python'))) { + return $symbols; + } + + if (!$query->matchesAnyType(array('builtin', 'function'))) { + return $symbols; + } + + $names = $query->getNames(); + + foreach ($names as $name) { + if (idx(self::$python2Builtins, $name)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('Standard Library')) + ->setLocation(pht('The Python 2 Standard Library')) + ->setSymbolLanguage('py') + ->setExternalURI( + 'https://docs.python.org/2/library/functions.html#'.$name); + } + if (idx(self::$python3Builtins, $name)) { + $symbols[] = $this->buildExternalSymbol() + ->setSymbolName($name) + ->setSymbolType('function') + ->setSource(pht('Standard Library')) + ->setLocation(pht('The Python 3 Standard Library')) + ->setSymbolLanguage('py') + ->setExternalURI( + 'https://docs.python.org/3/library/functions.html#'.$name); + } + } + return $symbols; + } + + private static $python2Builtins = array( + '__import__' => true, + 'abs' => true, + 'all' => true, + 'any' => true, + 'basestring' => true, + 'bin' => true, + 'bool' => true, + 'bytearray' => true, + 'callable' => true, + 'chr' => true, + 'classmethod' => true, + 'cmp' => true, + 'compile' => true, + 'complex' => true, + 'delattr' => true, + 'dict' => true, + 'dir' => true, + 'divmod' => true, + 'enumerate' => true, + 'eval' => true, + 'execfile' => true, + 'file' => true, + 'filter' => true, + 'float' => true, + 'format' => true, + 'frozenset' => true, + 'getattr' => true, + 'globals' => true, + 'hasattr' => true, + 'hash' => true, + 'help' => true, + 'hex' => true, + 'id' => true, + 'input' => true, + 'int' => true, + 'isinstance' => true, + 'issubclass' => true, + 'iter' => true, + 'len' => true, + 'list' => true, + 'locals' => true, + 'long' => true, + 'map' => true, + 'max' => true, + 'memoryview' => true, + 'min' => true, + 'next' => true, + 'object' => true, + 'oct' => true, + 'open' => true, + 'ord' => true, + 'pow' => true, + 'print' => true, + 'property' => true, + 'range' => true, + 'raw_input' => true, + 'reduce' => true, + 'reload' => true, + 'repr' => true, + 'reversed' => true, + 'round' => true, + 'set' => true, + 'setattr' => true, + 'slice' => true, + 'sorted' => true, + 'staticmethod' => true, + 'str' => true, + 'sum' => true, + 'super' => true, + 'tuple' => true, + 'type' => true, + 'unichr' => true, + 'unicode' => true, + 'vars' => true, + 'xrange' => true, + 'zip' => true, + ); + + // This list only contains functions that are new or changed between the + // Python versions. + private static $python3Builtins = array( + 'ascii' => true, + 'bytes' => true, + 'filter' => true, + 'map' => true, + 'next' => true, + 'range' => true, + 'super' => true, + 'zip' => true, + ); +} diff --git a/src/applications/repository/storage/PhabricatorRepositorySymbol.php b/src/applications/repository/storage/PhabricatorRepositorySymbol.php --- a/src/applications/repository/storage/PhabricatorRepositorySymbol.php +++ b/src/applications/repository/storage/PhabricatorRepositorySymbol.php @@ -15,6 +15,10 @@ protected $symbolLanguage; protected $pathID; protected $lineNumber; + private $isExternal; + private $source; + private $location; + private $externalURI; private $path = self::ATTACHABLE; private $repository = self::ATTACHABLE; @@ -40,6 +44,10 @@ } public function getURI() { + if ($this->isExternal) { + return $this->externalURI; + } + $request = DiffusionRequest::newFromDictionary( array( 'user' => PhabricatorUser::getOmnipotentUser(), @@ -71,4 +79,32 @@ return $this; } + public function isExternal() { + return $this->isExternal; + } + public function setIsExternal($is_external) { + $this->isExternal = $is_external; + return $this; + } + + public function getSource() { + return $this->source; + } + public function setSource($source) { + $this->source = $source; + return $this; + } + + public function getLocation() { + return $this->location; + } + public function setLocation($location) { + $this->location = $location; + return $this; + } + + public function setExternalURI($external_uri) { + $this->externalURI = $external_uri; + return $this; + } }