Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DX-6839: refactor and add tests #386

Merged
merged 13 commits into from
Oct 20, 2021
132 changes: 81 additions & 51 deletions src/languageServerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import * as os from 'os';
import * as path from 'path';
import {
ACTIVE_BUILD_TOOL_STATE,
ClientStatus,
JDKInfo,
JDTLS_CLIENT_PORT,
SYNTAXLS_CLIENT_PORT,
ServerMode,
ensureNoBuildToolConflicts,
getJavaFilePathOfTextDocument,
getJavaSDKInfo,
getTriggerFiles,
getJavaServerLaunchMode,
hasNoBuildToolConflicts,
isPrefix,
makeRandomHexString,
} from './stripeJavaLanguageClient/utils';
Expand Down Expand Up @@ -48,8 +47,7 @@ const syntaxClient: SyntaxLanguageClient = new SyntaxLanguageClient();
const standardClient: StandardLanguageClient = new StandardLanguageClient();
const onDidServerModeChangeEmitter: Emitter<ServerMode> = new Emitter<ServerMode>();

export let javaServerMode =
workspace.getConfiguration().get('java.server.launchMode') || ServerMode.HYBRID;
export let javaServerMode: ServerMode;

export class StripeLanguageClient {
static async activate(
Expand All @@ -66,19 +64,22 @@ export class StripeLanguageClient {
return;
}

// start the java server if this is a java project
const javaFiles = await this.getJavaProjectFiles();
if (javaFiles.length > 0) {
const jdkInfo = await getJavaSDKInfo(context, outputChannel);
if (jdkInfo.javaVersion < REQUIRED_JDK_VERSION) {
outputChannel.appendLine(
`Minimum JDK version required is ${REQUIRED_JDK_VERSION}. Please update the java.home setup in VSCode user settings.`,
);
telemetry.sendEvent('doesNotMeetRequiredJdkVersion');
return;
}
this.activateJavaServer(context, jdkInfo, outputChannel, javaFiles[0], telemetry);
this.activateJavaServer(context, jdkInfo, outputChannel, javaFiles, telemetry);
return;
}

// start the universal server for all other languages
this.activateUniversalServer(context, outputChannel, serverOptions, telemetry);
}

Expand Down Expand Up @@ -211,10 +212,10 @@ export class StripeLanguageClient {
context: ExtensionContext,
jdkInfo: JDKInfo,
outputChannel: OutputChannel,
projectFile: string,
projectFiles: string[],
telemetry: Telemetry,
) {
outputChannel.appendLine('Detected Java Project file: ' + projectFile);
outputChannel.appendLine('Detected Java Project file: ' + projectFiles[0]);

let storagePath = context.storagePath;
if (!storagePath) {
Expand All @@ -224,22 +225,10 @@ export class StripeLanguageClient {
const workspacePath = path.resolve(storagePath + '/jdt_ws');
const syntaxServerWorkspacePath = path.resolve(storagePath + '/ss_ws');

const isWorkspaceTrusted = (workspace as any).isTrusted; // TODO: use workspace.isTrusted directly when other clients catch up to adopt 1.56.0
if (isWorkspaceTrusted !== undefined && !isWorkspaceTrusted) {
// keep compatibility for old engines < 1.56.0
javaServerMode = ServerMode.LIGHTWEIGHT;
}
javaServerMode = getJavaServerLaunchMode();
commands.executeCommand('setContext', 'java:serverMode', javaServerMode);
const isDebugModeByClientPort =
!!process.env[SYNTAXLS_CLIENT_PORT] || !!process.env[JDTLS_CLIENT_PORT];
const requireSyntaxServer =
javaServerMode !== ServerMode.STANDARD &&
(!isDebugModeByClientPort || !!process.env[SYNTAXLS_CLIENT_PORT]);
const requireStandardServer =
javaServerMode !== ServerMode.LIGHTWEIGHT &&
(!isDebugModeByClientPort || !!process.env[JDTLS_CLIENT_PORT]);

const triggerFiles = getTriggerFiles();
const requireSyntaxServer = javaServerMode !== ServerMode.STANDARD;
const requireStandardServer = javaServerMode !== ServerMode.LIGHTWEIGHT;

// Options to control the language client
const clientOptions: LanguageClientOptions = {
Expand All @@ -255,7 +244,7 @@ export class StripeLanguageClient {
clientDocumentSymbolProvider: true,
shouldLanguageServerExitOnShutdown: true,
},
triggerFiles,
projectFiles,
},
revealOutputChannelOn: 4, // never
errorHandler: {
Expand All @@ -270,17 +259,29 @@ export class StripeLanguageClient {
};

if (requireSyntaxServer) {
this.startSyntaxServer(
context,
jdkInfo,
clientOptions,
syntaxServerWorkspacePath,
outputChannel,
);
try {
await this.startSyntaxServer(
clientOptions,
prepareExecutable(jdkInfo, syntaxServerWorkspacePath, context, true, outputChannel, telemetry),
outputChannel,
telemetry,
);
} catch (e) {
outputChannel.appendLine(`${e}`);
telemetry.sendEvent('syntaxJavaServerFailedToStart');
}
}

// handle server mode changes from syntax to standard
this.registerSwitchJavaServerModeCommand(context, jdkInfo, clientOptions, workspacePath, outputChannel);
this.registerSwitchJavaServerModeCommand(
context,
jdkInfo,
clientOptions,
workspacePath,
outputChannel,
telemetry
);

onDidServerModeChangeEmitter.event((event: ServerMode) => {
if (event === ServerMode.STANDARD) {
syntaxClient.stop();
Expand All @@ -292,10 +293,19 @@ export class StripeLanguageClient {
registerHoverProvider(context);

if (requireStandardServer) {
await this.startStandardServer(context, jdkInfo, clientOptions, workspacePath, outputChannel);
try {
await this.startStandardServer(
context,
clientOptions,
prepareExecutable(jdkInfo, workspacePath, context, false, outputChannel, telemetry),
outputChannel,
telemetry,
);
} catch (e) {
outputChannel.appendLine(`${e}`);
telemetry.sendEvent('standardJavaServerFailedToStart');
}
}

outputChannel.appendLine('Establishing connection with Java lang service');
}

/**
Expand Down Expand Up @@ -411,34 +421,33 @@ export class StripeLanguageClient {
return openedJavaFiles;
}

static startSyntaxServer(
context: ExtensionContext,
jdkInfo: JDKInfo,
static async startSyntaxServer(
clientOptions: LanguageClientOptions,
syntaxServerWorkspacePath: string,
serverOptions: ServerOptions,
outputChannel: OutputChannel,
telemetry: Telemetry,
) {
syntaxClient.initialize(
outputChannel,
clientOptions,
prepareExecutable(jdkInfo, syntaxServerWorkspacePath, context, true, outputChannel),
);
await syntaxClient.initialize(clientOptions, serverOptions);
syntaxClient.start();
outputChannel.appendLine('Java language service (syntax) is running.');
telemetry.sendEvent('syntaxJavaServerStarted');
}

static async startStandardServer(
context: ExtensionContext,
jdkInfo: JDKInfo,
clientOptions: LanguageClientOptions,
workspacePath: string,
serverOptions: ServerOptions,
outputChannel: OutputChannel,
telemetry: Telemetry,
) {
if (standardClient.getClientStatus() !== ClientStatus.Uninitialized) {
return;
}

const checkConflicts: boolean = await ensureNoBuildToolConflicts(context, outputChannel);
const checkConflicts: boolean = await hasNoBuildToolConflicts(context);
if (!checkConflicts) {
outputChannel.appendLine(`Build tool conflict detected in workspace. Please set '${ACTIVE_BUILD_TOOL_STATE}' to either maven or gradle.`);
telemetry.sendEvent('standardJavaServerHasBuildToolConflict');
return;
}

Expand All @@ -447,17 +456,25 @@ export class StripeLanguageClient {
javaServerMode = ServerMode.HYBRID;
}

await standardClient.initialize(context, jdkInfo, clientOptions, workspacePath, outputChannel);
await standardClient.initialize(clientOptions, serverOptions);
standardClient.start();

outputChannel.appendLine('Java language service (standard) is running.');
telemetry.sendEvent('standardJavaServerStarted');
}

static registerSwitchJavaServerModeCommand(
static async registerSwitchJavaServerModeCommand(
context: ExtensionContext,
jdkInfo: JDKInfo,
clientOptions: LanguageClientOptions,
workspacePath: string,
outputChannel: OutputChannel,
telemetry: Telemetry,
) {
if ((await commands.getCommands()).includes(Commands.SWITCH_SERVER_MODE)) {
return;
}

/**
* Command to switch the server mode. Currently it only supports switch from lightweight to standard.
* @param force force to switch server mode without asking
Expand Down Expand Up @@ -500,7 +517,20 @@ export class StripeLanguageClient {
}

if (choice === 'Yes') {
await this.startStandardServer(context, jdkInfo, clientOptions, workspacePath, outputChannel);
telemetry.sendEvent('switchToStandardMode');

try {
this.startStandardServer(
context,
clientOptions,
prepareExecutable(jdkInfo, workspacePath, context, false, outputChannel, telemetry),
outputChannel,
telemetry,
);
} catch (e) {
outputChannel.appendLine(`${e}`);
telemetry.sendEvent('failedToSwitchToStandardMode');
}
}
},
);
Expand Down
76 changes: 45 additions & 31 deletions src/stripeJavaLanguageClient/hoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {HoverRequest, LanguageClient, TextDocumentPositionParams} from 'vscode-l
import {getActiveJavaLanguageClient, javaServerMode} from '../languageServerClient';
import {Commands as javaCommands} from './commands';

export type provideHoverCommandFn = (params: TextDocumentPositionParams, token: CancellationToken) => ProviderResult<Command[] | undefined>;
export type provideHoverCommandFn = (
params: TextDocumentPositionParams,
token: CancellationToken,
) => ProviderResult<Command[] | undefined>;
const hoverCommandRegistry: provideHoverCommandFn[] = [];

export function registerHoverProvider(context: ExtensionContext) {
Expand Down Expand Up @@ -112,7 +115,7 @@ class ClientHoverProvider implements HoverProvider {
}
}

class JavaHoverProvider implements HoverProvider {
export class JavaHoverProvider implements HoverProvider {
constructor(readonly languageClient: LanguageClient) {
this.languageClient = languageClient;
}
Expand All @@ -122,50 +125,61 @@ class JavaHoverProvider implements HoverProvider {
position: Position,
token: CancellationToken,
): Promise<Hover | undefined> {
let contents: MarkedString[] = [];
let range;

const params = {
textDocument: this.languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document),
position: this.languageClient.code2ProtocolConverter.asPosition(position),
};

// Fetch the javadoc from Java language server.
try {
const hoverResponse = await this.languageClient.sendRequest(HoverRequest.type, params, token);
if (hoverResponse &&
hoverResponse.contents &&
Array.isArray(hoverResponse.contents)) {
const stripeMethod = Object.entries(hoverResponse.contents[0]).filter((item) => item[0] === 'value' && item[1].includes('com.stripe.model'));
if (stripeMethod.length > 0) {
const stripeNamespace = stripeMethod[0][1].split(' ')[1].split('(')[0];
const url = getJavaApiDocLink(stripeNamespace);
return new Hover([new MarkdownString('See this method in the [Stripe API Reference](' + url + ')')], undefined);
// get javs doc convent from server
const hoverResponse = await this.languageClient.sendRequest(HoverRequest.type, params, token);

// parse for stripe api hover content
let stripeApiHoverContent;
if (hoverResponse && hoverResponse.contents && Array.isArray(hoverResponse.contents)) {
const stripeFullClassPath = Object.entries(hoverResponse.contents[0])
.filter((item) => item[0] === 'value')
.filter((item) => item[1].includes('com.stripe.model'));
if (stripeFullClassPath.length > 0) {
const stripeMethod = stripeFullClassPath[0][1].split(' ')[1].split('(')[0];
const url = getJavaApiDocLink(stripeMethod);
if (url) {
stripeApiHoverContent = new MarkdownString(
'See this method in the [Stripe API Reference](' + url + ')',
);
stripeApiHoverContent.isTrusted = true;
}
}
}

const serverHover = this.languageClient.protocol2CodeConverter.asHover(hoverResponse);
if (!!stripeApiHoverContent) {
contents = contents.concat([stripeApiHoverContent] as MarkedString[]);
}

// Fetch the contributed hover commands from third party extensions.
const contributedCommands: Command[] = await this.getContributedHoverCommands(params, token);
if (!contributedCommands.length) {
return serverHover;
}
// get contributed hover commands from third party extensions.
const contributedCommands: Command[] = await this.getContributedHoverCommands(params, token);

const contributed = new MarkdownString(
if (contributedCommands.length > 0) {
const contributedContent = new MarkdownString(
contributedCommands.map((command) => this.convertCommandToMarkdown(command)).join(' | '),
);
contributed.isTrusted = true;
let contents: MarkedString[] = [contributed];
let range;
if (serverHover && serverHover.contents) {
contents = contents.concat(serverHover.contents);
range = serverHover.range;
}
return new Hover(contents, range);
} catch (e) {
console.log(e);
contributedContent.isTrusted = true;
contents = contents.concat([contributedContent] as MarkedString[]);
}

// combine all hover contents with java docs from server
const serverHover = this.languageClient.protocol2CodeConverter.asHover(hoverResponse);
if (serverHover && serverHover.contents) {
contents = contents.concat(serverHover.contents);
range = serverHover.range;
}

return new Hover(contents, range);
}

private async getContributedHoverCommands(
async getContributedHoverCommands(
params: TextDocumentPositionParams,
token: CancellationToken,
): Promise<Command[]> {
Expand Down
Loading