Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 42 additions & 19 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,52 @@ async function runJavaFile(uri: vscode.Uri, noDebug: boolean) {
throw ex;
}

const hasMainMethods: boolean = mainMethods.length > 0;
const canRunTests: boolean = await canDelegateToJavaTestRunner(uri);

if (!hasMainMethods && !canRunTests) {
const mainClasses: IMainClassOption[] = await utility.searchMainMethods();
await launchMain(mainClasses, uri, noDebug);
} else if (hasMainMethods && !canRunTests) {
await launchMain(mainMethods, uri, noDebug);
} else if (!hasMainMethods && canRunTests) {
await launchTesting(uri, noDebug);
} else {
const launchMainChoice: string = "main() method";
const launchTestChoice: string = "unit tests";
const choice: string = await vscode.window.showQuickPick(
[launchMainChoice, launchTestChoice],
{ placeHolder: "Please select which kind of task you would like to launch" },
);
if (choice === launchMainChoice) {
await launchMain(mainMethods, uri, noDebug);
} else if (choice === launchTestChoice) {
await launchTesting(uri, noDebug);
}
}
}

async function canDelegateToJavaTestRunner(uri: vscode.Uri): Promise<boolean> {
const fsPath: string = uri.fsPath;
const isTestFile: boolean = /.*[\/\\]src[\/\\]test[\/\\]java[\/\\].*[Tt]ests?\.java/.test(fsPath);
if (!isTestFile) {
return false;
}
return (await vscode.commands.getCommands()).includes("java.test.editor.run");
}

async function launchTesting(uri: vscode.Uri, noDebug: boolean): Promise<void> {
noDebug ? vscode.commands.executeCommand("java.test.editor.run", uri) : vscode.commands.executeCommand("java.test.editor.debug", uri);
}

async function launchMain(mainMethods: IMainClassOption[], uri: vscode.Uri, noDebug: boolean): Promise<void> {
if (!mainMethods || !mainMethods.length) {
vscode.window.showErrorMessage(
"Error: Main method not found in the file, please define the main method as: public static void main(String[] args)");
return;
}

const pick = await mainClassPicker.showQuickPick(mainMethods, "Select the main class to run.", (option) => option.mainClass);
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainMethods, "Select the main class to run.");
if (!pick) {
return;
}
Expand All @@ -287,31 +326,15 @@ async function runJavaProject(node: any, noDebug: boolean) {
throw error;
}

let mainClassesOptions: IMainClassOption[] = [];
try {
mainClassesOptions = await vscode.window.withProgress<IMainClassOption[]>(
{
location: vscode.ProgressLocation.Window,
},
async (p) => {
p.report({
message: "Searching main class...",
});
return resolveMainClass(vscode.Uri.parse(node.uri));
});
} catch (ex) {
vscode.window.showErrorMessage(String((ex && ex.message) || ex));
throw ex;
}

const mainClassesOptions: IMainClassOption[] = await utility.searchMainMethods(vscode.Uri.parse(node.uri));
if (!mainClassesOptions || !mainClassesOptions.length) {
vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' `
+ "because it does not contain any main class.");
return;
}

const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions,
"Select the main class to run.", (option) => option.mainClass);
"Select the main class to run.");
if (!pick) {
return;
}
Expand Down
8 changes: 6 additions & 2 deletions src/mainClassPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TextEditor, window } from "vscode";
import { IMainClassOption } from "./languageServerPlugin";

const defaultLabelFormatter = (option: IMainClassOption) => {
return option.mainClass + `${option.projectName ? "<" + option.projectName + ">" : ""}`;
return option.mainClass;
};
type Formatter = (option: IMainClassOption) => string;

Expand Down Expand Up @@ -44,7 +44,7 @@ class MainClassPicker {
return {
label: labelFormatter(option),
description: option.filePath ? path.basename(option.filePath) : "",
detail: undefined,
detail: option.projectName ? `Project: ${option.projectName}` : "",
data: option,
};
});
Expand Down Expand Up @@ -109,6 +109,10 @@ class MainClassPicker {
adjustedDetail.push(`$(file-text) active editor (${path.basename(option.filePath)})`);
}

if (option.projectName) {
adjustedDetail.push(`Project: ${option.projectName}`);
}

const detail: string = adjustedDetail.join(", ");

return {
Expand Down
23 changes: 23 additions & 0 deletions src/utility.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as _ from "lodash";
import * as path from "path";
import * as vscode from "vscode";
import { sendError, sendInfo, setUserError } from "vscode-extension-telemetry-wrapper";
import { IMainClassOption, resolveMainClass } from "./languageServerPlugin";
import { logger, Type } from "./logger";

const TROUBLESHOOTING_LINK = "https://github.com/Microsoft/vscode-java-debug/blob/master/Troubleshooting.md";
Expand Down Expand Up @@ -248,3 +250,24 @@ export async function waitForStandardMode(): Promise<boolean> {

return true;
}

export async function searchMainMethods(...uris: vscode.Uri[] | undefined): Promise<IMainClassOption[]> {
try {
return await vscode.window.withProgress<IMainClassOption[]>(
{ location: vscode.ProgressLocation.Window },
async (p) => {
p.report({ message: "Searching main classes..." });
if (_.isEmpty(uris)) {
uris = vscode.workspace.workspaceFolders.map((folder) => folder.uri);
}
const mainClasses: IMainClassOption[] = [];
for (const uri of uris) {
mainClasses.push(...await resolveMainClass(uri));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling search engine multiple times is not efficient. In current PR, i didn't see there is requirement to search for multiple uris at one time. I suggest to keep passing at most 1 uri in this method. If there is requirement in future to search for multiple uris, we can improve the Java side logic for that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what if the workspace has multiple folders? calling searchMainMethods() multiple times?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you didn't pass uri in this method, it means searching in all folders.

If you look at the Java implementation, what it did is searching all results first. and if there is a uri given, then filter the result with the uri, otherwise, return all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see

}
return mainClasses;
});
} catch (ex) {
vscode.window.showErrorMessage(String((ex && ex.message) || ex));
throw ex;
}
}