dsh-vscode-mode 0.14.11 → 0.14.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/lib/client.js +133 -75
- package/lib/client.js.map +1 -1
- package/lib/index.js +129 -37
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sidebar/panels/FileExplorer.ts +24 -76
- package/src/client/sidebar/panels/SearchPanel.ts +12 -4
- package/src/client/sidebar/panels/useExplorerReveal.ts +109 -0
- package/src/search/fallback.ts +12 -9
- package/src/search/orchestrator.ts +1 -1
- package/src/search/query.ts +30 -6
- package/src/search/ranker.ts +52 -13
- package/src/search/ripgrep.ts +29 -12
package/lib/index.js
CHANGED
|
@@ -6563,14 +6563,36 @@ function pathKey(value) {
|
|
|
6563
6563
|
return pathText(value).toLocaleLowerCase("en-US");
|
|
6564
6564
|
}
|
|
6565
6565
|
/**
|
|
6566
|
-
*
|
|
6567
|
-
* @author ddj 2026年
|
|
6568
|
-
* @
|
|
6566
|
+
* @public
|
|
6567
|
+
* @author ddj 2026年10月14号
|
|
6568
|
+
* @description 按顺序匹配不连续字符;每个位置最多消费一次。
|
|
6569
|
+
* @param value 已归一化候选文本
|
|
6570
|
+
* @param text 已归一化查询文本
|
|
6571
|
+
* @returns 是否完整匹配所有查询字符
|
|
6572
|
+
*/
|
|
6573
|
+
function subsequence(value, text) {
|
|
6574
|
+
let offset = 0;
|
|
6575
|
+
for (const char of text) {
|
|
6576
|
+
const index = value.indexOf(char, offset);
|
|
6577
|
+
if (index < 0) return false;
|
|
6578
|
+
offset = index + char.length;
|
|
6579
|
+
}
|
|
6580
|
+
return true;
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6583
|
+
* @public
|
|
6584
|
+
* @author ddj 2026年10月14号
|
|
6585
|
+
* @description 连续匹配相对路径,或对无目录查询模糊匹配文件名。
|
|
6586
|
+
* @param value 文件路径(调用方移除工作区根前缀)
|
|
6569
6587
|
* @param query 已规范化 query
|
|
6570
|
-
* @returns
|
|
6588
|
+
* @returns 是否命中;空查询不命中
|
|
6571
6589
|
*/
|
|
6572
6590
|
function pathMatch(value, query) {
|
|
6573
|
-
|
|
6591
|
+
if (!query.text) return false;
|
|
6592
|
+
const path = pathText(value).toLocaleLowerCase("en-US");
|
|
6593
|
+
if (path.includes(query.text)) return true;
|
|
6594
|
+
if (query.text.includes("/")) return false;
|
|
6595
|
+
return subsequence(path.slice(path.lastIndexOf("/") + 1), query.text);
|
|
6574
6596
|
}
|
|
6575
6597
|
//#endregion
|
|
6576
6598
|
//#region src/search/ripgrep.ts
|
|
@@ -6628,18 +6650,32 @@ const FILE_EXCLUDES = [
|
|
|
6628
6650
|
* @returns 安全 glob
|
|
6629
6651
|
*/
|
|
6630
6652
|
function queryGlob(query) {
|
|
6653
|
+
return "**/*" + Array.from(pathText(query), globChar).join("") + "*";
|
|
6654
|
+
}
|
|
6655
|
+
/**
|
|
6656
|
+
* @private
|
|
6657
|
+
* @author ddj 2026年10月14号
|
|
6658
|
+
* @description 将字面字符转为安全且大小写不敏感的 glob 片段。
|
|
6659
|
+
* @param char 单个 Unicode 字符
|
|
6660
|
+
* @returns 转义字符或大小写字符类
|
|
6661
|
+
*/
|
|
6662
|
+
function globChar(char) {
|
|
6663
|
+
const lower = char.toLowerCase();
|
|
6664
|
+
const upper = char.toUpperCase();
|
|
6665
|
+
if (lower !== upper && lower.length === 1 && upper.length === 1) return "[" + lower + upper + "]";
|
|
6666
|
+
return /[\*?\[\]{}!]/.test(char) ? "\\" + char : char;
|
|
6667
|
+
}
|
|
6668
|
+
/**
|
|
6669
|
+
* @public
|
|
6670
|
+
* @author ddj 2026年10月14号
|
|
6671
|
+
* @description 构造仅匹配文件名的有序子序列 glob,不让星号跨目录。
|
|
6672
|
+
* @param query 无目录的字面查询文本
|
|
6673
|
+
* @returns 模糊 glob;空查询或带目录时返回 null
|
|
6674
|
+
*/
|
|
6675
|
+
function fuzzyGlob(query) {
|
|
6631
6676
|
const value = pathText(query);
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
const lower = char.toLowerCase();
|
|
6635
|
-
const upper = char.toUpperCase();
|
|
6636
|
-
if (lower !== upper && lower.length === 1 && upper.length === 1) {
|
|
6637
|
-
body += "[" + lower + upper + "]";
|
|
6638
|
-
continue;
|
|
6639
|
-
}
|
|
6640
|
-
body += /[\*?\[\]{}!]/.test(char) ? "\\" + char : char;
|
|
6641
|
-
}
|
|
6642
|
-
return "**/*" + body + "*";
|
|
6677
|
+
if (!value || value.includes("/")) return null;
|
|
6678
|
+
return "**/*" + Array.from(value, globChar).join("*") + "*";
|
|
6643
6679
|
}
|
|
6644
6680
|
/**
|
|
6645
6681
|
* 返回当前平台的 rg 可执行文件路径,不让可选依赖影响模块加载。
|
|
@@ -6773,6 +6809,8 @@ function filesArgv(binary, root, query) {
|
|
|
6773
6809
|
"--glob",
|
|
6774
6810
|
queryGlob(query)
|
|
6775
6811
|
];
|
|
6812
|
+
const fuzzy = fuzzyGlob(query);
|
|
6813
|
+
if (fuzzy) argv.push("--glob", fuzzy);
|
|
6776
6814
|
for (const excluded of EXCLUDES) argv.push("--glob", "!**/" + excluded + "/**");
|
|
6777
6815
|
for (const excluded of FILE_EXCLUDES) argv.push("--glob", "!" + excluded);
|
|
6778
6816
|
argv.push("--", root);
|
|
@@ -6944,30 +6982,31 @@ const EXCLUDED = /* @__PURE__ */ new Set([
|
|
|
6944
6982
|
]);
|
|
6945
6983
|
/**
|
|
6946
6984
|
* 在一个目录树中递归查找匹配文件。
|
|
6947
|
-
* @
|
|
6985
|
+
* @private
|
|
6986
|
+
* @author ddj 2026年10月14号
|
|
6948
6987
|
* @param fs 文件系统能力
|
|
6949
6988
|
* @param target 当前目录目标
|
|
6950
6989
|
* @param input 搜索输入
|
|
6951
6990
|
* @param files 已保留候选
|
|
6952
6991
|
* @param signal 取消信号
|
|
6992
|
+
* @param query 已规范化查询
|
|
6993
|
+
* @param prefix 当前目录相对于搜索根的路径
|
|
6953
6994
|
* @returns 是否因达到上限而截断
|
|
6954
6995
|
*/
|
|
6955
|
-
async function walk(fs, target, input, files, signal) {
|
|
6996
|
+
async function walk(fs, target, input, files, signal, query, prefix = "") {
|
|
6956
6997
|
if (signal.aborted) throw new DOMException("搜索已取消", "AbortError");
|
|
6957
6998
|
const entries = await fs.listDir(target, signal);
|
|
6958
6999
|
for (const entry of entries) {
|
|
6959
7000
|
if (signal.aborted) throw new DOMException("搜索已取消", "AbortError");
|
|
7001
|
+
const relative = prefix + entry.name;
|
|
6960
7002
|
if (entry.type === "directory") {
|
|
6961
7003
|
if (EXCLUDED.has(entry.name)) continue;
|
|
6962
|
-
if (await walk(fs, entry.target, input, files, signal)) return true;
|
|
7004
|
+
if (await walk(fs, entry.target, input, files, signal, query, relative + "/")) return true;
|
|
6963
7005
|
continue;
|
|
6964
7006
|
}
|
|
6965
7007
|
if (entry.type !== "file") continue;
|
|
6966
|
-
const path = pathText(entry.target.displayPath ??
|
|
6967
|
-
if (!pathMatch(
|
|
6968
|
-
raw: input.query,
|
|
6969
|
-
text: pathText(input.query).toLocaleLowerCase("en-US")
|
|
6970
|
-
})) continue;
|
|
7008
|
+
const path = pathText(entry.target.displayPath ?? relative);
|
|
7009
|
+
if (!pathMatch(relative, query)) continue;
|
|
6971
7010
|
files.push(path);
|
|
6972
7011
|
if (files.length >= Math.min(input.maxResults, RESULT_CAP)) return true;
|
|
6973
7012
|
}
|
|
@@ -6990,7 +7029,7 @@ async function searchFallback(input) {
|
|
|
6990
7029
|
} : { signal: input.signal });
|
|
6991
7030
|
const signal = input.signal ?? new AbortController().signal;
|
|
6992
7031
|
const files = [];
|
|
6993
|
-
const truncated = await walk(fs, rootTarget, input, files, signal);
|
|
7032
|
+
const truncated = await walk(fs, rootTarget, input, files, signal, prepareQuery(input.query));
|
|
6994
7033
|
return {
|
|
6995
7034
|
files,
|
|
6996
7035
|
truncated,
|
|
@@ -7025,29 +7064,82 @@ function candidateOf(path, source) {
|
|
|
7025
7064
|
};
|
|
7026
7065
|
}
|
|
7027
7066
|
/**
|
|
7028
|
-
*
|
|
7029
|
-
* @author ddj 2026年
|
|
7067
|
+
* @private
|
|
7068
|
+
* @author ddj 2026年10月14号
|
|
7069
|
+
* @description 贪心匹配文件名,优先词/驼峰边界,其次紧凑程度;不枚举组合。
|
|
7070
|
+
* @param name 原始大小写文件名
|
|
7071
|
+
* @param text 已规范化查询
|
|
7072
|
+
* @returns 边界漏命中数与跨度代价;无匹配返回 null
|
|
7073
|
+
*/
|
|
7074
|
+
function fuzzyCost(name, text) {
|
|
7075
|
+
const base = name.toLocaleLowerCase("en-US");
|
|
7076
|
+
let offset = 0;
|
|
7077
|
+
let first = -1;
|
|
7078
|
+
let misses = 0;
|
|
7079
|
+
for (const char of text) {
|
|
7080
|
+
const index = base.indexOf(char, offset);
|
|
7081
|
+
if (index < 0) return null;
|
|
7082
|
+
if (first < 0) first = index;
|
|
7083
|
+
if (!(index === 0 || /[\s_.\-]/.test(name[index - 1]) || /[a-z0-9]/.test(name[index - 1]) && /[A-Z]/.test(name[index]))) misses++;
|
|
7084
|
+
offset = index + char.length;
|
|
7085
|
+
}
|
|
7086
|
+
return [misses, offset - first - text.length];
|
|
7087
|
+
}
|
|
7088
|
+
/**
|
|
7089
|
+
* @private
|
|
7090
|
+
* @author ddj 2026年10月14号
|
|
7091
|
+
* @description 计算完全/前缀/连续/模糊/路径命中的排序等级。
|
|
7092
|
+
* @param candidate 文件候选
|
|
7093
|
+
* @param text 已规范化查询
|
|
7094
|
+
* @returns 等级、边界与跨度代价
|
|
7095
|
+
*/
|
|
7096
|
+
function rankKey(candidate, text) {
|
|
7097
|
+
const base = candidate.basename.toLocaleLowerCase("en-US");
|
|
7098
|
+
if (base === text) return [
|
|
7099
|
+
0,
|
|
7100
|
+
0,
|
|
7101
|
+
0
|
|
7102
|
+
];
|
|
7103
|
+
if (base.startsWith(text)) return [
|
|
7104
|
+
1,
|
|
7105
|
+
0,
|
|
7106
|
+
0
|
|
7107
|
+
];
|
|
7108
|
+
if (base.includes(text)) return [
|
|
7109
|
+
2,
|
|
7110
|
+
0,
|
|
7111
|
+
0
|
|
7112
|
+
];
|
|
7113
|
+
const cost = text.includes("/") ? null : fuzzyCost(candidate.basename, text);
|
|
7114
|
+
if (cost) return [3, ...cost];
|
|
7115
|
+
return [
|
|
7116
|
+
candidate.normalizedPath.includes(text) ? 4 : 5,
|
|
7117
|
+
0,
|
|
7118
|
+
0
|
|
7119
|
+
];
|
|
7120
|
+
}
|
|
7121
|
+
/**
|
|
7122
|
+
* @public
|
|
7123
|
+
* @author ddj 2026年10月14号
|
|
7124
|
+
* @description 每个候选只评分一次,排序不修改调用方数组。
|
|
7030
7125
|
* @param candidates 候选列表
|
|
7031
7126
|
* @param query 已规范化 query
|
|
7032
7127
|
* @returns 排序后的候选
|
|
7033
7128
|
*/
|
|
7034
7129
|
function rankCandidates(candidates, query) {
|
|
7035
|
-
const
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
return 3;
|
|
7042
|
-
};
|
|
7043
|
-
return [...candidates].sort((left, right) => score(left) - score(right) || left.normalizedPath.localeCompare(right.normalizedPath, "en-US"));
|
|
7130
|
+
const ranked = candidates.map((candidate) => ({
|
|
7131
|
+
candidate,
|
|
7132
|
+
key: rankKey(candidate, query.text)
|
|
7133
|
+
}));
|
|
7134
|
+
ranked.sort((left, right) => left.key[0] - right.key[0] || left.key[1] - right.key[1] || left.key[2] - right.key[2] || left.candidate.normalizedPath.localeCompare(right.candidate.normalizedPath, "en-US") || left.candidate.path.localeCompare(right.candidate.path, "en-US"));
|
|
7135
|
+
return ranked.map((item) => item.candidate);
|
|
7044
7136
|
}
|
|
7045
7137
|
//#endregion
|
|
7046
7138
|
//#region src/search/orchestrator.ts
|
|
7047
7139
|
const CACHE_TTL = 6e4;
|
|
7048
7140
|
const CACHE_LIMIT = 100;
|
|
7049
7141
|
const RESULT_LIMIT = 50;
|
|
7050
|
-
const PROVIDER_VERSION$1 = "ripgrep-
|
|
7142
|
+
const PROVIDER_VERSION$1 = "ripgrep-fuzzy-v2";
|
|
7051
7143
|
const POLICY_VERSION = "search-policy-v1";
|
|
7052
7144
|
/**
|
|
7053
7145
|
* 短期有界搜索缓存(泛型:文件/内容搜索共用;clone 由调用方保证写隔离)。
|