dsh-vscode-mode 0.1.45 → 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/lib/index.js CHANGED
@@ -5760,6 +5760,7 @@ function createLspClient(transport, events = {}) {
5760
5760
  const pending = /* @__PURE__ */ new Map();
5761
5761
  let serverCapabilities = {
5762
5762
  definition: false,
5763
+ declaration: false,
5763
5764
  references: false,
5764
5765
  documentSymbol: false,
5765
5766
  workspaceSymbol: false,
@@ -5894,6 +5895,7 @@ function createLspClient(transport, events = {}) {
5894
5895
  didSave: true
5895
5896
  },
5896
5897
  definition: { dynamicRegistration: false },
5898
+ declaration: { dynamicRegistration: false },
5897
5899
  references: { dynamicRegistration: false },
5898
5900
  documentSymbol: { dynamicRegistration: false },
5899
5901
  hover: { dynamicRegistration: false },
@@ -5913,6 +5915,7 @@ function createLspClient(transport, events = {}) {
5913
5915
  const legend = caps.semanticTokensProvider?.legend;
5914
5916
  serverCapabilities = {
5915
5917
  definition: Boolean(caps.definitionProvider),
5918
+ declaration: Boolean(caps.declarationProvider),
5916
5919
  references: Boolean(caps.referencesProvider),
5917
5920
  documentSymbol: Boolean(caps.documentSymbolProvider),
5918
5921
  workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
@@ -5966,6 +5969,73 @@ function pathToFileUri(absPath) {
5966
5969
  return pathToFileURL(normalized).toString();
5967
5970
  }
5968
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
5969
6039
  //#region src/lsp/server.ts
5970
6040
  /**
5971
6041
  * dsh-vscode-mode host — 单语言服务器会话(绑定 工作区根 + 语言)。
@@ -5990,6 +6060,7 @@ function createLspServer(spec, root, languageId, logger) {
5990
6060
  let client = null;
5991
6061
  let capabilities = {
5992
6062
  definition: false,
6063
+ declaration: false,
5993
6064
  references: false,
5994
6065
  documentSymbol: false,
5995
6066
  workspaceSymbol: false,
@@ -6173,13 +6244,42 @@ function createLspServer(spec, root, languageId, logger) {
6173
6244
  async definition(path, line, character) {
6174
6245
  const doc = docs.get(path);
6175
6246
  if (!doc || !await readyWait) return [];
6176
- return normalizeLocations(await ensureClient().request("textDocument/definition", {
6247
+ const client = ensureClient();
6248
+ const position = {
6249
+ line,
6250
+ character
6251
+ };
6252
+ const direct = normalizeLocations(await client.request("textDocument/definition", {
6177
6253
  textDocument: { uri: doc.uri },
6178
- position: {
6179
- line,
6180
- character
6181
- }
6254
+ position
6182
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 [];
6183
6283
  },
6184
6284
  async references(path, line, character, includeDeclaration) {
6185
6285
  const doc = docs.get(path);