8000 Contribute Run and Debug menus to Project Explorer by testforstephen · Pull Request #878 · microsoft/vscode-java-debug · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@
"title": "Debug",
"icon": "$(debug-alt-small)"
},
{
"command": "java.debug.runFromProjectView",
"title": "Run"
},
{
"command": "java.debug.debugFromProjectView",
"title": "Debug"
},
{
"command": "java.debug.continueAll",
"title": "Continue All"
Expand All @@ -86,6 +94,18 @@
}
],
"menus": {
"view/item/context": [
{
"command": "java.debug.runFromProjectView",
"when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/",
"group": "debugger@1"
},
{
"command": "java.debug.debugFromProjectView",
"when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/",
"group": "debugger@2"
}
],
"explorer/context": [
{
"command": "java.debug.runJavaFile",
Expand Down Expand Up @@ -175,6 +195,14 @@
{
"command": "java.debug.pauseOthers",
"when": "false"
},
{
"command": "java.debug.runFromProjectView",
"when": "false"
},
{
"command": "java.debug.debugFromProjectView",
"when": "false"
}
]
},
Expand Down
125 changes: 6 additions & 119 deletions src/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as commands from "./commands";
import * as lsPlugin from "./languageServerPlugin";
import { addMoreHelpfulVMArgs, detectLaunchCommandStyle, validateRuntime } from "./launchCommand";
import { logger, Type } from "./logger";
import { mainClassPicker } from "./mainClassPicker";
import { resolveJavaProcess } from "./processPicker";
import * as utility from "./utility";

Expand All @@ -26,7 +27,6 @@ const platformName = platformNameMappings[process.platform];

export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private isUserSettingsDirty: boolean = true;
private debugHistory: MostRecentlyUsedHistory = new MostRecentlyUsedHistory();
constructor() {
vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration("java.debug")) {
Expand Down Expand Up 8000 @@ -331,11 +331,8 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
const currentFile = config.mainClass || _.get(vscode.window.activeTextEditor, "document.uri.fsPath");
if (currentFile) {
const mainEntries = await lsPlugin.resolveMainMethod(vscode.Uri.file(currentFile));
if (mainEntries.length === 1) {
return mainEntries[0];
} else if (mainEntries.length > 1) {
return this.showMainClassQuickPick(this.formatMainClassOptions(mainEntries),
`Please select a main class you want to run.`);
if (mainEntries.length) {
return mainClassPicker.showQuickPick(mainEntries, "Please select a main class you want to run.");
}
}

Expand Down Expand Up @@ -384,9 +381,8 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
anchor: anchor.FAILED_TO_RESOLVE_CLASSPATH,
}, "Fix");
if (answer === "Fix") {
const pickItems: IMainClassQuickPickItem[] = this.formatMainClassOptions(validationResponse.proposals);
const selectedFix: lsPlugin.IMainClassOption =
await this.showMainClassQuickPick(pickItems, "Please select main class<project name>.", false);
const selectedFix = await mainClassPicker.showQuickPick(validationResponse.proposals,
"Please select main class<project name>.", false);
if (selectedFix) {
sendInfo(null, {
fix: "yes",
Expand Down Expand Up @@ -445,92 +441,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
});
}

const pickItems: IMainClassQuickPickItem[] = this.formatRecentlyUsedMainClassOptions(res);
const selected = await this.showMainClassQuickPick(pickItems, hintMessage || "Select main class<project name>");
if (selected) {
this.debugHistory.updateMRUTimestamp(selected);
}

return selected;
}

private async showMainClassQuickPick(pickItems: IMainClassQuickPickItem[], quickPickHintMessage: string, autoPick: boolean = true):
Promise<lsPlugin.IMainClassOption | undefined> {
// return undefined when the user cancels QuickPick by pressing ESC.
const selected = (pickItems.length === 1 && autoPick) ?
pickItems[0] : await vscode.window.showQuickPick(pickItems, { placeHolder: quickPickHintMessage });

return selected && selected.item;
}

private formatRecentlyUsedMainClassOptions(options: lsPlugin.IMainClassOption[]): IMainClassQuickPickItem[] {
// Sort the Main Class options with the recently used timestamp.
options.sort((a: lsPlugin.IMainClassOption, b: lsPlugin.IMainClassOption) => {
return this.debugHistory.getMRUTimestamp(b) - this.debugHistory.getMRUTimestamp(a);
});

const mostRecentlyUsedOption: lsPlugin.IMainClassOption = (options.length && this.debugHistory.contains(options[0])) ? options[0] : undefined;
const isMostRecentlyUsed = (option: lsPlugin.IMainClassOption): boolean => {
return mostRecentlyUsedOption
&& mostRecentlyUsedOption.mainClass === option.mainClass
&& mostRecentlyUsedOption.projectName === option.projectName;
};
const isFromActiveEditor = (option: lsPlugin.IMainClassOption): boolean => {
const activeEditor: vscode.TextEditor = vscode.window.activeTextEditor;
const currentActiveFile: string = _.get(activeEditor, "document.uri.fsPath");
return option.filePath && currentActiveFile && path.relative(option.filePath, currentActiveFile) === "";
};
const isPrivileged = (option: lsPlugin.IMainClassOption): boolean => {
return isMostRecentlyUsed(option) || isFromActiveEditor(option);
};

// Show the most recently used Main Class as the first one,
// then the Main Class from Active Editor as second,
// finally other Main Class.
const adjustedOptions: lsPlugin.IMainClassOption[] = [];
options.forEach((option: lsPlugin.IMainClassOption) => {
if (isPrivileged(option)) {
adjustedOptions.push(option);
}
});
options.forEach((option: lsPlugin.IMainClassOption) => {
if (!isPrivileged(option)) {
adjustedOptions.push(option);
}
});

const pickItems: IMainClassQuickPickItem[] = this.formatMainClassOptions(adjustedOptions);
pickItems.forEach((pickItem: IMainClassQuickPickItem) => {
const adjustedDetail = [];
if (isMostRecentlyUsed(pickItem.item)) {
adjustedDetail.push("$(clock) recently used");
}

if (isFromActiveEditor(pickItem.item)) {
adjustedDetail.push(`$(file-text) active editor (${path.basename(pickItem.item.filePath)})`);
}

pickItem.detail = adjustedDetail.join(", ");
});

return pickItems;
}

private formatMainClassOptions(options: lsPlugin.IMainClassOption[]): IMainClassQuickPickItem[] {
return options.map((item) => {
let label = item.mainClass;
const description = item.filePath ? path.basename(item.filePath) : "";
if (item.projectName) {
label += `<${item.projectName}>`;
}

return {
label,
description,
detail: null,
item,
};
});
return mainClassPicker.showQuickPickWithRecentlyUsed(res, hintMessage || "Select main class<project name>");
}
}

Expand Down Expand Up @@ -595,27 +506,3 @@ function convertLogLevel(commonLogLevel: string) {
return "FINE";
}
}

export interface IMainClassQuickPickItem extends vscode.QuickPickItem {
item: lsPlugin.IMainClassOption;
}

class MostRecentlyUsedHistory {
private cache: { [key: string]: number } = {};

public getMRUTimestamp(mainClassOption: lsPlugin.IMainClassOption): number {
return this.cache[this.getKey(mainClassOption)] || 0;
}

public updateMRUTimestamp(mainClassOption: lsPlugin.IMainClassOption): void {
this.cache[this.getKey(mainClassOption)] = Date.now();
}

public contains(mainClassOption: lsPlugin.IMainClassOption): boolean {
return Boolean(this.cache[this.getKey(mainClassOption)]);
}

private getKey(mainClassOption: lsPlugin.IMainClassOption): string {
return mainClassOption.mainClass + "|" + mainClassOption.projectName;
}
}
78 changes: 68 additions & 10 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as _ from "lodash";
import * as path from "path";
import * as vscode from "vscode";
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation,
instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper";
instrumentOperationAsVsCodeCommand, setUserError } from "vscode-extension-telemetry-wrapper";
import * as commands from "./commands";
import { JavaDebugConfigurationProvider } from "./configurationProvider";
import { HCR_EVENT, JAVA_LANGID, USER_NOTIFICATION_EVENT } from "./constants";
Expand All @@ -14,8 +14,9 @@ import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvi
import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace, NO_BUTTON, YES_BUTTON } from "./hotCodeReplace";
import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory";
import { logJavaException, logJavaInfo } from "./javaLogger";
import { IMainMethod, resolveMainMethod } from "./languageServerPlugin";
import { IMainClassOption, IMainMethod, resolveMainClass, resolveMainMethod } from "./languageServerPlugin";
import { logger, Type } from "./logger";
import { mainClassPicker } from "./mainClassPicker";
import { pickJavaProcess } from "./processPicker";
import { initializeThreadOperations } from "./threadOperations";
import * as utility from "./utility";
Expand Down Expand Up @@ -60,6 +61,12 @@ function initializeExtension(operationId: string, context: vscode.ExtensionConte
context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.debugJavaFile", async (uri: vscode.Uri) => {
await runJavaFile(uri, false);
}));
context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.runFromProjectView", async (node: any) => {
await runJavaProject(node, true);
}));
context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.debugFromProjectView", async (node: any) => {
await runJavaProject(node, false);
}));
initializeHotCodeReplace(context);
initializeCodeLensProvider(context);
initializeThreadOperations(context);
Expand Down Expand Up @@ -261,17 +268,68 @@ async function runJavaFile(uri: vscode.Uri, noDebug: boolean) {
return;
}

const projectName = mainMethods[0].projectName;
let mainClass = mainMethods[0].mainClass;
if (mainMethods.length > 1) {
mainClass = await vscode.window.showQuickPick(mainMethods.map((mainMethod) => mainMethod.mainClass), {
placeHolder: "Select the main class to launch.",
});
const pick = await mainClassPicker.showQuickPick(mainMethods, "Select the main class to run.", (option) => option.mainClass);
if (!pick) {
return;
}

await startDebugging(pick.mainClass, pick.projectName, uri, noDebug);
}

async function runJavaProject(node: any, noDebug: boolean) {
if (!node || !node.name || !node.uri) {
vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} the project because of invalid project node. `
+ "This command only applies to Project Explorer view.");
const error = new Error(`Failed to ${noDebug ? "run" : "debug"} the project because of invalid project node.`);
setUserError(error);
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;
}

if (!mainClass) {
if (!mainClassesOptions || !mainClassesOptions.length) {
vscode.window.sh 4D1C owErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' `
+ "because it does not contain any main class.");
return;
}

await startDebugging(mainClass, projectName, uri, noDebug);
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions,
"Select the main class to run.", (option) => option.mainClass);
if (!pick) {
return;
}

const projectName: string = pick.projectName;
const mainClass: string = pick.mainClass;
const filePath: string = pick.filePath;
const workspaceFolder: vscode.WorkspaceFolder = filePath ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) : undefined;
const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspaceFolder);
const existingConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations;
const existConfig: vscode.DebugConfiguration = _.find(existingConfigs, (config) => {
return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName);
});
const debugConfig = existConfig || {
type: "java",
name: `Launch - ${mainClass.substr(mainClass.lastIndexOf(".") + 1)}`,
request: "launch",
mainClass,
projectName,
};
debugConfig.noDebug = noDebug;
vscode.debug.startDebugging(workspaceFolder, debugConfig);
}
Loading
0