Skip to content

Commit 8944dee

Browse files
authored
Fix issue #398:Not allowing {fileBasenameNoExtension} (#416)
* Fix issue #398:Not allowing {fileBasenameNoExtension} to be used in main class launch configuration
1 parent 51619ef commit 8944dee

File tree

3 files changed

+278
-1
lines changed

3 files changed

+278
-1
lines changed

TestPlan.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,23 @@ Exception in thread "main" java.lang.IllegalStateException
437437
```
438438
Tomcat started on port(s): 8080 (http)
439439
```
440+
441+
442+
443+
## Resolve Variables
444+
1. Open `17.argstest` in vscode.
445+
2. Change the launch.json as following:
446+
```
447+
"mainClass": "test.${fileBasenameNoExtension}",
448+
"args": [ "\"${execPath}\"",
449+
"${env:APPDATA}",
450+
"${fileExtname}",
451+
"${workspaceRootFolderName}"]
452+
]
453+
```
454+
3. Make a BP at line one of main method, Keep ArgsTest.java open and press <kbd>F5</kbd> to start.
455+
3. Verify the `args` has the values:
456+
- "C:\Users\andxu\AppData\Local\Programs\Microsoft VS Code\Code.exe"
457+
- "C:\Users\andxu\AppData\Roaming"
458+
- ".json"
459+
- "17.argstest"

src/configurationProvider.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import * as anchor from "./anchor";
99
import * as commands from "./commands";
1010
import { logger, Type } from "./logger";
1111
import * as utility from "./utility";
12+
import { VariableResolver } from "./variableResolver";
1213

1314
export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
1415
private isUserSettingsDirty: boolean = true;
1516
private debugHistory: MostRecentlyUsedHistory = new MostRecentlyUsedHistory();
16-
17+
private resolver: VariableResolver;
1718
constructor() {
19+
this.resolver = new VariableResolver();
1820
vscode.workspace.onDidChangeConfiguration((event) => {
1921
if (vscode.debug.activeDebugSession) {
2022
this.isUserSettingsDirty = false;
@@ -34,6 +36,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
3436
// Try to add all missing attributes to the debug configuration being launched.
3537
public resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken):
3638
vscode.ProviderResult<vscode.DebugConfiguration> {
39+
this.resolveVariables(folder, config);
3740
return this.heuristicallyResolveDebugConfiguration(folder, config);
3841
}
3942

@@ -79,6 +82,26 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
7982
});
8083
}
8184

85+
private resolveVariables(folder: vscode.WorkspaceFolder, config: vscode.DebugConfiguration): void {
86+
// all the properties whose values are string or array of string
87+
const keys = ["mainClass", "args", "vmArgs", "modulePaths", "classPaths", "projectName",
88+
"env", "sourcePaths", "encoding", "cwd", "hostName"];
89+
if (!config) {
90+
return;
91+
}
92+
for (const key of keys) {
93+
if (config.hasOwnProperty(key)) {
94+
const value = config[key];
95+
if (_.isString(value)) {
96+
config[key] = this.resolver.resolveString(folder.uri, value);
97+
} else if (_.isArray(value)) {
98+
config[key] = _.map(value, (item) =>
99+
_.isString(item) ? this.resolver.resolveString(folder.uri, item) : item);
100+
}
101+
}
102+
}
103+
}
104+
82105
private constructLaunchConfigName(mainClass: string, projectName: string, cache: {}) {
83106
const prefix = "Debug (Launch)-";
84107
let name = prefix + mainClass.substr(mainClass.lastIndexOf(".") + 1);

src/variableResolver.ts

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
// Originally copied from https://github.com/Microsoft/vscode/blob/1.27.1/src/vs/workbench/services/configurationResolver/node/variableResolver.ts
4+
5+
import * as _ from "lodash";
6+
import * as path from "path";
7+
import * as vscode from "vscode";
8+
export interface IProcessEnv {
9+
[key: string]: string | undefined;
10+
}
11+
12+
function isWin(): boolean {
13+
return /^win/.test(process.platform);
14+
}
15+
16+
function hasDriveLetter(_path: string): boolean {
17+
return isWindows && _path && _path[1] === ":";
18+
}
19+
20+
function normalizeDriveLetter(_path: string): string {
21+
if (hasDriveLetter(_path)) {
22+
return _path.charAt(0).toUpperCase() + _path.slice(1);
23+
}
24+
25+
return _path;
26+
}
27+
const isWindows = isWin();
28+
29+
export class VariableResolver {
30+
private static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
31+
32+
public constructor(private readonly _envVariables: IProcessEnv = process.env) {
33+
if (isWindows) {
34+
this._envVariables = Object.create(null);
35+
Object.keys(_envVariables).forEach((key) => {
36+
this._envVariables[key.toLowerCase()] = _envVariables[key];
37+
});
38+
}
39+
}
40+
public resolveString(folderUri: vscode.Uri, value: string): string {
41+
42+
const filePath = this.getFilePath();
43+
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
44+
45+
let argument: string;
46+
const parts = variable.split(":");
47+
if (parts && parts.length > 1) {
48+
variable = parts[0];
49+
argument = parts[1];
50+
}
51+
52+
switch (variable) {
53+
54+
case "env":
55+
if (argument) {
56+
if (isWindows) {
57+
argument = argument.toLowerCase();
58+
}
59+
const env = this._envVariables[argument];
60+
if (_.isString(env)) {
61+
return env;
62+
}
63+
// For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436
64+
return "";
65+
}
66+
throw new Error(`missingEnvVarName: '${match}' can not be resolved because no environment variable name is given.`);
67+
68+
case "config":
69+
if (argument) {
70+
const config = this.getConfigurationValue(folderUri, argument);
71+
if (_.isUndefined(config) || _.isNull(config)) {
72+
throw new Error(`configNotFound: '${match}' can not be resolved because setting '${argument}' not found.`);
73+
}
74+
if (_.isObject(config)) {
75+
throw new Error(`configNoString: '${match}' can not be resolved because '${argument}' is a structured value.`);
76+
}
77+
return config;
78+
}
79+
throw new Error(`missingConfigName: '${match}' can not be resolved because no settings name is given.`);
80+
81+
default: {
82+
83+
// common error handling for all variables that require an open folder and accept a folder name argument
84+
switch (variable) {
85+
case "workspaceRoot":
86+
case "workspaceFolder":
87+
case "workspaceRootFolderName":
88+
case "workspaceFolderBasename":
89+
case "relativeFile":
90+
if (argument) {
91+
const folder = this.getFolderUri(argument);
92+
if (folder) {
93+
folderUri = folder;
94+
} else {
95+
throw new Error(`canNotFindFolder: '${match}' can not be resolved. No such folder '${argument}'.`);
96+
}
97+
}
98+
if (!folderUri) {
99+
if (this.getWorkspaceFolderCount() > 1) {
100+
throw new Error(`canNotResolveWorkspaceFolderMultiRoot: '${match}' ` +
101+
`can not be resolved in a multi folder workspace. ` +
102+
`Scope this variable using ":' and a workspace folder name.`);
103+
}
104+
throw new Error(`canNotResolveWorkspaceFolder: '${match}' can not be resolved. Please open a folder.`);
105+
}
106+
break;
107+
default:
108+
break;
109+
}
110+
111+
// common error handling for all variables that require an open file
112+
switch (variable) {
113+
case "file":
114+
case "relativeFile":
115+
case "fileDirname":
116+
case "fileExtname":
117+
case "fileBasename":
118+
case "fileBasenameNoExtension":
119+
if (!filePath) {
120+
throw new Error(`canNotResolveFile: '${match}' can not be resolved. Please open an editor.`);
121+
}
122+
break;
123+
default:
124+
break;
125+
}
126+
127+
switch (variable) {
128+
case "workspaceRoot":
129+
case "workspaceFolder":
130+
return normalizeDriveLetter(folderUri.fsPath);
131+
132+
case "cwd":
133+
return folderUri ? normalizeDriveLetter(folderUri.fsPath) : process.cwd();
134+
135+
case "workspaceRootFolderName":
136+
case "workspaceFolderBasename":
137+
return path.basename(folderUri.fsPath);
138+
139+
case "lineNumber":
140+
const lineNumber = this.getLineNumber();
141+
if (lineNumber) {
142+
return lineNumber;
143+
}
144+
throw new Error(`canNotResolveLineNumber: '${match}' can not be resolved.` +
145+
` Make sure to have a line selected in the active editor.`);
146+
147+
case "selectedText":
148+
const selectedText = this.getSelectedText();
149+
if (selectedText) {
150+
return selectedText;
151+
}
152+
throw new Error(`canNotResolveSelectedText: '${match}' can not be resolved.` +
153+
` Make sure to have some text selected in the active editor.`);
154+
155+
case "file":
156+
return filePath;
157+
158+
case "relativeFile":
159+
if (folderUri) {
160+
return path.normalize(path.relative(folderUri.fsPath, filePath));
161+
}
162+
return filePath;
163+
164+
case "fileDirname":
165+
return path.dirname(filePath);
166+
167+
case "fileExtname":
168+
return path.extname(filePath);
169+
170+
case "fileBasename":
171+
return path.basename(filePath);
172+
173+
case "fileBasenameNoExtension":
174+
const basename = path.basename(filePath);
175+
return basename.slice(0, basename.length - path.extname(basename).length);
176+
177+
case "execPath":
178+
const ep = process.execPath;
179+
if (ep) {
180+
return ep;
181+
}
182+
return match;
183+
184+
default:
185+
return match;
186+
}
187+
}
188+
}
189+
});
190+
}
191+
192+
private getFilePath(): string | undefined {
193+
const activeEditor = vscode.window.activeTextEditor;
194+
if (activeEditor) {
195+
const resource = activeEditor.document.uri;
196+
if (resource.scheme === "file") {
197+
return path.normalize(resource.fsPath);
198+
}
199+
}
200+
return undefined;
201+
}
202+
203+
private getSelectedText(): string | undefined {
204+
const activeTextEditor = vscode.window.activeTextEditor;
205+
if (activeTextEditor && activeTextEditor.selection) {
206+
return activeTextEditor.document.getText(activeTextEditor.selection);
207+
}
208+
return undefined;
209+
}
210+
211+
private getLineNumber(): string | undefined {
212+
const activeTextEditor = vscode.window.activeTextEditor;
213+
if (activeTextEditor && activeTextEditor.selection) {
214+
return String(activeTextEditor.selection.start.line);
215+
}
216+
return undefined;
217+
}
218+
219+
private getConfigurationValue(folderUri: vscode.Uri, suffix: string): string {
220+
221+
const configuration = vscode.workspace.getConfiguration(undefined, folderUri);
222+
const configValue = configuration.get(suffix);
223+
return String(configValue);
224+
}
225+
226+
private getWorkspaceFolderCount(): number {
227+
return vscode.workspace.workspaceFolders.length;
228+
}
229+
230+
private getFolderUri(folderName: string): vscode.Uri {
231+
const folder = vscode.workspace.workspaceFolders.filter((f) => f.name === folderName).pop();
232+
return folder ? folder.uri : undefined;
233+
}
234+
}

0 commit comments

Comments
 (0)