Skip to content

Commit

Permalink
Add Vimspector config substitutions for convenience
Browse files Browse the repository at this point in the history
The following variables are now available for automatic substitution in
Vimspector config based on the currently loaded Java file. Note, these
only work if the Java file contains a main method.

- ClassPaths
- MainClass
- ModulePaths
- ProjectName

The variable substitution names are configurable via the plugin settings.
- java.debug.vimspector.substitution.classPaths
- java.debug.vimspector.substitution.mainClass
- java.debug.vimspector.substitution.modulePaths
- java.debug.vimspector.substitution.projectName
  • Loading branch information
dansomething committed Feb 20, 2024
1 parent db96c05 commit a3eca5a
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 25 deletions.
34 changes: 34 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@
"default": "AdapterPort",
"description": "Specifies the Vimspector adapter port substitution name in `.vimspector.json`. The actual port number will replace this value in the Vimspector config when the debug server is started.",
"scope": "window"
},
"java.debug.vimspector.substitution.classPaths": {
"type": "string",
"default": "ClassPaths",
"description": "Specifies the Vimspector class paths substitution name in `.vimspector.json`. The actual class paths will replace this value in the Vimspector config when the debug server is started.",
"scope": "window"
},
"java.debug.vimspector.substitution.mainClass": {
"type": "string",
"default": "MainClass",
"description": "Specifies the Vimspector main class substitution name in `.vimspector.json`. The actual main class will replace this value in the Vimspector config when the debug server is started.",
"scope": "window"
},
"java.debug.vimspector.substitution.modulePaths": {
"type": "string",
"default": "ModulePaths",
"description": "Specifies the Vimspector module paths substitution name in `.vimspector.json`. The actual module paths will replace this value in the Vimspector config when the debug server is started.",
"scope": "window"
},
"java.debug.vimspector.substitution.projectName": {
"type": "string",
"default": "ProjectName",
"description": "Specifies the Vimspector project name substitution name in `.vimspector.json`. The actual project name will replace this value in the Vimspector config when the debug server is started.",
"scope": "window"
}
}
},
Expand All @@ -95,6 +119,16 @@
"command": "java.debug.vimspector.start",
"title": "Launch Vimspector and connect it to the Java Debug Server.",
"category": "Java"
},
{
"command": "java.debug.resolveMainMethod",
"title": "Show resolved main methods.",
"category": "Java"
},
{
"command": "java.debug.resolveClasspath",
"title": "Show resolved class paths.",
"category": "Java"
}
]
}
Expand Down
27 changes: 16 additions & 11 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { commands } from 'coc.nvim';

export namespace Commands {
/**
* vscode-java-debug command to start a debug session.
*/
export const JAVA_START_DEBUG_SESSION = 'vscode.java.startDebugSession';

/**
* coc command to start a java debugger session and connect vimspector to it.
*/
export const JAVA_START_DEBUGSESSION = 'vscode.java.startDebugSession';

export const JAVA_RESOLVE_CLASSPATH = 'vscode.java.resolveClasspath';

export const JAVA_RESOLVE_MAINMETHOD = 'vscode.java.resolveMainMethod';

export const JAVA_DEBUG_VIMSPECTOR_START = 'java.debug.vimspector.start';

/**
* Execute Workspace Command
*/
export const JAVA_DEBUG_RESOLVE_MAINMETHOD = 'java.debug.resolveMainMethod';

export const JAVA_DEBUG_RESOLVE_CLASSPATH = 'java.debug.resolveClasspath';

export const EXECUTE_WORKSPACE_COMMAND = 'java.execute.workspaceCommand';
}

export async function executeCommand(...rest: any[]) {
return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, ...rest);
}
58 changes: 58 additions & 0 deletions src/debugserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { TextDocument, Uri, window, workspace } from 'coc.nvim';
import { Commands, executeCommand } from './commands';
import { IClassPath, IMainClassOption, MainMethodResult } from './protocol';

export async function resolveMainMethodCurrentFile(): Promise<IMainClassOption | undefined> {
const mainMethods = await resolveMainMethodsCurrentFile();
if (mainMethods.length === 1) {
return mainMethods[0];
} else if (mainMethods.length > 1) {
return await pickMainMethod(mainMethods);
}
return undefined;
}

export async function resolveMainMethodsCurrentFile(): Promise<MainMethodResult> {
const { document } = await workspace.getCurrentState();
return resolveMainMethod(document);
}

async function resolveMainMethod(document: TextDocument): Promise<MainMethodResult> {
const resourcePath = getJavaResourcePath(document);
return await executeCommand(Commands.JAVA_RESOLVE_MAINMETHOD, resourcePath);
}

export async function resolveClassPathCurrentFile(): Promise<IClassPath> {
const mainMethod = await resolveMainMethodCurrentFile();
if (mainMethod) {
return resolveClassPathMainMethod(mainMethod);
}
return { modulePaths: [], classPaths: [] };
}

export async function resolveClassPathMainMethod(mainMethod: IMainClassOption): Promise<IClassPath> {
const classPath: any[] = await resolveClasspath(mainMethod.mainClass, mainMethod.projectName || '');
const [modulePaths, classPaths] = classPath;
return { modulePaths, classPaths };
}

async function resolveClasspath(mainClass: string, projectName: string, scope?: string): Promise<any[]> {
return executeCommand(Commands.JAVA_RESOLVE_CLASSPATH, mainClass, projectName, scope);
}

function getJavaResourcePath(document: TextDocument): string | undefined {
const resource = Uri.parse(document.uri);
if (resource.scheme === 'file' && resource.fsPath.endsWith('.java')) {
return resource.toString();
}
return undefined;
}

export async function pickMainMethod(mainMethods: MainMethodResult): Promise<IMainClassOption> {
const items = mainMethods.map((method) => {
return method.mainClass;
});
const selected = await window.showQuickpick(items, 'Choose a main method.');
// Choose the first one if none is selected.
return mainMethods[selected >= 0 ? selected : 0];
}
68 changes: 54 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { commands, ExtensionContext, workspace, window } from 'coc.nvim';
import { Commands } from './commands';
import { ExtensionContext, commands, window, workspace } from 'coc.nvim';
import { Commands, executeCommand } from './commands';
import {
resolveClassPathCurrentFile,
resolveClassPathMainMethod,
resolveMainMethodCurrentFile,
resolveMainMethodsCurrentFile,
} from './debugserver';
import { ISubstitutionVar } from './protocol';

export async function activate(context: ExtensionContext): Promise<void> {
registerCommands(context);
Expand All @@ -8,36 +15,60 @@ export async function activate(context: ExtensionContext): Promise<void> {

function registerCommands(context: ExtensionContext): void {
context.subscriptions.push(commands.registerCommand(Commands.JAVA_DEBUG_VIMSPECTOR_START, startVimspector));
context.subscriptions.push(
commands.registerCommand(Commands.JAVA_DEBUG_RESOLVE_MAINMETHOD, showCommandResult(resolveMainMethodsCurrentFile)),
);
context.subscriptions.push(
commands.registerCommand(Commands.JAVA_DEBUG_RESOLVE_CLASSPATH, showCommandResult(resolveClassPathCurrentFile)),
);
}

async function startVimspector(...args: any[]): Promise<any> {
window.showMessage('Starting Java debug server...');
const debugPort: string = await executeCommand(Commands.JAVA_START_DEBUGSESSION);
const msg = `Java debug server started on port: ${debugPort}`;
console.info(msg);
window.showInformationMessage(msg);

const debugPort: string = await commands.executeCommand(
Commands.EXECUTE_WORKSPACE_COMMAND,
Commands.JAVA_START_DEBUG_SESSION,
);
const mainMethod = await resolveMainMethodCurrentFile();
if (!mainMethod) {
window.showErrorMessage(`A Java file must be active for :CocCommand ${Commands.JAVA_DEBUG_VIMSPECTOR_START}`);
return Promise.resolve();
}
const mainClass = mainMethod.mainClass;
const projectName = mainMethod.projectName;
const classPathMainMethod = await resolveClassPathMainMethod(mainMethod);

window.showMessage(`Java debug server started on port: ${debugPort}`);
// See https://puremourning.github.io/vimspector/configuration.html#the-splat-operator
const modulePaths = classPathMainMethod.modulePaths.join(' ');
const classPaths = classPathMainMethod.classPaths.join(' ');

const debugConfig = workspace.getConfiguration('java.debug');
// See package.json#configuration.properties
const vars = debugConfig.get<ISubstitutionVar>('vimspector.substitution');

// DEPRECATED Vimspector supports choosing a default now.
const profile = debugConfig.get<string>('vimspector.profile');
const adapterPort = debugConfig.get<string>('vimspector.substitution.adapterPort');
const overrides = getOverrides(args);
const defaults = {};
if (profile) {
defaults['configuration'] = profile;
}

const overrides = getOverrides(args);

const settings = {
[adapterPort as string]: debugPort,
[vars?.adapterPort as string]: debugPort,
[vars?.classPaths as string]: classPaths,
[vars?.mainClass as string]: mainClass,
[vars?.modulePaths as string]: modulePaths,
[vars?.projectName as string]: projectName,
...defaults,
...overrides,
};

const vimspectorSettings = JSON.stringify(settings);

// See https://github.com/puremourning/vimspector#launch-with-options
window.showMessage(`Launching Vimspector with settings: ${vimspectorSettings}`);
// View logs with :CocOpenLog
console.info(`Launching Vimspector with settings: ${vimspectorSettings}`);
return workspace.nvim.eval(`vimspector#LaunchWithSettings(${vimspectorSettings})`);
}

Expand Down Expand Up @@ -68,8 +99,17 @@ function parseOverrides(args: string): any {
try {
overrides = JSON.parse(args);
} catch (e) {
window.showMessage(`Expected valid JSON for Vimspector settings, but got: ${args}`, 'error');
window.showErrorMessage(`Expected valid JSON for Vimspector settings, but got: ${args}`, 'error');
}
}
return overrides;
}

function showCommandResult(func: () => Promise<any>): (...args: any[]) => Promise<void> {
return async () => {
const result = await func();
const json = JSON.stringify(result, null, 2);
window.showInformationMessage(json);
return Promise.resolve();
};
}
27 changes: 27 additions & 0 deletions src/protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Range } from 'coc.nvim';

export interface IMainClassOption {
readonly mainClass: string;
readonly projectName?: string;
readonly filePath?: string;
}

export interface IMainMethod extends IMainClassOption {
readonly range: Range;
}

export type MainMethodResult = Array<IMainClassOption>;

// See https://github.com/microsoft/vscode-java-debug#options
export interface ISubstitutionVar {
readonly adapterPort: string;
readonly classPaths: string;
readonly mainClass: string;
readonly modulePaths: string;
readonly projectName: string;
}

export interface IClassPath {
readonly modulePaths: string[];
readonly classPaths: string[];
}

0 comments on commit a3eca5a

Please sign in to comment.