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

Configure Feed

Select the types of activity you want to include in your feed.

Modularize the "jump nav" behaviors in global search

Summary: Depends on D19087. Ref T13079. This still doesn't feel like the most clean, general system in the world, but is a step forward from hard-coded `switch()` stuff.

Test Plan:
- Jumped to `r`.
- Jumped to `a`.
- Jumped to `r poe` (multiple results).
- Jumped to `r poetry` (one result).
- Jumped to `r syzygy` (no results).
- Jumped to `p`.
- Jumped to `p robot` (multiple results); `p assessment` (one result).
- The behavior for `p <string>` has changed slightly but should be more powerful now (it's consistent with `r <string>`).
- Jumped to `s <symbol>` and `s <context>-><symbol>`.
- Jumped to `d`.
- Jumped to `f`.
- Jumped to `t`.
- Jumped to `T123`, `D123`, `@dog`, `PHID-DREV-abcd`, etc.

Maniphest Tasks: T13079

Differential Revision: https://secure.phabricator.com/D19088

+246 -148
-2
src/__phutil_library_map__.php
··· 3154 3154 'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php', 3155 3155 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 3156 3156 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 3157 - 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 3158 3157 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 3159 3158 'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php', 3160 3159 'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php', ··· 8708 8707 'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat', 8709 8708 'PhabricatorJavelinLinter' => 'ArcanistLinter', 8710 8709 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 8711 - 'PhabricatorJumpNavHandler' => 'Phobject', 8712 8710 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 8713 8711 'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy', 8714 8712 'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule',
+70
src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php
··· 9 9 new DiffusionSymbolDatasource(), 10 10 ); 11 11 } 12 + 13 + public function newJumpURI($query) { 14 + $viewer = $this->getViewer(); 15 + 16 + // Send "r" to Diffusion. 17 + if (preg_match('/^r\z/i', $query)) { 18 + return '/diffusion/'; 19 + } 20 + 21 + // Send "a" to the commit list ("Audit"). 22 + if (preg_match('/^a\z/i', $query)) { 23 + return '/diffusion/commit/'; 24 + } 25 + 26 + // Send "r <string>" to a search for a matching repository. 27 + $matches = null; 28 + if (preg_match('/^r\s+(.+)\z/i', $query, $matches)) { 29 + $raw_query = $matches[1]; 30 + 31 + $engine = id(new PhabricatorRepository()) 32 + ->newFerretEngine(); 33 + 34 + $compiler = id(new PhutilSearchQueryCompiler()) 35 + ->setEnableFunctions(true); 36 + 37 + $raw_tokens = $compiler->newTokens($raw_query); 38 + 39 + $fulltext_tokens = array(); 40 + foreach ($raw_tokens as $raw_token) { 41 + $fulltext_token = id(new PhabricatorFulltextToken()) 42 + ->setToken($raw_token); 43 + $fulltext_tokens[] = $fulltext_token; 44 + } 45 + 46 + $repositories = id(new PhabricatorRepositoryQuery()) 47 + ->setViewer($viewer) 48 + ->withFerretConstraint($engine, $fulltext_tokens) 49 + ->execute(); 50 + if (count($repositories) == 1) { 51 + // Just one match, jump to repository. 52 + return head($repositories)->getURI(); 53 + } else { 54 + // More than one match, jump to search. 55 + return urisprintf( 56 + '/diffusion/?order=relevance&query=%s#R', 57 + $raw_query); 58 + } 59 + } 60 + 61 + // Send "s <string>" to a symbol search. 62 + $matches = null; 63 + if (preg_match('/^s\s+(.+)\z/i', $query, $matches)) { 64 + $symbol = $matches[1]; 65 + 66 + $parts = null; 67 + if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { 68 + return urisprintf( 69 + '/diffusion/symbol/%s/?jump=true&context=%s', 70 + $parts[2], 71 + $parts[1]); 72 + } else { 73 + return urisprintf( 74 + '/diffusion/symbol/%s/?jump=true', 75 + $symbol); 76 + } 77 + } 78 + 79 + return null; 80 + } 81 + 12 82 }
+25
src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php
··· 8 8 new PhabricatorPeopleDatasource(), 9 9 ); 10 10 } 11 + 12 + public function newJumpURI($query) { 13 + $viewer = $this->getViewer(); 14 + 15 + // Send "u" to the user directory. 16 + if (preg_match('/^u\z/i', $query)) { 17 + return '/people/'; 18 + } 19 + 20 + // Send "u <string>" to the user's profile page. 21 + $matches = null; 22 + if (preg_match('/^u\s+(.+)\z/i', $query, $matches)) { 23 + $raw_query = $matches[1]; 24 + 25 + // TODO: We could test that this is a valid username and jump to 26 + // a search in the user directory if it isn't. 27 + 28 + return urisprintf( 29 + '/p/%s/', 30 + $raw_query); 31 + } 32 + 33 + return null; 34 + } 35 + 11 36 }
+46
src/applications/project/engineextension/ProjectDatasourceEngineExtension.php
··· 8 8 new PhabricatorProjectDatasource(), 9 9 ); 10 10 } 11 + 12 + public function newJumpURI($query) { 13 + $viewer = $this->getViewer(); 14 + 15 + // Send "p" to Projects. 16 + if (preg_match('/^p\z/i', $query)) { 17 + return '/diffusion/'; 18 + } 19 + 20 + // Send "p <string>" to a search for similar projects. 21 + $matches = null; 22 + if (preg_match('/^p\s+(.+)\z/i', $query, $matches)) { 23 + $raw_query = $matches[1]; 24 + 25 + $engine = id(new PhabricatorProject()) 26 + ->newFerretEngine(); 27 + 28 + $compiler = id(new PhutilSearchQueryCompiler()) 29 + ->setEnableFunctions(true); 30 + 31 + $raw_tokens = $compiler->newTokens($raw_query); 32 + 33 + $fulltext_tokens = array(); 34 + foreach ($raw_tokens as $raw_token) { 35 + $fulltext_token = id(new PhabricatorFulltextToken()) 36 + ->setToken($raw_token); 37 + $fulltext_tokens[] = $fulltext_token; 38 + } 39 + 40 + $projects = id(new PhabricatorProjectQuery()) 41 + ->setViewer($viewer) 42 + ->withFerretConstraint($engine, $fulltext_tokens) 43 + ->execute(); 44 + if (count($projects) == 1) { 45 + // Just one match, jump to project. 46 + return head($projects)->getURI(); 47 + } else { 48 + // More than one match, jump to search. 49 + return urisprintf( 50 + '/project/?order=relevance&query=%s#R', 51 + $raw_query); 52 + } 53 + } 54 + 55 + return null; 56 + } 11 57 }
+10 -8
src/applications/search/controller/PhabricatorSearchController.php
··· 11 11 12 12 public function handleRequest(AphrontRequest $request) { 13 13 $viewer = $this->getViewer(); 14 + $query = $request->getStr('query'); 14 15 15 - if ($request->getStr('jump') != 'no') { 16 - $response = PhabricatorJumpNavHandler::getJumpResponse( 17 - $viewer, 18 - $request->getStr('query')); 19 - if ($response) { 20 - return $response; 16 + if ($request->getStr('jump') != 'no' && strlen($query)) { 17 + $jump_uri = id(new PhabricatorDatasourceEngine()) 18 + ->setViewer($viewer) 19 + ->newJumpURI($query); 20 + 21 + if ($jump_uri !== null) { 22 + return id(new AphrontRedirectResponse())->setURI($jump_uri); 21 23 } 22 24 } 23 25 ··· 29 31 if ($request->getBool('search:primary')) { 30 32 31 33 // If there's no query, just take the user to advanced search. 32 - if (!strlen($request->getStr('query'))) { 34 + if (!strlen($query)) { 33 35 $advanced_uri = '/search/query/advanced/'; 34 36 return id(new AphrontRedirectResponse())->setURI($advanced_uri); 35 37 } ··· 71 73 72 74 // Add the user's query, then save this as a new saved query and send 73 75 // the user to the results page. 74 - $saved->setParameter('query', $request->getStr('query')); 76 + $saved->setParameter('query', $query); 75 77 76 78 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 77 79 try {
+29
src/applications/search/engine/PhabricatorDatasourceEngine.php
··· 2 2 3 3 final class PhabricatorDatasourceEngine extends Phobject { 4 4 5 + private $viewer; 6 + 7 + public function setViewer(PhabricatorUser $viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + public function getViewer() { 13 + return $this->viewer; 14 + } 15 + 5 16 public function getAllQuickSearchDatasources() { 6 17 return PhabricatorDatasourceEngineExtension::getAllQuickSearchDatasources(); 7 18 } 19 + 20 + public function newJumpURI($query) { 21 + $viewer = $this->getViewer(); 22 + $extensions = PhabricatorDatasourceEngineExtension::getAllExtensions(); 23 + 24 + foreach ($extensions as $extension) { 25 + $jump_uri = id(clone $extension) 26 + ->setViewer($viewer) 27 + ->newJumpURI($query); 28 + 29 + if ($jump_uri !== null) { 30 + return $jump_uri; 31 + } 32 + } 33 + 34 + return null; 35 + } 36 + 8 37 }
-135
src/applications/search/engine/PhabricatorJumpNavHandler.php
··· 1 - <?php 2 - 3 - final class PhabricatorJumpNavHandler extends Phobject { 4 - 5 - public static function getJumpResponse(PhabricatorUser $viewer, $jump) { 6 - $jump = trim($jump); 7 - 8 - $patterns = array( 9 - '/^a$/i' => 'uri:/diffusion/commit/', 10 - '/^f$/i' => 'uri:/feed/', 11 - '/^d$/i' => 'uri:/differential/', 12 - '/^r$/i' => 'uri:/diffusion/', 13 - '/^t$/i' => 'uri:/maniphest/', 14 - '/^p$/i' => 'uri:/project/', 15 - '/^u$/i' => 'uri:/people/', 16 - '/^p\s+(.+)$/i' => 'project', 17 - '/^u\s+(\S+)$/i' => 'user', 18 - '/^(?:s)\s+(\S+)/i' => 'find-symbol', 19 - '/^r\s+(.+)$/i' => 'find-repository', 20 - ); 21 - 22 - foreach ($patterns as $pattern => $effect) { 23 - $matches = null; 24 - if (preg_match($pattern, $jump, $matches)) { 25 - if (!strncmp($effect, 'uri:', 4)) { 26 - return id(new AphrontRedirectResponse()) 27 - ->setURI(substr($effect, 4)); 28 - } else { 29 - switch ($effect) { 30 - case 'user': 31 - return id(new AphrontRedirectResponse()) 32 - ->setURI('/p/'.$matches[1].'/'); 33 - case 'project': 34 - $project = self::findCloselyNamedProject($matches[1]); 35 - if ($project) { 36 - return id(new AphrontRedirectResponse()) 37 - ->setURI('/project/view/'.$project->getID().'/'); 38 - } else { 39 - $jump = $matches[1]; 40 - } 41 - break; 42 - case 'find-symbol': 43 - $context = ''; 44 - $symbol = $matches[1]; 45 - $parts = array(); 46 - if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { 47 - $context = '&context='.phutil_escape_uri($parts[1]); 48 - $symbol = $parts[2]; 49 - } 50 - return id(new AphrontRedirectResponse()) 51 - ->setURI("/diffusion/symbol/$symbol/?jump=true$context"); 52 - case 'find-repository': 53 - $raw_query = $matches[1]; 54 - 55 - $engine = id(new PhabricatorRepository()) 56 - ->newFerretEngine(); 57 - 58 - $compiler = id(new PhutilSearchQueryCompiler()) 59 - ->setEnableFunctions(true); 60 - 61 - $raw_tokens = $compiler->newTokens($raw_query); 62 - 63 - $fulltext_tokens = array(); 64 - foreach ($raw_tokens as $raw_token) { 65 - $fulltext_token = id(new PhabricatorFulltextToken()) 66 - ->setToken($raw_token); 67 - $fulltext_tokens[] = $fulltext_token; 68 - } 69 - 70 - $repositories = id(new PhabricatorRepositoryQuery()) 71 - ->setViewer($viewer) 72 - ->withFerretConstraint($engine, $fulltext_tokens) 73 - ->execute(); 74 - if (count($repositories) == 1) { 75 - // Just one match, jump to repository. 76 - $uri = head($repositories)->getURI(); 77 - } else { 78 - // More than one match, jump to search. 79 - $uri = urisprintf( 80 - '/diffusion/?order=name&query=%s', 81 - $raw_query); 82 - } 83 - return id(new AphrontRedirectResponse())->setURI($uri); 84 - default: 85 - throw new Exception(pht("Unknown jump effect '%s'!", $effect)); 86 - } 87 - } 88 - } 89 - } 90 - 91 - // If none of the patterns matched, look for an object by name. 92 - $objects = id(new PhabricatorObjectQuery()) 93 - ->setViewer($viewer) 94 - ->withNames(array($jump)) 95 - ->execute(); 96 - 97 - if (count($objects) == 1) { 98 - $handle = id(new PhabricatorHandleQuery()) 99 - ->setViewer($viewer) 100 - ->withPHIDs(mpull($objects, 'getPHID')) 101 - ->executeOne(); 102 - 103 - return id(new AphrontRedirectResponse())->setURI($handle->getURI()); 104 - } 105 - 106 - return null; 107 - } 108 - 109 - private static function findCloselyNamedProject($name) { 110 - $project = id(new PhabricatorProject())->loadOneWhere( 111 - 'name = %s', 112 - $name); 113 - if ($project) { 114 - return $project; 115 - } else { // no exact match, try a fuzzy match 116 - $projects = id(new PhabricatorProject())->loadAllWhere( 117 - 'name LIKE %~', 118 - $name); 119 - if ($projects) { 120 - $min_name_length = PHP_INT_MAX; 121 - $best_project = null; 122 - foreach ($projects as $project) { 123 - $name_length = strlen($project->getName()); 124 - if ($name_length <= $min_name_length) { 125 - $min_name_length = $name_length; 126 - $best_project = $project; 127 - } 128 - } 129 - return $best_project; 130 - } else { 131 - return null; 132 - } 133 - } 134 - } 135 - }
+24 -3
src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php
··· 2 2 3 3 abstract class PhabricatorDatasourceEngineExtension extends Phobject { 4 4 5 - abstract public function newQuickSearchDatasources(); 5 + private $viewer; 6 + 7 + final public function setViewer(PhabricatorUser $viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + final public function getViewer() { 13 + return $this->viewer; 14 + } 15 + 16 + public function newQuickSearchDatasources() { 17 + return array(); 18 + } 19 + 20 + public function newJumpURI($query) { 21 + return null; 22 + } 6 23 7 - final public static function getAllQuickSearchDatasources() { 8 - $extensions = id(new PhutilClassMapQuery()) 24 + final public static function getAllExtensions() { 25 + return id(new PhutilClassMapQuery()) 9 26 ->setAncestorClass(__CLASS__) 10 27 ->execute(); 28 + } 29 + 30 + final public static function getAllQuickSearchDatasources() { 31 + $extensions = self::getAllExtensions(); 11 32 12 33 $datasources = array(); 13 34 foreach ($extensions as $extension) {
+42
src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php
··· 8 8 new PhabricatorTypeaheadMonogramDatasource(), 9 9 ); 10 10 } 11 + 12 + public function newJumpURI($query) { 13 + $viewer = $this->getViewer(); 14 + 15 + // These first few rules are sort of random but don't fit anywhere else 16 + // today and don't feel worth adding separate extensions for. 17 + 18 + // Send "f" to Feed. 19 + if (preg_match('/^f\z/i', $query)) { 20 + return '/feed/'; 21 + } 22 + 23 + // Send "d" to Differential. 24 + if (preg_match('/^d\z/i', $query)) { 25 + return '/differential/'; 26 + } 27 + 28 + // Send "t" to Maniphest. 29 + if (preg_match('/^t\z/i', $query)) { 30 + return '/maniphest/'; 31 + } 32 + 33 + // Otherwise, if the user entered an object name, jump to that object. 34 + $objects = id(new PhabricatorObjectQuery()) 35 + ->setViewer($viewer) 36 + ->withNames(array($query)) 37 + ->execute(); 38 + if (count($objects) == 1) { 39 + $object = head($objects); 40 + $object_phid = $object->getPHID(); 41 + 42 + $handles = $viewer->loadHandles(array($object_phid)); 43 + $handle = $handles[$object_phid]; 44 + 45 + if ($handle->isComplete()) { 46 + return $handle->getURI(); 47 + } 48 + } 49 + 50 + return null; 51 + } 52 + 11 53 }