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 @@ -2823,6 +2823,7 @@ 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', + 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php', 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php', @@ -6015,6 +6016,7 @@ 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranch' => array( diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php --- a/src/applications/project/remarkup/ProjectRemarkupRule.php +++ b/src/applications/project/remarkup/ProjectRemarkupRule.php @@ -30,7 +30,7 @@ // In other contexts, the PhabricatorProjectProjectPHIDType pattern is // controlling and these names should parse correctly. - return '[^\s.!,:;{}#]*[^\s\d!,:;{}#]+(?:[^\s.!,:;{}#][^\s!,:;{}#]*)*'; + return '[^\s.\d!,:;{}#]+(?:[^\s!,:;{}#][^\s.!,:;{}#]+)*'; } protected function loadObjects(array $ids) { diff --git a/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php new file mode 100644 --- /dev/null +++ b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php @@ -0,0 +1,57 @@ + array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 8, + 'id' => 'ducks', + ), + ), + ), + 'We should make a post on #blog.example.com tomorrow.' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 26, + 'id' => 'blog.example.com', + ), + ), + ), + 'We should make a post on #blog.example.com.' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 26, + 'id' => 'blog.example.com', + ), + ), + ), + '#123' => array( + 'embed' => array(), + 'ref' => array(), + ), + '#security#123' => array( + 'embed' => array(), + 'ref' => array( + array( + 'offset' => 1, + 'id' => 'security', + 'tail' => '123', + ), + ), + ), + ); + + foreach ($cases as $input => $expect) { + $rule = new ProjectRemarkupRule(); + $matches = $rule->extractReferences($input); + $this->assertEqual($expect, $matches, $input); + } + } + +} diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -95,15 +95,33 @@ } public function apply($text) { - $prefix = $this->getObjectNamePrefix(); - $prefix = preg_quote($prefix, '@'); - $id = $this->getObjectIDPattern(); - $text = preg_replace_callback( - '@\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B@u', + $this->getObjectEmbedPattern(), array($this, 'markupObjectEmbed'), $text); + $text = preg_replace_callback( + $this->getObjectReferencePattern(), + array($this, 'markupObjectReference'), + $text); + + return $text; + } + + private function getObjectEmbedPattern() { + $prefix = $this->getObjectNamePrefix(); + $prefix = preg_quote($prefix); + $id = $this->getObjectIDPattern(); + + return '(\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B)u'; + } + + private function getObjectReferencePattern() { + $prefix = $this->getObjectNamePrefix(); + $prefix = preg_quote($prefix); + + $id = $this->getObjectIDPattern(); + // If the prefix starts with a word character (like "D"), we want to // require a word boundary so that we don't match "XD1" as "D1". If the // prefix does not start with a word character, we want to require no word @@ -121,12 +139,55 @@ // The "\b" allows us to link "(abcdef)" or similar without linking things // in the middle of words. - $text = preg_replace_callback( - '((?getObjectEmbedPattern(), + $text, + $embed_matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + $ref_matches = null; + preg_match_all( + $this->getObjectReferencePattern(), + $text, + $ref_matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + $results = array(); + $sets = array( + 'embed' => $embed_matches, + 'ref' => $ref_matches, + ); + foreach ($sets as $type => $matches) { + $formatted = array(); + foreach ($matches as $match) { + $format = array( + 'offset' => $match[1][1], + 'id' => $match[1][0], + ); + if (isset($match[2][0])) { + $format['tail'] = $match[2][0]; + } + $formatted[] = $format; + } + $results[$type] = $formatted; + } + + return $results; } public function markupObjectEmbed($matches) {