Skip to content

Commit 62dcab9

Browse files
committed
Runconfig settings now support vscode variables resolution and refactored the runconfig settings merge logic with launch.json for better testiblity
1 parent 86aa3aa commit 62dcab9

File tree

6 files changed

+283
-62
lines changed

6 files changed

+283
-62
lines changed

vscode/src/configurations/listener.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616

1717
import { ConfigurationChangeEvent, ExtensionContext, workspace } from "vscode";
18-
import { userConfigsListened } from "./configuration";
18+
import { configKeys, userConfigsListened } from "./configuration";
1919
import { Disposable } from "vscode-languageclient";
2020
import { globalState } from "../globalState";
21+
import { runConfigurationUpdateAll } from "../views/runConfiguration";
22+
import { appendPrefixToCommand } from "../utils";
2123

2224
const configChangeHandler = (params: ConfigurationChangeEvent) => {
2325
userConfigsListened.forEach((config: string) => {
@@ -26,6 +28,11 @@ const configChangeHandler = (params: ConfigurationChangeEvent) => {
2628
globalState.getClientPromise().restartExtension(globalState.getNbProcessManager(), true);
2729
}
2830
});
31+
32+
const runConfigSection = appendPrefixToCommand(configKeys.runConfigArguments.split('.')[0]);
33+
if (params.affectsConfiguration(runConfigSection)) {
34+
runConfigurationUpdateAll();
35+
}
2936
}
3037

3138
const configChangeListener = workspace.onDidChangeConfiguration(configChangeHandler);

vscode/src/debugger/debugger.ts

Lines changed: 5 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import { extConstants } from '../constants';
2323
import { l10n } from '../localiser';
2424
import { StreamDebugAdapter } from './streamDebugAdapter';
2525
import { extCommands, nbCommands } from '../commands/commands';
26-
import { argumentsNode, environmentVariablesNode, vmOptionsNode, workingDirectoryNode } from '../views/runConfiguration';
27-
import { initializeRunConfiguration, parseArguments } from '../utils';
26+
import { getRunConfigurationNodeValues } from '../views/runConfiguration';
27+
import { initializeRunConfiguration } from '../utils';
2828
import { globalState } from '../globalState';
29+
import { applyRunConfigurationOverrides } from '../views/runConfigurationUtils';
2930

3031
export function registerDebugger(context: ExtensionContext): void {
3132
let debugTrackerFactory = new NetBeansDebugAdapterTrackerFactory();
@@ -217,59 +218,6 @@ class NetBeansConfigurationResolver implements vscode.DebugConfigurationProvider
217218
class RunConfigurationProvider implements vscode.DebugConfigurationProvider {
218219

219220
resolveDebugConfiguration(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
220-
return new Promise<vscode.DebugConfiguration>(resolve => {
221-
resolve(config);
222-
});
223-
}
224-
225-
resolveDebugConfigurationWithSubstitutedVariables?(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
226-
return new Promise<vscode.DebugConfiguration>(resolve => {
227-
const args = argumentsNode.getValue();
228-
if (args) {
229-
if (!config.args) {
230-
config.args = args;
231-
} else {
232-
config.args = `${config.args} ${args}`;
233-
}
234-
}
235-
236-
const vmArgs = vmOptionsNode.getValue();
237-
if (vmArgs) {
238-
if (!config.vmArgs) {
239-
config.vmArgs = vmArgs;
240-
} else if (Array.isArray(config.vmArgs)) {
241-
let cfg: string[] = config.vmArgs;
242-
243-
const result = parseArguments(vmArgs);
244-
cfg.push(...result);
245-
} else {
246-
// assume the config is a string
247-
config.vmArgs = `${config.vmArgs} ${vmArgs}`;
248-
}
249-
}
250-
251-
const env = environmentVariablesNode.getValue();
252-
if (env) {
253-
const envs = env.split(',');
254-
if (!config.env) {
255-
config.env = {};
256-
}
257-
for (let val of envs) {
258-
val = val.trim();
259-
const div = val.indexOf('=');
260-
if (div > 0) { // div === 0 means bad format (no ENV name)
261-
config.env[val.substring(0, div)] = val.substring(div + 1, val.length);
262-
}
263-
}
264-
}
265-
266-
const cwd = workingDirectoryNode.getValue();
267-
if (cwd) {
268-
config.cwd = cwd;
269-
}
270-
271-
resolve(config);
272-
});
221+
return applyRunConfigurationOverrides(config, getRunConfigurationNodeValues());
273222
}
274-
275-
}
223+
}

vscode/src/lsp/listeners/requests/handlers.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ const updateConfigRequestHandler = async (param: any) => {
9999
wsConfig.update(param.key, param.value, wsFile ? null : true)
100100
.then(() => {
101101
LOGGER.log("Updated configuration: " + param.section + "." + param.key + "=" + param.value + "; in: " + (wsFile ? wsFile.toString() : "Global"));
102-
})
103-
.then(() => {
104-
runConfigurationUpdateAll();
105102
});
106103
} catch (err) {
107104
LOGGER.error("Failed to update configuration. Reason: " + (isString(err) ? err : isError(err) ? err.message : "error"));
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
Copyright (c) 2026, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import { expect } from 'chai';
17+
import { describe, it } from 'mocha';
18+
import { applyRunConfigurationOverrides } from '../../../views/runConfigurationUtils';
19+
import { DebugConfiguration } from 'vscode';
20+
21+
describe('runConfigurationUtils', () => {
22+
it('leaves the configuration unchanged when no overrides are provided', () => {
23+
const config = createDebugConfiguration({
24+
args: '--existing',
25+
vmArgs: '-Xmx512m',
26+
env: { EXISTING: '1' },
27+
cwd: '/existing'
28+
});
29+
30+
const updated = applyRunConfigurationOverrides(config, {});
31+
32+
expect(updated).to.deep.equal(config);
33+
});
34+
35+
it('appends launch arguments to an existing string value', () => {
36+
const config = createDebugConfiguration({
37+
args: '--existing'
38+
});
39+
40+
const updated = applyRunConfigurationOverrides(config, {
41+
args: '--added'
42+
});
43+
44+
expect(updated.args).to.equal('--existing --added');
45+
});
46+
47+
it('adds parsed vm arguments into an existing array', () => {
48+
const config = createDebugConfiguration({
49+
vmArgs: ['-Xmx512m']
50+
});
51+
52+
const updated = applyRunConfigurationOverrides(config, {
53+
vmArgs: '-Dfoo=bar "-Dquoted=value here"'
54+
});
55+
56+
expect(updated.vmArgs).to.deep.equal([
57+
'-Xmx512m',
58+
'-Dfoo=bar',
59+
'"-Dquoted=value here"'
60+
]);
61+
});
62+
63+
it('appends vm arguments to an existing string value', () => {
64+
const config = createDebugConfiguration({
65+
vmArgs: '-Xmx512m'
66+
});
67+
68+
const updated = applyRunConfigurationOverrides(config, {
69+
vmArgs: '-Dfoo=bar'
70+
});
71+
72+
expect(updated.vmArgs).to.equal('-Xmx512m -Dfoo=bar');
73+
});
74+
75+
it('uses new vm arguments as-is when the config has none yet', () => {
76+
const config = createDebugConfiguration();
77+
78+
const updated = applyRunConfigurationOverrides(config, {
79+
vmArgs: '-Dfoo=bar'
80+
});
81+
82+
expect(updated.vmArgs).to.equal('-Dfoo=bar');
83+
});
84+
85+
it('merges environment variables and ignores malformed entries', () => {
86+
const config = createDebugConfiguration({
87+
env: { EXISTING: '1' }
88+
});
89+
90+
const updated = applyRunConfigurationOverrides(config, {
91+
env: ' FIRST = one , invalid, =missingName, SECOND=two=2 '
92+
});
93+
94+
expect(updated.env).to.deep.equal({
95+
EXISTING: '1',
96+
'FIRST ': ' one',
97+
SECOND: 'two=2'
98+
});
99+
});
100+
101+
it('parses environment variables through applyRunConfigurationOverrides', () => {
102+
const config = createDebugConfiguration();
103+
104+
const updated = applyRunConfigurationOverrides(config, {
105+
env: 'A=1, B=two'
106+
});
107+
108+
expect(updated.env).to.deep.equal({
109+
A: '1',
110+
B: 'two'
111+
});
112+
});
113+
114+
it('overrides existing environment variable keys with new values', () => {
115+
const config = createDebugConfiguration({
116+
env: { A: '1', B: '2' }
117+
});
118+
119+
const updated = applyRunConfigurationOverrides(config, {
120+
env: 'B=updated, C=3'
121+
});
122+
123+
expect(updated.env).to.deep.equal({
124+
A: '1',
125+
B: 'updated',
126+
C: '3'
127+
});
128+
});
129+
130+
it('creates a fresh env record when the current env is not an object', () => {
131+
const config = createDebugConfiguration({
132+
env: 'invalid-env-shape'
133+
});
134+
135+
const updated = applyRunConfigurationOverrides(config, {
136+
env: 'A=1'
137+
});
138+
139+
expect(updated.env).to.deep.equal({
140+
A: '1'
141+
});
142+
});
143+
144+
it('overrides cwd when provided', () => {
145+
const config = createDebugConfiguration();
146+
147+
const updated = applyRunConfigurationOverrides(config, {
148+
cwd: '/tmp/workdir'
149+
});
150+
151+
expect(updated.cwd).to.equal('/tmp/workdir');
152+
});
153+
154+
it('does not replace existing values with empty-string overrides', () => {
155+
const config = createDebugConfiguration({
156+
args: '--existing',
157+
vmArgs: '-Xmx512m',
158+
env: { EXISTING: '1' },
159+
cwd: '/existing'
160+
});
161+
162+
const updated = applyRunConfigurationOverrides(config, {
163+
args: '',
164+
vmArgs: '',
165+
env: '',
166+
cwd: ''
167+
});
168+
169+
expect(updated).to.deep.equal({
170+
type: 'java+',
171+
name: 'Test Config',
172+
request: 'launch',
173+
args: '--existing',
174+
vmArgs: '-Xmx512m',
175+
env: { EXISTING: '1' },
176+
cwd: '/existing'
177+
});
178+
});
179+
});
180+
181+
const createDebugConfiguration = (overrides: Record<string, unknown> = {}): DebugConfiguration => {
182+
return {
183+
type: 'java+',
184+
name: 'Test Config',
185+
request: 'launch',
186+
...overrides
187+
};
188+
}

vscode/src/views/runConfiguration.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class RunConfigurationNode extends vscode.TreeItem {
9898

9999
updateNode(reload?: boolean) {
100100
if (reload) {
101-
this.value = getConfigurationValue(this.settingsKey) as string;
101+
this.value = getConfigurationValue(this.settingsKey);
102102
}
103103
this.description = this.value ? this.value : l10n.value("jdk.extension.runConfig.default.label");
104104
this.tooltip = `${this.label} ${this.description}`;
@@ -158,3 +158,16 @@ export function runConfigurationUpdateAll() {
158158
environmentVariablesNode.updateNode(true);
159159
workingDirectoryNode.updateNode(true);
160160
}
161+
export interface RunConfigurationNodes {
162+
readonly args?: string;
163+
readonly vmArgs?: string;
164+
readonly env?: string;
165+
readonly cwd?: string;
166+
}
167+
168+
export const getRunConfigurationNodeValues = (): RunConfigurationNodes => ({
169+
args: argumentsNode.getValue(),
170+
vmArgs: vmOptionsNode.getValue(),
171+
env: environmentVariablesNode.getValue(),
172+
cwd: workingDirectoryNode.getValue(),
173+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { DebugConfiguration } from 'vscode';
2+
import { isObject, parseArguments } from '../utils';
3+
import { RunConfigurationNodes } from './runConfiguration';
4+
5+
export const applyRunConfigurationOverrides = (
6+
config: DebugConfiguration,
7+
overrides: RunConfigurationNodes
8+
): DebugConfiguration => {
9+
if (overrides.args) {
10+
config.args = appendStringValue(config.args, overrides.args);
11+
}
12+
13+
if (overrides.vmArgs) {
14+
config.vmArgs = appendVmArguments(config.vmArgs, overrides.vmArgs);
15+
}
16+
17+
if (overrides.env) {
18+
config.env = mergeEnvironmentVariables(config.env, overrides.env);
19+
}
20+
21+
if (overrides.cwd) {
22+
config.cwd = overrides.cwd;
23+
}
24+
25+
return config;
26+
};
27+
28+
const appendStringValue = (currentValue: unknown, appendedValue: string): string =>
29+
currentValue ? `${currentValue} ${appendedValue}` : appendedValue;
30+
31+
const appendVmArguments = (currentValue: unknown, appendedValue: string): string | string[] => {
32+
if (!currentValue) {
33+
return appendedValue;
34+
}
35+
36+
if (Array.isArray(currentValue)) {
37+
currentValue.push(...parseArguments(appendedValue));
38+
return currentValue;
39+
}
40+
41+
return `${currentValue} ${appendedValue}`;
42+
};
43+
44+
const mergeEnvironmentVariables = (currentEnv: unknown, rawEnv: string): Record<string, string> => {
45+
const targetEnv = isStringMap(currentEnv) ? { ...currentEnv } : {};
46+
return {
47+
...targetEnv,
48+
...parseEnvironmentVariables(rawEnv)
49+
};
50+
};
51+
52+
const parseEnvironmentVariables = (rawEnv: string): Record<string, string> => {
53+
return rawEnv
54+
.split(',')
55+
.map(entry => entry.trim())
56+
.reduce<Record<string, string>>((parsedEnv, entry) => {
57+
const separatorIndex = entry.indexOf('=');
58+
if (separatorIndex <= 0) {
59+
return parsedEnv;
60+
}
61+
62+
parsedEnv[entry.substring(0, separatorIndex)] = entry.substring(separatorIndex + 1);
63+
return parsedEnv;
64+
}, {});
65+
};
66+
67+
const isStringMap = (value: unknown): value is Record<string, string> =>
68+
!!value && isObject(value) && !Array.isArray(value);

0 commit comments

Comments
 (0)