|
1 | 1 | import $ from 'jquery';
|
2 | 2 |
|
3 | 3 | import {svg} from '../svg.js';
|
4 |
| -import {strSubMatch} from '../utils.js'; |
5 | 4 | const {csrf} = window.config;
|
6 | 5 |
|
7 | 6 | const threshold = 50;
|
8 | 7 | let files = [];
|
9 | 8 | let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult;
|
10 | 9 |
|
11 |
| -function filterRepoFiles(filter) { |
12 |
| - const treeLink = $repoFindFileInput.attr('data-url-tree-link'); |
13 |
| - $repoFindFileTableBody.empty(); |
14 | 10 |
|
15 |
| - const fileRes = []; |
| 11 | +// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...] |
| 12 | +// res[even] is unmatched, res[odd] is matched, see unit tests for examples |
| 13 | +// argument subLower must be a lower-cased string. |
| 14 | +export function strSubMatch(full, subLower) { |
| 15 | + const res = ['']; |
| 16 | + let i = 0, j = 0; |
| 17 | + const fullLower = full.toLowerCase(); |
| 18 | + while (i < subLower.length && j < fullLower.length) { |
| 19 | + if (subLower[i] === fullLower[j]) { |
| 20 | + if (res.length % 2 !== 0) res.push(''); |
| 21 | + res[res.length - 1] += full[j]; |
| 22 | + j++; |
| 23 | + i++; |
| 24 | + } else { |
| 25 | + if (res.length % 2 === 0) res.push(''); |
| 26 | + res[res.length - 1] += full[j]; |
| 27 | + j++; |
| 28 | + } |
| 29 | + } |
| 30 | + if (i !== subLower.length) { |
| 31 | + // if the sub string doesn't match the full, only return the full as unmatched. |
| 32 | + return [full]; |
| 33 | + } |
| 34 | + if (j < full.length) { |
| 35 | + // append remaining chars from full to result as unmatched |
| 36 | + if (res.length % 2 === 0) res.push(''); |
| 37 | + res[res.length - 1] += full.substring(j); |
| 38 | + } |
| 39 | + return res; |
| 40 | +} |
| 41 | + |
| 42 | +export function calcMatchedWeight(matchResult) { |
| 43 | + let weight = 0; |
| 44 | + for (let i = 0; i < matchResult.length; i++) { |
| 45 | + if (i % 2 === 1) { // matches are on odd indices, see strSubMatch |
| 46 | + // use a function f(x+x) > f(x) + f(x) to make the longer matched string has higher weight. |
| 47 | + weight += matchResult[i].length * matchResult[i].length; |
| 48 | + } |
| 49 | + } |
| 50 | + return weight; |
| 51 | +} |
| 52 | + |
| 53 | +export function filterRepoFilesWeighted(files, filter) { |
| 54 | + let filterResult = []; |
16 | 55 | if (filter) {
|
17 |
| - for (let i = 0; i < files.length && fileRes.length < threshold; i++) { |
18 |
| - const subMatch = strSubMatch(files[i], filter); |
19 |
| - if (subMatch.length > 1) { |
20 |
| - fileRes.push(subMatch); |
| 56 | + const filterLower = filter.toLowerCase(); |
| 57 | + // TODO: for large repo, this loop could be slow, maybe there could be one more limit: |
| 58 | + // ... && filterResult.length < threshold * 20, wait for more feedbacks |
| 59 | + for (let i = 0; i < files.length; i++) { |
| 60 | + const res = strSubMatch(files[i], filterLower); |
| 61 | + if (res.length > 1) { // length==1 means unmatched, >1 means having matched sub strings |
| 62 | + filterResult.push({matchResult: res, matchWeight: calcMatchedWeight(res)}); |
21 | 63 | }
|
22 | 64 | }
|
| 65 | + filterResult.sort((a, b) => b.matchWeight - a.matchWeight); |
| 66 | + filterResult = filterResult.slice(0, threshold); |
23 | 67 | } else {
|
24 | 68 | for (let i = 0; i < files.length && i < threshold; i++) {
|
25 |
| - fileRes.push([files[i]]); |
| 69 | + filterResult.push({matchResult: [files[i]], matchWeight: 0}); |
26 | 70 | }
|
27 | 71 | }
|
| 72 | + return filterResult; |
| 73 | +} |
| 74 | + |
| 75 | +function filterRepoFiles(filter) { |
| 76 | + const treeLink = $repoFindFileInput.attr('data-url-tree-link'); |
| 77 | + $repoFindFileTableBody.empty(); |
28 | 78 |
|
| 79 | + const filterResult = filterRepoFilesWeighted(files, filter); |
29 | 80 | const tmplRow = `<tr><td><a></a></td></tr>`;
|
30 | 81 |
|
31 |
| - $repoFindFileNoResult.toggle(fileRes.length === 0); |
32 |
| - for (const matchRes of fileRes) { |
| 82 | + $repoFindFileNoResult.toggle(filterResult.length === 0); |
| 83 | + for (const r of filterResult) { |
33 | 84 | const $row = $(tmplRow);
|
34 | 85 | const $a = $row.find('a');
|
35 |
| - $a.attr('href', `${treeLink}/${matchRes.join('')}`); |
| 86 | + $a.attr('href', `${treeLink}/${r.matchResult.join('')}`); |
36 | 87 | const $octiconFile = $(svg('octicon-file')).addClass('mr-3');
|
37 | 88 | $a.append($octiconFile);
|
38 |
| - // if the target file path is "abc/xyz", to search "bx", then the matchRes is ['a', 'b', 'c/', 'x', 'yz'] |
39 |
| - // the matchRes[odd] is matched and highlighted to red. |
40 |
| - for (let j = 0; j < matchRes.length; j++) { |
41 |
| - if (!matchRes[j]) continue; |
42 |
| - const $span = $('<span>').text(matchRes[j]); |
| 89 | + // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] |
| 90 | + // the matchResult[odd] is matched and highlighted to red. |
| 91 | + for (let j = 0; j < r.matchResult.length; j++) { |
| 92 | + if (!r.matchResult[j]) continue; |
| 93 | + const $span = $('<span>').text(r.matchResult[j]); |
43 | 94 | if (j % 2 === 1) $span.addClass('ui text red');
|
44 | 95 | $a.append($span);
|
45 | 96 | }
|
|
0 commit comments