bigparse
Version:
MCP server that gives Claude instant, intelligent access to your codebase using Language Server Protocol
619 lines • 24.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.LSPManager = void 0;
exports.setupLanguageServers = setupLanguageServers;
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const node_1 = require("vscode-jsonrpc/node");
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const vscode_uri_1 = require("vscode-uri");
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const fs = __importStar(require("fs/promises"));
const events_1 = require("events");
class LSPManager extends events_1.EventEmitter {
servers = new Map();
documents = new Map();
fileToServer = new Map();
serverConfigs;
rootUri;
constructor(rootPath) {
super();
this.rootUri = vscode_uri_1.URI.file(rootPath || process.cwd()).toString();
this.serverConfigs = this.loadServerConfigs();
}
loadServerConfigs() {
const configs = new Map();
// Load from config file
try {
const configPath = path.join(__dirname, '../config/languages.json');
const configData = require(configPath);
for (const [language, config] of Object.entries(configData.languages)) {
configs.set(language, config);
}
}
catch (error) {
console.error('Failed to load language configurations:', error);
}
// Add default configs as fallback
const defaults = [
{
language: 'typescript',
command: 'typescript-language-server',
args: ['--stdio'],
fileExtensions: ['.ts', '.tsx', '.js', '.jsx'],
initializationOptions: {
preferences: {
includeInlayParameterNameHints: 'all',
includeInlayParameterNameHintsWhenArgumentMatchesName: true,
includeInlayFunctionParameterTypeHints: true,
includeInlayVariableTypeHints: true,
includeInlayPropertyDeclarationTypeHints: true,
includeInlayFunctionLikeReturnTypeHints: true,
includeInlayEnumMemberValueHints: true,
}
}
},
{
language: 'python',
command: 'pylsp',
fileExtensions: ['.py'],
initializationOptions: {
pylsp: {
plugins: {
pycodestyle: { enabled: true },
pyflakes: { enabled: true },
pylint: { enabled: false },
yapf: { enabled: true },
autopep8: { enabled: false },
mccabe: { enabled: true },
}
}
}
},
{
language: 'rust',
command: 'rust-analyzer',
fileExtensions: ['.rs'],
initializationOptions: {
cargo: {
features: "all"
},
procMacro: {
enable: true
}
}
},
{
language: 'go',
command: 'gopls',
fileExtensions: ['.go'],
initializationOptions: {
"ui.diagnostic.analyses": {
"composites": true,
"unusedparams": true,
"unusedwrite": true,
"useany": true
}
}
},
];
for (const config of defaults) {
if (!configs.has(config.language)) {
configs.set(config.language, config);
}
}
return configs;
}
async initialize() {
// Don't pre-start servers, start them on demand
console.error('LSP Manager initialized. Language servers will be started on demand.');
}
async startServer(language) {
const config = this.serverConfigs.get(language);
if (!config) {
throw new Error(`No configuration found for language: ${language}`);
}
const existing = this.servers.get(language);
if (existing && existing.process.isRunning()) {
return existing.process;
}
try {
const server = new LanguageServerProcess(config, this.rootUri);
await server.start();
this.servers.set(language, {
process: server,
supportedExtensions: config.fileExtensions,
});
// Set up error handling
server.on('error', (error) => {
console.error(`Language server ${language} error:`, error.message);
if (error.code === 'ENOENT') {
console.error(`To enable LSP features for ${language}, install the language server.`);
}
this.servers.delete(language);
this.emit('server-error', { language, error });
});
server.on('exit', (code) => {
console.error(`Language server ${language} exited with code ${code}`);
this.servers.delete(language);
this.emit('server-exit', { language, code });
});
return server;
}
catch (error) {
// Don't throw, just log and continue without LSP features
console.error(`Warning: Could not start ${language} language server: ${error.message}`);
console.error(`BigParse will continue with basic file indexing for ${language} files.`);
this.servers.delete(language);
throw error; // Re-throw to be caught by caller
}
}
async stopServer(language) {
const serverInfo = this.servers.get(language);
if (serverInfo) {
await serverInfo.process.stop();
this.servers.delete(language);
// Clean up file associations
for (const [file, lang] of this.fileToServer.entries()) {
if (lang === language) {
this.fileToServer.delete(file);
}
}
}
}
async getDocumentSymbols(filePath, symbolType) {
const language = this.detectLanguage(filePath);
const server = await this.ensureServer(language);
const uri = vscode_uri_1.URI.file(filePath).toString();
await this.openDocument(uri, filePath, language);
try {
const symbols = await server.getDocumentSymbols({
textDocument: { uri },
});
if (symbolType && symbols.length > 0) {
const typeFilter = symbolType.toLowerCase();
return symbols.filter(s => {
const symbolKindName = this.symbolKindToString(s.kind).toLowerCase();
return symbolKindName.includes(typeFilter);
});
}
return symbols;
}
catch (error) {
console.error(`Failed to get document symbols for ${filePath}:`, error);
return [];
}
}
async findReferences(filePath, line, character) {
const language = this.detectLanguage(filePath);
const server = await this.ensureServer(language);
const uri = vscode_uri_1.URI.file(filePath).toString();
await this.openDocument(uri, filePath, language);
try {
return await server.findReferences({
textDocument: { uri },
position: { line, character },
context: { includeDeclaration: true },
});
}
catch (error) {
console.error(`Failed to find references for ${filePath}:`, error);
return [];
}
}
async goToDefinition(filePath, line, character) {
const language = this.detectLanguage(filePath);
const server = await this.ensureServer(language);
const uri = vscode_uri_1.URI.file(filePath).toString();
await this.openDocument(uri, filePath, language);
try {
return await server.goToDefinition({
textDocument: { uri },
position: { line, character },
});
}
catch (error) {
console.error(`Failed to go to definition for ${filePath}:`, error);
return null;
}
}
async ensureServer(language) {
let serverInfo = this.servers.get(language);
if (!serverInfo || !serverInfo.process.isRunning()) {
try {
await this.startServer(language);
serverInfo = this.servers.get(language);
if (!serverInfo) {
throw new Error(`Failed to start server for ${language}`);
}
}
catch (error) {
// Language server not available, throw to let caller handle
throw new Error(`Language server not available for ${language}: ${error.message}`);
}
}
return serverInfo.process;
}
async openDocument(uri, filePath, language) {
if (!this.documents.has(uri)) {
const content = await this.readFile(filePath);
const document = vscode_languageserver_textdocument_1.TextDocument.create(uri, language, 1, content);
this.documents.set(uri, document);
this.fileToServer.set(uri, language);
const server = await this.ensureServer(language);
await server.openDocument({
textDocument: {
uri,
languageId: language,
version: 1,
text: content,
},
});
}
}
detectLanguage(filePath) {
const ext = path.extname(filePath).toLowerCase();
for (const [language, serverInfo] of this.servers) {
if (serverInfo.supportedExtensions.includes(ext)) {
return language;
}
}
for (const [language, config] of this.serverConfigs) {
if (config.fileExtensions.includes(ext)) {
return language;
}
}
throw new Error(`Unsupported file extension: ${ext}`);
}
async readFile(filePath) {
return fs.readFile(filePath, 'utf-8');
}
symbolKindToString(kind) {
const kinds = [
'File', 'Module', 'Namespace', 'Package', 'Class', 'Method',
'Property', 'Field', 'Constructor', 'Enum', 'Interface',
'Function', 'Variable', 'Constant', 'String', 'Number',
'Boolean', 'Array', 'Object', 'Key', 'Null', 'EnumMember',
'Struct', 'Event', 'Operator', 'TypeParameter'
];
return kinds[kind - 1] || 'Unknown';
}
async shutdown() {
const shutdownPromises = Array.from(this.servers.values()).map(serverInfo => serverInfo.process.stop().catch(err => {
console.error('Error shutting down server:', err);
}));
await Promise.allSettled(shutdownPromises);
this.servers.clear();
this.documents.clear();
this.fileToServer.clear();
}
getActiveServers() {
return Array.from(this.servers.keys());
}
isServerRunning(language) {
const serverInfo = this.servers.get(language);
return serverInfo ? serverInfo.process.isRunning() : false;
}
}
exports.LSPManager = LSPManager;
class LanguageServerProcess extends events_1.EventEmitter {
config;
rootUri;
process = null;
connection = null;
initialized = false;
// private _capabilities: Record<string, any> = {};
initializePromise = null;
constructor(config, rootUri) {
super();
this.config = config;
this.rootUri = rootUri;
}
isRunning() {
return this.process !== null && !this.process.killed && this.initialized;
}
getInstallCommand() {
const installCommands = {
typescript: 'npm install -g typescript-language-server typescript',
javascript: 'npm install -g typescript-language-server typescript',
python: 'pip install python-lsp-server',
rust: 'rustup component add rust-analyzer',
go: 'go install golang.org/x/tools/gopls@latest',
};
return installCommands[this.config.language] || `Install language server for ${this.config.language}`;
}
async start() {
if (this.initializePromise) {
return this.initializePromise;
}
this.initializePromise = this._start();
return this.initializePromise;
}
async _start() {
return new Promise((resolve, reject) => {
try {
this.process = (0, child_process_1.spawn)(this.config.command, this.config.args || [], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
NODE_ENV: 'production',
},
});
let errorOccurred = false;
// Handle spawn errors (e.g., command not found)
this.process.on('error', (error) => {
errorOccurred = true;
if (error.code === 'ENOENT') {
console.error(`Language server '${this.config.command}' not found. Please install it first.`);
console.error(`For ${this.config.language}, run: ${this.getInstallCommand()}`);
}
this.cleanup();
reject(error);
});
this.process.on('exit', (code) => {
this.emit('exit', code);
this.cleanup();
});
this.process.stderr?.on('data', (data) => {
console.error(`[${this.config.language}] stderr:`, data.toString());
});
const reader = new node_1.StreamMessageReader(this.process.stdout);
const writer = new node_1.StreamMessageWriter(this.process.stdin);
this.connection = (0, vscode_languageserver_protocol_1.createMessageConnection)(reader, writer);
this.connection.onError((error) => {
console.error(`[${this.config.language}] Connection error:`, error);
this.emit('error', error);
});
this.connection.onClose(() => {
this.cleanup();
});
this.connection.listen();
// Wait a bit to see if the process crashes immediately
setTimeout(async () => {
if (!errorOccurred) {
try {
await this.initialize();
resolve();
}
catch (error) {
reject(error);
}
}
}, 100);
}
catch (error) {
this.cleanup();
reject(error);
}
});
}
async initialize() {
if (!this.connection) {
throw new Error('Connection not established');
}
const initParams = {
processId: process.pid,
rootUri: this.rootUri,
capabilities: {
workspace: {
applyEdit: true,
workspaceEdit: {
documentChanges: true,
resourceOperations: ['create', 'rename', 'delete'],
failureHandling: 'textOnlyTransactional',
},
didChangeConfiguration: { dynamicRegistration: true },
didChangeWatchedFiles: { dynamicRegistration: true },
symbol: {
dynamicRegistration: true,
symbolKind: {
valueSet: Array.from({ length: 26 }, (_, i) => (i + 1)),
},
},
executeCommand: { dynamicRegistration: true },
},
textDocument: {
synchronization: {
dynamicRegistration: true,
willSave: true,
willSaveWaitUntil: true,
didSave: true,
},
completion: {
dynamicRegistration: true,
contextSupport: true,
completionItem: {
snippetSupport: true,
commitCharactersSupport: true,
documentationFormat: ['markdown', 'plaintext'],
deprecatedSupport: true,
preselectSupport: true,
},
completionItemKind: {
valueSet: Array.from({ length: 25 }, (_, i) => (i + 1)),
},
},
hover: {
dynamicRegistration: true,
contentFormat: ['markdown', 'plaintext'],
},
signatureHelp: {
dynamicRegistration: true,
signatureInformation: {
documentationFormat: ['markdown', 'plaintext'],
parameterInformation: { labelOffsetSupport: true },
},
},
definition: { dynamicRegistration: true },
references: { dynamicRegistration: true },
documentHighlight: { dynamicRegistration: true },
documentSymbol: {
dynamicRegistration: true,
symbolKind: {
valueSet: Array.from({ length: 26 }, (_, i) => (i + 1)),
},
hierarchicalDocumentSymbolSupport: true,
},
codeAction: {
dynamicRegistration: true,
codeActionLiteralSupport: {
codeActionKind: {
valueSet: [
'',
'quickfix',
'refactor',
'refactor.extract',
'refactor.inline',
'refactor.rewrite',
'source',
'source.organizeImports',
],
},
},
},
codeLens: { dynamicRegistration: true },
formatting: { dynamicRegistration: true },
rangeFormatting: { dynamicRegistration: true },
onTypeFormatting: { dynamicRegistration: true },
rename: { dynamicRegistration: true, prepareSupport: true },
documentLink: { dynamicRegistration: true, tooltipSupport: true },
typeDefinition: { dynamicRegistration: true },
implementation: { dynamicRegistration: true },
colorProvider: { dynamicRegistration: true },
foldingRange: {
dynamicRegistration: true,
rangeLimit: 5000,
lineFoldingOnly: true,
},
},
},
initializationOptions: this.config.initializationOptions,
trace: 'off',
workspaceFolders: [],
};
await this.connection.sendRequest('initialize', initParams);
// this._capabilities = result.capabilities;
this.initialized = true;
await this.connection.sendNotification('initialized', {});
}
async openDocument(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
await this.connection.sendNotification('textDocument/didOpen', params);
}
async changeDocument(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
await this.connection.sendNotification('textDocument/didChange', params);
}
async closeDocument(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
await this.connection.sendNotification('textDocument/didClose', params);
}
async getDocumentSymbols(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
try {
const result = await this.connection.sendRequest('textDocument/documentSymbol', params);
return result || [];
}
catch (error) {
console.error('Document symbols request failed:', error);
return [];
}
}
async findReferences(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
try {
const result = await this.connection.sendRequest('textDocument/references', params);
return result || [];
}
catch (error) {
console.error('References request failed:', error);
return [];
}
}
async goToDefinition(params) {
if (!this.initialized || !this.connection) {
throw new Error('Language server not initialized');
}
try {
const result = await this.connection.sendRequest('textDocument/definition', params);
return result || null;
}
catch (error) {
console.error('Definition request failed:', error);
return null;
}
}
async stop() {
if (this.connection) {
try {
await this.connection.sendRequest('shutdown');
await this.connection.sendNotification('exit');
}
catch (error) {
console.error('Error during shutdown:', error);
}
}
this.cleanup();
}
cleanup() {
if (this.process && !this.process.killed) {
this.process.kill('SIGTERM');
setTimeout(() => {
if (this.process && !this.process.killed) {
this.process.kill('SIGKILL');
}
}, 5000);
}
this.process = null;
this.connection = null;
this.initialized = false;
this.initializePromise = null;
}
}
function setupLanguageServers(rootPath) {
return new LSPManager(rootPath);
}
//# sourceMappingURL=manager.js.map