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