dsh-vscode-mode 0.1.44 → 0.1.46
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 -1
- package/lib/client.js +481 -29
- package/lib/client.js.map +1 -1
- package/lib/index.js +111 -8
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/keybindings.ts +41 -15
- package/src/client/monaco/lsp/providers.ts +10 -1
- package/src/client/monaco/lsp/underline.ts +101 -0
- package/src/client/navHistory.ts +104 -0
- package/src/client/styles/editor.css +4 -0
- package/src/client/ui/EditorView.ts +202 -10
- package/src/client/ui/KeybindingsPanel.ts +3 -3
- package/src/client/ui/QuickOpen.ts +2 -2
- package/src/lsp/client.ts +4 -0
- package/src/lsp/derive.ts +96 -0
- package/src/lsp/server.ts +50 -4
- package/src/shared/keybindings.ts +5 -2
- package/src/shared/lsp.ts +57 -0
package/lib/index.js
CHANGED
|
@@ -112,15 +112,18 @@ async function refreshMcp(ctx, id) {
|
|
|
112
112
|
/**
|
|
113
113
|
* dsh-vscode-mode shared — 快捷键命令目录与默认键位(双面契约)。
|
|
114
114
|
* 纯数据模块:host(settings schema 默认值)与 client(执行匹配/设置页)共用。
|
|
115
|
-
* 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 =
|
|
115
|
+
* 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 = 未绑定;
|
|
116
|
+
* `|` 连接多个候选(任一命中即触发),如 `Alt+ArrowLeft|Ctrl+Alt+-`。
|
|
116
117
|
* 作者 ddj 2026年08月26号
|
|
117
118
|
*/
|
|
118
|
-
/** 命令 id →
|
|
119
|
+
/** 命令 id → 默认键位(可为多候选)。命令目录以此为准,新增命令只需加一项。 */
|
|
119
120
|
const KEYBINDING_DEFAULTS = {
|
|
120
121
|
"edrv.save": "Ctrl+S",
|
|
121
122
|
"edrv.quickOpen": "Ctrl+P",
|
|
122
123
|
"edrv.toggleSidebar": "Ctrl+B",
|
|
123
|
-
"edrv.searchInFiles": "Ctrl+Shift+F"
|
|
124
|
+
"edrv.searchInFiles": "Ctrl+Shift+F",
|
|
125
|
+
"edrv.navigateBack": "Alt+ArrowLeft|Ctrl+Alt+-",
|
|
126
|
+
"edrv.navigateForward": "Alt+ArrowRight|Ctrl+Shift+-"
|
|
124
127
|
};
|
|
125
128
|
//#endregion
|
|
126
129
|
//#region src/fileOpenSettings.ts
|
|
@@ -5757,6 +5760,7 @@ function createLspClient(transport, events = {}) {
|
|
|
5757
5760
|
const pending = /* @__PURE__ */ new Map();
|
|
5758
5761
|
let serverCapabilities = {
|
|
5759
5762
|
definition: false,
|
|
5763
|
+
declaration: false,
|
|
5760
5764
|
references: false,
|
|
5761
5765
|
documentSymbol: false,
|
|
5762
5766
|
workspaceSymbol: false,
|
|
@@ -5891,6 +5895,7 @@ function createLspClient(transport, events = {}) {
|
|
|
5891
5895
|
didSave: true
|
|
5892
5896
|
},
|
|
5893
5897
|
definition: { dynamicRegistration: false },
|
|
5898
|
+
declaration: { dynamicRegistration: false },
|
|
5894
5899
|
references: { dynamicRegistration: false },
|
|
5895
5900
|
documentSymbol: { dynamicRegistration: false },
|
|
5896
5901
|
hover: { dynamicRegistration: false },
|
|
@@ -5910,6 +5915,7 @@ function createLspClient(transport, events = {}) {
|
|
|
5910
5915
|
const legend = caps.semanticTokensProvider?.legend;
|
|
5911
5916
|
serverCapabilities = {
|
|
5912
5917
|
definition: Boolean(caps.definitionProvider),
|
|
5918
|
+
declaration: Boolean(caps.declarationProvider),
|
|
5913
5919
|
references: Boolean(caps.referencesProvider),
|
|
5914
5920
|
documentSymbol: Boolean(caps.documentSymbolProvider),
|
|
5915
5921
|
workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
|
|
@@ -5963,6 +5969,73 @@ function pathToFileUri(absPath) {
|
|
|
5963
5969
|
return pathToFileURL(normalized).toString();
|
|
5964
5970
|
}
|
|
5965
5971
|
//#endregion
|
|
5972
|
+
//#region src/lsp/derive.ts
|
|
5973
|
+
/** file:// URI(或裸路径)→ 归一化绝对路径(小写、/ 分隔)。 */
|
|
5974
|
+
function normAbs(uri) {
|
|
5975
|
+
const path = String(uri ?? "").replace(/^file:\/\/\/?/, "");
|
|
5976
|
+
return decodeURIComponent(path).replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1").toLowerCase();
|
|
5977
|
+
}
|
|
5978
|
+
/** 取文档 text 在 LSP range(0-based)内的切片。 */
|
|
5979
|
+
function sliceRange(text, range) {
|
|
5980
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
5981
|
+
const start = range.start;
|
|
5982
|
+
const end = range.end;
|
|
5983
|
+
const startLine = Math.max(0, start.line);
|
|
5984
|
+
const endLine = Math.max(startLine, end.line);
|
|
5985
|
+
if (startLine >= lines.length || endLine >= lines.length) return "";
|
|
5986
|
+
const startChar = Math.max(0, start.character);
|
|
5987
|
+
if (startLine === endLine) return lines[startLine].slice(startChar, Math.max(startChar, end.character));
|
|
5988
|
+
const first = lines[startLine].slice(startChar);
|
|
5989
|
+
const middle = lines.slice(startLine + 1, endLine);
|
|
5990
|
+
const last = lines[endLine].slice(0, Math.max(0, end.character));
|
|
5991
|
+
return [
|
|
5992
|
+
first,
|
|
5993
|
+
...middle,
|
|
5994
|
+
last
|
|
5995
|
+
].join("\n");
|
|
5996
|
+
}
|
|
5997
|
+
/** LSP position 是否落在 range 内(start 包含、end 排除;零宽按点匹配)。 */
|
|
5998
|
+
function posInLspRange(pos, range) {
|
|
5999
|
+
const start = range.start;
|
|
6000
|
+
const end = range.end ?? start;
|
|
6001
|
+
if (start.line === end.line && start.character === end.character) return pos.line === start.line && pos.character === start.character;
|
|
6002
|
+
if (pos.line < start.line || pos.line > end.line) return false;
|
|
6003
|
+
if (pos.line === start.line && pos.character < start.character) return false;
|
|
6004
|
+
if (pos.line === end.line && pos.character >= end.character) return false;
|
|
6005
|
+
return true;
|
|
6006
|
+
}
|
|
6007
|
+
/** 两个 range 是否完全一致。 */
|
|
6008
|
+
function sameRange(a, b) {
|
|
6009
|
+
return a.start.line === b.start.line && a.start.character === b.start.character && a.end.line === b.end.line && a.end.character === b.end.character;
|
|
6010
|
+
}
|
|
6011
|
+
/** range 长度(行/列均按 0-based 差估算,仅用于取"最小包裹"排序)。 */
|
|
6012
|
+
function rangeSize(r) {
|
|
6013
|
+
return (r.end.line - r.start.line) * 1e6 + (r.end.line === r.start.line ? r.end.character - r.start.character : r.end.character);
|
|
6014
|
+
}
|
|
6015
|
+
/**
|
|
6016
|
+
* 从引用列表推导定义位置。
|
|
6017
|
+
* @author ddj 2026年09月02号
|
|
6018
|
+
* @param queryPos 查询位置(LSP 0-based)
|
|
6019
|
+
* @param locations references(includeDeclaration) 结果
|
|
6020
|
+
* @param docText 当前文档全文(词文本一致校验)
|
|
6021
|
+
* @param docAbsPath 当前文档绝对路径(root+相对路径,与 loc.uri 归一化后比较)
|
|
6022
|
+
* @returns 推导出的定义位置;无法确定返回 null
|
|
6023
|
+
*/
|
|
6024
|
+
function deriveDefinitionFromLocations(queryPos, locations, docText, docAbsPath) {
|
|
6025
|
+
if (!locations.length) return null;
|
|
6026
|
+
const docAbs = normAbs(docAbsPath);
|
|
6027
|
+
const containing = locations.filter((loc) => posInLspRange(queryPos, loc.range)).sort((a, b) => rangeSize(a.range) - rangeSize(b.range))[0];
|
|
6028
|
+
if (!containing) return null;
|
|
6029
|
+
const queryWord = sliceRange(docText, containing.range);
|
|
6030
|
+
if (!queryWord) return null;
|
|
6031
|
+
const candidates = locations.filter((loc) => {
|
|
6032
|
+
return normAbs(loc.uri) === docAbs && sliceRange(docText, loc.range) === queryWord;
|
|
6033
|
+
});
|
|
6034
|
+
if (!candidates.length) return null;
|
|
6035
|
+
const first = candidates[0];
|
|
6036
|
+
return sameRange(first.range, containing.range) ? containing : first;
|
|
6037
|
+
}
|
|
6038
|
+
//#endregion
|
|
5966
6039
|
//#region src/lsp/server.ts
|
|
5967
6040
|
/**
|
|
5968
6041
|
* dsh-vscode-mode host — 单语言服务器会话(绑定 工作区根 + 语言)。
|
|
@@ -5987,6 +6060,7 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
5987
6060
|
let client = null;
|
|
5988
6061
|
let capabilities = {
|
|
5989
6062
|
definition: false,
|
|
6063
|
+
declaration: false,
|
|
5990
6064
|
references: false,
|
|
5991
6065
|
documentSymbol: false,
|
|
5992
6066
|
workspaceSymbol: false,
|
|
@@ -6170,13 +6244,42 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
6170
6244
|
async definition(path, line, character) {
|
|
6171
6245
|
const doc = docs.get(path);
|
|
6172
6246
|
if (!doc || !await readyWait) return [];
|
|
6173
|
-
|
|
6247
|
+
const client = ensureClient();
|
|
6248
|
+
const position = {
|
|
6249
|
+
line,
|
|
6250
|
+
character
|
|
6251
|
+
};
|
|
6252
|
+
const direct = normalizeLocations(await client.request("textDocument/definition", {
|
|
6174
6253
|
textDocument: { uri: doc.uri },
|
|
6175
|
-
position
|
|
6176
|
-
line,
|
|
6177
|
-
character
|
|
6178
|
-
}
|
|
6254
|
+
position
|
|
6179
6255
|
}));
|
|
6256
|
+
if (direct.length) return direct;
|
|
6257
|
+
if (capabilities.declaration) try {
|
|
6258
|
+
const decl = normalizeLocations(await client.request("textDocument/declaration", {
|
|
6259
|
+
textDocument: { uri: doc.uri },
|
|
6260
|
+
position
|
|
6261
|
+
}));
|
|
6262
|
+
if (decl.length) {
|
|
6263
|
+
log("definition fallback → declaration (" + path + ":" + line + ":" + character + ")");
|
|
6264
|
+
return decl;
|
|
6265
|
+
}
|
|
6266
|
+
} catch (error) {
|
|
6267
|
+
log("declaration request failed: " + String(error));
|
|
6268
|
+
}
|
|
6269
|
+
try {
|
|
6270
|
+
const derived = deriveDefinitionFromLocations(position, normalizeLocations(await client.request("textDocument/references", {
|
|
6271
|
+
textDocument: { uri: doc.uri },
|
|
6272
|
+
position,
|
|
6273
|
+
context: { includeDeclaration: true }
|
|
6274
|
+
})), doc.text, doc.uri);
|
|
6275
|
+
if (derived) {
|
|
6276
|
+
log("definition fallback → references-derived (" + path + ":" + line + ":" + character + ")");
|
|
6277
|
+
return [derived];
|
|
6278
|
+
}
|
|
6279
|
+
} catch (error) {
|
|
6280
|
+
log("references fallback failed: " + String(error));
|
|
6281
|
+
}
|
|
6282
|
+
return [];
|
|
6180
6283
|
},
|
|
6181
6284
|
async references(path, line, character, includeDeclaration) {
|
|
6182
6285
|
const doc = docs.get(path);
|