Skip to content

Commit 51619ef

Browse files
Use codeLens to run Java program (#410)
* Use codeLens to run Java program Signed-off-by: Jinbo Wang <[email protected]> * Move codeLens action name to local constants * use lodash _.flatten to flatten the codeLens result * Add return statement
1 parent b3ce681 commit 51619ef

File tree

5 files changed

+164
-15
lines changed

5 files changed

+164
-15
lines changed

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export const JAVA_FETCH_USAGE_DATA = "vscode.java.fetchUsageData";
2323

2424
export const JAVA_UPDATE_DEBUG_SETTINGS = "vscode.java.updateDebugSettings";
2525

26+
export const JAVA_RESOLVE_MAINMETHOD = "vscode.java.resolveMainMethod";
27+
2628
export function executeJavaLanguageServerCommand(...rest) {
2729
// TODO: need to handle error and trace telemetry
2830
return vscode.commands.executeCommand(JAVA_EXECUTE_WORKSPACE_COMMAND, ...rest);

src/configurationProvider.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -189,23 +189,10 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
189189
}
190190

191191
const errorMessage = (ex && ex.message) || ex;
192-
const exception = (ex && ex.data && ex.data.cause)
193-
|| { stackTrace: (ex && ex.stack), detailMessage: String((ex && ex.message) || ex || "Unknown exception") };
194-
const properties = {
195-
message: "",
196-
stackTrace: "",
197-
};
198-
if (exception && typeof exception === "object") {
199-
properties.message = exception.detailMessage;
200-
properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace))
201-
|| String(exception.stackTrace);
202-
} else {
203-
properties.message = String(exception);
204-
}
205192
utility.showErrorMessageWithTroubleshooting({
206193
message: String(errorMessage),
207194
type: Type.EXCEPTION,
208-
details: properties,
195+
details: utility.formatErrorProperties(ex),
209196
});
210197
return undefined;
211198
}

src/debugCodeLensProvider.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import * as _ from "lodash";
5+
import * as vscode from "vscode";
6+
7+
import * as commands from "./commands";
8+
import { JAVA_LANGID } from "./constants";
9+
import { logger, Type } from "./logger";
10+
import * as utility from "./utility";
11+
12+
const onDidChange: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
13+
const JAVA_RUN_COMMAND = "vscode.java.run";
14+
const JAVA_DEBUG_COMMAND = "vscode.java.debug";
15+
16+
export function initializeCodeLensProvider(context: vscode.ExtensionContext): void {
17+
const watcher = vscode.workspace.createFileSystemWatcher("**/*.{[jJ][aA][vV][aA]}");
18+
context.subscriptions.push(watcher);
19+
watcher.onDidChange((uri) => {
20+
onDidChange.fire();
21+
});
22+
23+
context.subscriptions.push(vscode.languages.registerCodeLensProvider(JAVA_LANGID, new DebugCodeLensProvider(onDidChange)));
24+
context.subscriptions.push(vscode.commands.registerCommand(JAVA_RUN_COMMAND, runJavaProgram));
25+
context.subscriptions.push(vscode.commands.registerCommand(JAVA_DEBUG_COMMAND, debugJavaProgram));
26+
}
27+
28+
class DebugCodeLensProvider implements vscode.CodeLensProvider {
29+
constructor(private _onDidChange: vscode.EventEmitter<void>) {
30+
}
31+
32+
get onDidChangeCodeLenses(): vscode.Event<void> {
33+
return this._onDidChange.event;
34+
}
35+
36+
public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
37+
const mainMethods: IMainMethod[] = await resolveMainMethod(document);
38+
return _.flatten(mainMethods.map((method) => {
39+
return [
40+
new vscode.CodeLens(method.range, {
41+
title: "▶ Run",
42+
command: JAVA_RUN_COMMAND,
43+
tooltip: "Run Java Program",
44+
arguments: [ method.mainClass, method.projectName, document.uri ],
45+
}),
46+
new vscode.CodeLens(method.range, {
47+
title: "🐞 Debug",
48+
command: JAVA_DEBUG_COMMAND,
49+
tooltip: "Debug Java Program",
50+
arguments: [ method.mainClass, method.projectName, document.uri ],
51+
}),
52+
];
53+
}));
54+
}
55+
}
56+
57+
function runJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise<void> {
58+
return runCodeLens(mainClass, projectName, uri, true);
59+
}
60+
61+
function debugJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise<void> {
62+
return runCodeLens(mainClass, projectName, uri, false);
63+
}
64+
65+
async function runCodeLens(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean): Promise<void> {
66+
const workspaceFolder: vscode.WorkspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
67+
const workspaceUri: vscode.Uri = workspaceFolder ? workspaceFolder.uri : undefined;
68+
69+
const debugConfig: vscode.DebugConfiguration = await constructDebugConfig(mainClass, projectName, workspaceUri);
70+
debugConfig.projectName = projectName;
71+
debugConfig.noDebug = noDebug;
72+
73+
vscode.debug.startDebugging(workspaceFolder, debugConfig);
74+
75+
logger.log(Type.USAGEDATA, {
76+
runCodeLens: "yes",
77+
noDebug: String(noDebug),
78+
});
79+
}
80+
81+
async function constructDebugConfig(mainClass: string, projectName: string, workspace: vscode.Uri): Promise<vscode.DebugConfiguration> {
82+
const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspace);
83+
const rawConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations;
84+
85+
let debugConfig: vscode.DebugConfiguration = _.find(rawConfigs, (config) => {
86+
return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName);
87+
});
88+
89+
if (!debugConfig) {
90+
debugConfig = _.find(rawConfigs, (config) => {
91+
return config.mainClass === mainClass && !config.projectName;
92+
});
93+
}
94+
95+
if (!debugConfig) {
96+
debugConfig = {
97+
type: "java",
98+
name: `CodeLens (Launch) - ${mainClass.substr(mainClass.lastIndexOf(".") + 1)}`,
99+
request: "launch",
100+
// tslint:disable-next-line
101+
cwd: workspace ? "${workspaceFolder}" : undefined,
102+
console: "internalConsole",
103+
stopOnEntry: false,
104+
mainClass,
105+
args: "",
106+
projectName,
107+
};
108+
109+
// Persist the default debug configuration only if the workspace exists.
110+
if (workspace) {
111+
// Insert the default debug configuration to the beginning of launch.json.
112+
rawConfigs.splice(0, 0, debugConfig);
113+
await launchConfigurations.update("configurations", rawConfigs, vscode.ConfigurationTarget.WorkspaceFolder);
114+
}
115+
}
116+
117+
return _.cloneDeep(debugConfig);
118+
}
119+
120+
async function resolveMainMethod(document: vscode.TextDocument): Promise<IMainMethod[]> {
121+
try {
122+
return <IMainMethod[]> await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINMETHOD, document.uri.toString());
123+
} catch (ex) {
124+
logger.log(Type.EXCEPTION, utility.formatErrorProperties(ex));
125+
return [];
126+
}
127+
}
128+
129+
interface IMainMethod {
130+
range: vscode.Range;
131+
mainClass: string;
132+
projectName: string;
133+
}

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as vscode from "vscode";
66
import * as commands from "./commands";
77
import { JavaDebugConfigurationProvider } from "./configurationProvider";
88
import { HCR_EVENT, JAVA_LANGID, USER_NOTIFICATION_EVENT } from "./constants";
9+
import { initializeCodeLensProvider } from "./debugCodeLensProvider"
910
import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace } from "./hotCodeReplace";
1011
import { logger, Type } from "./logger";
1112
import * as utility from "./utility";
@@ -53,6 +54,8 @@ export function activate(context: vscode.ExtensionContext) {
5354
handleUserNotification(customEvent);
5455
}
5556
}));
57+
58+
initializeCodeLensProvider(context);
5659
}
5760

5861
// this method is called when your extension is deactivated

src/utility.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ export class UserError extends Error {
1717
}
1818
}
1919

20+
interface IProperties {
21+
[key: string]: string;
22+
}
23+
2024
interface ILoggingMessage {
2125
message: string;
2226
type?: Type;
23-
details?: { [key: string]: string; };
27+
details?: IProperties;
2428
}
2529

2630
interface ITroubleshootingMessage extends ILoggingMessage {
@@ -85,3 +89,23 @@ function handleTroubleshooting(choice: string, message: string, anchor: string):
8589

8690
return choice;
8791
}
92+
93+
export function formatErrorProperties(ex: any): IProperties {
94+
const exception = (ex && ex.data && ex.data.cause)
95+
|| { stackTrace: (ex && ex.stack), detailMessage: String((ex && ex.message) || ex || "Unknown exception") };
96+
97+
const properties = {
98+
message: "",
99+
stackTrace: "",
100+
};
101+
102+
if (exception && typeof exception === "object") {
103+
properties.message = exception.detailMessage;
104+
properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace))
105+
|| String(exception.stackTrace);
106+
} else {
107+
properties.message = String(exception);
108+
}
109+
110+
return properties;
111+
}

0 commit comments

Comments
 (0)