diff --git a/src/fileEventHandler.ts b/src/fileEventHandler.ts index 2fa9a35cf..2c732a7cb 100644 --- a/src/fileEventHandler.ts +++ b/src/fileEventHandler.ts @@ -13,7 +13,6 @@ import * as stringInterpolate from 'fmtr'; import { apiManager } from './apiManager'; let serverReady: boolean = false; -type LanguageClientProvider = LanguageClient | (() => LanguageClient); const BRACE_POSITION_KEY = "org.eclipse.jdt.core.formatter.brace_position_for_type_declaration"; const END_OF_LINE = "end_of_line"; @@ -25,7 +24,7 @@ export function setServerStatus(ready: boolean) { serverReady = ready; } -export function registerFileEventHandlers(client: LanguageClientProvider, context: ExtensionContext) { +export function registerFileEventHandlers(client: LanguageClient, context: ExtensionContext) { if (workspace.onDidCreateFiles) {// Theia doesn't support workspace.onDidCreateFiles yet context.subscriptions.push(workspace.onDidCreateFiles(handleNewJavaFiles)); } @@ -191,11 +190,7 @@ async function handleNewJavaFiles(e: FileCreateEvent) { }, 100); } -function getLanguageClient(client: LanguageClientProvider): LanguageClient { - return typeof client === 'function' ? client() : client; -} - -function getWillRenameHandler(client: LanguageClientProvider) { +function getWillRenameHandler(client: LanguageClient) { return function handleWillRenameFiles(e: FileWillRenameEvent): void { if (!serverReady) { return; @@ -220,11 +215,10 @@ function getWillRenameHandler(client: LanguageClientProvider) { return; } - const languageClient = getLanguageClient(client); - const edit = await languageClient.sendRequest(WillRenameFiles.type, { + const edit = await client.sendRequest(WillRenameFiles.type, { files: javaRenameEvents }); - resolve(await languageClient.protocol2CodeConverter.asWorkspaceEdit(edit)); + resolve(await client.protocol2CodeConverter.asWorkspaceEdit(edit)); } catch (ex) { reject(ex); } diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 8677708fc..fc73cbadc 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -4,7 +4,7 @@ import * as net from 'net'; import * as path from 'path'; import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceConfiguration } from "vscode"; import { ConfigurationParams, ConfigurationRequest, LanguageClientOptions, Location as LSLocation, MessageType, Position as LSPosition, TextDocumentPositionParams, WorkspaceEdit, StaticFeature, ClientCapabilities, FeatureState, TelemetryEventNotification } from "vscode-languageclient"; -import { LanguageClient, ServerOptions, StreamInfo } from "vscode-languageclient/node"; +import { LanguageClient, StreamInfo } from "vscode-languageclient/node"; import { apiManager } from "./apiManager"; import * as buildPath from './buildpath'; import { javaRefactorKinds, RefactorDocumentProvider } from "./codeActionProvider"; @@ -42,7 +42,6 @@ import { listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils'; import { ClientCodeActionProvider } from './clientCodeActionProvider'; import { BuildFileSelector } from './buildFilesSelector'; import { extendedOutlineQuickPick } from "./outline/extendedOutlineQuickPick"; -import { startWithStdioFallback } from './standardLanguageClientStart'; const extensionName = 'Language Support for Java'; const GRADLE_CHECKSUM = "gradle/checksum/prompt"; @@ -51,7 +50,6 @@ const USE_JAVA = "Use Java "; const AS_GRADLE_JVM = " as Gradle JVM"; const UPGRADE_GRADLE = "Upgrade Gradle to "; const GRADLE_IMPORT_JVM = "java.import.gradle.java.home"; -const PIPE_START_TIMEOUT_MS = 30000; export const JAVA_SELECTOR: DocumentSelector = [ { scheme: "file", language: "java", pattern: "**/*.java" }, { scheme: "jdt", language: "java", pattern: "**/*.class" }, @@ -61,8 +59,6 @@ export const JAVA_SELECTOR: DocumentSelector = [ export class StandardLanguageClient { private languageClient: LanguageClient; - private serverOptions: ServerOptions; - private clientOptions: LanguageClientOptions; private status: ClientStatus = ClientStatus.uninitialized; public async initialize(context: ExtensionContext, requirements: RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, jdtEventEmitter: EventEmitter): Promise { @@ -89,7 +85,7 @@ export class StandardLanguageClient { } }); - let serverOptions: ServerOptions; + let serverOptions; const port = process.env['JDTLS_SERVER_PORT']; if (!port) { const lsPort = process.env['JDTLS_CLIENT_PORT']; @@ -109,26 +105,19 @@ export class StandardLanguageClient { // used during development serverOptions = awaitServerConnection.bind(null, port); } - this.serverOptions = serverOptions; - this.clientOptions = clientOptions; // Create the language client and start the client. - this.languageClient = this.createLanguageClient(serverOptions, clientOptions); + this.languageClient = new TracingLanguageClient('java', extensionName, serverOptions, clientOptions, DEBUG); + this.languageClient.registerFeature(new DisableWillRenameFeature()); this.registerCommandsForStandardServer(context, jdtEventEmitter); - fileEventHandler.registerFileEventHandlers(() => this.languageClient, context); + fileEventHandler.registerFileEventHandlers(this.languageClient, context); collectBuildFilePattern(extensions.all); this.status = ClientStatus.initialized; } - private createLanguageClient(serverOptions: ServerOptions, clientOptions: LanguageClientOptions): LanguageClient { - const languageClient = new TracingLanguageClient('java', extensionName, serverOptions, clientOptions, DEBUG); - languageClient.registerFeature(new DisableWillRenameFeature()); - return languageClient; - } - public registerLanguageClientActions(context: ExtensionContext, hasImported: boolean, jdtEventEmitter: EventEmitter) { activationProgressNotification.showProgress(); this.languageClient.onNotification(StatusNotification.type, async (report) => { @@ -788,16 +777,7 @@ export class StandardLanguageClient { public start(): Promise { if (this.languageClient && this.status === ClientStatus.initialized) { this.status = ClientStatus.starting; - return startWithStdioFallback({ - languageClient: this.languageClient, - serverOptions: this.serverOptions, - createLanguageClient: (serverOptions) => this.createLanguageClient(serverOptions, this.clientOptions), - pipeStartTimeout: PIPE_START_TIMEOUT_MS, - onFallback: (error) => logger.warn(`Falling back to 'stdio' (from 'pipe') because starting the pipe transport failed: ${error}`), - }).then(result => { - this.languageClient = result.client; - this.serverOptions = result.serverOptions; - }); + return this.languageClient.start(); } } diff --git a/src/standardLanguageClientStart.ts b/src/standardLanguageClientStart.ts deleted file mode 100644 index 5ba496859..000000000 --- a/src/standardLanguageClientStart.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Executable, ServerOptions, TransportKind } from "vscode-languageclient/node"; - -export interface StartableLanguageClient { - start(): Promise; -} - -export interface StartWithStdioFallbackOptions { - languageClient: T; - serverOptions: ServerOptions; - createLanguageClient(serverOptions: Executable): T; - pipeStartTimeout: number; - onFallback?(error: any): void; -} - -class PipeStartTimeoutError extends Error { -} - -export async function startWithStdioFallback(options: StartWithStdioFallbackOptions): Promise<{ client: T; serverOptions: ServerOptions }> { - if (!isPipeExecutable(options.serverOptions)) { - await options.languageClient.start(); - return { client: options.languageClient, serverOptions: options.serverOptions }; - } - - try { - await startWithTimeout(options.languageClient, options.pipeStartTimeout); - return { client: options.languageClient, serverOptions: options.serverOptions }; - } catch (error) { - if (!(error instanceof PipeStartTimeoutError)) { - throw error; - } - options.onFallback?.(error); - const stdioServerOptions = createStdioServerOptions(options.serverOptions); - const stdioClient = options.createLanguageClient(stdioServerOptions); - await stdioClient.start(); - return { client: stdioClient, serverOptions: stdioServerOptions }; - } -} - -async function startWithTimeout(client: StartableLanguageClient, timeout: number): Promise { - let timeoutHandle: NodeJS.Timeout | undefined; - try { - await Promise.race([ - client.start(), - new Promise((_resolve, reject) => { - timeoutHandle = setTimeout(() => reject(new PipeStartTimeoutError(`Starting pipe transport timed out after ${timeout}ms.`)), timeout); - }) - ]); - } finally { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - } -} - -function isPipeExecutable(serverOptions: ServerOptions): serverOptions is Executable { - return !!serverOptions && typeof (serverOptions as Executable).command === 'string' && (serverOptions as Executable).transport === TransportKind.pipe; -} - -function createStdioServerOptions(serverOptions: Executable): Executable { - return { - ...serverOptions, - args: serverOptions.args?.slice(), - options: serverOptions.options ? { ...serverOptions.options } : undefined, - transport: TransportKind.stdio, - }; -} diff --git a/test/standard-mode-suite/standardLanguageClient.test.ts b/test/standard-mode-suite/standardLanguageClient.test.ts deleted file mode 100644 index f30564da3..000000000 --- a/test/standard-mode-suite/standardLanguageClient.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -import * as assert from 'assert'; -import { Executable, TransportKind } from 'vscode-languageclient/node'; -import { startWithStdioFallback, StartableLanguageClient } from '../../src/standardLanguageClientStart'; - -class TestLanguageClient implements StartableLanguageClient { - public startCount = 0; - - constructor(private readonly startResult: Promise) { - } - - public start(): Promise { - this.startCount++; - return this.startResult; - } -} - -suite('Standard Language Client Test', () => { - - test('startWithStdioFallback() - does not fall back when pipe start rejects', async () => { - const pipeClient = new TestLanguageClient(Promise.reject(new Error('pipe failed'))); - const stdioClient = new TestLanguageClient(Promise.resolve()); - const pipeOptions = createServerOptions(TransportKind.pipe); - let fallbackOptions: Executable | undefined; - - await assert.rejects(startWithStdioFallback({ - languageClient: pipeClient, - serverOptions: pipeOptions, - createLanguageClient: serverOptions => { - fallbackOptions = serverOptions; - return stdioClient; - }, - pipeStartTimeout: 1000, - }), /pipe failed/); - - assert.equal(pipeClient.startCount, 1); - assert.equal(stdioClient.startCount, 0); - assert.equal(fallbackOptions, undefined); - }); - - test('startWithStdioFallback() - falls back when pipe start times out', async () => { - const pipeClient = new TestLanguageClient(new Promise(() => { /* never resolves */ })); - const stdioClient = new TestLanguageClient(Promise.resolve()); - let fallbackError: any; - - const result = await startWithStdioFallback({ - languageClient: pipeClient, - serverOptions: createServerOptions(TransportKind.pipe), - createLanguageClient: () => stdioClient, - pipeStartTimeout: 1, - onFallback: error => fallbackError = error, - }); - - assert.equal(pipeClient.startCount, 1); - assert.equal(stdioClient.startCount, 1); - assert.equal(result.client, stdioClient); - assert.equal((result.serverOptions as Executable).transport, TransportKind.stdio); - assert.ok(String(fallbackError).includes('timed out')); - }); - - test('startWithStdioFallback() - does not fall back for stdio start failures', async () => { - const stdioClient = new TestLanguageClient(Promise.reject(new Error('stdio failed'))); - - await assert.rejects(startWithStdioFallback({ - languageClient: stdioClient, - serverOptions: createServerOptions(TransportKind.stdio), - createLanguageClient: () => new TestLanguageClient(Promise.resolve()), - pipeStartTimeout: 1, - }), /stdio failed/); - - assert.equal(stdioClient.startCount, 1); - }); -}); - -function createServerOptions(transport: TransportKind): Executable { - return { - command: 'java', - args: ['-version'], - options: { env: { test: 'true' } }, - transport, - }; -}