From 1e7a701e1aa1dee96dc697c1cbf9653a496d2cde Mon Sep 17 00:00:00 2001 From: Fred Wu Date: Wed, 15 Jul 2026 16:22:16 +1000 Subject: [PATCH] Added a console language-server output channel --- CHANGELOG.md | 6 +++ package-lock.json | 4 +- package.json | 2 +- src/Language/consoleLspClient.ts | 69 ++++++++++++++++++++++++++++++-- src/extension.ts | 2 + 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd52b3b..217f513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to R Console will be documented in this file. +## [0.4.2] - 2026-07-15 + +### Added + +- Added a dedicated `R Console Language Server` output channel that reports server starts, stops, errors and the R executable location, without showing language-server protocol traffic. + ## [0.4.1] - 2026-07-11 ### Added diff --git a/package-lock.json b/package-lock.json index 954180a..c942b4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vsc-r-console", - "version": "0.4.1", + "version": "0.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vsc-r-console", - "version": "0.4.1", + "version": "0.4.2", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@xterm/headless": "^6.0.0", diff --git a/package.json b/package.json index 986ef91..343bd07 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vsc-r-console", "displayName": "R Console for VS Code", "description": "A lightweight R console for VS Code", - "version": "0.4.1", + "version": "0.4.2", "publisher": "RConsole", "license": "SEE LICENSE IN LICENSE", "icon": "images/Rlogo.png", diff --git a/src/Language/consoleLspClient.ts b/src/Language/consoleLspClient.ts index 7db9150..e7ce53a 100644 --- a/src/Language/consoleLspClient.ts +++ b/src/Language/consoleLspClient.ts @@ -20,6 +20,19 @@ import { import type { CompletionProvider } from "./completion"; const CONSOLE_LSP_HOST = "127.0.0.1"; +const lifecycleOutputChannel = vscode.window.createOutputChannel( + "R Console Language Server" +); + +function formatLogTimestamp(date = new Date()): string { + const pad = (value: number): string => String(value).padStart(2, "0"); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; +} + +export function disposeConsoleLspOutputChannel(): void { + lifecycleOutputChannel.dispose(); +} type ConsoleLspClientOptions = { consoleId: string; @@ -112,6 +125,9 @@ export class ConsoleLspClient implements CompletionProvider { await this.startInternal(); })() .catch(async (error) => { + if (!this.disposed) { + this.logServerError(error, this.spawnedServer); + } const failedClient = this.client; this.client = undefined; this.closePendingSocketServer(); @@ -317,8 +333,18 @@ export class ConsoleLspClient implements CompletionProvider { outputChannel: this.outputChannel, revealOutputChannelOn: RevealOutputChannelOn.Never, errorHandler: { - error: () => ({ action: ErrorAction.Continue, handled: true }), - closed: () => ({ action: CloseAction.DoNotRestart, handled: true }), + error: (error) => { + if (this.client) { + this.logServerError(error, this.spawnedServer); + } + return { action: ErrorAction.Continue, handled: true }; + }, + closed: () => { + if (this.client) { + this.logServerError("connection closed unexpectedly", this.spawnedServer); + } + return { action: CloseAction.DoNotRestart, handled: true }; + }, }, }; @@ -357,6 +383,7 @@ export class ConsoleLspClient implements CompletionProvider { this.outputChannel.appendLine(data.toString()); }); child.once("spawn", () => { + this.logServerStarted(child); if (settled) { return; } @@ -369,12 +396,16 @@ export class ConsoleLspClient implements CompletionProvider { resolve({ reader: child.stdout, writer: child.stdin }); }); child.once("error", (error) => { + if (settled) { + this.logServerError(error, child); + } if (!settled) { settled = true; reject(error); } }); child.once("exit", (code, signal) => { + this.logServerStopped(child); if (code === 10) { void vscode.window.showWarningMessage( "R package {languageserver} is required for console autocompletion." @@ -408,7 +439,7 @@ export class ConsoleLspClient implements CompletionProvider { this.pendingSocketServer = undefined; } socket.on("error", (error) => { - this.outputChannel.appendLine(`LSP socket error: ${error.message}`); + this.logServerError(error); }); server.close(); resolve({ reader: socket, writer: socket }); @@ -466,10 +497,17 @@ export class ConsoleLspClient implements CompletionProvider { child.stderr?.on("data", (data: Buffer | string) => { this.outputChannel.appendLine(data.toString()); }); + child.once("spawn", () => { + this.logServerStarted(child); + }); child.once("error", (error) => { + if (settled) { + this.logServerError(error, child); + } rejectOnce(error); }); child.once("exit", (code, signal) => { + this.logServerStopped(child); if (code === 10) { void vscode.window.showWarningMessage( "R package {languageserver} is required for console autocompletion." @@ -583,6 +621,31 @@ export class ConsoleLspClient implements CompletionProvider { } } + private logServerStarted(child: ChildProcess): void { + const timestamp = formatLogTimestamp(); + lifecycleOutputChannel.appendLine( + `[Info - ${timestamp}] R Console Language Server (${child.pid ?? "unknown"}) started` + ); + lifecycleOutputChannel.appendLine( + `[Info - ${timestamp}] R executable: "${this.options.rPath}"` + ); + } + + private logServerError(error: unknown, child: ChildProcess | undefined = this.spawnedServer): void { + const message = error instanceof Error ? error.message : String(error); + lifecycleOutputChannel.appendLine( + `[Error - ${formatLogTimestamp()}] ` + + `R Console Language Server (${child?.pid ?? "unknown"}) error: ${message}` + ); + } + + private logServerStopped(child: ChildProcess): void { + lifecycleOutputChannel.appendLine( + `[Info - ${formatLogTimestamp()}] ` + + `R Console Language Server (${child.pid ?? "unknown"}) stopped` + ); + } + private terminateSpawnedServer(): Promise { if (this.terminationPromise) { return this.terminationPromise; diff --git a/src/extension.ts b/src/extension.ts index 70eeeb6..5266337 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; +import { disposeConsoleLspOutputChannel } from "./Language/consoleLspClient"; import { RTerminal, type PersistedRTerminalState, @@ -1359,6 +1360,7 @@ export async function deactivate(): Promise { pidToRecord.clear(); editorTabToRecord.clear(); await Promise.allSettled([...pendingTerminalCleanups]); + disposeConsoleLspOutputChannel(); } function startPersistentSessionRegistry(context: vscode.ExtensionContext): void {