gitea

Development moved to Codeberg

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
import $ from 'jquery';
import {svg} from '../svg.js';

const {csrf} = window.config;

const threshold = 50;
let files = [];
let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult;


// return the case-insensitive sub-match result as an array:  [unmatched, matched, unmatched, matched, ...]
// res[even] is unmatched, res[odd] is matched, see unit tests for examples
// argument subLower must be a lower-cased string.
export function strSubMatch(full, subLower) {
  const res = [''];
  let i = 0, j = 0;
  const fullLower = full.toLowerCase();
  while (i < subLower.length && j < fullLower.length) {
    if (subLower[i] === fullLower[j]) {
      if (res.length % 2 !== 0) res.push('');
      res[res.length - 1] += full[j];
      j++;
      i++;
    } else {
      if (res.length % 2 === 0) res.push('');
      res[res.length - 1] += full[j];
      j++;
    }
  }
  if (i !== subLower.length) {
    // if the sub string doesn't match the full, only return the full as unmatched.
    return [full];
  }
  if (j < full.length) {
    // append remaining chars from full to result as unmatched
    if (res.length % 2 === 0) res.push('');
    res[res.length - 1] += full.substring(j);
  }
  return res;
}

export function calcMatchedWeight(matchResult) {
  let weight = 0;
  for (let i = 0; i < matchResult.length; i++) {
    if (i % 2 === 1) { // matches are on odd indices, see strSubMatch
      // use a function f(x+x) > f(x) + f(x) to make the longer matched string has higher weight.
      weight += matchResult[i].length * matchResult[i].length;
    }
  }
  return weight;
}

export function filterRepoFilesWeighted(files, filter) {
  let filterResult = [];
  if (filter) {
    const filterLower = filter.toLowerCase();
    // TODO: for large repo, this loop could be slow, maybe there could be one more limit:
    // ... && filterResult.length < threshold * 20,  wait for more feedbacks
    for (let i = 0; i < files.length; i++) {
      const res = strSubMatch(files[i], filterLower);
      if (res.length > 1) { // length==1 means unmatched, >1 means having matched sub strings
        filterResult.push({matchResult: res, matchWeight: calcMatchedWeight(res)});
      }
    }
    filterResult.sort((a, b) => b.matchWeight - a.matchWeight);
    filterResult = filterResult.slice(0, threshold);
  } else {
    for (let i = 0; i < files.length && i < threshold; i++) {
      filterResult.push({matchResult: [files[i]], matchWeight: 0});
    }
  }
  return filterResult;
}

function filterRepoFiles(filter) {
  const treeLink = $repoFindFileInput.attr('data-url-tree-link');
  $repoFindFileTableBody.empty();

  const filterResult = filterRepoFilesWeighted(files, filter);
  const tmplRow = `<tr><td><a></a></td></tr>`;

  $repoFindFileNoResult.toggle(filterResult.length === 0);
  for (const r of filterResult) {
    const $row = $(tmplRow);
    const $a = $row.find('a');
    $a.attr('href', `${treeLink}/${r.matchResult.join('')}`);
    const $octiconFile = $(svg('octicon-file')).addClass('mr-3');
    $a.append($octiconFile);
    // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz']
    // the matchResult[odd] is matched and highlighted to red.
    for (let j = 0; j < r.matchResult.length; j++) {
      if (!r.matchResult[j]) continue;
      const $span = $('<span>').text(r.matchResult[j]);
      if (j % 2 === 1) $span.addClass('ui text red');
      $a.append($span);
    }
    $repoFindFileTableBody.append($row);
  }
}

async function loadRepoFiles() {
  files = await $.ajax({
    url: $repoFindFileInput.attr('data-url-data-link'),
    headers: {'X-Csrf-Token': csrf}
  });
  filterRepoFiles($repoFindFileInput.val());
}

export function initFindFileInRepo() {
  $repoFindFileInput = $('#repo-file-find-input');
  if (!$repoFindFileInput.length) return;

  $repoFindFileTableBody = $('#repo-find-file-table tbody');
  $repoFindFileNoResult = $('#repo-find-file-no-result');
  $repoFindFileInput.on('input', () => filterRepoFiles($repoFindFileInput.val()));

  loadRepoFiles();
}