Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / search / view / PhabricatorSearchResultView.php
blobb209b4422a468d19ddeaff28d22211ddddd4fd2c
1 <?php
3 final class PhabricatorSearchResultView extends AphrontView {
5 private $handle;
6 private $object;
7 private $tokens;
9 public function setHandle(PhabricatorObjectHandle $handle) {
10 $this->handle = $handle;
11 return $this;
14 public function setTokens(array $tokens) {
15 assert_instances_of($tokens, 'PhabricatorFulltextToken');
16 $this->tokens = $tokens;
17 return $this;
20 public function setObject($object) {
21 $this->object = $object;
22 return $this;
25 public function render() {
26 $handle = $this->handle;
27 if (!$handle->isComplete()) {
28 return;
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())
39 ->setHeader($title)
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'));
50 return $item;
54 /**
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;
62 if (!$tokens) {
63 return $str;
66 if (count($tokens) > 16) {
67 return $str;
70 if (!strlen($str)) {
71 return $str;
74 if (strlen($str) > 2048) {
75 return $str;
78 $patterns = array();
79 foreach ($tokens as $token) {
80 $raw_token = $token->getToken();
81 $operator = $raw_token->getOperator();
83 $value = $raw_token->getValue();
85 switch ($operator) {
86 case PhutilSearchQueryCompiler::OPERATOR_SUBSTRING:
87 case PhutilSearchQueryCompiler::OPERATOR_EXACT:
88 $patterns[] = '(('.preg_quote($value).'))ui';
89 break;
90 case PhutilSearchQueryCompiler::OPERATOR_AND:
91 $patterns[] = '((?<=\W|^)('.preg_quote($value).')(?=\W|\z))ui';
92 break;
93 default:
94 // Don't highlight anything else, particularly "NOT".
95 break;
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) {
104 $matches = null;
105 $ok = preg_match_all(
106 $pattern,
107 $str,
108 $matches,
109 PREG_OFFSET_CAPTURE);
110 if (!$ok) {
111 continue;
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.
133 $parts = array();
134 $highlight = 0;
135 $offset = 0;
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;
146 } else {
147 $is_highlighted = false;
150 $parts[] = array(
151 'text' => $character,
152 'highlighted' => $is_highlighted,
155 $offset += $length;
158 // Combine all the sequences together so we aren't emitting a tag around
159 // every individual character.
160 $last = null;
161 foreach ($parts as $key => $part) {
162 if ($last !== null) {
163 if ($part['highlighted'] == $parts[$last]['highlighted']) {
164 $parts[$last]['text'] .= $part['text'];
165 unset($parts[$key]);
166 continue;
170 $last = $key;
173 // Finally, add tags.
174 $result = array();
175 foreach ($parts as $part) {
176 if ($part['highlighted']) {
177 $result[] = phutil_tag('strong', array(), $part['text']);
178 } else {
179 $result[] = $part['text'];
183 return $result;