From 0d2b775937235c473c9727b577d0c48495a79bc5 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Mon, 12 Apr 2021 15:33:29 +0800 Subject: [PATCH 1/4] Use InlineValuesProvider to provide inline debugging feature --- package-lock.json | 8 ++-- package.json | 4 +- src/JavaInlineValueProvider.ts | 76 ++++++++++++++++++++++++++++++++++ src/commands.ts | 2 + src/extension.ts | 2 + src/languageServerPlugin.ts | 38 +++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/JavaInlineValueProvider.ts diff --git a/package-lock.json b/package-lock.json index e9b16ea4..587a4cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,9 +103,9 @@ "dev": true }, "@types/vscode": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.54.0.tgz", - "integrity": "sha512-sHHw9HG4bTrnKhLGgmEiOS88OLO/2RQytUN4COX9Djv81zc0FSZsSiYaVyjNidDzUSpXsySKBkZ31lk2/FbdCg==", + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.55.0.tgz", + "integrity": "sha512-49hysH7jneTQoSC8TWbAi7nKK9Lc5osQNjmDHVosrcU8o3jecD9GrK0Qyul8q4aGPSXRfNGqIp9CBdb13akETg==", "dev": true }, "@webassemblyjs/ast": { @@ -456,7 +456,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-map": { diff --git a/package.json b/package.json index 2bcd2577..8142a8e4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "debugger" ], "engines": { - "vscode": "^1.54.0" + "vscode": "^1.55.0" }, "license": "SEE LICENSE IN LICENSE.txt", "repository": { @@ -768,7 +768,7 @@ "@types/mocha": "^5.2.7", "@types/node": "^14.14.10", "@types/uuid": "^8.3.0", - "@types/vscode": "1.54.0", + "@types/vscode": "1.55.0", "cross-env": "^5.2.0", "gulp": "^4.0.2", "gulp-tslint": "^8.1.4", diff --git a/src/JavaInlineValueProvider.ts b/src/JavaInlineValueProvider.ts new file mode 100644 index 00000000..6f3421ec --- /dev/null +++ b/src/JavaInlineValueProvider.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as compareVersions from "compare-versions"; +import { debug, InlineValue, InlineValueContext, InlineValueEvaluatableExpression, InlineValuesProvider, InlineValueText, InlineValueVariableLookup, + Position, Range, TextDocument, version } from "vscode"; +import { InlineKind, InlineVariable, LSPRange, resolveInlineVariables } from "./languageServerPlugin"; + +// In VS Code 1.55.0, viewport doesn't change while scrolling the editor and it's fixed in 1.56.0. +// So dynamically enable viewport support based on the user's VS Code version. +const isViewPortSupported = compareVersions(version.replace(/-insider$/i, ""), "1.56.0") >= 0; +export class JavaInlineValuesProvider implements InlineValuesProvider { + + public async provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext): Promise { + const variables: InlineVariable[] = (await resolveInlineVariables({ + uri: document.uri.toString(), + viewPort: isViewPortSupported ? asLSPRange(viewPort) : undefined, + stoppedLocation: asLSPRange(context.stoppedLocation), + })); + if (!variables || !variables.length) { + return []; + } + + const unresolvedVariables: any[] = variables.filter((variable) => variable.kind === InlineKind.Evaluation).map((variable) => { + return { + expression: variable.expression || variable.name, + declaringClass: variable.declaringClass, + }; + }); + let resolvedVariables: any; + if (unresolvedVariables.length && debug.activeDebugSession) { + const response = await debug.activeDebugSession.customRequest("inlineValues", { + frameId: context.frameId, + variables: unresolvedVariables, + }); + resolvedVariables = response && response.variables ? response.variables : undefined; + } + + const result: InlineValue[] = []; + let next = 0; + for (const variable of variables) { + if (variable.kind === InlineKind.VariableLookup) { + result.push(new InlineValueVariableLookup(asClientRange(variable.range), variable.name, true)); + } else if (resolvedVariables && resolvedVariables.length > next) { + const resolvedValue = resolvedVariables[next++]; + if (resolvedValue) { + result.push(new InlineValueText(asClientRange(variable.range), `${variable.name} = ${resolvedValue.value}`)); + } else { + result.push(new InlineValueEvaluatableExpression(asClientRange(variable.range), variable.name)); + } + } else { + result.push(new InlineValueEvaluatableExpression(asClientRange(variable.range), variable.name)); + } + } + + return result; + } + +} + +function asLSPRange(range: Range): LSPRange { + return { + start: { + line: range.start.line, + character: range.start.character, + }, + end: { + line: range.end.line, + character: range.end.character, + }, + }; +} + +function asClientRange(range: LSPRange) { + return new Range(new Position(range.start.line, range.start.character), new Position(range.end.line, range.end.character)); +} diff --git a/src/commands.ts b/src/commands.ts index 9aa3dfd1..b1368447 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -44,6 +44,8 @@ export const JAVA_RESOLVE_CLASSFILTERS = "vscode.java.resolveClassFilters"; export const JAVA_RESOLVE_SOURCE_URI = "vscode.java.resolveSourceUri"; +export const JAVA_RESOLVE_INLINE_VARIABLES = "vscode.java.resolveInlineVariables"; + export function executeJavaLanguageServerCommand(...rest: any[]) { return executeJavaExtensionCommand(JAVA_EXECUTE_WORKSPACE_COMMAND, ...rest); } diff --git a/src/extension.ts b/src/extension.ts index 09f03489..c2e4570f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,7 @@ import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvi import { initExpService } from "./experimentationService"; import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace, NO_BUTTON, YES_BUTTON } from "./hotCodeReplace"; import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory"; +import { JavaInlineValuesProvider } from "./JavaInlineValueProvider"; import { logJavaException, logJavaInfo } from "./javaLogger"; import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServerPlugin"; import { logger, Type } from "./logger"; @@ -78,6 +79,7 @@ function initializeExtension(_operationId: string, context: vscode.ExtensionCont initializeCodeLensProvider(context); initializeThreadOperations(context); + context.subscriptions.push(vscode.languages.registerInlineValuesProvider("java", new JavaInlineValuesProvider())); return { progressProvider, }; diff --git a/src/languageServerPlugin.ts b/src/languageServerPlugin.ts index 1c7cd59b..45f7e7a5 100644 --- a/src/languageServerPlugin.ts +++ b/src/languageServerPlugin.ts @@ -116,3 +116,41 @@ export async function resolveClassFilters(patterns: string[]): Promise export async function resolveSourceUri(line: string): Promise { return await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_SOURCE_URI, line); } + +export async function resolveInlineVariables(inlineParams: InlineParams): Promise { + return await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_INLINE_VARIABLES, JSON.stringify(inlineParams)); +} + +// tslint:disable-next-line:interface-name +export interface LSPPosition { + line: number; + character: number; +} + +// tslint:disable-next-line:interface-name +export interface LSPRange { + start: LSPPosition; + end: LSPPosition; +} + +// tslint:disable-next-line:interface-name +export interface InlineParams { + uri: string; + viewPort?: LSPRange; + stoppedLocation: LSPRange; +} + +// tslint:disable-next-line:interface-name +export enum InlineKind { + VariableLookup = 0, + Evaluation = 1, +} + +// tslint:disable-next-line:interface-name +export interface InlineVariable { + range: LSPRange; + name: string; + kind: InlineKind; + expression: string; + declaringClass: string; +} From 7022f96b17b657ef0bb4f8c3b7cef35335005d75 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 13 Apr 2021 17:03:35 +0800 Subject: [PATCH 2/4] Use utilities from vscode-languageclient to handle model converter --- package-lock.json | 35 +++++++++++++++++++++++++++++ package.json | 1 + src/JavaInlineValueProvider.ts | 41 +++++++++++++--------------------- src/languageServerPlugin.ts | 19 ++++------------ tslint.json | 3 ++- 5 files changed, 57 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 587a4cbb..72941b21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4973,6 +4973,41 @@ } } }, + "vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==" + }, + "vscode-languageclient": { + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.0.0-next.9.tgz", + "integrity": "sha512-NEpeeFM9FKrrRqlBHXGfwpkhtnjruDz3zfFBP+Cymr10qigAEtE/JsODJsIG/ErGqjh3/JXxu8SUOVTGu5oK+w==", + "requires": { + "semver": "^6.3.0", + "vscode-languageserver-protocol": "^3.15.0-next.14" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "requires": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" + }, "vscode-tas-client": { "version": "0.1.22", "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.22.tgz", diff --git a/package.json b/package.json index 8142a8e4..9017bf68 100644 --- a/package.json +++ b/package.json @@ -787,6 +787,7 @@ "uuid": "^8.3.1", "vscode-extension-telemetry": "^0.1.6", "vscode-extension-telemetry-wrapper": "^0.9.0", + "vscode-languageclient": "6.0.0-next.9", "vscode-tas-client": "^0.1.22" } } diff --git a/src/JavaInlineValueProvider.ts b/src/JavaInlineValueProvider.ts index 6f3421ec..49d10754 100644 --- a/src/JavaInlineValueProvider.ts +++ b/src/JavaInlineValueProvider.ts @@ -3,19 +3,25 @@ import * as compareVersions from "compare-versions"; import { debug, InlineValue, InlineValueContext, InlineValueEvaluatableExpression, InlineValuesProvider, InlineValueText, InlineValueVariableLookup, - Position, Range, TextDocument, version } from "vscode"; -import { InlineKind, InlineVariable, LSPRange, resolveInlineVariables } from "./languageServerPlugin"; + Range, TextDocument, version } from "vscode"; +import * as CodeConverter from "vscode-languageclient/lib/codeConverter"; +import * as ProtocolConverter from "vscode-languageclient/lib/protocolConverter"; +import { InlineKind, InlineVariable, resolveInlineVariables } from "./languageServerPlugin"; // In VS Code 1.55.0, viewport doesn't change while scrolling the editor and it's fixed in 1.56.0. // So dynamically enable viewport support based on the user's VS Code version. const isViewPortSupported = compareVersions(version.replace(/-insider$/i, ""), "1.56.0") >= 0; + +const protoConverter: ProtocolConverter.Converter = ProtocolConverter.createConverter(); +const codeConverter: CodeConverter.Converter = CodeConverter.createConverter(); + export class JavaInlineValuesProvider implements InlineValuesProvider { public async provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext): Promise { const variables: InlineVariable[] = (await resolveInlineVariables({ uri: document.uri.toString(), - viewPort: isViewPortSupported ? asLSPRange(viewPort) : undefined, - stoppedLocation: asLSPRange(context.stoppedLocation), + viewPort: isViewPortSupported ? codeConverter.asRange(viewPort) : undefined, + stoppedLocation: codeConverter.asRange(context.stoppedLocation), })); if (!variables || !variables.length) { return []; @@ -33,23 +39,23 @@ export class JavaInlineValuesProvider implements InlineValuesProvider { frameId: context.frameId, variables: unresolvedVariables, }); - resolvedVariables = response && response.variables ? response.variables : undefined; + resolvedVariables = response?.variables; } const result: InlineValue[] = []; let next = 0; for (const variable of variables) { if (variable.kind === InlineKind.VariableLookup) { - result.push(new InlineValueVariableLookup(asClientRange(variable.range), variable.name, true)); + result.push(new InlineValueVariableLookup(protoConverter.asRange(variable.range), variable.name, true)); } else if (resolvedVariables && resolvedVariables.length > next) { const resolvedValue = resolvedVariables[next++]; if (resolvedValue) { - result.push(new InlineValueText(asClientRange(variable.range), `${variable.name} = ${resolvedValue.value}`)); + result.push(new InlineValueText(protoConverter.asRange(variable.range), `${variable.name} = ${resolvedValue.value}`)); } else { - result.push(new InlineValueEvaluatableExpression(asClientRange(variable.range), variable.name)); + result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); } } else { - result.push(new InlineValueEvaluatableExpression(asClientRange(variable.range), variable.name)); + result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); } } @@ -57,20 +63,3 @@ export class JavaInlineValuesProvider implements InlineValuesProvider { } } - -function asLSPRange(range: Range): LSPRange { - return { - start: { - line: range.start.line, - character: range.start.character, - }, - end: { - line: range.end.line, - character: range.end.character, - }, - }; -} - -function asClientRange(range: LSPRange) { - return new Range(new Position(range.start.line, range.start.character), new Position(range.end.line, range.end.character)); -} diff --git a/src/languageServerPlugin.ts b/src/languageServerPlugin.ts index 45f7e7a5..2a61fa6f 100644 --- a/src/languageServerPlugin.ts +++ b/src/languageServerPlugin.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import * as vscode from "vscode"; +import { Range } from "vscode-languageclient/lib/client"; import * as commands from "./commands"; @@ -121,23 +122,11 @@ export async function resolveInlineVariables(inlineParams: InlineParams): Promis return await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_INLINE_VARIABLES, JSON.stringify(inlineParams)); } -// tslint:disable-next-line:interface-name -export interface LSPPosition { - line: number; - character: number; -} - -// tslint:disable-next-line:interface-name -export interface LSPRange { - start: LSPPosition; - end: LSPPosition; -} - // tslint:disable-next-line:interface-name export interface InlineParams { uri: string; - viewPort?: LSPRange; - stoppedLocation: LSPRange; + viewPort?: Range; + stoppedLocation: Range; } // tslint:disable-next-line:interface-name @@ -148,7 +137,7 @@ export enum InlineKind { // tslint:disable-next-line:interface-name export interface InlineVariable { - range: LSPRange; + range: Range; name: string; kind: InlineKind; expression: string; diff --git a/tslint.json b/tslint.json index b51ab794..d9005209 100644 --- a/tslint.json +++ b/tslint.json @@ -36,6 +36,7 @@ true, "log", "error" - ] + ], + "no-submodule-imports": false } } From ccf1eaca6872d5b8555a25df7e61427d74e08618 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 13 Apr 2021 17:27:50 +0800 Subject: [PATCH 3/4] Fix build errors --- package.json | 1 + src/languageServerPlugin.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9017bf68..3a17c5d2 100644 --- a/package.json +++ b/package.json @@ -788,6 +788,7 @@ "vscode-extension-telemetry": "^0.1.6", "vscode-extension-telemetry-wrapper": "^0.9.0", "vscode-languageclient": "6.0.0-next.9", + "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.22" } } diff --git a/src/languageServerPlugin.ts b/src/languageServerPlugin.ts index 2a61fa6f..b22bbbeb 100644 --- a/src/languageServerPlugin.ts +++ b/src/languageServerPlugin.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import * as vscode from "vscode"; -import { Range } from "vscode-languageclient/lib/client"; +import { Range } from "vscode-languageserver-types"; import * as commands from "./commands"; From e8a367075ba1c0169cacd2fa261c3c91fa57ad92 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Thu, 15 Apr 2021 09:53:41 +0800 Subject: [PATCH 4/4] Add metrics to track the performance of inline values feature --- src/JavaInlineValueProvider.ts | 97 +++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/src/JavaInlineValueProvider.ts b/src/JavaInlineValueProvider.ts index 49d10754..74537f6a 100644 --- a/src/JavaInlineValueProvider.ts +++ b/src/JavaInlineValueProvider.ts @@ -4,6 +4,7 @@ import * as compareVersions from "compare-versions"; import { debug, InlineValue, InlineValueContext, InlineValueEvaluatableExpression, InlineValuesProvider, InlineValueText, InlineValueVariableLookup, Range, TextDocument, version } from "vscode"; +import { instrumentOperation, instrumentOperationStep, sendInfo } from "vscode-extension-telemetry-wrapper"; import * as CodeConverter from "vscode-languageclient/lib/codeConverter"; import * as ProtocolConverter from "vscode-languageclient/lib/protocolConverter"; import { InlineKind, InlineVariable, resolveInlineVariables } from "./languageServerPlugin"; @@ -18,48 +19,68 @@ const codeConverter: CodeConverter.Converter = CodeConverter.createConverter(); export class JavaInlineValuesProvider implements InlineValuesProvider { public async provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext): Promise { - const variables: InlineVariable[] = (await resolveInlineVariables({ - uri: document.uri.toString(), - viewPort: isViewPortSupported ? codeConverter.asRange(viewPort) : undefined, - stoppedLocation: codeConverter.asRange(context.stoppedLocation), - })); - if (!variables || !variables.length) { - return []; - } - - const unresolvedVariables: any[] = variables.filter((variable) => variable.kind === InlineKind.Evaluation).map((variable) => { - return { - expression: variable.expression || variable.name, - declaringClass: variable.declaringClass, - }; - }); - let resolvedVariables: any; - if (unresolvedVariables.length && debug.activeDebugSession) { - const response = await debug.activeDebugSession.customRequest("inlineValues", { - frameId: context.frameId, - variables: unresolvedVariables, + const provideInlineValuesOperation = instrumentOperation("provideInlineValues", async (operationId) => { + const resolveInlineVariablesStep = instrumentOperationStep(operationId, "resolveInlineVariables", async () => { + return (await resolveInlineVariables({ + uri: document.uri.toString(), + viewPort: isViewPortSupported ? codeConverter.asRange(viewPort) : undefined, + stoppedLocation: codeConverter.asRange(context.stoppedLocation), + })); }); - resolvedVariables = response?.variables; - } + const variables: InlineVariable[] = await resolveInlineVariablesStep(); + + const resolveInlineValuesStep = instrumentOperationStep(operationId, "resolveInlineValues", async () => { + if (!variables || !variables.length) { + sendInfo(operationId, { + inlineVariableCount: 0, + }); + return []; + } + + const unresolvedVariables: any[] = variables.filter((variable) => variable.kind === InlineKind.Evaluation).map((variable) => { + return { + expression: variable.expression || variable.name, + declaringClass: variable.declaringClass, + }; + }); + sendInfo(operationId, { + inlineVariableCount: variables.length, + inlineVariableLookupCount: variables.length - unresolvedVariables.length, + inlineVariableEvaluationCount: unresolvedVariables.length, + }); - const result: InlineValue[] = []; - let next = 0; - for (const variable of variables) { - if (variable.kind === InlineKind.VariableLookup) { - result.push(new InlineValueVariableLookup(protoConverter.asRange(variable.range), variable.name, true)); - } else if (resolvedVariables && resolvedVariables.length > next) { - const resolvedValue = resolvedVariables[next++]; - if (resolvedValue) { - result.push(new InlineValueText(protoConverter.asRange(variable.range), `${variable.name} = ${resolvedValue.value}`)); - } else { - result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); + let resolvedVariables: any; + if (unresolvedVariables.length && debug.activeDebugSession) { + const response = await debug.activeDebugSession.customRequest("inlineValues", { + frameId: context.frameId, + variables: unresolvedVariables, + }); + resolvedVariables = response?.variables; } - } else { - result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); - } - } - return result; + const result: InlineValue[] = []; + let next = 0; + for (const variable of variables) { + if (variable.kind === InlineKind.VariableLookup) { + result.push(new InlineValueVariableLookup(protoConverter.asRange(variable.range), variable.name, true)); + } else if (resolvedVariables && resolvedVariables.length > next) { + const resolvedValue = resolvedVariables[next++]; + if (resolvedValue) { + result.push(new InlineValueText(protoConverter.asRange(variable.range), `${variable.name} = ${resolvedValue.value}`)); + } else { + result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); + } + } else { + result.push(new InlineValueEvaluatableExpression(protoConverter.asRange(variable.range), variable.name)); + } + } + + return result; + }); + return resolveInlineValuesStep(); + }); + + return provideInlineValuesOperation(); } }