@treedy/pyright-mcp 1.0.0
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 +223 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/lsp-client.d.ts +15 -0
- package/dist/lsp-client.js +80 -0
- package/dist/pyright-worker.mjs +148 -0
- package/dist/tools/completions.d.ts +18 -0
- package/dist/tools/completions.js +71 -0
- package/dist/tools/definition.d.ts +16 -0
- package/dist/tools/definition.js +65 -0
- package/dist/tools/diagnostics.d.ts +12 -0
- package/dist/tools/diagnostics.js +35 -0
- package/dist/tools/hover.d.ts +16 -0
- package/dist/tools/hover.js +44 -0
- package/dist/tools/references.d.ts +18 -0
- package/dist/tools/references.js +28 -0
- package/dist/tools/rename.d.ts +18 -0
- package/dist/tools/rename.js +44 -0
- package/dist/tools/search.d.ts +20 -0
- package/dist/tools/search.js +91 -0
- package/dist/tools/signature-help.d.ts +16 -0
- package/dist/tools/signature-help.js +54 -0
- package/dist/tools/status.d.ts +14 -0
- package/dist/tools/status.js +147 -0
- package/dist/utils/position.d.ts +33 -0
- package/dist/utils/position.js +74 -0
- package/package.json +52 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getLspClient } from '../lsp-client.js';
|
|
3
|
+
import { fromPosition } from '../utils/position.js';
|
|
4
|
+
import { DiagnosticSeverity } from 'vscode-languageserver-protocol';
|
|
5
|
+
export const diagnosticsSchema = {
|
|
6
|
+
file: z.string().describe('Absolute path to the Python file'),
|
|
7
|
+
};
|
|
8
|
+
const severityNames = {
|
|
9
|
+
[DiagnosticSeverity.Error]: 'Error',
|
|
10
|
+
[DiagnosticSeverity.Warning]: 'Warning',
|
|
11
|
+
[DiagnosticSeverity.Information]: 'Information',
|
|
12
|
+
[DiagnosticSeverity.Hint]: 'Hint',
|
|
13
|
+
};
|
|
14
|
+
export async function diagnostics(args) {
|
|
15
|
+
const client = getLspClient();
|
|
16
|
+
const result = await client.getDiagnostics(args.file);
|
|
17
|
+
if (result.length === 0) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: 'text', text: `No diagnostics for ${args.file}` }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
let output = `**Diagnostics** for ${args.file}\n\n`;
|
|
23
|
+
output += `Found ${result.length} issue(s):\n\n`;
|
|
24
|
+
for (const diag of result) {
|
|
25
|
+
const pos = fromPosition(diag.range.start);
|
|
26
|
+
const severity = diag.severity ? severityNames[diag.severity] || 'Unknown' : 'Unknown';
|
|
27
|
+
const source = diag.source ? `[${diag.source}] ` : '';
|
|
28
|
+
const code = diag.code ? ` (${diag.code})` : '';
|
|
29
|
+
output += `- **${severity}** at line ${pos.line}:${pos.column}${code}\n`;
|
|
30
|
+
output += ` ${source}${diag.message}\n\n`;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: 'text', text: output }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const hoverSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
};
|
|
7
|
+
export declare function hover(args: {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const referencesSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
includeDeclaration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function references(args: {
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
includeDeclaration?: boolean;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const renameSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
newName: z.ZodString;
|
|
7
|
+
};
|
|
8
|
+
export declare function rename(args: {
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
newName: string;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const searchSchema: {
|
|
3
|
+
pattern: z.ZodString;
|
|
4
|
+
path: z.ZodOptional<z.ZodString>;
|
|
5
|
+
glob: z.ZodOptional<z.ZodString>;
|
|
6
|
+
caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
maxResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
};
|
|
9
|
+
export declare function search(args: {
|
|
10
|
+
pattern: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
glob?: string;
|
|
13
|
+
caseSensitive?: boolean;
|
|
14
|
+
maxResults?: number;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
content: Array<{
|
|
17
|
+
type: 'text';
|
|
18
|
+
text: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const signatureHelpSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
};
|
|
7
|
+
export declare function signatureHelp(args: {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getLspClient } from '../lsp-client.js';
|
|
3
|
+
import { toPosition } from '../utils/position.js';
|
|
4
|
+
export const signatureHelpSchema = {
|
|
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 signatureHelp(args) {
|
|
10
|
+
const client = getLspClient();
|
|
11
|
+
const position = toPosition(args.line, args.column);
|
|
12
|
+
const result = await client.signatureHelp(args.file, position);
|
|
13
|
+
if (!result || result.signatures.length === 0) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: 'text', text: 'No signature help available at this position.' }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let output = `**Signature Help** at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
19
|
+
const activeIndex = result.activeSignature ?? 0;
|
|
20
|
+
const activeParam = result.activeParameter ?? 0;
|
|
21
|
+
for (let i = 0; i < result.signatures.length; i++) {
|
|
22
|
+
const sig = result.signatures[i];
|
|
23
|
+
const isActive = i === activeIndex;
|
|
24
|
+
output += `${isActive ? '→ ' : ' '}**${sig.label}**\n`;
|
|
25
|
+
if (sig.documentation) {
|
|
26
|
+
const doc = typeof sig.documentation === 'string'
|
|
27
|
+
? sig.documentation
|
|
28
|
+
: sig.documentation.value;
|
|
29
|
+
output += ` ${doc}\n`;
|
|
30
|
+
}
|
|
31
|
+
if (sig.parameters && sig.parameters.length > 0) {
|
|
32
|
+
output += `\n Parameters:\n`;
|
|
33
|
+
for (let j = 0; j < sig.parameters.length; j++) {
|
|
34
|
+
const param = sig.parameters[j];
|
|
35
|
+
const isActiveParam = isActive && j === activeParam;
|
|
36
|
+
const label = typeof param.label === 'string'
|
|
37
|
+
? param.label
|
|
38
|
+
: sig.label.slice(param.label[0], param.label[1]);
|
|
39
|
+
output += ` ${isActiveParam ? '→ ' : ' '}${label}`;
|
|
40
|
+
if (param.documentation) {
|
|
41
|
+
const paramDoc = typeof param.documentation === 'string'
|
|
42
|
+
? param.documentation
|
|
43
|
+
: param.documentation.value;
|
|
44
|
+
output += ` - ${paramDoc}`;
|
|
45
|
+
}
|
|
46
|
+
output += '\n';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
output += '\n';
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: output }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const statusSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
};
|
|
5
|
+
type StatusArgs = {
|
|
6
|
+
file: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function status(args: StatusArgs): Promise<{
|
|
9
|
+
content: Array<{
|
|
10
|
+
type: 'text';
|
|
11
|
+
text: string;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { findProjectRoot } from '../utils/position.js';
|
|
6
|
+
export const statusSchema = {
|
|
7
|
+
file: z.string().describe('A Python file path to check the project status for'),
|
|
8
|
+
};
|
|
9
|
+
export async function status(args) {
|
|
10
|
+
const { file } = args;
|
|
11
|
+
const lines = [];
|
|
12
|
+
// Find project root
|
|
13
|
+
const projectRoot = findProjectRoot(file);
|
|
14
|
+
lines.push(`## Project Root`);
|
|
15
|
+
lines.push(`\`${projectRoot}\``);
|
|
16
|
+
lines.push('');
|
|
17
|
+
// Check pyright installation
|
|
18
|
+
lines.push(`## Pyright`);
|
|
19
|
+
try {
|
|
20
|
+
const pyrightVersion = execSync('pyright --version', { encoding: 'utf-8' }).trim();
|
|
21
|
+
lines.push(`- Version: ${pyrightVersion}`);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
lines.push(`- ⚠️ **Not installed or not in PATH**`);
|
|
25
|
+
lines.push(` Install with: \`npm install -g pyright\``);
|
|
26
|
+
}
|
|
27
|
+
lines.push('');
|
|
28
|
+
// Check pyright config
|
|
29
|
+
lines.push(`## Pyright Config`);
|
|
30
|
+
const pyrightConfigPath = join(projectRoot, 'pyrightconfig.json');
|
|
31
|
+
const pyprojectPath = join(projectRoot, 'pyproject.toml');
|
|
32
|
+
if (existsSync(pyrightConfigPath)) {
|
|
33
|
+
lines.push(`- Config file: \`pyrightconfig.json\``);
|
|
34
|
+
try {
|
|
35
|
+
const config = JSON.parse(readFileSync(pyrightConfigPath, 'utf-8'));
|
|
36
|
+
if (config.pythonVersion) {
|
|
37
|
+
lines.push(`- Python version: ${config.pythonVersion}`);
|
|
38
|
+
}
|
|
39
|
+
if (config.pythonPlatform) {
|
|
40
|
+
lines.push(`- Platform: ${config.pythonPlatform}`);
|
|
41
|
+
}
|
|
42
|
+
if (config.venvPath) {
|
|
43
|
+
lines.push(`- Venv path: ${config.venvPath}`);
|
|
44
|
+
}
|
|
45
|
+
if (config.venv) {
|
|
46
|
+
lines.push(`- Venv: ${config.venv}`);
|
|
47
|
+
}
|
|
48
|
+
if (config.typeCheckingMode) {
|
|
49
|
+
lines.push(`- Type checking mode: ${config.typeCheckingMode}`);
|
|
50
|
+
}
|
|
51
|
+
if (config.include) {
|
|
52
|
+
lines.push(`- Include: ${JSON.stringify(config.include)}`);
|
|
53
|
+
}
|
|
54
|
+
if (config.exclude) {
|
|
55
|
+
lines.push(`- Exclude: ${JSON.stringify(config.exclude)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
lines.push(`- ⚠️ Failed to parse config: ${e}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (existsSync(pyprojectPath)) {
|
|
63
|
+
lines.push(`- Config file: \`pyproject.toml\` (may contain [tool.pyright] section)`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
lines.push(`- ⚠️ No pyrightconfig.json or pyproject.toml found`);
|
|
67
|
+
lines.push(` Pyright will use default settings`);
|
|
68
|
+
}
|
|
69
|
+
lines.push('');
|
|
70
|
+
// Check Python environment
|
|
71
|
+
lines.push(`## Python Environment`);
|
|
72
|
+
try {
|
|
73
|
+
const pythonVersion = execSync('python3 --version', { encoding: 'utf-8' }).trim();
|
|
74
|
+
lines.push(`- System Python: ${pythonVersion}`);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
try {
|
|
78
|
+
const pythonVersion = execSync('python --version', { encoding: 'utf-8' }).trim();
|
|
79
|
+
lines.push(`- System Python: ${pythonVersion}`);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
lines.push(`- ⚠️ Python not found in PATH`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Check for virtual environment
|
|
86
|
+
const venvPaths = ['.venv', 'venv', '.env', 'env'];
|
|
87
|
+
for (const venv of venvPaths) {
|
|
88
|
+
const venvPath = join(projectRoot, venv);
|
|
89
|
+
if (existsSync(venvPath)) {
|
|
90
|
+
lines.push(`- Virtual env found: \`${venv}/\``);
|
|
91
|
+
// Try to get venv python version
|
|
92
|
+
const venvPython = join(venvPath, 'bin', 'python');
|
|
93
|
+
if (existsSync(venvPython)) {
|
|
94
|
+
try {
|
|
95
|
+
const venvVersion = execSync(`"${venvPython}" --version`, { encoding: 'utf-8' }).trim();
|
|
96
|
+
lines.push(` - ${venvVersion}`);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
lines.push('');
|
|
106
|
+
// Quick pyright check on the file
|
|
107
|
+
lines.push(`## File Check`);
|
|
108
|
+
lines.push(`- File: \`${file}\``);
|
|
109
|
+
if (existsSync(file)) {
|
|
110
|
+
lines.push(`- Exists: ✅`);
|
|
111
|
+
try {
|
|
112
|
+
const result = execSync(`pyright "${file}" --outputjson`, {
|
|
113
|
+
encoding: 'utf-8',
|
|
114
|
+
cwd: projectRoot,
|
|
115
|
+
timeout: 30000,
|
|
116
|
+
});
|
|
117
|
+
const output = JSON.parse(result);
|
|
118
|
+
const errors = output.generalDiagnostics?.filter((d) => d.severity === 'error')?.length || 0;
|
|
119
|
+
const warnings = output.generalDiagnostics?.filter((d) => d.severity === 'warning')?.length || 0;
|
|
120
|
+
lines.push(`- Diagnostics: ${errors} errors, ${warnings} warnings`);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
// pyright returns non-zero exit code if there are errors
|
|
124
|
+
const error = e;
|
|
125
|
+
if (error.stdout) {
|
|
126
|
+
try {
|
|
127
|
+
const output = JSON.parse(error.stdout);
|
|
128
|
+
const errors = output.generalDiagnostics?.filter((d) => d.severity === 'error')?.length || 0;
|
|
129
|
+
const warnings = output.generalDiagnostics?.filter((d) => d.severity === 'warning')?.length || 0;
|
|
130
|
+
lines.push(`- Diagnostics: ${errors} errors, ${warnings} warnings`);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
lines.push(`- ⚠️ Could not run pyright check`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(`- ⚠️ Could not run pyright check`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
lines.push(`- Exists: ❌ File not found`);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Position, Range, Location } from 'vscode-languageserver-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Convert 1-based line/column (user input) to 0-based LSP Position
|
|
4
|
+
*/
|
|
5
|
+
export declare function toPosition(line: number, column: number): Position;
|
|
6
|
+
/**
|
|
7
|
+
* Convert 0-based LSP Position to 1-based line/column (user output)
|
|
8
|
+
*/
|
|
9
|
+
export declare function fromPosition(pos: Position): {
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Format a Location for display
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatLocation(loc: Location): string;
|
|
17
|
+
/**
|
|
18
|
+
* Format a Range for display
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatRange(range: Range): string;
|
|
21
|
+
/**
|
|
22
|
+
* Convert file path to URI
|
|
23
|
+
*/
|
|
24
|
+
export declare function pathToUri(filePath: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Convert URI to file path
|
|
27
|
+
*/
|
|
28
|
+
export declare function uriToPath(uri: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Find project root by looking for pyrightconfig.json or pyproject.toml
|
|
31
|
+
* starting from the given file path and walking up the directory tree
|
|
32
|
+
*/
|
|
33
|
+
export declare function findProjectRoot(filePath: string): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Position } from 'vscode-languageserver-protocol';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Convert 1-based line/column (user input) to 0-based LSP Position
|
|
6
|
+
*/
|
|
7
|
+
export function toPosition(line, column) {
|
|
8
|
+
return Position.create(line - 1, column - 1);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Convert 0-based LSP Position to 1-based line/column (user output)
|
|
12
|
+
*/
|
|
13
|
+
export function fromPosition(pos) {
|
|
14
|
+
return {
|
|
15
|
+
line: pos.line + 1,
|
|
16
|
+
column: pos.character + 1,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format a Location for display
|
|
21
|
+
*/
|
|
22
|
+
export function formatLocation(loc) {
|
|
23
|
+
const start = fromPosition(loc.range.start);
|
|
24
|
+
const end = fromPosition(loc.range.end);
|
|
25
|
+
return `${loc.uri}:${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Format a Range for display
|
|
29
|
+
*/
|
|
30
|
+
export function formatRange(range) {
|
|
31
|
+
const start = fromPosition(range.start);
|
|
32
|
+
const end = fromPosition(range.end);
|
|
33
|
+
return `${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convert file path to URI
|
|
37
|
+
*/
|
|
38
|
+
export function pathToUri(filePath) {
|
|
39
|
+
if (filePath.startsWith('file://')) {
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
return `file://${filePath}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convert URI to file path
|
|
46
|
+
*/
|
|
47
|
+
export function uriToPath(uri) {
|
|
48
|
+
if (uri.startsWith('file://')) {
|
|
49
|
+
return uri.slice(7);
|
|
50
|
+
}
|
|
51
|
+
return uri;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find project root by looking for pyrightconfig.json or pyproject.toml
|
|
55
|
+
* starting from the given file path and walking up the directory tree
|
|
56
|
+
*/
|
|
57
|
+
export function findProjectRoot(filePath) {
|
|
58
|
+
const configFiles = ['pyrightconfig.json', 'pyproject.toml', '.git'];
|
|
59
|
+
let dir = dirname(resolve(filePath));
|
|
60
|
+
const root = '/';
|
|
61
|
+
while (dir !== root) {
|
|
62
|
+
for (const configFile of configFiles) {
|
|
63
|
+
if (existsSync(join(dir, configFile))) {
|
|
64
|
+
return dir;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const parent = dirname(dir);
|
|
68
|
+
if (parent === dir)
|
|
69
|
+
break;
|
|
70
|
+
dir = parent;
|
|
71
|
+
}
|
|
72
|
+
// Fallback to file's directory
|
|
73
|
+
return dirname(resolve(filePath));
|
|
74
|
+
}
|