Skip to content

Commit 02da60c

Browse files
committed
feat: implement LSP info dialog for monitoring and managing language servers, including status, logs, and controls.
1 parent 5d8c8b3 commit 02da60c

File tree

7 files changed

+1288
-2
lines changed

7 files changed

+1288
-2
lines changed

src/cm/lsp/serverLauncher.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
InstallStatus,
88
LauncherConfig,
99
LspServerDefinition,
10+
LspServerStats,
11+
LspServerStatsFormatted,
1012
ManagedServerEntry,
1113
PortInfo,
1214
WaitOptions,
@@ -476,9 +478,9 @@ async function startInteractiveServer(
476478
const callback: ExecutorCallback = (type, data) => {
477479
if (type === "stderr") {
478480
if (/proot warning/i.test(data)) return;
479-
console.warn(`[${serverId}] ${data}`);
481+
console.warn(`[LSP:${serverId}] ${data}`);
480482
} else if (type === "stdout" && data && data.trim()) {
481-
console.info(`[${serverId}] ${data}`);
483+
console.info(`[LSP:${serverId}] ${data}`);
482484
// Detect when the axs proxy signals it's listening
483485
if (/listening on/i.test(data)) {
484486
signalServerReady(serverId);
@@ -634,6 +636,11 @@ export async function ensureServerRunning(
634636
console.info(
635637
`[LSP:${server.id}] Auto-discovered port ${discoveredPort}`,
636638
);
639+
// Update managed server entry with the port
640+
const entry = managedServers.get(key);
641+
if (entry) {
642+
entry.port = discoveredPort;
643+
}
637644
}
638645
} else if (
639646
server.transport?.url &&
@@ -705,3 +712,96 @@ export function resetManagedServers(): void {
705712
.stopService()
706713
.catch(() => {});
707714
}
715+
716+
/**
717+
* Get managed server info by server ID
718+
*/
719+
export function getManagedServerInfo(
720+
serverId: string,
721+
): ManagedServerEntry | null {
722+
return managedServers.get(serverId) ?? null;
723+
}
724+
725+
/**
726+
* Get all managed servers
727+
*/
728+
export function getAllManagedServers(): Map<string, ManagedServerEntry> {
729+
return new Map(managedServers);
730+
}
731+
732+
function formatMemory(bytes: number): string {
733+
if (!bytes || bytes <= 0) return "—";
734+
const mb = bytes / (1024 * 1024);
735+
if (mb >= 1) return `${mb.toFixed(1)} MB`;
736+
const kb = bytes / 1024;
737+
return `${kb.toFixed(0)} KB`;
738+
}
739+
740+
function formatUptime(seconds: number): string {
741+
if (!seconds || seconds <= 0) return "—";
742+
if (seconds < 60) return `${seconds}s`;
743+
const mins = Math.floor(seconds / 60);
744+
const secs = seconds % 60;
745+
if (mins < 60) return `${mins}m ${secs}s`;
746+
const hours = Math.floor(mins / 60);
747+
const remainingMins = mins % 60;
748+
return `${hours}h ${remainingMins}m`;
749+
}
750+
751+
/**
752+
* Fetch server stats from the axs proxy /status endpoint
753+
* @param serverId - The server ID to fetch stats for
754+
* @param timeout - Timeout in milliseconds (default: 2000)
755+
*/
756+
export async function getServerStats(
757+
serverId: string,
758+
timeout = 2000,
759+
): Promise<LspServerStatsFormatted | null> {
760+
const entry = managedServers.get(serverId);
761+
if (!entry?.port) {
762+
return null;
763+
}
764+
765+
try {
766+
const controller = new AbortController();
767+
const timeoutId = setTimeout(() => controller.abort(), timeout);
768+
769+
const response = await fetch(`http://127.0.0.1:${entry.port}/status`, {
770+
signal: controller.signal,
771+
});
772+
773+
clearTimeout(timeoutId);
774+
775+
if (!response.ok) {
776+
return null;
777+
}
778+
779+
const data = (await response.json()) as LspServerStats;
780+
781+
// Aggregate stats from all processes
782+
let totalMemory = 0;
783+
let maxUptime = 0;
784+
let firstPid: number | null = null;
785+
786+
for (const proc of data.processes || []) {
787+
totalMemory += proc.memory_bytes || 0;
788+
if (proc.uptime_secs > maxUptime) {
789+
maxUptime = proc.uptime_secs;
790+
}
791+
if (firstPid === null && proc.pid) {
792+
firstPid = proc.pid;
793+
}
794+
}
795+
796+
return {
797+
memoryBytes: totalMemory,
798+
memoryFormatted: formatMemory(totalMemory),
799+
uptimeSeconds: maxUptime,
800+
uptimeFormatted: formatUptime(maxUptime),
801+
pid: firstPid,
802+
processCount: data.processes?.length ?? 0,
803+
};
804+
} catch {
805+
return null;
806+
}
807+
}

src/cm/lsp/types.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ export interface ManagedServerEntry {
234234
uuid: string;
235235
command: string;
236236
startedAt: number;
237+
/** Port number for the axs proxy (for stats endpoint) */
238+
port?: number;
237239
}
238240

239241
export type InstallStatus = "present" | "declined" | "failed";
@@ -265,6 +267,30 @@ export interface EnsureServerResult {
265267
discoveredPort?: number;
266268
}
267269

270+
/**
271+
* Stats returned from the axs proxy /status endpoint
272+
*/
273+
export interface LspServerStats {
274+
program: string;
275+
processes: Array<{
276+
pid: number;
277+
uptime_secs: number;
278+
memory_bytes: number;
279+
}>;
280+
}
281+
282+
/**
283+
* Formatted stats for UI display
284+
*/
285+
export interface LspServerStatsFormatted {
286+
memoryBytes: number;
287+
memoryFormatted: string;
288+
uptimeSeconds: number;
289+
uptimeFormatted: string;
290+
pid: number | null;
291+
processCount: number;
292+
}
293+
268294
// ============================================================================
269295
// Workspace Types
270296
// ============================================================================

0 commit comments

Comments
 (0)