diff --git a/resources/sql/patches/076.indexedlanguages.sql b/resources/sql/patches/076.indexedlanguages.sql new file mode 100644 index 0000000000..14c160ddc5 --- /dev/null +++ b/resources/sql/patches/076.indexedlanguages.sql @@ -0,0 +1,4 @@ +ALTER TABLE phabricator_repository.repository_arcanistproject + ADD symbolIndexLanguages LONGBLOB NOT NULL; +ALTER TABLE phabricator_repository.repository_arcanistproject + ADD symbolIndexProjects LONGBLOB NOT NULL; \ No newline at end of file diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 50e94b14db..8553953890 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -759,7 +759,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-repository-crossreference' => array( - 'uri' => '/res/e80365e9/rsrc/js/application/repository/repository-crossreference.js', + 'uri' => '/res/49472f48/rsrc/js/application/repository/repository-crossreference.js', 'type' => 'js', 'requires' => array( diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index f09b7ae649..2215758b2b 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -180,11 +180,11 @@ class DifferentialRevisionViewController extends DifferentialController { $whitespace = $request->getStr( 'whitespace', - DifferentialChangesetParser::WHITESPACE_IGNORE_ALL - ); + DifferentialChangesetParser::WHITESPACE_IGNORE_ALL); + + $symbol_indexes = $this->buildSymbolIndexes($target, $visible_changesets); $revision_detail->setActions($actions); - $revision_detail->setUser($user); $comment_view = new DifferentialRevisionCommentListView(); @@ -202,6 +202,7 @@ class DifferentialRevisionViewController extends DifferentialController { $changeset_view->setRevision($revision); $changeset_view->setRenderingReferences($rendering_references); $changeset_view->setWhitespace($whitespace); + $changeset_view->setSymbolIndexes($symbol_indexes); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); @@ -571,5 +572,40 @@ class DifferentialRevisionViewController extends DifferentialController { $special_properties)); } + private function buildSymbolIndexes( + DifferentialDiff $target, + array $visible_changesets) { + + $engine = PhabricatorSyntaxHighlighter::newEngine(); + + $symbol_indexes = array(); + $arc_project = $target->loadArcanistProject(); + if (!$arc_project) { + return array(); + } + + $langs = $arc_project->getSymbolIndexLanguages(); + if (!$langs) { + return array(); + } + + $project_phids = array_merge( + array($arc_project->getPHID()), + nonempty($arc_project->getSymbolIndexProjects(), array())); + + $indexed_langs = array_fill_keys($langs, true); + foreach ($visible_changesets as $key => $changeset) { + $lang = $engine->getLanguageFromFilename($changeset->getFileName()); + if (isset($indexed_langs[$lang])) { + $symbol_indexes[$key] = array( + 'lang' => $lang, + 'projects' => $project_phids, + ); + } + } + + return $symbol_indexes; + } + } diff --git a/src/applications/differential/controller/revisionview/__init__.php b/src/applications/differential/controller/revisionview/__init__.php index dee7918034..0218e5abf0 100644 --- a/src/applications/differential/controller/revisionview/__init__.php +++ b/src/applications/differential/controller/revisionview/__init__.php @@ -29,6 +29,7 @@ phutil_require_module('phabricator', 'applications/differential/view/revisioncom phutil_require_module('phabricator', 'applications/differential/view/revisiondetail'); phutil_require_module('phabricator', 'applications/differential/view/revisionupdatehistory'); phutil_require_module('phabricator', 'applications/draft/storage/draft'); +phutil_require_module('phabricator', 'applications/markup/syntax'); phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/env'); diff --git a/src/applications/differential/view/changesetdetailview/DifferentialChangesetDetailView.php b/src/applications/differential/view/changesetdetailview/DifferentialChangesetDetailView.php index 4dd6f8be2e..74c788064f 100644 --- a/src/applications/differential/view/changesetdetailview/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/changesetdetailview/DifferentialChangesetDetailView.php @@ -21,6 +21,7 @@ class DifferentialChangesetDetailView extends AphrontView { private $changeset; private $buttons = array(); private $revisionID; + private $symbolIndex; public function setChangeset($changeset) { $this->changeset = $changeset; @@ -37,6 +38,11 @@ class DifferentialChangesetDetailView extends AphrontView { return $this; } + public function setSymbolIndex($symbol_index) { + $this->symbolIndex = $symbol_index; + return $this; + } + public function render() { require_celerity_resource('differential-changeset-view-css'); require_celerity_resource('syntax-highlighting-css'); @@ -61,6 +67,16 @@ class DifferentialChangesetDetailView extends AphrontView { ''; } + $id = celerity_generate_unique_node_id(); + + if ($this->symbolIndex) { + Javelin::initBehavior( + 'repository-crossreference', + array( + 'container' => $id, + ) + $this->symbolIndex); + } + $display_filename = $changeset->getDisplayFilename(); $output = javelin_render_tag( 'div', @@ -71,6 +87,7 @@ class DifferentialChangesetDetailView extends AphrontView { 'right' => $this->changeset->getID(), ), 'class' => $class, + 'id' => $id, ), phutil_render_tag( 'a', diff --git a/src/applications/differential/view/changesetdetailview/__init__.php b/src/applications/differential/view/changesetdetailview/__init__.php index 4e50ea0f06..783a68af76 100644 --- a/src/applications/differential/view/changesetdetailview/__init__.php +++ b/src/applications/differential/view/changesetdetailview/__init__.php @@ -7,6 +7,7 @@ phutil_require_module('phabricator', 'infrastructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/api'); phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/base'); diff --git a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php index 4df7b0fa4c..a847219e0b 100644 --- a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php +++ b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php @@ -24,6 +24,7 @@ class DifferentialChangesetListView extends AphrontView { private $renderURI = '/differential/changeset/'; private $whitespace; private $standaloneViews; + private $symbolIndexes = array(); public function setChangesets($changesets) { $this->changesets = $changesets; @@ -50,6 +51,11 @@ class DifferentialChangesetListView extends AphrontView { return $this; } + public function setSymbolIndexes(array $indexes) { + $this->symbolIndexes = $indexes; + return $this; + } + public function setRenderURI($render_uri) { $this->renderURI = $render_uri; return $this; @@ -100,6 +106,7 @@ class DifferentialChangesetListView extends AphrontView { $detail = new DifferentialChangesetDetailView(); $detail->setChangeset($changeset); $detail->addButton($detail_button); + $detail->setSymbolIndex(idx($this->symbolIndexes, $key)); $detail->appendChild( phutil_render_tag( 'div', @@ -136,12 +143,6 @@ class DifferentialChangesetListView extends AphrontView { )); } - Javelin::initBehavior( - 'repository-crossreference', - array( - 'container' => 'differential-review-stage', - )); - return '
'. implode("\n", $output). diff --git a/src/applications/differential/view/changesetlistview/__init__.php b/src/applications/differential/view/changesetlistview/__init__.php index b8ca61df16..4d64482a9e 100644 --- a/src/applications/differential/view/changesetlistview/__init__.php +++ b/src/applications/differential/view/changesetlistview/__init__.php @@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'view/base'); phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'parser/uri'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DifferentialChangesetListView.php'); diff --git a/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php b/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php index 55a0f5ef19..cd786670ec 100644 --- a/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php @@ -29,7 +29,7 @@ class DiffusionSymbolController extends DiffusionController { $user = $request->getUser(); $query = new DiffusionSymbolQuery(); - $query->setNamePrefix($this->name); + $query->setName($this->name); if ($request->getStr('type')) { $query->setType($request->getStr('type')); @@ -39,14 +39,104 @@ class DiffusionSymbolController extends DiffusionController { $query->setLanguage($request->getStr('lang')); } + if ($request->getStr('projects')) { + $phids = $request->getStr('projects'); + $phids = explode(',', $phids); + $phids = array_filter($phids); + + if ($phids) { + $projects = id(new PhabricatorRepositoryArcanistProject()) + ->loadAllWhere( + 'phid IN (%Ls)', + $phids); + $projects = mpull($projects, 'getID'); + if ($projects) { + $query->setProjectIDs($projects); + } + } + } + $symbols = $query->execute(); + // For PHP builtins, jump to php.net documentation. + if ($request->getBool('jump') && count($symbols) == 0) { + if ($request->getStr('lang') == 'php') { + if ($request->getStr('type') == 'function') { + if (in_array($this->name, idx(get_defined_functions(), 'internal'))) { + return id(new AphrontRedirectResponse()) + ->setURI('http://www.php.net/'.$this->name); + } + } + } + } + + if ($symbols) { + $projects = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere( + 'id IN (%Ld)', + mpull($symbols, 'getArcanistProjectID', 'getID')); + } else { + $projects = array(); + } + + $path_map = array(); + if ($symbols) { + $path_map = queryfx_all( + id(new PhabricatorRepository())->establishConnection('r'), + 'SELECT * FROM %T WHERE id IN (%Ld)', + PhabricatorRepository::TABLE_PATH, + mpull($symbols, 'getPathID')); + $path_map = ipull($path_map, 'path', 'id'); + } + + $repo_ids = array_filter(mpull($projects, 'getRepositoryID')); + if ($repo_ids) { + $repos = id(new PhabricatorRepository())->loadAllWhere( + 'id IN (%Ld)', + $repo_ids); + } else { + $repos = array(); + } + $rows = array(); foreach ($symbols as $symbol) { + $project = idx($projects, $symbol->getArcanistProjectID()); + if ($project) { + $project_name = $project->getName(); + } else { + $project_name = '-'; + } + + $file = phutil_escape_html(idx($path_map, $symbol->getPathID())); + $line = phutil_escape_html($symbol->getLineNumber()); + + $repo = idx($repos, $project->getRepositoryID()); + if ($repo) { + $href = '/diffusion/'.$repo->getCallsign().'/browse'.$file.'$'.$line; + + 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); + } + + $location = phutil_render_tag( + 'a', + array( + 'href' => $href, + ), + phutil_escape_html($file.':'.$line)); + } else if ($file) { + $location = phutil_escape_html($file.':'.$line); + } else { + $location = '?'; + } + $rows[] = array( phutil_escape_html($symbol->getSymbolType()), phutil_escape_html($symbol->getSymbolName()), phutil_escape_html($symbol->getSymbolLanguage()), + phutil_escape_html($project_name), + $location, ); } @@ -56,13 +146,20 @@ class DiffusionSymbolController extends DiffusionController { 'Type', 'Name', 'Language', + 'Project', + 'File', )); $table->setColumnClasses( array( '', 'pri', '', + '', + '', + 'n' )); + $table->setNoDataString( + "No matching symbol could be found in any indexed project."); $panel = new AphrontPanelView(); $panel->setHeader('Similar Symbols'); diff --git a/src/applications/diffusion/controller/symbol/__init__.php b/src/applications/diffusion/controller/symbol/__init__.php index a16792507e..a89701bdea 100644 --- a/src/applications/diffusion/controller/symbol/__init__.php +++ b/src/applications/diffusion/controller/symbol/__init__.php @@ -6,12 +6,17 @@ +phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/diffusion/controller/base'); phutil_require_module('phabricator', 'applications/diffusion/query/symbol'); +phutil_require_module('phabricator', 'applications/repository/storage/arcanistproject'); +phutil_require_module('phabricator', 'applications/repository/storage/repository'); +phutil_require_module('phabricator', 'storage/queryfx'); phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phutil', 'markup'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DiffusionSymbolController.php'); diff --git a/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php index 6f961178fb..4d8e3827d8 100644 --- a/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php +++ b/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php @@ -46,6 +46,15 @@ class PhabricatorRepositoryArcanistProjectEditController } if ($request->isFormPost()) { + + $indexed = $request->getStr('symbolIndexLanguages'); + $indexed = strtolower($indexed); + $indexed = preg_split('/[ ,]+/', $indexed); + $indexed = array_filter($indexed); + $project->setSymbolIndexLanguages($indexed); + + $project->setSymbolIndexProjects($request->getArr('symbolIndexProjects')); + $repo_id = $request->getInt('repository', 0); if (isset($repos[$repo_id])) { $project->setRepositoryID($repo_id); @@ -56,6 +65,22 @@ class PhabricatorRepositoryArcanistProjectEditController } } + $langs = $project->getSymbolIndexLanguages(); + if ($langs) { + $langs = implode(', ', $langs); + } else { + $langs = null; + } + + if ($project->getSymbolIndexProjects()) { + $uses = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere( + 'phid in (%Ls)', + $project->getSymbolIndexProjects()); + $uses = mpull($uses, 'getName', 'getPHID'); + } else { + $uses = array(); + } + $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( @@ -72,13 +97,25 @@ class PhabricatorRepositoryArcanistProjectEditController ->setOptions($repos) ->setName('repository') ->setValue($project->getRepositoryID())) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Indexed Languages') + ->setName('symbolIndexLanguages') + ->setCaption('Separate with commas, for example: php, py') + ->setValue($langs)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setLabel('Uses Symbols From') + ->setName('symbolIndexProjects') + ->setDatasource('/typeahead/common/arcanistprojects/') + ->setValue($uses)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/repository/') ->setValue('Save')); $panel = new AphrontPanelView(); - $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->setHeader('Edit Arcanist Project'); $panel->appendChild($form); diff --git a/src/applications/repository/controller/arcansistprojectedit/__init__.php b/src/applications/repository/controller/arcansistprojectedit/__init__.php index b364131bac..0c8d1f5b06 100644 --- a/src/applications/repository/controller/arcansistprojectedit/__init__.php +++ b/src/applications/repository/controller/arcansistprojectedit/__init__.php @@ -15,6 +15,8 @@ phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/select'); phutil_require_module('phabricator', 'view/form/control/static'); phutil_require_module('phabricator', 'view/form/control/submit'); +phutil_require_module('phabricator', 'view/form/control/text'); +phutil_require_module('phabricator', 'view/form/control/tokenizer'); phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php b/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php index 3ef03cb4ad..5a95053a1c 100644 --- a/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php +++ b/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php @@ -23,10 +23,17 @@ class PhabricatorRepositoryArcanistProject protected $phid; protected $repositoryID; + protected $symbolIndexLanguages = array(); + protected $symbolIndexProjects = array(); + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'symbolIndexLanguages' => self::SERIALIZATION_JSON, + 'symbolIndexProjects' => self::SERIALIZATION_JSON, + ), ) + parent::getConfiguration(); } diff --git a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php index 686f14f001..46e3368beb 100644 --- a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php +++ b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php @@ -35,6 +35,7 @@ class PhabricatorTypeaheadCommonDatasourceController $need_repos = false; $need_packages = false; $need_upforgrabs = false; + $need_arcanist_projects = false; switch ($this->type) { case 'searchowner': $need_users = true; @@ -60,6 +61,10 @@ class PhabricatorTypeaheadCommonDatasourceController $need_users = true; $need_all_users = true; break; + case 'arcanistprojects': + $need_arcanist_projects = true; + break; + } $data = array(); @@ -150,6 +155,17 @@ class PhabricatorTypeaheadCommonDatasourceController } } + if ($need_arcanist_projects) { + $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); + foreach ($arcprojs as $proj) { + $data[] = array( + $proj->getName(), + null, + $proj->getPHID(), + ); + } + } + return id(new AphrontAjaxResponse()) ->setContent($data); } diff --git a/src/applications/typeahead/controller/common/__init__.php b/src/applications/typeahead/controller/common/__init__.php index 850fce97d5..89e2754bf6 100644 --- a/src/applications/typeahead/controller/common/__init__.php +++ b/src/applications/typeahead/controller/common/__init__.php @@ -12,6 +12,7 @@ phutil_require_module('phabricator', 'applications/metamta/storage/mailinglist') phutil_require_module('phabricator', 'applications/owners/storage/package'); phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'applications/project/storage/project'); +phutil_require_module('phabricator', 'applications/repository/storage/arcanistproject'); phutil_require_module('phabricator', 'applications/repository/storage/repository'); phutil_require_module('phabricator', 'applications/typeahead/controller/base'); diff --git a/webroot/rsrc/js/application/repository/repository-crossreference.js b/webroot/rsrc/js/application/repository/repository-crossreference.js index b72a8d3309..bf12431279 100644 --- a/webroot/rsrc/js/application/repository/repository-crossreference.js +++ b/webroot/rsrc/js/application/repository/repository-crossreference.js @@ -22,7 +22,10 @@ JX.behavior('repository-crossreference', function(config) { if (JX.DOM.isNode(target, 'span') && (target.className in map)) { var uri = JX.$U('/diffusion/symbol/' + target.innerHTML + '/'); uri.addQueryParams({ - type : map[target.className] + type : map[target.className], + lang : config.lang, + projects : config.projects.join(','), + jump : true }); window.open(uri); e.kill();