cfb2175916
- 新增 /api/lsp/servers 端点,列出所有语言服务器 - 新增 /api/lsp/servers/:id 端点,获取服务器详情 - 新增 /api/lsp/servers/:id/install 端点,安装服务器 - 新增 /api/lsp/servers/:id/start 端点,启动服务器 - 新增 /api/lsp/servers/:id/stop 端点,停止服务器 - 新增 /api/lsp/diagnostics 端点,获取诊断信息 - 新增 /api/lsp/running 端点,获取运行中服务器列表 - core/lsp 添加 stopServer, getRunningServers, isServerRunning 方法
362 lines
7.4 KiB
TypeScript
362 lines
7.4 KiB
TypeScript
/**
|
|
* LSP API Routes
|
|
*
|
|
* 提供 LSP 语言服务器管理的 REST API
|
|
*/
|
|
|
|
import { Hono } from 'hono';
|
|
import { getConfig } from './config.js';
|
|
import type {
|
|
FileDiagnostic,
|
|
ServerStatus,
|
|
} from '@ai-assistant/core';
|
|
import {
|
|
initLSP,
|
|
listServers,
|
|
installServer,
|
|
touchFile,
|
|
getDiagnostics,
|
|
getFileDiagnostics,
|
|
getRunningServers,
|
|
stopServer,
|
|
} from '@ai-assistant/core';
|
|
|
|
export const lspRouter = new Hono();
|
|
|
|
// LSP 初始化标志
|
|
let lspInitialized = false;
|
|
|
|
/**
|
|
* 初始化 LSP 系统
|
|
*/
|
|
function ensureLSPInitialized(): boolean {
|
|
if (lspInitialized) return true;
|
|
|
|
try {
|
|
const config = getConfig();
|
|
initLSP(config.workdir);
|
|
lspInitialized = true;
|
|
console.log('[LSP] LSP module initialized');
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('[LSP] Failed to initialize LSP module:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /lsp/servers - 获取所有语言服务器列表
|
|
*/
|
|
lspRouter.get('/servers', async (c) => {
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
const servers = listServers();
|
|
const runningServers = new Set(getRunningServers());
|
|
|
|
// 添加运行状态
|
|
const serversWithRunningStatus = servers.map((server) => ({
|
|
...server,
|
|
running: runningServers.has(server.id) || server.languages.some(lang => runningServers.has(lang)),
|
|
}));
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: serversWithRunningStatus,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* GET /lsp/servers/:id - 获取单个服务器详情
|
|
*/
|
|
lspRouter.get('/servers/:id', async (c) => {
|
|
const id = c.req.param('id');
|
|
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
const servers = listServers();
|
|
const server = servers.find(
|
|
(s) => s.id === id || s.displayName.toLowerCase() === id.toLowerCase()
|
|
);
|
|
|
|
if (!server) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: `Server not found: ${id}`,
|
|
},
|
|
404
|
|
);
|
|
}
|
|
|
|
const runningServers = new Set(getRunningServers());
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
...server,
|
|
running: runningServers.has(server.id) || server.languages.some(lang => runningServers.has(lang)),
|
|
},
|
|
});
|
|
});
|
|
|
|
/**
|
|
* POST /lsp/servers/:id/install - 安装语言服务器
|
|
*/
|
|
lspRouter.post('/servers/:id/install', async (c) => {
|
|
const id = c.req.param('id');
|
|
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
try {
|
|
const success = await installServer(id);
|
|
|
|
if (success) {
|
|
const servers = listServers();
|
|
const server = servers.find((s) => s.id === id);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
message: `Server ${id} installed successfully`,
|
|
server,
|
|
},
|
|
});
|
|
} else {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: `Failed to install server: ${id}`,
|
|
},
|
|
500
|
|
);
|
|
}
|
|
} catch (error) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Installation failed',
|
|
},
|
|
500
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /lsp/servers/:id/start - 启动语言服务器
|
|
* Body: { filePath: string } - 需要提供一个文件路径来触发对应语言的服务器
|
|
*/
|
|
lspRouter.post('/servers/:id/start', async (c) => {
|
|
const id = c.req.param('id');
|
|
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
try {
|
|
const body = await c.req.json<{ filePath?: string }>();
|
|
|
|
if (!body.filePath) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'filePath is required to start the server',
|
|
},
|
|
400
|
|
);
|
|
}
|
|
|
|
const isFirstStart = await touchFile(body.filePath);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
message: `Server started for file: ${body.filePath}`,
|
|
isFirstStart,
|
|
runningServers: getRunningServers(),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Start failed',
|
|
},
|
|
500
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /lsp/servers/:id/stop - 停止语言服务器
|
|
*/
|
|
lspRouter.post('/servers/:id/stop', async (c) => {
|
|
const id = c.req.param('id');
|
|
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
try {
|
|
// id 可能是命令名或语言 ID,尝试从服务器列表找到对应的语言
|
|
const servers = listServers();
|
|
const server = servers.find(
|
|
(s) => s.id === id || s.displayName.toLowerCase() === id.toLowerCase()
|
|
);
|
|
|
|
// 尝试停止对应语言的服务器
|
|
let stopped = false;
|
|
if (server) {
|
|
for (const lang of server.languages) {
|
|
const result = await stopServer(lang);
|
|
if (result) {
|
|
stopped = true;
|
|
}
|
|
}
|
|
} else {
|
|
// 直接尝试用 id 作为语言 ID
|
|
stopped = await stopServer(id);
|
|
}
|
|
|
|
if (stopped) {
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
message: `Server ${id} stopped`,
|
|
runningServers: getRunningServers(),
|
|
},
|
|
});
|
|
} else {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: `Server ${id} is not running or could not be stopped`,
|
|
},
|
|
400
|
|
);
|
|
}
|
|
} catch (error) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Stop failed',
|
|
},
|
|
500
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /lsp/running - 获取正在运行的服务器列表
|
|
*/
|
|
lspRouter.get('/running', async (c) => {
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: getRunningServers(),
|
|
});
|
|
});
|
|
|
|
/**
|
|
* GET /lsp/diagnostics - 获取诊断信息
|
|
* Query: file (可选) - 指定文件路径
|
|
*/
|
|
lspRouter.get('/diagnostics', async (c) => {
|
|
if (!ensureLSPInitialized()) {
|
|
return c.json(
|
|
{
|
|
success: false,
|
|
error: 'LSP module not available',
|
|
},
|
|
503
|
|
);
|
|
}
|
|
|
|
const file = c.req.query('file');
|
|
|
|
if (file) {
|
|
// 获取单个文件的诊断信息
|
|
const diagnostics = getFileDiagnostics(file);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
file,
|
|
diagnostics,
|
|
summary: {
|
|
totalErrors: diagnostics.filter((d) => d.severity === 'error').length,
|
|
totalWarnings: diagnostics.filter((d) => d.severity === 'warning').length,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// 获取所有文件的诊断信息
|
|
const allDiagnostics = getDiagnostics();
|
|
const files: Array<{ file: string; diagnostics: FileDiagnostic[] }> = [];
|
|
let totalErrors = 0;
|
|
let totalWarnings = 0;
|
|
|
|
for (const [filePath, diagnostics] of allDiagnostics) {
|
|
files.push({ file: filePath, diagnostics });
|
|
totalErrors += diagnostics.filter((d) => d.severity === 'error').length;
|
|
totalWarnings += diagnostics.filter((d) => d.severity === 'warning').length;
|
|
}
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
files,
|
|
summary: {
|
|
totalFiles: files.length,
|
|
totalErrors,
|
|
totalWarnings,
|
|
},
|
|
},
|
|
});
|
|
});
|