3 final class PhabricatorSearchResultView
extends AphrontView
{
9 public function setHandle(PhabricatorObjectHandle
$handle) {
10 $this->handle
= $handle;
14 public function setTokens(array $tokens) {
15 assert_instances_of($tokens, 'PhabricatorFulltextToken');
16 $this->tokens
= $tokens;
20 public function setObject($object) {
21 $this->object = $object;
25 public function render() {
26 $handle = $this->handle
;
27 if (!$handle->isComplete()) {
31 require_celerity_resource('phabricator-search-results-css');
33 $type_name = nonempty($handle->getTypeName(), pht('Document'));
35 $raw_title = $handle->getFullName();
36 $title = $this->emboldenQuery($raw_title);
38 $item = id(new PHUIObjectItemView())
40 ->setTitleText($raw_title)
41 ->setHref($handle->getURI())
42 ->setImageURI($handle->getImageURI())
43 ->addAttribute($type_name);
45 if ($handle->getStatus() == PhabricatorObjectHandle
::STATUS_CLOSED
) {
46 $item->setDisabled(true);
47 $item->addAttribute(pht('Closed'));
55 * Find the words which are part of the query string, and bold them in a
56 * result string. This makes it easier for users to see why a result
57 * matched their query.
59 private function emboldenQuery($str) {
60 $tokens = $this->tokens
;
66 if (count($tokens) > 16) {
74 if (strlen($str) > 2048) {
79 foreach ($tokens as $token) {
80 $raw_token = $token->getToken();
81 $operator = $raw_token->getOperator();
83 $value = $raw_token->getValue();
86 case PhutilSearchQueryCompiler
::OPERATOR_SUBSTRING
:
87 case PhutilSearchQueryCompiler
::OPERATOR_EXACT
:
88 $patterns[] = '(('.preg_quote($value).'))ui';
90 case PhutilSearchQueryCompiler
::OPERATOR_AND
:
91 $patterns[] = '((?<=\W|^)('.preg_quote($value).')(?=\W|\z))ui';
94 // Don't highlight anything else, particularly "NOT".
99 // Find all matches for all query terms in the document title, then reduce
100 // them to a map from offsets to highlighted sequence lengths. If two terms
101 // match at the same position, we choose the longer one.
102 $all_matches = array();
103 foreach ($patterns as $pattern) {
105 $ok = preg_match_all(
109 PREG_OFFSET_CAPTURE
);
114 foreach ($matches[1] as $match) {
115 $match_text = $match[0];
116 $match_offset = $match[1];
118 if (!isset($all_matches[$match_offset])) {
119 $all_matches[$match_offset] = 0;
122 $all_matches[$match_offset] = max(
123 $all_matches[$match_offset],
124 strlen($match_text));
128 // Go through the string one display glyph at a time. If a glyph starts
129 // on a highlighted byte position, turn on highlighting for the number
130 // of matching bytes. If a query searches for "e" and the document contains
131 // an "e" followed by a bunch of combining marks, this will correctly
132 // highlight the entire glyph.
136 foreach (phutil_utf8v_combined($str) as $character) {
137 $length = strlen($character);
139 if (isset($all_matches[$offset])) {
140 $highlight = $all_matches[$offset];
143 if ($highlight > 0) {
144 $is_highlighted = true;
145 $highlight -= $length;
147 $is_highlighted = false;
151 'text' => $character,
152 'highlighted' => $is_highlighted,
158 // Combine all the sequences together so we aren't emitting a tag around
159 // every individual character.
161 foreach ($parts as $key => $part) {
162 if ($last !== null) {
163 if ($part['highlighted'] == $parts[$last]['highlighted']) {
164 $parts[$last]['text'] .= $part['text'];
173 // Finally, add tags.
175 foreach ($parts as $part) {
176 if ($part['highlighted']) {
177 $result[] = phutil_tag('strong', array(), $part['text']);
179 $result[] = $part['text'];