@treedy/pyright-mcp 1.1.1 → 1.1.3
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 +13 -6
- package/dist/index.js +1611 -43
- package/dist/index.js.map +26 -0
- package/dist/lsp/connection.d.ts +71 -0
- package/dist/lsp/document-manager.d.ts +67 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/types.d.ts +55 -0
- package/dist/lsp-client.d.ts +42 -2
- package/dist/tools/references.d.ts +0 -2
- package/dist/tools/symbols.d.ts +17 -0
- package/dist/tools/update-document.d.ts +14 -0
- package/package.json +9 -8
- package/dist/lsp-client.js +0 -80
- package/dist/pyright-worker.mjs +0 -148
- package/dist/tools/completions.js +0 -71
- package/dist/tools/definition.js +0 -65
- package/dist/tools/diagnostics.js +0 -93
- package/dist/tools/hover.js +0 -44
- package/dist/tools/references.js +0 -28
- package/dist/tools/rename.js +0 -44
- package/dist/tools/search.js +0 -91
- package/dist/tools/signature-help.js +0 -54
- package/dist/tools/status.js +0 -147
- package/dist/utils/position.js +0 -74
package/dist/lsp-client.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { dirname, resolve } from 'path';
|
|
4
|
-
import { findProjectRoot } from './utils/position.js';
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = dirname(__filename);
|
|
7
|
-
const workerScript = resolve(__dirname, 'pyright-worker.mjs');
|
|
8
|
-
function log(message) {
|
|
9
|
-
console.error(`[LSP] ${message}`);
|
|
10
|
-
}
|
|
11
|
-
export class LspClient {
|
|
12
|
-
constructor() {
|
|
13
|
-
// No longer stores workspace root - it's determined per-file
|
|
14
|
-
}
|
|
15
|
-
async start() {
|
|
16
|
-
// No-op, worker starts fresh for each request
|
|
17
|
-
}
|
|
18
|
-
async stop() {
|
|
19
|
-
// No-op
|
|
20
|
-
}
|
|
21
|
-
runWorker(method, filePath, args) {
|
|
22
|
-
// Automatically find project root from file path
|
|
23
|
-
const workspaceRoot = findProjectRoot(filePath);
|
|
24
|
-
log(`Workspace root: ${workspaceRoot}`);
|
|
25
|
-
const params = JSON.stringify({
|
|
26
|
-
workspaceRoot,
|
|
27
|
-
method,
|
|
28
|
-
filePath,
|
|
29
|
-
...args,
|
|
30
|
-
});
|
|
31
|
-
log(`Running worker: ${method}`);
|
|
32
|
-
try {
|
|
33
|
-
const result = execSync(`node "${workerScript}" '${params.replace(/'/g, "'\\''")}'`, {
|
|
34
|
-
encoding: 'utf-8',
|
|
35
|
-
timeout: 30000,
|
|
36
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
37
|
-
});
|
|
38
|
-
const parsed = JSON.parse(result.trim());
|
|
39
|
-
if (parsed.success) {
|
|
40
|
-
log(`Worker success: ${method}`);
|
|
41
|
-
return parsed.result;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
throw new Error(parsed.error);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
log(`Worker error: ${error}`);
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async hover(filePath, position) {
|
|
53
|
-
return this.runWorker('hover', filePath, { position });
|
|
54
|
-
}
|
|
55
|
-
async definition(filePath, position) {
|
|
56
|
-
return this.runWorker('definition', filePath, { position });
|
|
57
|
-
}
|
|
58
|
-
async references(filePath, position, includeDeclaration = true) {
|
|
59
|
-
return this.runWorker('references', filePath, { position, includeDeclaration });
|
|
60
|
-
}
|
|
61
|
-
async completions(filePath, position) {
|
|
62
|
-
return this.runWorker('completions', filePath, { position });
|
|
63
|
-
}
|
|
64
|
-
async signatureHelp(filePath, position) {
|
|
65
|
-
return this.runWorker('signatureHelp', filePath, { position });
|
|
66
|
-
}
|
|
67
|
-
async rename(filePath, position, newName) {
|
|
68
|
-
return this.runWorker('rename', filePath, { position, newName });
|
|
69
|
-
}
|
|
70
|
-
async getDiagnostics(filePath) {
|
|
71
|
-
return this.runWorker('diagnostics', filePath, {});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
let client = null;
|
|
75
|
-
export function getLspClient() {
|
|
76
|
-
if (!client) {
|
|
77
|
-
client = new LspClient();
|
|
78
|
-
}
|
|
79
|
-
return client;
|
|
80
|
-
}
|
package/dist/pyright-worker.mjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Standalone worker script that communicates with pyright-langserver
|
|
3
|
-
// Uses vscode-jsonrpc for reliable LSP communication
|
|
4
|
-
|
|
5
|
-
import { spawn } from 'child_process';
|
|
6
|
-
import * as rpc from 'vscode-jsonrpc/node.js';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
|
|
9
|
-
const args = JSON.parse(process.argv[2]);
|
|
10
|
-
const { workspaceRoot, method, filePath, position, newName, includeDeclaration } = args;
|
|
11
|
-
|
|
12
|
-
function log(msg) {
|
|
13
|
-
console.error(`[worker] ${msg}`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function pathToUri(p) {
|
|
17
|
-
return p.startsWith('file://') ? p : `file://${p}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Create process and connection at module level like the working test script
|
|
21
|
-
const proc = spawn('pyright-langserver', ['--stdio'], {
|
|
22
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
proc.stderr.on('data', (data) => {
|
|
26
|
-
log(`stderr: ${data.toString().trim()}`);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const connection = rpc.createMessageConnection(
|
|
30
|
-
new rpc.StreamMessageReader(proc.stdout),
|
|
31
|
-
new rpc.StreamMessageWriter(proc.stdin)
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
connection.onError((err) => log(`Connection error: ${err}`));
|
|
35
|
-
connection.onClose(() => log('Connection closed'));
|
|
36
|
-
connection.listen();
|
|
37
|
-
|
|
38
|
-
async function main() {
|
|
39
|
-
try {
|
|
40
|
-
// Initialize with simpler capabilities like the working test
|
|
41
|
-
log('Sending initialize...');
|
|
42
|
-
await connection.sendRequest('initialize', {
|
|
43
|
-
processId: process.pid,
|
|
44
|
-
rootUri: pathToUri(workspaceRoot),
|
|
45
|
-
capabilities: {
|
|
46
|
-
textDocument: {
|
|
47
|
-
hover: { contentFormat: ['markdown', 'plaintext'] },
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
log('Initialize done');
|
|
52
|
-
|
|
53
|
-
await connection.sendNotification('initialized', {});
|
|
54
|
-
log('Sent initialized notification');
|
|
55
|
-
|
|
56
|
-
// Open document
|
|
57
|
-
let uri = null;
|
|
58
|
-
if (filePath) {
|
|
59
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
-
uri = pathToUri(filePath);
|
|
61
|
-
|
|
62
|
-
log(`Opening document: ${uri}`);
|
|
63
|
-
await connection.sendNotification('textDocument/didOpen', {
|
|
64
|
-
textDocument: {
|
|
65
|
-
uri,
|
|
66
|
-
languageId: 'python',
|
|
67
|
-
version: 1,
|
|
68
|
-
text: content,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Wait for analysis
|
|
73
|
-
log('Waiting for analysis...');
|
|
74
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let result;
|
|
78
|
-
|
|
79
|
-
switch (method) {
|
|
80
|
-
case 'hover':
|
|
81
|
-
log(`Sending hover request at ${JSON.stringify(position)}`);
|
|
82
|
-
result = await connection.sendRequest('textDocument/hover', {
|
|
83
|
-
textDocument: { uri },
|
|
84
|
-
position,
|
|
85
|
-
});
|
|
86
|
-
break;
|
|
87
|
-
|
|
88
|
-
case 'definition':
|
|
89
|
-
log('Sending definition request');
|
|
90
|
-
result = await connection.sendRequest('textDocument/definition', {
|
|
91
|
-
textDocument: { uri },
|
|
92
|
-
position,
|
|
93
|
-
});
|
|
94
|
-
break;
|
|
95
|
-
|
|
96
|
-
case 'references':
|
|
97
|
-
log('Sending references request');
|
|
98
|
-
result = await connection.sendRequest('textDocument/references', {
|
|
99
|
-
textDocument: { uri },
|
|
100
|
-
position,
|
|
101
|
-
context: { includeDeclaration: includeDeclaration !== false },
|
|
102
|
-
});
|
|
103
|
-
break;
|
|
104
|
-
|
|
105
|
-
case 'completions':
|
|
106
|
-
log('Sending completions request');
|
|
107
|
-
result = await connection.sendRequest('textDocument/completion', {
|
|
108
|
-
textDocument: { uri },
|
|
109
|
-
position,
|
|
110
|
-
});
|
|
111
|
-
break;
|
|
112
|
-
|
|
113
|
-
case 'signatureHelp':
|
|
114
|
-
log('Sending signatureHelp request');
|
|
115
|
-
result = await connection.sendRequest('textDocument/signatureHelp', {
|
|
116
|
-
textDocument: { uri },
|
|
117
|
-
position,
|
|
118
|
-
});
|
|
119
|
-
break;
|
|
120
|
-
|
|
121
|
-
case 'rename':
|
|
122
|
-
log('Sending rename request');
|
|
123
|
-
result = await connection.sendRequest('textDocument/rename', {
|
|
124
|
-
textDocument: { uri },
|
|
125
|
-
position,
|
|
126
|
-
newName,
|
|
127
|
-
});
|
|
128
|
-
break;
|
|
129
|
-
|
|
130
|
-
case 'diagnostics':
|
|
131
|
-
result = [];
|
|
132
|
-
break;
|
|
133
|
-
|
|
134
|
-
default:
|
|
135
|
-
throw new Error(`Unknown method: ${method}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
console.log(JSON.stringify({ success: true, result }));
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.log(JSON.stringify({ success: false, error: error.message }));
|
|
141
|
-
} finally {
|
|
142
|
-
connection.dispose();
|
|
143
|
-
proc.kill();
|
|
144
|
-
process.exit(0);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
main();
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getLspClient } from '../lsp-client.js';
|
|
3
|
-
import { toPosition } from '../utils/position.js';
|
|
4
|
-
import { CompletionItemKind } from 'vscode-languageserver-protocol';
|
|
5
|
-
export const completionsSchema = {
|
|
6
|
-
file: z.string().describe('Absolute path to the Python file'),
|
|
7
|
-
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
8
|
-
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
9
|
-
limit: z.number().int().positive().optional().default(20).describe('Maximum number of completions to return'),
|
|
10
|
-
};
|
|
11
|
-
const kindNames = {
|
|
12
|
-
[CompletionItemKind.Text]: 'Text',
|
|
13
|
-
[CompletionItemKind.Method]: 'Method',
|
|
14
|
-
[CompletionItemKind.Function]: 'Function',
|
|
15
|
-
[CompletionItemKind.Constructor]: 'Constructor',
|
|
16
|
-
[CompletionItemKind.Field]: 'Field',
|
|
17
|
-
[CompletionItemKind.Variable]: 'Variable',
|
|
18
|
-
[CompletionItemKind.Class]: 'Class',
|
|
19
|
-
[CompletionItemKind.Interface]: 'Interface',
|
|
20
|
-
[CompletionItemKind.Module]: 'Module',
|
|
21
|
-
[CompletionItemKind.Property]: 'Property',
|
|
22
|
-
[CompletionItemKind.Unit]: 'Unit',
|
|
23
|
-
[CompletionItemKind.Value]: 'Value',
|
|
24
|
-
[CompletionItemKind.Enum]: 'Enum',
|
|
25
|
-
[CompletionItemKind.Keyword]: 'Keyword',
|
|
26
|
-
[CompletionItemKind.Snippet]: 'Snippet',
|
|
27
|
-
[CompletionItemKind.Color]: 'Color',
|
|
28
|
-
[CompletionItemKind.File]: 'File',
|
|
29
|
-
[CompletionItemKind.Reference]: 'Reference',
|
|
30
|
-
[CompletionItemKind.Folder]: 'Folder',
|
|
31
|
-
[CompletionItemKind.EnumMember]: 'EnumMember',
|
|
32
|
-
[CompletionItemKind.Constant]: 'Constant',
|
|
33
|
-
[CompletionItemKind.Struct]: 'Struct',
|
|
34
|
-
[CompletionItemKind.Event]: 'Event',
|
|
35
|
-
[CompletionItemKind.Operator]: 'Operator',
|
|
36
|
-
[CompletionItemKind.TypeParameter]: 'TypeParameter',
|
|
37
|
-
};
|
|
38
|
-
export async function completions(args) {
|
|
39
|
-
const client = getLspClient();
|
|
40
|
-
const position = toPosition(args.line, args.column);
|
|
41
|
-
const limit = args.limit ?? 20;
|
|
42
|
-
const result = await client.completions(args.file, position);
|
|
43
|
-
if (!result) {
|
|
44
|
-
return {
|
|
45
|
-
content: [{ type: 'text', text: 'No completions available at this position.' }],
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
let items;
|
|
49
|
-
if (Array.isArray(result)) {
|
|
50
|
-
items = result;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
items = result.items;
|
|
54
|
-
}
|
|
55
|
-
if (items.length === 0) {
|
|
56
|
-
return {
|
|
57
|
-
content: [{ type: 'text', text: 'No completions available at this position.' }],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const limitedItems = items.slice(0, limit);
|
|
61
|
-
let output = `**Completions** at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
62
|
-
output += `Showing ${limitedItems.length} of ${items.length} completion(s):\n\n`;
|
|
63
|
-
for (const item of limitedItems) {
|
|
64
|
-
const kind = item.kind ? kindNames[item.kind] || 'Unknown' : 'Unknown';
|
|
65
|
-
const detail = item.detail ? ` - ${item.detail}` : '';
|
|
66
|
-
output += `- **${item.label}** (${kind})${detail}\n`;
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
content: [{ type: 'text', text: output }],
|
|
70
|
-
};
|
|
71
|
-
}
|
package/dist/tools/definition.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getLspClient } from '../lsp-client.js';
|
|
3
|
-
import { toPosition, fromPosition, uriToPath } from '../utils/position.js';
|
|
4
|
-
export const definitionSchema = {
|
|
5
|
-
file: z.string().describe('Absolute path to the Python file'),
|
|
6
|
-
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
7
|
-
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
8
|
-
};
|
|
9
|
-
export async function definition(args) {
|
|
10
|
-
const client = getLspClient();
|
|
11
|
-
const position = toPosition(args.line, args.column);
|
|
12
|
-
const result = await client.definition(args.file, position);
|
|
13
|
-
if (!result) {
|
|
14
|
-
return {
|
|
15
|
-
content: [{ type: 'text', text: 'No definition found at this position.' }],
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const locations = [];
|
|
19
|
-
if (Array.isArray(result)) {
|
|
20
|
-
for (const item of result) {
|
|
21
|
-
if ('targetUri' in item) {
|
|
22
|
-
// LocationLink
|
|
23
|
-
const link = item;
|
|
24
|
-
const pos = fromPosition(link.targetSelectionRange.start);
|
|
25
|
-
locations.push({
|
|
26
|
-
file: uriToPath(link.targetUri),
|
|
27
|
-
line: pos.line,
|
|
28
|
-
column: pos.column,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
// Location
|
|
33
|
-
const loc = item;
|
|
34
|
-
const pos = fromPosition(loc.range.start);
|
|
35
|
-
locations.push({
|
|
36
|
-
file: uriToPath(loc.uri),
|
|
37
|
-
line: pos.line,
|
|
38
|
-
column: pos.column,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// Single Location
|
|
45
|
-
const loc = result;
|
|
46
|
-
const pos = fromPosition(loc.range.start);
|
|
47
|
-
locations.push({
|
|
48
|
-
file: uriToPath(loc.uri),
|
|
49
|
-
line: pos.line,
|
|
50
|
-
column: pos.column,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (locations.length === 0) {
|
|
54
|
-
return {
|
|
55
|
-
content: [{ type: 'text', text: 'No definition found at this position.' }],
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
let output = `**Definition(s)** for symbol at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
59
|
-
for (const loc of locations) {
|
|
60
|
-
output += `- ${loc.file}:${loc.line}:${loc.column}\n`;
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
content: [{ type: 'text', text: output }],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import { findProjectRoot } from '../utils/position.js';
|
|
4
|
-
export const diagnosticsSchema = {
|
|
5
|
-
path: z
|
|
6
|
-
.string()
|
|
7
|
-
.describe('Path to a Python file or directory to check'),
|
|
8
|
-
};
|
|
9
|
-
export async function diagnostics(args) {
|
|
10
|
-
const { path } = args;
|
|
11
|
-
// Determine project root from path
|
|
12
|
-
const projectRoot = findProjectRoot(path);
|
|
13
|
-
// Build pyright command - path can be file or directory
|
|
14
|
-
const target = path;
|
|
15
|
-
const cmd = `pyright "${target}" --outputjson`;
|
|
16
|
-
let output;
|
|
17
|
-
try {
|
|
18
|
-
const result = execSync(cmd, {
|
|
19
|
-
encoding: 'utf-8',
|
|
20
|
-
cwd: projectRoot,
|
|
21
|
-
timeout: 60000,
|
|
22
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
23
|
-
});
|
|
24
|
-
output = JSON.parse(result);
|
|
25
|
-
}
|
|
26
|
-
catch (e) {
|
|
27
|
-
const error = e;
|
|
28
|
-
// pyright returns non-zero exit code if there are errors
|
|
29
|
-
if (error.stdout) {
|
|
30
|
-
try {
|
|
31
|
-
output = JSON.parse(error.stdout);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return {
|
|
35
|
-
content: [
|
|
36
|
-
{
|
|
37
|
-
type: 'text',
|
|
38
|
-
text: `Error running pyright: ${error.message || 'Unknown error'}`,
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
return {
|
|
46
|
-
content: [
|
|
47
|
-
{
|
|
48
|
-
type: 'text',
|
|
49
|
-
text: `Error running pyright: ${error.message || 'Unknown error'}\n\nMake sure pyright is installed: npm install -g pyright`,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const diags = output.generalDiagnostics || [];
|
|
56
|
-
const summary = output.summary;
|
|
57
|
-
if (diags.length === 0) {
|
|
58
|
-
let text = `**No issues found**\n\n`;
|
|
59
|
-
text += `- Files analyzed: ${summary.filesAnalyzed}\n`;
|
|
60
|
-
text += `- Time: ${summary.timeInSec}s`;
|
|
61
|
-
return {
|
|
62
|
-
content: [{ type: 'text', text }],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
// Group by file
|
|
66
|
-
const byFile = new Map();
|
|
67
|
-
for (const diag of diags) {
|
|
68
|
-
const list = byFile.get(diag.file) || [];
|
|
69
|
-
list.push(diag);
|
|
70
|
-
byFile.set(diag.file, list);
|
|
71
|
-
}
|
|
72
|
-
let text = `**Diagnostics Summary**\n\n`;
|
|
73
|
-
text += `- Errors: ${summary.errorCount}\n`;
|
|
74
|
-
text += `- Warnings: ${summary.warningCount}\n`;
|
|
75
|
-
text += `- Information: ${summary.informationCount}\n`;
|
|
76
|
-
text += `- Files analyzed: ${summary.filesAnalyzed}\n`;
|
|
77
|
-
text += `- Time: ${summary.timeInSec}s\n\n`;
|
|
78
|
-
text += `---\n\n`;
|
|
79
|
-
for (const [filePath, fileDiags] of byFile) {
|
|
80
|
-
text += `### ${filePath}\n\n`;
|
|
81
|
-
for (const diag of fileDiags) {
|
|
82
|
-
const line = diag.range.start.line + 1;
|
|
83
|
-
const col = diag.range.start.character + 1;
|
|
84
|
-
const rule = diag.rule ? ` (${diag.rule})` : '';
|
|
85
|
-
const icon = diag.severity === 'error' ? '❌' : diag.severity === 'warning' ? '⚠️' : 'ℹ️';
|
|
86
|
-
text += `- ${icon} **${diag.severity}** at ${line}:${col}${rule}\n`;
|
|
87
|
-
text += ` ${diag.message}\n\n`;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
content: [{ type: 'text', text }],
|
|
92
|
-
};
|
|
93
|
-
}
|
package/dist/tools/hover.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getLspClient } from '../lsp-client.js';
|
|
3
|
-
import { toPosition, formatRange } from '../utils/position.js';
|
|
4
|
-
export const hoverSchema = {
|
|
5
|
-
file: z.string().describe('Absolute path to the Python file'),
|
|
6
|
-
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
7
|
-
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
8
|
-
};
|
|
9
|
-
export async function hover(args) {
|
|
10
|
-
console.error(`[hover] Starting hover for ${args.file}:${args.line}:${args.column}`);
|
|
11
|
-
const client = getLspClient();
|
|
12
|
-
const position = toPosition(args.line, args.column);
|
|
13
|
-
console.error(`[hover] Calling LSP hover...`);
|
|
14
|
-
const result = await client.hover(args.file, position);
|
|
15
|
-
console.error(`[hover] Got result: ${result ? 'yes' : 'no'}`);
|
|
16
|
-
if (!result) {
|
|
17
|
-
return {
|
|
18
|
-
content: [{ type: 'text', text: 'No hover information available at this position.' }],
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
let hoverText = '';
|
|
22
|
-
if (typeof result.contents === 'string') {
|
|
23
|
-
hoverText = result.contents;
|
|
24
|
-
}
|
|
25
|
-
else if (Array.isArray(result.contents)) {
|
|
26
|
-
hoverText = result.contents
|
|
27
|
-
.map((c) => (typeof c === 'string' ? c : c.value))
|
|
28
|
-
.join('\n\n');
|
|
29
|
-
}
|
|
30
|
-
else if ('kind' in result.contents) {
|
|
31
|
-
hoverText = result.contents.value;
|
|
32
|
-
}
|
|
33
|
-
else if ('value' in result.contents) {
|
|
34
|
-
hoverText = result.contents.value;
|
|
35
|
-
}
|
|
36
|
-
let output = `**Hover Info** at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
37
|
-
output += hoverText;
|
|
38
|
-
if (result.range) {
|
|
39
|
-
output += `\n\n**Range:** ${formatRange(result.range)}`;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
content: [{ type: 'text', text: output }],
|
|
43
|
-
};
|
|
44
|
-
}
|
package/dist/tools/references.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getLspClient } from '../lsp-client.js';
|
|
3
|
-
import { toPosition, fromPosition, uriToPath } from '../utils/position.js';
|
|
4
|
-
export const referencesSchema = {
|
|
5
|
-
file: z.string().describe('Absolute path to the Python file'),
|
|
6
|
-
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
7
|
-
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
8
|
-
includeDeclaration: z.boolean().optional().default(true).describe('Include the declaration in results'),
|
|
9
|
-
};
|
|
10
|
-
export async function references(args) {
|
|
11
|
-
const client = getLspClient();
|
|
12
|
-
const position = toPosition(args.line, args.column);
|
|
13
|
-
const result = await client.references(args.file, position, args.includeDeclaration ?? true);
|
|
14
|
-
if (!result || result.length === 0) {
|
|
15
|
-
return {
|
|
16
|
-
content: [{ type: 'text', text: 'No references found at this position.' }],
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
let output = `**References** for symbol at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
20
|
-
output += `Found ${result.length} reference(s):\n\n`;
|
|
21
|
-
for (const loc of result) {
|
|
22
|
-
const pos = fromPosition(loc.range.start);
|
|
23
|
-
output += `- ${uriToPath(loc.uri)}:${pos.line}:${pos.column}\n`;
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
content: [{ type: 'text', text: output }],
|
|
27
|
-
};
|
|
28
|
-
}
|
package/dist/tools/rename.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getLspClient } from '../lsp-client.js';
|
|
3
|
-
import { toPosition, fromPosition, uriToPath } from '../utils/position.js';
|
|
4
|
-
export const renameSchema = {
|
|
5
|
-
file: z.string().describe('Absolute path to the Python file'),
|
|
6
|
-
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
7
|
-
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
8
|
-
newName: z.string().describe('New name for the symbol'),
|
|
9
|
-
};
|
|
10
|
-
export async function rename(args) {
|
|
11
|
-
const client = getLspClient();
|
|
12
|
-
const position = toPosition(args.line, args.column);
|
|
13
|
-
const result = await client.rename(args.file, position, args.newName);
|
|
14
|
-
if (!result) {
|
|
15
|
-
return {
|
|
16
|
-
content: [{ type: 'text', text: 'Cannot rename symbol at this position.' }],
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
let output = `**Rename Preview** for symbol at ${args.file}:${args.line}:${args.column}\n`;
|
|
20
|
-
output += `New name: **${args.newName}**\n\n`;
|
|
21
|
-
if (result.changes) {
|
|
22
|
-
let totalEdits = 0;
|
|
23
|
-
for (const [uri, edits] of Object.entries(result.changes)) {
|
|
24
|
-
const filePath = uriToPath(uri);
|
|
25
|
-
output += `**${filePath}**\n`;
|
|
26
|
-
for (const edit of edits) {
|
|
27
|
-
const start = fromPosition(edit.range.start);
|
|
28
|
-
const end = fromPosition(edit.range.end);
|
|
29
|
-
output += ` - Line ${start.line}:${start.column}-${end.line}:${end.column}: "${edit.newText}"\n`;
|
|
30
|
-
totalEdits++;
|
|
31
|
-
}
|
|
32
|
-
output += '\n';
|
|
33
|
-
}
|
|
34
|
-
output += `Total: ${totalEdits} edit(s) across ${Object.keys(result.changes).length} file(s)\n`;
|
|
35
|
-
}
|
|
36
|
-
else if (result.documentChanges) {
|
|
37
|
-
output += 'Document changes detected (complex rename operation)\n';
|
|
38
|
-
output += JSON.stringify(result.documentChanges, null, 2);
|
|
39
|
-
}
|
|
40
|
-
output += '\n**Note:** This is a preview. The actual rename has not been applied.';
|
|
41
|
-
return {
|
|
42
|
-
content: [{ type: 'text', text: output }],
|
|
43
|
-
};
|
|
44
|
-
}
|
package/dist/tools/search.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
export const searchSchema = {
|
|
5
|
-
pattern: z.string().describe('The regex pattern to search for'),
|
|
6
|
-
path: z.string().optional().describe('Directory or file to search in (defaults to current working directory)'),
|
|
7
|
-
glob: z.string().optional().describe('Glob pattern to filter files (e.g., "*.py", "**/*.ts")'),
|
|
8
|
-
caseSensitive: z.boolean().optional().default(true).describe('Whether the search is case sensitive'),
|
|
9
|
-
maxResults: z.number().int().positive().optional().default(50).describe('Maximum number of results to return'),
|
|
10
|
-
};
|
|
11
|
-
export async function search(args) {
|
|
12
|
-
const searchPath = args.path || process.cwd();
|
|
13
|
-
const caseSensitive = args.caseSensitive ?? true;
|
|
14
|
-
const maxResults = args.maxResults ?? 50;
|
|
15
|
-
// Build rg command
|
|
16
|
-
const rgArgs = [
|
|
17
|
-
'--json',
|
|
18
|
-
'--line-number',
|
|
19
|
-
'--column',
|
|
20
|
-
];
|
|
21
|
-
if (!caseSensitive) {
|
|
22
|
-
rgArgs.push('--ignore-case');
|
|
23
|
-
}
|
|
24
|
-
if (args.glob) {
|
|
25
|
-
rgArgs.push('--glob', args.glob);
|
|
26
|
-
}
|
|
27
|
-
rgArgs.push('--', args.pattern, searchPath);
|
|
28
|
-
try {
|
|
29
|
-
const result = execSync(`rg ${rgArgs.map(a => `'${a}'`).join(' ')}`, {
|
|
30
|
-
encoding: 'utf-8',
|
|
31
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
32
|
-
});
|
|
33
|
-
const results = [];
|
|
34
|
-
const lines = result.split('\n').filter(Boolean);
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
if (results.length >= maxResults)
|
|
37
|
-
break;
|
|
38
|
-
try {
|
|
39
|
-
const json = JSON.parse(line);
|
|
40
|
-
if (json.type === 'match') {
|
|
41
|
-
const data = json.data;
|
|
42
|
-
const filePath = data.path.text;
|
|
43
|
-
const lineNumber = data.line_number;
|
|
44
|
-
const lineText = data.lines.text.trimEnd();
|
|
45
|
-
// Get all matches in this line
|
|
46
|
-
for (const submatch of data.submatches) {
|
|
47
|
-
if (results.length >= maxResults)
|
|
48
|
-
break;
|
|
49
|
-
results.push({
|
|
50
|
-
file: path.resolve(filePath),
|
|
51
|
-
line: lineNumber,
|
|
52
|
-
column: submatch.start + 1, // Convert to 1-based
|
|
53
|
-
text: lineText,
|
|
54
|
-
match: submatch.match.text,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// Skip non-JSON lines
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (results.length === 0) {
|
|
64
|
-
return {
|
|
65
|
-
content: [{ type: 'text', text: `No matches found for pattern: ${args.pattern}` }],
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
let output = `**Search Results** for \`${args.pattern}\`\n\n`;
|
|
69
|
-
output += `Found ${results.length} match(es)${results.length >= maxResults ? ` (limited to ${maxResults})` : ''}:\n\n`;
|
|
70
|
-
for (const r of results) {
|
|
71
|
-
output += `**${r.file}:${r.line}:${r.column}**\n`;
|
|
72
|
-
output += ` \`${r.text}\`\n`;
|
|
73
|
-
output += ` Match: \`${r.match}\`\n\n`;
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
content: [{ type: 'text', text: output }],
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
const error = err;
|
|
81
|
-
if (error.status === 1) {
|
|
82
|
-
// rg returns 1 when no matches found
|
|
83
|
-
return {
|
|
84
|
-
content: [{ type: 'text', text: `No matches found for pattern: ${args.pattern}` }],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
content: [{ type: 'text', text: `Search error: ${error.message || 'Unknown error'}` }],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|