@treedy/vue-lsp-mcp 0.2.0 → 0.2.2
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 +33 -0
- package/dist/index.js +1115 -27
- package/dist/vue-service.d.ts +139 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19589,6 +19589,61 @@ ${responseNotification}`;
|
|
|
19589
19589
|
}
|
|
19590
19590
|
}, 30000);
|
|
19591
19591
|
}
|
|
19592
|
+
var activeWorkspace = null;
|
|
19593
|
+
function setActiveWorkspace(workspace) {
|
|
19594
|
+
activeWorkspace = path.resolve(workspace);
|
|
19595
|
+
return activeWorkspace;
|
|
19596
|
+
}
|
|
19597
|
+
function resolveFilePath(filePath) {
|
|
19598
|
+
const pathObj = path.parse(filePath);
|
|
19599
|
+
let absPath;
|
|
19600
|
+
if (!path.isAbsolute(filePath)) {
|
|
19601
|
+
if (activeWorkspace) {
|
|
19602
|
+
absPath = path.resolve(activeWorkspace, filePath);
|
|
19603
|
+
} else {
|
|
19604
|
+
absPath = path.resolve(filePath);
|
|
19605
|
+
}
|
|
19606
|
+
} else {
|
|
19607
|
+
absPath = filePath;
|
|
19608
|
+
}
|
|
19609
|
+
absPath = path.resolve(absPath);
|
|
19610
|
+
if (activeWorkspace && !absPath.startsWith(activeWorkspace)) {
|
|
19611
|
+
return {
|
|
19612
|
+
absPath: null,
|
|
19613
|
+
error: JSON.stringify({
|
|
19614
|
+
error: "Context Mismatch",
|
|
19615
|
+
message: `The file '${filePath}' resolves to '${absPath}', which is outside the active workspace '${activeWorkspace}'.
|
|
19616
|
+
|
|
19617
|
+
Current Logic:
|
|
19618
|
+
1. I only analyze files from the active project to ensure accuracy and save resources.
|
|
19619
|
+
2. You must explicitly switch the workspace if you want to work on a different project.
|
|
19620
|
+
|
|
19621
|
+
Action Required:
|
|
19622
|
+
Please call 'switch_workspace(path="...")' with the new project root before retrying.`,
|
|
19623
|
+
currentWorkspace: activeWorkspace,
|
|
19624
|
+
resolvedPath: absPath
|
|
19625
|
+
})
|
|
19626
|
+
};
|
|
19627
|
+
}
|
|
19628
|
+
return { absPath, error: null };
|
|
19629
|
+
}
|
|
19630
|
+
function validateFileWorkspace(filePath) {
|
|
19631
|
+
const { error: error2 } = resolveFilePath(filePath);
|
|
19632
|
+
return error2;
|
|
19633
|
+
}
|
|
19634
|
+
function clearAllConnections() {
|
|
19635
|
+
for (const conn of activeConnections) {
|
|
19636
|
+
try {
|
|
19637
|
+
conn.process.kill();
|
|
19638
|
+
if (conn.tsserver)
|
|
19639
|
+
conn.tsserver.kill();
|
|
19640
|
+
} catch (e) {}
|
|
19641
|
+
}
|
|
19642
|
+
activeConnections.clear();
|
|
19643
|
+
connectionCache.clear();
|
|
19644
|
+
documentContents.clear();
|
|
19645
|
+
documentVersions.clear();
|
|
19646
|
+
}
|
|
19592
19647
|
function findProjectRoot(filePath) {
|
|
19593
19648
|
let dir = path.dirname(path.resolve(filePath));
|
|
19594
19649
|
while (dir !== path.dirname(dir)) {
|
|
@@ -19611,6 +19666,17 @@ function getFileContent(filePath) {
|
|
|
19611
19666
|
}
|
|
19612
19667
|
return "";
|
|
19613
19668
|
}
|
|
19669
|
+
function positionToOffset(content, line, character) {
|
|
19670
|
+
const lines = content.split(`
|
|
19671
|
+
`);
|
|
19672
|
+
const safeLine = Math.max(0, Math.min(line, lines.length - 1));
|
|
19673
|
+
let offset = 0;
|
|
19674
|
+
for (let i = 0;i < safeLine; i++) {
|
|
19675
|
+
offset += lines[i].length + 1;
|
|
19676
|
+
}
|
|
19677
|
+
const safeChar = Math.max(0, Math.min(character, lines[safeLine].length));
|
|
19678
|
+
return offset + safeChar;
|
|
19679
|
+
}
|
|
19614
19680
|
function toUri(filePath) {
|
|
19615
19681
|
return `file://${path.resolve(filePath)}`;
|
|
19616
19682
|
}
|
|
@@ -19820,7 +19886,7 @@ async function getConnection(projectRoot) {
|
|
|
19820
19886
|
}
|
|
19821
19887
|
}
|
|
19822
19888
|
}
|
|
19823
|
-
await sendMessage(conn, "initialize", {
|
|
19889
|
+
const initResult = await sendMessage(conn, "initialize", {
|
|
19824
19890
|
processId: process.pid,
|
|
19825
19891
|
rootUri: toUri(projectRoot),
|
|
19826
19892
|
rootPath: projectRoot,
|
|
@@ -19830,7 +19896,23 @@ async function getConnection(projectRoot) {
|
|
|
19830
19896
|
completion: { completionItem: { snippetSupport: true } },
|
|
19831
19897
|
signatureHelp: {},
|
|
19832
19898
|
definition: {},
|
|
19899
|
+
implementation: {},
|
|
19900
|
+
typeDefinition: {},
|
|
19833
19901
|
references: {},
|
|
19902
|
+
documentHighlight: {},
|
|
19903
|
+
selectionRange: {},
|
|
19904
|
+
foldingRange: {},
|
|
19905
|
+
documentLink: {},
|
|
19906
|
+
callHierarchy: {},
|
|
19907
|
+
codeAction: {
|
|
19908
|
+
dynamicRegistration: false,
|
|
19909
|
+
codeActionLiteralSupport: {
|
|
19910
|
+
codeActionKind: {
|
|
19911
|
+
valueSet: ["quickfix", "refactor", "source", "source.organizeImports"]
|
|
19912
|
+
}
|
|
19913
|
+
}
|
|
19914
|
+
},
|
|
19915
|
+
rename: { prepareSupport: true },
|
|
19834
19916
|
publishDiagnostics: {},
|
|
19835
19917
|
synchronization: {
|
|
19836
19918
|
didOpen: true,
|
|
@@ -19849,7 +19931,7 @@ async function getConnection(projectRoot) {
|
|
|
19849
19931
|
tsdk: tsSdkPath || ""
|
|
19850
19932
|
},
|
|
19851
19933
|
vue: {
|
|
19852
|
-
hybridMode:
|
|
19934
|
+
hybridMode: true
|
|
19853
19935
|
}
|
|
19854
19936
|
},
|
|
19855
19937
|
workspaceFolders: [
|
|
@@ -19859,6 +19941,7 @@ async function getConnection(projectRoot) {
|
|
|
19859
19941
|
}
|
|
19860
19942
|
]
|
|
19861
19943
|
});
|
|
19944
|
+
conn.serverCapabilities = initResult?.capabilities || {};
|
|
19862
19945
|
sendNotification(conn, "initialized", {});
|
|
19863
19946
|
conn.initialized = true;
|
|
19864
19947
|
return conn;
|
|
@@ -19956,6 +20039,52 @@ async function getDefinition(filePath, line, column) {
|
|
|
19956
20039
|
return [];
|
|
19957
20040
|
}
|
|
19958
20041
|
}
|
|
20042
|
+
async function getImplementation(filePath, line, column) {
|
|
20043
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20044
|
+
const conn = await getConnection(projectRoot);
|
|
20045
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20046
|
+
const result = await sendMessage(conn, "textDocument/implementation", {
|
|
20047
|
+
textDocument: { uri: toUri(filePath) },
|
|
20048
|
+
position: { line: line - 1, character: column - 1 }
|
|
20049
|
+
});
|
|
20050
|
+
if (!result)
|
|
20051
|
+
return [];
|
|
20052
|
+
const locations = Array.isArray(result) ? result : [result];
|
|
20053
|
+
return locations.map((loc) => {
|
|
20054
|
+
const uri = loc.targetUri || loc.uri;
|
|
20055
|
+
const range = loc.targetRange || loc.range;
|
|
20056
|
+
return {
|
|
20057
|
+
file: fromUri(uri),
|
|
20058
|
+
line: range.start.line + 1,
|
|
20059
|
+
column: range.start.character + 1,
|
|
20060
|
+
endLine: range.end.line + 1,
|
|
20061
|
+
endColumn: range.end.character + 1
|
|
20062
|
+
};
|
|
20063
|
+
});
|
|
20064
|
+
}
|
|
20065
|
+
async function getTypeDefinition(filePath, line, column) {
|
|
20066
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20067
|
+
const conn = await getConnection(projectRoot);
|
|
20068
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20069
|
+
const result = await sendMessage(conn, "textDocument/typeDefinition", {
|
|
20070
|
+
textDocument: { uri: toUri(filePath) },
|
|
20071
|
+
position: { line: line - 1, character: column - 1 }
|
|
20072
|
+
});
|
|
20073
|
+
if (!result)
|
|
20074
|
+
return [];
|
|
20075
|
+
const locations = Array.isArray(result) ? result : [result];
|
|
20076
|
+
return locations.map((loc) => {
|
|
20077
|
+
const uri = loc.targetUri || loc.uri;
|
|
20078
|
+
const range = loc.targetRange || loc.range;
|
|
20079
|
+
return {
|
|
20080
|
+
file: fromUri(uri),
|
|
20081
|
+
line: range.start.line + 1,
|
|
20082
|
+
column: range.start.character + 1,
|
|
20083
|
+
endLine: range.end.line + 1,
|
|
20084
|
+
endColumn: range.end.character + 1
|
|
20085
|
+
};
|
|
20086
|
+
});
|
|
20087
|
+
}
|
|
19959
20088
|
async function getReferences(filePath, line, column) {
|
|
19960
20089
|
const projectRoot = findProjectRoot(filePath);
|
|
19961
20090
|
const conn = await getConnection(projectRoot);
|
|
@@ -19979,6 +20108,25 @@ async function getReferences(filePath, line, column) {
|
|
|
19979
20108
|
return [];
|
|
19980
20109
|
}
|
|
19981
20110
|
}
|
|
20111
|
+
async function getDocumentHighlights(filePath, line, column) {
|
|
20112
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20113
|
+
const conn = await getConnection(projectRoot);
|
|
20114
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20115
|
+
const result = await sendMessage(conn, "textDocument/documentHighlight", {
|
|
20116
|
+
textDocument: { uri: toUri(filePath) },
|
|
20117
|
+
position: { line: line - 1, character: column - 1 }
|
|
20118
|
+
});
|
|
20119
|
+
if (!result || !Array.isArray(result))
|
|
20120
|
+
return [];
|
|
20121
|
+
return result.map((item) => ({
|
|
20122
|
+
file: path.resolve(filePath),
|
|
20123
|
+
line: item.range.start.line + 1,
|
|
20124
|
+
column: item.range.start.character + 1,
|
|
20125
|
+
endLine: item.range.end.line + 1,
|
|
20126
|
+
endColumn: item.range.end.character + 1,
|
|
20127
|
+
kind: item.kind ?? 1
|
|
20128
|
+
}));
|
|
20129
|
+
}
|
|
19982
20130
|
async function getCompletions(filePath, line, column, limit = 20) {
|
|
19983
20131
|
const projectRoot = findProjectRoot(filePath);
|
|
19984
20132
|
const conn = await getConnection(projectRoot);
|
|
@@ -20059,6 +20207,275 @@ async function getSignatureHelp(filePath, line, column) {
|
|
|
20059
20207
|
return null;
|
|
20060
20208
|
}
|
|
20061
20209
|
}
|
|
20210
|
+
async function getPrepareRename(filePath, line, column) {
|
|
20211
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20212
|
+
const conn = await getConnection(projectRoot);
|
|
20213
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20214
|
+
const result = await sendMessage(conn, "textDocument/prepareRename", {
|
|
20215
|
+
textDocument: { uri: toUri(filePath) },
|
|
20216
|
+
position: { line: line - 1, character: column - 1 }
|
|
20217
|
+
});
|
|
20218
|
+
if (!result)
|
|
20219
|
+
return { canRename: false };
|
|
20220
|
+
const range = "range" in result ? result.range : result;
|
|
20221
|
+
const placeholder = "placeholder" in result ? result.placeholder : undefined;
|
|
20222
|
+
return {
|
|
20223
|
+
canRename: true,
|
|
20224
|
+
placeholder,
|
|
20225
|
+
range: {
|
|
20226
|
+
line: range.start.line + 1,
|
|
20227
|
+
column: range.start.character + 1,
|
|
20228
|
+
endLine: range.end.line + 1,
|
|
20229
|
+
endColumn: range.end.character + 1
|
|
20230
|
+
}
|
|
20231
|
+
};
|
|
20232
|
+
}
|
|
20233
|
+
async function getInlayHints(filePath) {
|
|
20234
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20235
|
+
const conn = await getConnection(projectRoot);
|
|
20236
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20237
|
+
try {
|
|
20238
|
+
const content = getFileContent(filePath);
|
|
20239
|
+
const lines = content.split(`
|
|
20240
|
+
`);
|
|
20241
|
+
const result = await sendMessage(conn, "textDocument/inlayHint", {
|
|
20242
|
+
textDocument: { uri: toUri(filePath) },
|
|
20243
|
+
range: {
|
|
20244
|
+
start: { line: 0, character: 0 },
|
|
20245
|
+
end: { line: lines.length, character: lines[lines.length - 1].length }
|
|
20246
|
+
}
|
|
20247
|
+
});
|
|
20248
|
+
if (!result) {
|
|
20249
|
+
return [];
|
|
20250
|
+
}
|
|
20251
|
+
return Array.isArray(result) ? result : [];
|
|
20252
|
+
} catch (error2) {
|
|
20253
|
+
console.error("Inlay hints error:", error2);
|
|
20254
|
+
return [];
|
|
20255
|
+
}
|
|
20256
|
+
}
|
|
20257
|
+
async function getSemanticTokens(filePath) {
|
|
20258
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20259
|
+
const conn = await getConnection(projectRoot);
|
|
20260
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20261
|
+
const result = await sendMessage(conn, "textDocument/semanticTokens/full", {
|
|
20262
|
+
textDocument: { uri: toUri(filePath) }
|
|
20263
|
+
});
|
|
20264
|
+
if (!result || !Array.isArray(result.data)) {
|
|
20265
|
+
return { tokens: [], count: 0 };
|
|
20266
|
+
}
|
|
20267
|
+
const legend = conn.serverCapabilities?.semanticTokensProvider?.legend || {};
|
|
20268
|
+
const tokenTypes = Array.isArray(legend.tokenTypes) ? legend.tokenTypes : [
|
|
20269
|
+
"namespace",
|
|
20270
|
+
"type",
|
|
20271
|
+
"class",
|
|
20272
|
+
"enum",
|
|
20273
|
+
"interface",
|
|
20274
|
+
"struct",
|
|
20275
|
+
"typeParameter",
|
|
20276
|
+
"parameter",
|
|
20277
|
+
"variable",
|
|
20278
|
+
"property",
|
|
20279
|
+
"enumMember",
|
|
20280
|
+
"event",
|
|
20281
|
+
"function",
|
|
20282
|
+
"method",
|
|
20283
|
+
"macro",
|
|
20284
|
+
"keyword",
|
|
20285
|
+
"modifier",
|
|
20286
|
+
"comment",
|
|
20287
|
+
"string",
|
|
20288
|
+
"number",
|
|
20289
|
+
"regexp",
|
|
20290
|
+
"operator",
|
|
20291
|
+
"decorator"
|
|
20292
|
+
];
|
|
20293
|
+
const tokenModifiers = Array.isArray(legend.tokenModifiers) ? legend.tokenModifiers : [];
|
|
20294
|
+
let line = 0;
|
|
20295
|
+
let character = 0;
|
|
20296
|
+
const decoded = [];
|
|
20297
|
+
for (let i = 0;i < result.data.length; i += 5) {
|
|
20298
|
+
const deltaLine = result.data[i];
|
|
20299
|
+
const deltaStart = result.data[i + 1];
|
|
20300
|
+
const length = result.data[i + 2];
|
|
20301
|
+
const tokenType = result.data[i + 3];
|
|
20302
|
+
const modifierBits = result.data[i + 4];
|
|
20303
|
+
line = line + deltaLine;
|
|
20304
|
+
character = deltaLine > 0 ? deltaStart : character + deltaStart;
|
|
20305
|
+
const modifiers = tokenModifiers.filter((_, idx) => (modifierBits & 1 << idx) !== 0);
|
|
20306
|
+
decoded.push({
|
|
20307
|
+
line: line + 1,
|
|
20308
|
+
column: character + 1,
|
|
20309
|
+
endLine: line + 1,
|
|
20310
|
+
endColumn: character + length + 1,
|
|
20311
|
+
token_type: tokenTypes[tokenType] || String(tokenType),
|
|
20312
|
+
token_modifiers: modifiers
|
|
20313
|
+
});
|
|
20314
|
+
}
|
|
20315
|
+
return { tokens: decoded, count: decoded.length };
|
|
20316
|
+
}
|
|
20317
|
+
async function getSelectionRanges(filePath, line, column) {
|
|
20318
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20319
|
+
const conn = await getConnection(projectRoot);
|
|
20320
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20321
|
+
const result = await sendMessage(conn, "textDocument/selectionRange", {
|
|
20322
|
+
textDocument: { uri: toUri(filePath) },
|
|
20323
|
+
positions: [{ line: line - 1, character: column - 1 }]
|
|
20324
|
+
});
|
|
20325
|
+
if (!Array.isArray(result) || result.length === 0)
|
|
20326
|
+
return [];
|
|
20327
|
+
const ranges = [];
|
|
20328
|
+
let current = result[0];
|
|
20329
|
+
while (current) {
|
|
20330
|
+
const itemRange = current.range;
|
|
20331
|
+
ranges.push({
|
|
20332
|
+
line: itemRange.start.line + 1,
|
|
20333
|
+
column: itemRange.start.character + 1,
|
|
20334
|
+
endLine: itemRange.end.line + 1,
|
|
20335
|
+
endColumn: itemRange.end.character + 1
|
|
20336
|
+
});
|
|
20337
|
+
current = current.parent;
|
|
20338
|
+
}
|
|
20339
|
+
return ranges;
|
|
20340
|
+
}
|
|
20341
|
+
async function getFoldingRanges(filePath) {
|
|
20342
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20343
|
+
const conn = await getConnection(projectRoot);
|
|
20344
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20345
|
+
const result = await sendMessage(conn, "textDocument/foldingRange", {
|
|
20346
|
+
textDocument: { uri: toUri(filePath) }
|
|
20347
|
+
});
|
|
20348
|
+
if (!Array.isArray(result))
|
|
20349
|
+
return [];
|
|
20350
|
+
return result.map((item) => ({
|
|
20351
|
+
kind: item.kind,
|
|
20352
|
+
line: (item.startLine ?? 0) + 1,
|
|
20353
|
+
column: (item.startCharacter ?? 0) + 1,
|
|
20354
|
+
endLine: (item.endLine ?? 0) + 1,
|
|
20355
|
+
endColumn: (item.endCharacter ?? 0) + 1
|
|
20356
|
+
}));
|
|
20357
|
+
}
|
|
20358
|
+
async function getDocumentLinks(filePath) {
|
|
20359
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20360
|
+
const conn = await getConnection(projectRoot);
|
|
20361
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20362
|
+
const result = await sendMessage(conn, "textDocument/documentLink", {
|
|
20363
|
+
textDocument: { uri: toUri(filePath) }
|
|
20364
|
+
});
|
|
20365
|
+
if (!Array.isArray(result))
|
|
20366
|
+
return [];
|
|
20367
|
+
return result.map((item) => ({
|
|
20368
|
+
target: item.target,
|
|
20369
|
+
line: item.range.start.line + 1,
|
|
20370
|
+
column: item.range.start.character + 1,
|
|
20371
|
+
endLine: item.range.end.line + 1,
|
|
20372
|
+
endColumn: item.range.end.character + 1
|
|
20373
|
+
}));
|
|
20374
|
+
}
|
|
20375
|
+
async function getCallHierarchy(filePath, line, column, direction = "both") {
|
|
20376
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20377
|
+
const conn = await getConnection(projectRoot);
|
|
20378
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20379
|
+
const prepared = await sendMessage(conn, "textDocument/prepareCallHierarchy", {
|
|
20380
|
+
textDocument: { uri: toUri(filePath) },
|
|
20381
|
+
position: { line: line - 1, character: column - 1 }
|
|
20382
|
+
});
|
|
20383
|
+
if (!prepared || Array.isArray(prepared) && prepared.length === 0) {
|
|
20384
|
+
return { items: [], count: 0, direction };
|
|
20385
|
+
}
|
|
20386
|
+
const baseItems = Array.isArray(prepared) ? prepared : [prepared];
|
|
20387
|
+
const includeIncoming = direction === "both" || direction === "incoming";
|
|
20388
|
+
const includeOutgoing = direction === "both" || direction === "outgoing";
|
|
20389
|
+
const items = await Promise.all(baseItems.map(async (item) => {
|
|
20390
|
+
const normalizeItem = (node) => ({
|
|
20391
|
+
name: node.name,
|
|
20392
|
+
kind: node.kind,
|
|
20393
|
+
containerName: node.containerName,
|
|
20394
|
+
file: fromUri(node.uri),
|
|
20395
|
+
line: (node.selectionRange?.start?.line ?? node.range.start.line) + 1,
|
|
20396
|
+
column: (node.selectionRange?.start?.character ?? node.range.start.character) + 1
|
|
20397
|
+
});
|
|
20398
|
+
const incoming = includeIncoming ? await sendMessage(conn, "callHierarchy/incomingCalls", { item }) || [] : [];
|
|
20399
|
+
const outgoing = includeOutgoing ? await sendMessage(conn, "callHierarchy/outgoingCalls", { item }) || [] : [];
|
|
20400
|
+
return {
|
|
20401
|
+
symbol: normalizeItem(item),
|
|
20402
|
+
incoming: incoming.map((call) => ({
|
|
20403
|
+
from: normalizeItem(call.from),
|
|
20404
|
+
call_count: Array.isArray(call.fromRanges) ? call.fromRanges.length : 0
|
|
20405
|
+
})),
|
|
20406
|
+
outgoing: outgoing.map((call) => ({
|
|
20407
|
+
to: normalizeItem(call.to),
|
|
20408
|
+
call_count: Array.isArray(call.fromRanges) ? call.fromRanges.length : 0
|
|
20409
|
+
}))
|
|
20410
|
+
};
|
|
20411
|
+
}));
|
|
20412
|
+
return { items, count: items.length, direction };
|
|
20413
|
+
}
|
|
20414
|
+
async function getCodeActions(filePath, line, column) {
|
|
20415
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20416
|
+
const conn = await getConnection(projectRoot);
|
|
20417
|
+
await ensureDocumentOpen(conn, filePath);
|
|
20418
|
+
const result = await sendMessage(conn, "textDocument/codeAction", {
|
|
20419
|
+
textDocument: { uri: toUri(filePath) },
|
|
20420
|
+
range: {
|
|
20421
|
+
start: { line: line - 1, character: column - 1 },
|
|
20422
|
+
end: { line: line - 1, character: column - 1 }
|
|
20423
|
+
},
|
|
20424
|
+
context: { diagnostics: [] }
|
|
20425
|
+
});
|
|
20426
|
+
return Array.isArray(result) ? result : [];
|
|
20427
|
+
}
|
|
20428
|
+
async function executeCommand(filePath, command, args = []) {
|
|
20429
|
+
const projectRoot = findProjectRoot(filePath);
|
|
20430
|
+
const conn = await getConnection(projectRoot);
|
|
20431
|
+
return sendMessage(conn, "workspace/executeCommand", { command, arguments: args });
|
|
20432
|
+
}
|
|
20433
|
+
async function applyWorkspaceEdit(edit) {
|
|
20434
|
+
const allEdits = new Map;
|
|
20435
|
+
if (edit?.changes && typeof edit.changes === "object") {
|
|
20436
|
+
for (const [uri, edits] of Object.entries(edit.changes)) {
|
|
20437
|
+
allEdits.set(uri, Array.isArray(edits) ? edits : []);
|
|
20438
|
+
}
|
|
20439
|
+
}
|
|
20440
|
+
if (Array.isArray(edit?.documentChanges)) {
|
|
20441
|
+
for (const change of edit.documentChanges) {
|
|
20442
|
+
if (change?.textDocument?.uri && Array.isArray(change.edits)) {
|
|
20443
|
+
const uri = change.textDocument.uri;
|
|
20444
|
+
const existing = allEdits.get(uri) || [];
|
|
20445
|
+
existing.push(...change.edits);
|
|
20446
|
+
allEdits.set(uri, existing);
|
|
20447
|
+
}
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20450
|
+
let filesChanged = 0;
|
|
20451
|
+
let editsApplied = 0;
|
|
20452
|
+
for (const [uri, edits] of allEdits) {
|
|
20453
|
+
const filePath = fromUri(uri);
|
|
20454
|
+
if (!fs.existsSync(filePath))
|
|
20455
|
+
continue;
|
|
20456
|
+
const original = getFileContent(filePath);
|
|
20457
|
+
let content = original;
|
|
20458
|
+
const sorted = [...edits].sort((a, b) => {
|
|
20459
|
+
if (a.range.start.line !== b.range.start.line)
|
|
20460
|
+
return b.range.start.line - a.range.start.line;
|
|
20461
|
+
return b.range.start.character - a.range.start.character;
|
|
20462
|
+
});
|
|
20463
|
+
for (const textEdit of sorted) {
|
|
20464
|
+
const start = textEdit.range.start;
|
|
20465
|
+
const end = textEdit.range.end;
|
|
20466
|
+
const startOffset = positionToOffset(content, start.line, start.character);
|
|
20467
|
+
const endOffset = positionToOffset(content, end.line, end.character);
|
|
20468
|
+
content = content.slice(0, startOffset) + (textEdit.newText || "") + content.slice(endOffset);
|
|
20469
|
+
editsApplied += 1;
|
|
20470
|
+
}
|
|
20471
|
+
if (content !== original) {
|
|
20472
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
20473
|
+
await updateDocument(filePath, content);
|
|
20474
|
+
filesChanged += 1;
|
|
20475
|
+
}
|
|
20476
|
+
}
|
|
20477
|
+
return { filesChanged, editsApplied };
|
|
20478
|
+
}
|
|
20062
20479
|
async function getDiagnostics(filePath) {
|
|
20063
20480
|
const projectRoot = findProjectRoot(filePath);
|
|
20064
20481
|
const absPath = path.resolve(filePath);
|
|
@@ -20312,7 +20729,7 @@ async function ensureFileOpen(conn, filePath) {
|
|
|
20312
20729
|
await sendCommand(conn, "open", {
|
|
20313
20730
|
file: absPath,
|
|
20314
20731
|
fileContent: content,
|
|
20315
|
-
scriptKindName: "
|
|
20732
|
+
scriptKindName: "Unknown",
|
|
20316
20733
|
projectRootPath: conn.projectRoot
|
|
20317
20734
|
});
|
|
20318
20735
|
conn.openedFiles.add(absPath);
|
|
@@ -20589,39 +21006,105 @@ async function getRenameLocations(filePath, line, column) {
|
|
|
20589
21006
|
|
|
20590
21007
|
// src/index.ts
|
|
20591
21008
|
async function getQuickInfo3(file, line, column) {
|
|
20592
|
-
const
|
|
21009
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21010
|
+
if (error2 || !absPath)
|
|
21011
|
+
throw new Error(error2 || "Invalid path");
|
|
21012
|
+
const lspResult = await getQuickInfo(absPath, line, column);
|
|
20593
21013
|
if (lspResult && lspResult.contents) {
|
|
20594
21014
|
return lspResult;
|
|
20595
21015
|
}
|
|
20596
|
-
return getQuickInfo2(
|
|
21016
|
+
return getQuickInfo2(absPath, line, column);
|
|
20597
21017
|
}
|
|
20598
21018
|
async function getDefinition3(file, line, column) {
|
|
20599
|
-
const
|
|
21019
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21020
|
+
if (error2 || !absPath)
|
|
21021
|
+
throw new Error(error2 || "Invalid path");
|
|
21022
|
+
const lspResult = await getDefinition(absPath, line, column);
|
|
20600
21023
|
if (lspResult && lspResult.length > 0) {
|
|
20601
21024
|
return lspResult;
|
|
20602
21025
|
}
|
|
20603
|
-
return getDefinition2(
|
|
21026
|
+
return getDefinition2(absPath, line, column);
|
|
21027
|
+
}
|
|
21028
|
+
async function getImplementation2(file, line, column) {
|
|
21029
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21030
|
+
if (error2 || !absPath)
|
|
21031
|
+
throw new Error(error2 || "Invalid path");
|
|
21032
|
+
return getImplementation(absPath, line, column);
|
|
21033
|
+
}
|
|
21034
|
+
async function getTypeDefinition2(file, line, column) {
|
|
21035
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21036
|
+
if (error2 || !absPath)
|
|
21037
|
+
throw new Error(error2 || "Invalid path");
|
|
21038
|
+
return getTypeDefinition(absPath, line, column);
|
|
20604
21039
|
}
|
|
20605
21040
|
async function getReferences3(file, line, column) {
|
|
20606
|
-
const
|
|
21041
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21042
|
+
if (error2 || !absPath)
|
|
21043
|
+
throw new Error(error2 || "Invalid path");
|
|
21044
|
+
const lspResult = await getReferences(absPath, line, column);
|
|
20607
21045
|
if (lspResult && lspResult.length > 0) {
|
|
20608
21046
|
return lspResult;
|
|
20609
21047
|
}
|
|
20610
|
-
return getReferences2(
|
|
21048
|
+
return getReferences2(absPath, line, column);
|
|
21049
|
+
}
|
|
21050
|
+
async function getDocumentHighlights2(file, line, column) {
|
|
21051
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21052
|
+
if (error2 || !absPath)
|
|
21053
|
+
throw new Error(error2 || "Invalid path");
|
|
21054
|
+
return getDocumentHighlights(absPath, line, column);
|
|
20611
21055
|
}
|
|
20612
21056
|
async function getCompletions3(file, line, column, limit = 20) {
|
|
20613
|
-
const
|
|
21057
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21058
|
+
if (error2 || !absPath)
|
|
21059
|
+
throw new Error(error2 || "Invalid path");
|
|
21060
|
+
const lspResult = await getCompletions(absPath, line, column, limit);
|
|
20614
21061
|
if (lspResult && lspResult.items && lspResult.items.length > 0) {
|
|
20615
21062
|
return lspResult;
|
|
20616
21063
|
}
|
|
20617
|
-
return getCompletions2(
|
|
21064
|
+
return getCompletions2(absPath, line, column, limit);
|
|
20618
21065
|
}
|
|
20619
21066
|
async function getSignatureHelp3(file, line, column) {
|
|
20620
|
-
const
|
|
21067
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21068
|
+
if (error2 || !absPath)
|
|
21069
|
+
throw new Error(error2 || "Invalid path");
|
|
21070
|
+
const lspResult = await getSignatureHelp(absPath, line, column);
|
|
20621
21071
|
if (lspResult && lspResult.signatures && lspResult.signatures.length > 0) {
|
|
20622
21072
|
return lspResult;
|
|
20623
21073
|
}
|
|
20624
|
-
return getSignatureHelp2(
|
|
21074
|
+
return getSignatureHelp2(absPath, line, column);
|
|
21075
|
+
}
|
|
21076
|
+
function symbolAtPosition(content, line, column) {
|
|
21077
|
+
const lines = content.split(`
|
|
21078
|
+
`);
|
|
21079
|
+
if (line < 1 || line > lines.length)
|
|
21080
|
+
return "";
|
|
21081
|
+
const text = lines[line - 1] || "";
|
|
21082
|
+
if (!text.length)
|
|
21083
|
+
return "";
|
|
21084
|
+
const idx = Math.max(0, Math.min(text.length - 1, column - 1));
|
|
21085
|
+
const re = /[A-Za-z_][A-Za-z0-9_]*/g;
|
|
21086
|
+
let match;
|
|
21087
|
+
while ((match = re.exec(text)) !== null) {
|
|
21088
|
+
if (match.index <= idx && idx < match.index + match[0].length) {
|
|
21089
|
+
return match[0];
|
|
21090
|
+
}
|
|
21091
|
+
}
|
|
21092
|
+
return "";
|
|
21093
|
+
}
|
|
21094
|
+
function packageNameFromPath(filePath) {
|
|
21095
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
21096
|
+
const marker = "/node_modules/";
|
|
21097
|
+
const idx = normalized.lastIndexOf(marker);
|
|
21098
|
+
if (idx < 0)
|
|
21099
|
+
return null;
|
|
21100
|
+
const rest = normalized.slice(idx + marker.length);
|
|
21101
|
+
const parts = rest.split("/");
|
|
21102
|
+
if (!parts.length)
|
|
21103
|
+
return null;
|
|
21104
|
+
if (parts[0].startsWith("@") && parts.length > 1) {
|
|
21105
|
+
return `${parts[0]}/${parts[1]}`;
|
|
21106
|
+
}
|
|
21107
|
+
return parts[0] || null;
|
|
20625
21108
|
}
|
|
20626
21109
|
var require2 = createRequire3(import.meta.url);
|
|
20627
21110
|
var packageJson = require2("../package.json");
|
|
@@ -20629,6 +21112,35 @@ var server = new McpServer({
|
|
|
20629
21112
|
name: "vue-lsp-mcp",
|
|
20630
21113
|
version: packageJson.version
|
|
20631
21114
|
});
|
|
21115
|
+
server.tool("switch_workspace", "Switch the active workspace to a new project directory", {
|
|
21116
|
+
path: exports_external.string().describe("Absolute path to the new project root directory")
|
|
21117
|
+
}, async ({ path: inputPath }) => {
|
|
21118
|
+
try {
|
|
21119
|
+
const absPath = path3.resolve(inputPath);
|
|
21120
|
+
if (!fs3.existsSync(absPath) || !fs3.statSync(absPath).isDirectory()) {
|
|
21121
|
+
return {
|
|
21122
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Invalid Path", message: `'${inputPath}' is not a directory.` }) }]
|
|
21123
|
+
};
|
|
21124
|
+
}
|
|
21125
|
+
clearAllConnections();
|
|
21126
|
+
const newWorkspace = setActiveWorkspace(absPath);
|
|
21127
|
+
return {
|
|
21128
|
+
content: [{
|
|
21129
|
+
type: "text",
|
|
21130
|
+
text: JSON.stringify({
|
|
21131
|
+
success: true,
|
|
21132
|
+
message: `Switched active workspace to: ${newWorkspace}`,
|
|
21133
|
+
workspace: newWorkspace,
|
|
21134
|
+
info: "All previous Vue language server connections have been closed."
|
|
21135
|
+
})
|
|
21136
|
+
}]
|
|
21137
|
+
};
|
|
21138
|
+
} catch (error2) {
|
|
21139
|
+
return {
|
|
21140
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
|
|
21141
|
+
};
|
|
21142
|
+
}
|
|
21143
|
+
});
|
|
20632
21144
|
server.tool("hover", "Get type information and documentation at a specific position in a Vue SFC file", {
|
|
20633
21145
|
file: exports_external.string().describe("Absolute path to the .vue file"),
|
|
20634
21146
|
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
@@ -20677,6 +21189,88 @@ server.tool("definition", "Go to definition of a symbol at a specific position i
|
|
|
20677
21189
|
};
|
|
20678
21190
|
}
|
|
20679
21191
|
});
|
|
21192
|
+
server.tool("implementation", "Go to implementation of a symbol at a specific position in a Vue SFC file", {
|
|
21193
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21194
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21195
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21196
|
+
}, async ({ file, line, column }) => {
|
|
21197
|
+
try {
|
|
21198
|
+
const implementations = await getImplementation2(file, line, column);
|
|
21199
|
+
if (!implementations || implementations.length === 0) {
|
|
21200
|
+
return {
|
|
21201
|
+
content: [{ type: "text", text: JSON.stringify({ error: "No implementation found" }) }]
|
|
21202
|
+
};
|
|
21203
|
+
}
|
|
21204
|
+
return {
|
|
21205
|
+
content: [{
|
|
21206
|
+
type: "text",
|
|
21207
|
+
text: JSON.stringify(implementations.length === 1 ? implementations[0] : implementations)
|
|
21208
|
+
}]
|
|
21209
|
+
};
|
|
21210
|
+
} catch (error2) {
|
|
21211
|
+
const message = String(error2);
|
|
21212
|
+
if (message.includes("Method not found") || message.includes("implementation")) {
|
|
21213
|
+
return {
|
|
21214
|
+
content: [{
|
|
21215
|
+
type: "text",
|
|
21216
|
+
text: JSON.stringify({
|
|
21217
|
+
error: "NOT_IMPLEMENTED",
|
|
21218
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21219
|
+
message: "Vue backend does not expose implementation in this environment.",
|
|
21220
|
+
next_step: "Use definition/references or switch to TypeScript backend for this feature.",
|
|
21221
|
+
install_commands: [],
|
|
21222
|
+
missing_packages: [],
|
|
21223
|
+
strict_mode: true
|
|
21224
|
+
})
|
|
21225
|
+
}]
|
|
21226
|
+
};
|
|
21227
|
+
}
|
|
21228
|
+
return {
|
|
21229
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21230
|
+
};
|
|
21231
|
+
}
|
|
21232
|
+
});
|
|
21233
|
+
server.tool("type_definition", "Go to type definition of a symbol at a specific position in a Vue SFC file", {
|
|
21234
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21235
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21236
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21237
|
+
}, async ({ file, line, column }) => {
|
|
21238
|
+
try {
|
|
21239
|
+
const typeDefinitions = await getTypeDefinition2(file, line, column);
|
|
21240
|
+
if (!typeDefinitions || typeDefinitions.length === 0) {
|
|
21241
|
+
return {
|
|
21242
|
+
content: [{ type: "text", text: JSON.stringify({ error: "No type definition found" }) }]
|
|
21243
|
+
};
|
|
21244
|
+
}
|
|
21245
|
+
return {
|
|
21246
|
+
content: [{
|
|
21247
|
+
type: "text",
|
|
21248
|
+
text: JSON.stringify(typeDefinitions.length === 1 ? typeDefinitions[0] : typeDefinitions)
|
|
21249
|
+
}]
|
|
21250
|
+
};
|
|
21251
|
+
} catch (error2) {
|
|
21252
|
+
const message = String(error2);
|
|
21253
|
+
if (message.includes("Method not found") || message.includes("typeDefinition")) {
|
|
21254
|
+
return {
|
|
21255
|
+
content: [{
|
|
21256
|
+
type: "text",
|
|
21257
|
+
text: JSON.stringify({
|
|
21258
|
+
error: "NOT_IMPLEMENTED",
|
|
21259
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21260
|
+
message: "Vue backend does not expose type_definition in this environment.",
|
|
21261
|
+
next_step: "Use hover/definition or switch to TypeScript backend for this feature.",
|
|
21262
|
+
install_commands: [],
|
|
21263
|
+
missing_packages: [],
|
|
21264
|
+
strict_mode: true
|
|
21265
|
+
})
|
|
21266
|
+
}]
|
|
21267
|
+
};
|
|
21268
|
+
}
|
|
21269
|
+
return {
|
|
21270
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21271
|
+
};
|
|
21272
|
+
}
|
|
21273
|
+
});
|
|
20680
21274
|
server.tool("references", "Find all references to a symbol at a specific position in a Vue SFC file", {
|
|
20681
21275
|
file: exports_external.string().describe("Absolute path to the .vue file"),
|
|
20682
21276
|
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
@@ -20696,6 +21290,85 @@ server.tool("references", "Find all references to a symbol at a specific positio
|
|
|
20696
21290
|
};
|
|
20697
21291
|
}
|
|
20698
21292
|
});
|
|
21293
|
+
server.tool("call_hierarchy", "Get incoming/outgoing call hierarchy for symbol at a specific position in a Vue SFC file", {
|
|
21294
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21295
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21296
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)"),
|
|
21297
|
+
direction: exports_external.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("Call hierarchy direction to include")
|
|
21298
|
+
}, async ({ file, line, column, direction }) => {
|
|
21299
|
+
try {
|
|
21300
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21301
|
+
if (error2 || !absPath) {
|
|
21302
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21303
|
+
}
|
|
21304
|
+
const result = await getCallHierarchy(absPath, line, column, direction || "both");
|
|
21305
|
+
if (!result.count) {
|
|
21306
|
+
return {
|
|
21307
|
+
content: [{ type: "text", text: JSON.stringify({ error: "No call hierarchy found", ...result }) }]
|
|
21308
|
+
};
|
|
21309
|
+
}
|
|
21310
|
+
return {
|
|
21311
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
21312
|
+
};
|
|
21313
|
+
} catch (error2) {
|
|
21314
|
+
const message = String(error2);
|
|
21315
|
+
if (message.includes("Method not found") || message.includes("callHierarchy")) {
|
|
21316
|
+
return {
|
|
21317
|
+
content: [{
|
|
21318
|
+
type: "text",
|
|
21319
|
+
text: JSON.stringify({
|
|
21320
|
+
error: "NOT_IMPLEMENTED",
|
|
21321
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21322
|
+
message: "Vue backend does not expose call_hierarchy in this environment.",
|
|
21323
|
+
next_step: "Use references/definition or switch to TypeScript backend for call_hierarchy.",
|
|
21324
|
+
install_commands: [],
|
|
21325
|
+
missing_packages: [],
|
|
21326
|
+
strict_mode: true
|
|
21327
|
+
})
|
|
21328
|
+
}]
|
|
21329
|
+
};
|
|
21330
|
+
}
|
|
21331
|
+
return {
|
|
21332
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21333
|
+
};
|
|
21334
|
+
}
|
|
21335
|
+
});
|
|
21336
|
+
server.tool("document_highlight", "Find symbol highlights in current Vue document at a specific position", {
|
|
21337
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21338
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21339
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21340
|
+
}, async ({ file, line, column }) => {
|
|
21341
|
+
try {
|
|
21342
|
+
const highlights = await getDocumentHighlights2(file, line, column);
|
|
21343
|
+
return {
|
|
21344
|
+
content: [{
|
|
21345
|
+
type: "text",
|
|
21346
|
+
text: JSON.stringify({ highlights, count: highlights.length })
|
|
21347
|
+
}]
|
|
21348
|
+
};
|
|
21349
|
+
} catch (error2) {
|
|
21350
|
+
const message = String(error2);
|
|
21351
|
+
if (message.includes("Method not found") || message.includes("documentHighlight")) {
|
|
21352
|
+
return {
|
|
21353
|
+
content: [{
|
|
21354
|
+
type: "text",
|
|
21355
|
+
text: JSON.stringify({
|
|
21356
|
+
error: "NOT_IMPLEMENTED",
|
|
21357
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21358
|
+
message: "Vue backend does not expose document_highlight in this environment.",
|
|
21359
|
+
next_step: "Use references as fallback for symbol occurrences.",
|
|
21360
|
+
install_commands: [],
|
|
21361
|
+
missing_packages: [],
|
|
21362
|
+
strict_mode: true
|
|
21363
|
+
})
|
|
21364
|
+
}]
|
|
21365
|
+
};
|
|
21366
|
+
}
|
|
21367
|
+
return {
|
|
21368
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21369
|
+
};
|
|
21370
|
+
}
|
|
21371
|
+
});
|
|
20699
21372
|
server.tool("completions", "Get code completion suggestions at a specific position in a Vue SFC file", {
|
|
20700
21373
|
file: exports_external.string().describe("Absolute path to the .vue file"),
|
|
20701
21374
|
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
@@ -20744,11 +21417,106 @@ server.tool("signature_help", "Get function signature help at a specific positio
|
|
|
20744
21417
|
};
|
|
20745
21418
|
}
|
|
20746
21419
|
});
|
|
21420
|
+
server.tool("inlay_hints", "Get inlay hints (type annotations, parameter names) for a Vue SFC file", {
|
|
21421
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
|
|
21422
|
+
}, async ({ file }) => {
|
|
21423
|
+
try {
|
|
21424
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21425
|
+
if (error2 || !absPath) {
|
|
21426
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21427
|
+
}
|
|
21428
|
+
const hints = await getInlayHints(absPath);
|
|
21429
|
+
return {
|
|
21430
|
+
content: [{
|
|
21431
|
+
type: "text",
|
|
21432
|
+
text: JSON.stringify({ hints, count: hints.length })
|
|
21433
|
+
}]
|
|
21434
|
+
};
|
|
21435
|
+
} catch (error2) {
|
|
21436
|
+
return {
|
|
21437
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
|
|
21438
|
+
};
|
|
21439
|
+
}
|
|
21440
|
+
});
|
|
21441
|
+
server.tool("semantic_tokens", "Get semantic tokens for a Vue SFC file", {
|
|
21442
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
|
|
21443
|
+
}, async ({ file }) => {
|
|
21444
|
+
try {
|
|
21445
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21446
|
+
if (error2 || !absPath) {
|
|
21447
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21448
|
+
}
|
|
21449
|
+
const result = await getSemanticTokens(absPath);
|
|
21450
|
+
return {
|
|
21451
|
+
content: [{
|
|
21452
|
+
type: "text",
|
|
21453
|
+
text: JSON.stringify(result)
|
|
21454
|
+
}]
|
|
21455
|
+
};
|
|
21456
|
+
} catch (error2) {
|
|
21457
|
+
const message = String(error2);
|
|
21458
|
+
if (message.includes("Method not found") || message.includes("semanticTokens")) {
|
|
21459
|
+
return {
|
|
21460
|
+
content: [{
|
|
21461
|
+
type: "text",
|
|
21462
|
+
text: JSON.stringify({
|
|
21463
|
+
error: "NOT_IMPLEMENTED",
|
|
21464
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21465
|
+
message: "Vue backend does not expose semantic tokens in this environment.",
|
|
21466
|
+
next_step: "Use hover/definition/references or switch to TypeScript/Python where semantic_tokens is available.",
|
|
21467
|
+
install_commands: [],
|
|
21468
|
+
missing_packages: [],
|
|
21469
|
+
strict_mode: true
|
|
21470
|
+
})
|
|
21471
|
+
}]
|
|
21472
|
+
};
|
|
21473
|
+
}
|
|
21474
|
+
return {
|
|
21475
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21476
|
+
};
|
|
21477
|
+
}
|
|
21478
|
+
});
|
|
21479
|
+
server.tool("moniker", "Get moniker-like symbol identity for cross-package tracking in Vue files", {
|
|
21480
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21481
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21482
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21483
|
+
}, async ({ file, line, column }) => {
|
|
21484
|
+
try {
|
|
21485
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21486
|
+
if (error2 || !absPath) {
|
|
21487
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21488
|
+
}
|
|
21489
|
+
const definitions = await getDefinition3(absPath, line, column);
|
|
21490
|
+
const sourceFile = definitions.length > 0 ? definitions[0].file : absPath;
|
|
21491
|
+
const content = getFileContent(absPath);
|
|
21492
|
+
const symbol = symbolAtPosition(content, line, column);
|
|
21493
|
+
const packageName = packageNameFromPath(sourceFile);
|
|
21494
|
+
const identifier = packageName ? `npm:${packageName}:${symbol || `${line}:${column}`}` : `workspace:${sourceFile}:${symbol || `${line}:${column}`}`;
|
|
21495
|
+
return {
|
|
21496
|
+
content: [{
|
|
21497
|
+
type: "text",
|
|
21498
|
+
text: JSON.stringify({
|
|
21499
|
+
symbol,
|
|
21500
|
+
identifier,
|
|
21501
|
+
package_name: packageName,
|
|
21502
|
+
source_file: sourceFile
|
|
21503
|
+
})
|
|
21504
|
+
}]
|
|
21505
|
+
};
|
|
21506
|
+
} catch (error2) {
|
|
21507
|
+
return {
|
|
21508
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
|
|
21509
|
+
};
|
|
21510
|
+
}
|
|
21511
|
+
});
|
|
20747
21512
|
server.tool("diagnostics", "Get type errors and warnings for Vue SFC files", {
|
|
20748
|
-
path: exports_external.string().describe("Path to a .vue file or directory to check")
|
|
21513
|
+
path: exports_external.string().describe("Path to a .vue file or directory to check (absolute or relative to active workspace)")
|
|
20749
21514
|
}, async ({ path: inputPath }) => {
|
|
20750
21515
|
try {
|
|
20751
|
-
const absPath =
|
|
21516
|
+
const { absPath, error: error2 } = resolveFilePath(inputPath);
|
|
21517
|
+
if (error2 || !absPath) {
|
|
21518
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21519
|
+
}
|
|
20752
21520
|
const stats = fs3.statSync(absPath);
|
|
20753
21521
|
let files = [];
|
|
20754
21522
|
if (stats.isDirectory()) {
|
|
@@ -20793,15 +21561,19 @@ server.tool("diagnostics", "Get type errors and warnings for Vue SFC files", {
|
|
|
20793
21561
|
}
|
|
20794
21562
|
});
|
|
20795
21563
|
server.tool("update_document", "Update Vue file content for incremental analysis without writing to disk", {
|
|
20796
|
-
file: exports_external.string().describe("
|
|
21564
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
20797
21565
|
content: exports_external.string().describe("New content for the file")
|
|
20798
21566
|
}, async ({ file, content }) => {
|
|
20799
21567
|
try {
|
|
20800
|
-
|
|
21568
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21569
|
+
if (error2 || !absPath) {
|
|
21570
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21571
|
+
}
|
|
21572
|
+
await updateDocument(absPath, content);
|
|
20801
21573
|
return {
|
|
20802
21574
|
content: [{
|
|
20803
21575
|
type: "text",
|
|
20804
|
-
text: JSON.stringify({ success: true, file })
|
|
21576
|
+
text: JSON.stringify({ success: true, file: absPath })
|
|
20805
21577
|
}]
|
|
20806
21578
|
};
|
|
20807
21579
|
} catch (error2) {
|
|
@@ -20811,7 +21583,7 @@ server.tool("update_document", "Update Vue file content for incremental analysis
|
|
|
20811
21583
|
}
|
|
20812
21584
|
});
|
|
20813
21585
|
server.tool("symbols", "Extract symbols (variables, functions, components) from a Vue SFC file", {
|
|
20814
|
-
file: exports_external.string().describe("
|
|
21586
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
20815
21587
|
query: exports_external.string().optional().describe("Optional filter query for symbol names")
|
|
20816
21588
|
}, async ({ file, query }) => {
|
|
20817
21589
|
try {
|
|
@@ -20846,7 +21618,11 @@ server.tool("symbols", "Extract symbols (variables, functions, components) from
|
|
|
20846
21618
|
}
|
|
20847
21619
|
}
|
|
20848
21620
|
};
|
|
20849
|
-
const
|
|
21621
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21622
|
+
if (error2 || !absPath) {
|
|
21623
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21624
|
+
}
|
|
21625
|
+
const tree = await getDocumentSymbols(absPath);
|
|
20850
21626
|
if (!tree) {
|
|
20851
21627
|
return {
|
|
20852
21628
|
content: [{ type: "text", text: JSON.stringify({ error: "Failed to get symbols" }) }]
|
|
@@ -20870,14 +21646,162 @@ server.tool("symbols", "Extract symbols (variables, functions, components) from
|
|
|
20870
21646
|
};
|
|
20871
21647
|
}
|
|
20872
21648
|
});
|
|
21649
|
+
server.tool("code_action", "Get available code actions (quick fixes/refactors) at a specific position in a Vue SFC file", {
|
|
21650
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21651
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21652
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21653
|
+
}, async ({ file, line, column }) => {
|
|
21654
|
+
try {
|
|
21655
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21656
|
+
if (error2 || !absPath) {
|
|
21657
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21658
|
+
}
|
|
21659
|
+
const actions = await getCodeActions(absPath, line, column);
|
|
21660
|
+
const formatted = actions.map((action) => ({
|
|
21661
|
+
title: action.title,
|
|
21662
|
+
kind: action.kind || "quickfix",
|
|
21663
|
+
hasEdit: Boolean(action.edit),
|
|
21664
|
+
hasCommand: Boolean(action.command),
|
|
21665
|
+
data: action.data
|
|
21666
|
+
}));
|
|
21667
|
+
return {
|
|
21668
|
+
content: [{ type: "text", text: JSON.stringify({ actions: formatted, count: formatted.length }) }]
|
|
21669
|
+
};
|
|
21670
|
+
} catch (error2) {
|
|
21671
|
+
const message = String(error2);
|
|
21672
|
+
if (message.includes("Method not found") || message.includes("codeAction")) {
|
|
21673
|
+
return {
|
|
21674
|
+
content: [{
|
|
21675
|
+
type: "text",
|
|
21676
|
+
text: JSON.stringify({
|
|
21677
|
+
error: "NOT_IMPLEMENTED",
|
|
21678
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21679
|
+
message: "Vue backend does not expose code_action in this environment.",
|
|
21680
|
+
next_step: "Use diagnostics + manual edits as fallback.",
|
|
21681
|
+
install_commands: [],
|
|
21682
|
+
missing_packages: [],
|
|
21683
|
+
strict_mode: true
|
|
21684
|
+
})
|
|
21685
|
+
}]
|
|
21686
|
+
};
|
|
21687
|
+
}
|
|
21688
|
+
return {
|
|
21689
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21690
|
+
};
|
|
21691
|
+
}
|
|
21692
|
+
});
|
|
21693
|
+
server.tool("run_code_action", "Run a specific code action at a position in a Vue SFC file", {
|
|
21694
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21695
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21696
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)"),
|
|
21697
|
+
title: exports_external.string().describe("Code action title (exact match from code_action output)")
|
|
21698
|
+
}, async ({ file, line, column, title }) => {
|
|
21699
|
+
try {
|
|
21700
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21701
|
+
if (error2 || !absPath) {
|
|
21702
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21703
|
+
}
|
|
21704
|
+
const actions = await getCodeActions(absPath, line, column);
|
|
21705
|
+
const action = actions.find((a) => a.title === title);
|
|
21706
|
+
if (!action) {
|
|
21707
|
+
return {
|
|
21708
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Action '${title}' not found` }) }]
|
|
21709
|
+
};
|
|
21710
|
+
}
|
|
21711
|
+
let editResult = null;
|
|
21712
|
+
if (action.edit) {
|
|
21713
|
+
editResult = await applyWorkspaceEdit(action.edit);
|
|
21714
|
+
}
|
|
21715
|
+
if (action.command) {
|
|
21716
|
+
const commandName = typeof action.command === "string" ? action.command : action.command.command;
|
|
21717
|
+
const args = typeof action.command === "string" ? [] : action.command.arguments || [];
|
|
21718
|
+
await executeCommand(absPath, commandName, args);
|
|
21719
|
+
}
|
|
21720
|
+
return {
|
|
21721
|
+
content: [{
|
|
21722
|
+
type: "text",
|
|
21723
|
+
text: JSON.stringify({
|
|
21724
|
+
success: true,
|
|
21725
|
+
title: action.title,
|
|
21726
|
+
kind: action.kind || "quickfix",
|
|
21727
|
+
appliedEdit: Boolean(action.edit),
|
|
21728
|
+
executedCommand: Boolean(action.command),
|
|
21729
|
+
editResult
|
|
21730
|
+
})
|
|
21731
|
+
}]
|
|
21732
|
+
};
|
|
21733
|
+
} catch (error2) {
|
|
21734
|
+
const message = String(error2);
|
|
21735
|
+
if (message.includes("Method not found") || message.includes("codeAction") || message.includes("executeCommand")) {
|
|
21736
|
+
return {
|
|
21737
|
+
content: [{
|
|
21738
|
+
type: "text",
|
|
21739
|
+
text: JSON.stringify({
|
|
21740
|
+
error: "NOT_IMPLEMENTED",
|
|
21741
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21742
|
+
message: "Vue backend cannot run code actions in this environment.",
|
|
21743
|
+
next_step: "Use code_action for hints and apply edits manually.",
|
|
21744
|
+
install_commands: [],
|
|
21745
|
+
missing_packages: [],
|
|
21746
|
+
strict_mode: true
|
|
21747
|
+
})
|
|
21748
|
+
}]
|
|
21749
|
+
};
|
|
21750
|
+
}
|
|
21751
|
+
return {
|
|
21752
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21753
|
+
};
|
|
21754
|
+
}
|
|
21755
|
+
});
|
|
21756
|
+
server.tool("prepare_rename", "Check whether a symbol can be safely renamed at a specific position in a Vue SFC file", {
|
|
21757
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21758
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21759
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21760
|
+
}, async ({ file, line, column }) => {
|
|
21761
|
+
try {
|
|
21762
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21763
|
+
if (error2 || !absPath) {
|
|
21764
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21765
|
+
}
|
|
21766
|
+
const result = await getPrepareRename(absPath, line, column);
|
|
21767
|
+
return {
|
|
21768
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
21769
|
+
};
|
|
21770
|
+
} catch (error2) {
|
|
21771
|
+
const message = String(error2);
|
|
21772
|
+
if (message.includes("Method not found") || message.includes("prepareRename")) {
|
|
21773
|
+
return {
|
|
21774
|
+
content: [{
|
|
21775
|
+
type: "text",
|
|
21776
|
+
text: JSON.stringify({
|
|
21777
|
+
error: "NOT_IMPLEMENTED",
|
|
21778
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21779
|
+
message: "Vue backend does not expose prepare_rename in this environment.",
|
|
21780
|
+
next_step: "Use rename preview and verify affected ranges manually.",
|
|
21781
|
+
install_commands: [],
|
|
21782
|
+
missing_packages: [],
|
|
21783
|
+
strict_mode: true
|
|
21784
|
+
})
|
|
21785
|
+
}]
|
|
21786
|
+
};
|
|
21787
|
+
}
|
|
21788
|
+
return {
|
|
21789
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21790
|
+
};
|
|
21791
|
+
}
|
|
21792
|
+
});
|
|
20873
21793
|
server.tool("rename", "Preview renaming a symbol at a specific position (shows all locations that would be renamed)", {
|
|
20874
|
-
file: exports_external.string().describe("
|
|
21794
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
20875
21795
|
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
20876
21796
|
column: exports_external.number().int().positive().describe("Column number (1-based)"),
|
|
20877
21797
|
newName: exports_external.string().describe("New name for the symbol")
|
|
20878
21798
|
}, async ({ file, line, column, newName }) => {
|
|
20879
21799
|
try {
|
|
20880
|
-
const
|
|
21800
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21801
|
+
if (error2 || !absPath) {
|
|
21802
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21803
|
+
}
|
|
21804
|
+
const locations = await getRenameLocations(absPath, line, column);
|
|
20881
21805
|
if (!locations || locations.length === 0) {
|
|
20882
21806
|
return {
|
|
20883
21807
|
content: [{ type: "text", text: JSON.stringify({ error: "Cannot rename symbol at this position" }) }]
|
|
@@ -20912,14 +21836,174 @@ server.tool("rename", "Preview renaming a symbol at a specific position (shows a
|
|
|
20912
21836
|
};
|
|
20913
21837
|
}
|
|
20914
21838
|
});
|
|
21839
|
+
server.tool("linked_editing_range", "Get linked editing ranges at a specific position in a Vue SFC file", {
|
|
21840
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21841
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21842
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21843
|
+
}, async ({ file, line, column }) => {
|
|
21844
|
+
try {
|
|
21845
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21846
|
+
if (error2 || !absPath) {
|
|
21847
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21848
|
+
}
|
|
21849
|
+
const locations = await getRenameLocations(absPath, line, column);
|
|
21850
|
+
if (!locations || locations.length === 0) {
|
|
21851
|
+
return {
|
|
21852
|
+
content: [{ type: "text", text: JSON.stringify({ ranges: [], count: 0 }) }]
|
|
21853
|
+
};
|
|
21854
|
+
}
|
|
21855
|
+
const current = path3.resolve(absPath);
|
|
21856
|
+
const ranges = locations.filter((loc) => path3.resolve(loc.file) === current).map((loc) => ({
|
|
21857
|
+
line: loc.line,
|
|
21858
|
+
column: loc.column,
|
|
21859
|
+
endLine: loc.line,
|
|
21860
|
+
endColumn: loc.column + loc.length
|
|
21861
|
+
}));
|
|
21862
|
+
return {
|
|
21863
|
+
content: [{
|
|
21864
|
+
type: "text",
|
|
21865
|
+
text: JSON.stringify({ ranges, count: ranges.length })
|
|
21866
|
+
}]
|
|
21867
|
+
};
|
|
21868
|
+
} catch (error2) {
|
|
21869
|
+
return {
|
|
21870
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
|
|
21871
|
+
};
|
|
21872
|
+
}
|
|
21873
|
+
});
|
|
21874
|
+
server.tool("selection_range", "Get nested smart selection ranges at a specific position in a Vue SFC file", {
|
|
21875
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
|
|
21876
|
+
line: exports_external.number().int().positive().describe("Line number (1-based)"),
|
|
21877
|
+
column: exports_external.number().int().positive().describe("Column number (1-based)")
|
|
21878
|
+
}, async ({ file, line, column }) => {
|
|
21879
|
+
try {
|
|
21880
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21881
|
+
if (error2 || !absPath) {
|
|
21882
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21883
|
+
}
|
|
21884
|
+
const ranges = await getSelectionRanges(absPath, line, column);
|
|
21885
|
+
if (!ranges.length) {
|
|
21886
|
+
return {
|
|
21887
|
+
content: [{ type: "text", text: JSON.stringify({ error: "No selection range found" }) }]
|
|
21888
|
+
};
|
|
21889
|
+
}
|
|
21890
|
+
return {
|
|
21891
|
+
content: [{ type: "text", text: JSON.stringify({ ranges, count: ranges.length }) }]
|
|
21892
|
+
};
|
|
21893
|
+
} catch (error2) {
|
|
21894
|
+
const message = String(error2);
|
|
21895
|
+
if (message.includes("Method not found") || message.includes("selectionRange")) {
|
|
21896
|
+
return {
|
|
21897
|
+
content: [{
|
|
21898
|
+
type: "text",
|
|
21899
|
+
text: JSON.stringify({
|
|
21900
|
+
error: "NOT_IMPLEMENTED",
|
|
21901
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21902
|
+
message: "Vue backend does not expose selection_range in this environment.",
|
|
21903
|
+
next_step: "Use read_file_with_hints/symbols and explicit ranges as fallback.",
|
|
21904
|
+
install_commands: [],
|
|
21905
|
+
missing_packages: [],
|
|
21906
|
+
strict_mode: true
|
|
21907
|
+
})
|
|
21908
|
+
}]
|
|
21909
|
+
};
|
|
21910
|
+
}
|
|
21911
|
+
return {
|
|
21912
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21913
|
+
};
|
|
21914
|
+
}
|
|
21915
|
+
});
|
|
21916
|
+
server.tool("folding_range", "Get foldable ranges in a Vue SFC file", {
|
|
21917
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
|
|
21918
|
+
}, async ({ file }) => {
|
|
21919
|
+
try {
|
|
21920
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21921
|
+
if (error2 || !absPath) {
|
|
21922
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21923
|
+
}
|
|
21924
|
+
const ranges = await getFoldingRanges(absPath);
|
|
21925
|
+
return {
|
|
21926
|
+
content: [{ type: "text", text: JSON.stringify({ ranges, count: ranges.length }) }]
|
|
21927
|
+
};
|
|
21928
|
+
} catch (error2) {
|
|
21929
|
+
const message = String(error2);
|
|
21930
|
+
if (message.includes("Method not found") || message.includes("foldingRange")) {
|
|
21931
|
+
return {
|
|
21932
|
+
content: [{
|
|
21933
|
+
type: "text",
|
|
21934
|
+
text: JSON.stringify({
|
|
21935
|
+
error: "NOT_IMPLEMENTED",
|
|
21936
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21937
|
+
message: "Vue backend does not expose folding_range in this environment.",
|
|
21938
|
+
next_step: "Use symbols-based sections for manual folding.",
|
|
21939
|
+
install_commands: [],
|
|
21940
|
+
missing_packages: [],
|
|
21941
|
+
strict_mode: true
|
|
21942
|
+
})
|
|
21943
|
+
}]
|
|
21944
|
+
};
|
|
21945
|
+
}
|
|
21946
|
+
return {
|
|
21947
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21948
|
+
};
|
|
21949
|
+
}
|
|
21950
|
+
});
|
|
21951
|
+
server.tool("document_link", "Extract links (imports/URLs) from a Vue SFC file", {
|
|
21952
|
+
file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
|
|
21953
|
+
}, async ({ file }) => {
|
|
21954
|
+
try {
|
|
21955
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
21956
|
+
if (error2 || !absPath) {
|
|
21957
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
21958
|
+
}
|
|
21959
|
+
const links = await getDocumentLinks(absPath);
|
|
21960
|
+
return {
|
|
21961
|
+
content: [{ type: "text", text: JSON.stringify({ links, count: links.length }) }]
|
|
21962
|
+
};
|
|
21963
|
+
} catch (error2) {
|
|
21964
|
+
const message = String(error2);
|
|
21965
|
+
if (message.includes("Method not found") || message.includes("documentLink")) {
|
|
21966
|
+
return {
|
|
21967
|
+
content: [{
|
|
21968
|
+
type: "text",
|
|
21969
|
+
text: JSON.stringify({
|
|
21970
|
+
error: "NOT_IMPLEMENTED",
|
|
21971
|
+
error_code: "NOT_IMPLEMENTED",
|
|
21972
|
+
message: "Vue backend does not expose document_link in this environment.",
|
|
21973
|
+
next_step: "Use search over import/url patterns as fallback.",
|
|
21974
|
+
install_commands: [],
|
|
21975
|
+
missing_packages: [],
|
|
21976
|
+
strict_mode: true
|
|
21977
|
+
})
|
|
21978
|
+
}]
|
|
21979
|
+
};
|
|
21980
|
+
}
|
|
21981
|
+
return {
|
|
21982
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }]
|
|
21983
|
+
};
|
|
21984
|
+
}
|
|
21985
|
+
});
|
|
20915
21986
|
server.tool("search", "Search for a pattern in Vue files using ripgrep", {
|
|
20916
21987
|
pattern: exports_external.string().describe("The regex pattern to search for"),
|
|
20917
|
-
path: exports_external.string().optional().describe("Directory or file to search in"),
|
|
21988
|
+
path: exports_external.string().optional().describe("Directory or file to search in (absolute or relative to active workspace)"),
|
|
20918
21989
|
glob: exports_external.string().optional().describe("Glob pattern to filter files (e.g., '*.vue')"),
|
|
20919
21990
|
caseSensitive: exports_external.boolean().default(true).describe("Whether the search is case sensitive"),
|
|
20920
21991
|
maxResults: exports_external.number().int().positive().default(50).describe("Maximum number of results")
|
|
20921
21992
|
}, async ({ pattern, path: searchPath, glob, caseSensitive, maxResults }) => {
|
|
20922
21993
|
try {
|
|
21994
|
+
let absSearchPath;
|
|
21995
|
+
if (searchPath) {
|
|
21996
|
+
const { absPath, error: error2 } = validateFileWorkspace(searchPath) ? { absPath: null, error: validateFileWorkspace(searchPath) } : { absPath: path3.resolve(searchPath), error: null };
|
|
21997
|
+
const result2 = resolveFilePath(searchPath);
|
|
21998
|
+
if (result2.error || !result2.absPath) {
|
|
21999
|
+
return { content: [{ type: "text", text: result2.error || "Invalid path" }] };
|
|
22000
|
+
}
|
|
22001
|
+
absSearchPath = result2.absPath;
|
|
22002
|
+
} else {
|
|
22003
|
+
const { absPath } = resolveFilePath(".");
|
|
22004
|
+
if (absPath)
|
|
22005
|
+
absSearchPath = absPath;
|
|
22006
|
+
}
|
|
20923
22007
|
const { execSync } = await import("child_process");
|
|
20924
22008
|
const args = ["rg", "--json", "-n"];
|
|
20925
22009
|
if (!caseSensitive)
|
|
@@ -20931,8 +22015,8 @@ server.tool("search", "Search for a pattern in Vue files using ripgrep", {
|
|
|
20931
22015
|
}
|
|
20932
22016
|
args.push("--max-count", maxResults.toString());
|
|
20933
22017
|
args.push(pattern);
|
|
20934
|
-
if (
|
|
20935
|
-
args.push(
|
|
22018
|
+
if (absSearchPath)
|
|
22019
|
+
args.push(absSearchPath);
|
|
20936
22020
|
const result = execSync(args.join(" "), {
|
|
20937
22021
|
encoding: "utf-8",
|
|
20938
22022
|
maxBuffer: 10485760,
|
|
@@ -20973,10 +22057,14 @@ server.tool("search", "Search for a pattern in Vue files using ripgrep", {
|
|
|
20973
22057
|
}
|
|
20974
22058
|
});
|
|
20975
22059
|
server.tool("status", "Check Vue Language Server status for a project", {
|
|
20976
|
-
file: exports_external.string().describe("A .vue file path to check the project status for")
|
|
22060
|
+
file: exports_external.string().describe("A .vue file path to check the project status for (absolute or relative to active workspace)")
|
|
20977
22061
|
}, async ({ file }) => {
|
|
20978
22062
|
try {
|
|
20979
|
-
const
|
|
22063
|
+
const { absPath, error: error2 } = resolveFilePath(file);
|
|
22064
|
+
if (error2 || !absPath) {
|
|
22065
|
+
return { content: [{ type: "text", text: error2 || "Invalid path" }] };
|
|
22066
|
+
}
|
|
22067
|
+
const status = await getProjectStatus(absPath);
|
|
20980
22068
|
return {
|
|
20981
22069
|
content: [{
|
|
20982
22070
|
type: "text",
|