[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 /** 2 * @provides javelin-diffusion-locate-file-source 3 * @requires javelin-install 4 * javelin-dom 5 * javelin-typeahead-preloaded-source 6 * javelin-util 7 * @javelin 8 */ 9 10 JX.install('DiffusionLocateFileSource', { 11 12 extend: 'TypeaheadPreloadedSource', 13 14 construct: function(uri) { 15 JX.TypeaheadPreloadedSource.call(this, uri); 16 this.cache = {}; 17 }, 18 19 members: { 20 tree: null, 21 limit: 20, 22 cache: null, 23 24 ondata: function(results) { 25 this.tree = results.tree; 26 this.setReady(true); 27 }, 28 29 30 /** 31 * Match a query and show results in the typeahead. 32 */ 33 matchResults: function(value, partial) { 34 // For now, just pretend spaces don't exist. 35 var search = value.toLowerCase(); 36 search = search.replace(' ', ''); 37 38 var paths = this.findResults(search); 39 40 var nodes = []; 41 for (var ii = 0; ii < paths.length; ii++) { 42 var path = paths[ii]; 43 var name = []; 44 name.push(path.path.substr(0, path.pos)); 45 name.push( 46 JX.$N('strong', {}, path.path.substr(path.pos, path.score))); 47 48 var pos = path.score; 49 var lower = path.path.toLowerCase(); 50 for (var jj = path.pos + path.score; jj < path.path.length; jj++) { 51 if (lower.charAt(jj) == search.charAt(pos)) { 52 pos++; 53 name.push(JX.$N('strong', {}, path.path.charAt(jj))); 54 if (pos == search.length) { 55 break; 56 } 57 } else { 58 name.push(path.path.charAt(jj)); 59 } 60 } 61 62 if (jj < path.path.length - 1 ) { 63 name.push(path.path.substr(jj + 1)); 64 } 65 66 var attr = { 67 className: 'visual-only phui-icon-view phui-font-fa fa-file' 68 }; 69 var icon = JX.$N('span', attr, ''); 70 71 nodes.push( 72 JX.$N( 73 'a', 74 { 75 sigil: 'typeahead-result', 76 className: 'jx-result diffusion-locate-file', 77 ref: path.path 78 }, 79 [icon, name])); 80 } 81 82 this.invoke('resultsready', nodes, value); 83 if (!partial) { 84 this.invoke('complete'); 85 } 86 }, 87 88 89 /** 90 * Find the results matching a query. 91 */ 92 findResults: function(search) { 93 if (!search.length) { 94 return []; 95 } 96 97 // We know that the results for "abc" are always a subset of the results 98 // for "a" and "ab" -- and there's a good chance we already computed 99 // those result sets. Find the longest cached result which is a prefix 100 // of the search query. 101 var best = 0; 102 var start = this.tree; 103 for (var k in this.cache) { 104 if ((k.length <= search.length) && 105 (k.length > best) && 106 (search.substr(0, k.length) == k)) { 107 best = k.length; 108 start = this.cache[k]; 109 } 110 } 111 112 var matches; 113 if (start === null) { 114 matches = null; 115 } else { 116 matches = this.matchTree(start, search, 0); 117 } 118 119 // Save this tree in cache; throw the cache away after a few minutes. 120 if (!(search in this.cache)) { 121 this.cache[search] = matches; 122 setTimeout( 123 JX.bind(this, function() { delete this.cache[search]; }), 124 1000 * 60 * 5); 125 } 126 127 if (!matches) { 128 return []; 129 } 130 131 var paths = []; 132 this.buildPaths(matches, paths, '', search, []); 133 134 paths.sort( 135 function(u, v) { 136 if (u.score != v.score) { 137 return (v.score - u.score); 138 } 139 140 if (u.pos != v.pos) { 141 return (u.pos - v.pos); 142 } 143 144 return ((u.path > v.path) ? 1 : -1); 145 }); 146 147 var num = Math.min(paths.length, this.limit); 148 var results = []; 149 for (var ii = 0; ii < num; ii++) { 150 results.push(paths[ii]); 151 } 152 153 return results; 154 }, 155 156 157 /** 158 * Select the subtree that matches a query. 159 */ 160 matchTree: function(tree, value, pos) { 161 var matches = null; 162 for (var k in tree) { 163 var p = pos; 164 165 if (p != value.length) { 166 p = this.matchString(k, value, pos); 167 } 168 169 var result; 170 if (p == value.length) { 171 result = tree[k]; 172 } else { 173 if (tree == 1) { 174 continue; 175 } else { 176 result = this.matchTree(tree[k], value, p); 177 if (!result) { 178 continue; 179 } 180 } 181 } 182 183 if (!matches) { 184 matches = {}; 185 } 186 matches[k] = result; 187 } 188 189 return matches; 190 }, 191 192 193 /** 194 * Look for the needle in a string, returning how much of it was found. 195 */ 196 matchString: function(haystack, needle, pos) { 197 var str = haystack.toLowerCase(); 198 var len = str.length; 199 for (var ii = 0; ii < len; ii++) { 200 if (str.charAt(ii) == needle.charAt(pos)) { 201 pos++; 202 if (pos == needle.length) { 203 break; 204 } 205 } 206 } 207 return pos; 208 }, 209 210 211 /** 212 * Flatten a tree into paths. 213 */ 214 buildPaths: function(matches, paths, prefix, search) { 215 var first = search.charAt(0); 216 217 for (var k in matches) { 218 if (matches[k] == 1) { 219 var path = prefix + k; 220 var lower = path.toLowerCase(); 221 222 var best = 0; 223 var pos = 0; 224 for (var jj = 0; jj < lower.length; jj++) { 225 if (lower.charAt(jj) != first) { 226 continue; 227 } 228 229 var score = this.scoreMatch(lower, jj, search); 230 if (score == -1) { 231 break; 232 } 233 234 if (score > best) { 235 best = score; 236 pos = jj; 237 if (best == search.length) { 238 break; 239 } 240 } 241 } 242 243 paths.push({ 244 path: path, 245 score: best, 246 pos: pos 247 }); 248 249 } else { 250 this.buildPaths(matches[k], paths, prefix + k, search); 251 } 252 } 253 }, 254 255 256 /** 257 * Score a matching string by finding the longest prefix of the search 258 * query it contains continguously. 259 */ 260 scoreMatch: function(haystack, haypos, search) { 261 var pos = 0; 262 for (var ii = haypos; ii < haystack.length; ii++) { 263 if (haystack.charAt(ii) == search.charAt(pos)) { 264 pos++; 265 if (pos == search.length) { 266 return pos; 267 } 268 } else { 269 ii++; 270 break; 271 } 272 } 273 274 var rem = pos; 275 for (/* keep going */; ii < haystack.length; ii++) { 276 if (haystack.charAt(ii) == search.charAt(rem)) { 277 rem++; 278 if (rem == search.length) { 279 return pos; 280 } 281 } 282 } 283 284 return -1; 285 } 286 287 } 288 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |