Skip to content

Commit 9d63ce5

Browse files
committed
Add variables for xml.fileAssociations
Adds three variables that can be used in `xml.fileAssociations`: * ${workspaceFolder} * ${fileDirname} * ${fileBasenameNoExtension} These variables can be used for both the `pattern` and the `systemId`. Closes #307 Signed-off-by: David Thompson <davthomp@redhat.com>
1 parent c79e830 commit 9d63ce5

File tree

2 files changed

+118
-46
lines changed

2 files changed

+118
-46
lines changed

docs/Validation.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,18 @@ Please note that you can use wildcards in the pattern (ex: `foo*.xml`):
199199

200200
In this case, all XML files that start with foo and end with .xml will be associated with the XSD (foo1.xml, foo2.xml, etc)
201201

202+
You can also use the following three variables in either the `pattern` or `systemId`:
203+
204+
| Variable | Meaning |
205+
| --------------------------- | ------------------------------------------------------------------------ |
206+
| ${workspaceFolder} | The absolute path to root folder of the workspace that is currently open |
207+
| ${fileDirname} | The absolute path to the folder of the file that is currently opened |
208+
| ${fileBasenameNoExtension} | The current opened file's basename with no file extension |
209+
210+
If one of the variables for an association can't be expanded (eg. because vscode is opened in rootless mode),
211+
the association is ignored.
212+
This feature is specific to the VSCode client.
213+
202214
## Validation with DTD grammar
203215

204216
To associate your XML with a DTD grammar you can use several strategies:
@@ -322,7 +334,17 @@ Please note that you can use wildcards in the pattern (ex: `foo*.xml`):
322334

323335
In this case, all XML files that start with foo and end with .xml will be associated with the DTD (foo1.xml, foo2.xml, etc)
324336

337+
You can also use the following three variables in either the `pattern` or `systemId`:
338+
339+
| Variable | Meaning |
340+
| --------------------------- | ------------------------------------------------------------------------ |
341+
| ${workspaceFolder} | The absolute path to root folder of the workspace that is currently open |
342+
| ${fileDirname} | The absolute path to the folder of the file that is currently opened |
343+
| ${fileBasenameNoExtension} | The current opened file's basename with no file extension |
325344

345+
If one of the variables for an association can't be expanded (eg. because vscode is opened in rootless mode),
346+
the association is ignored.
347+
This feature is specific to the VSCode client.
326348

327349
# Other Validation Settings
328350

src/extension.ts

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,17 @@
1010
* Microsoft Corporation - Auto Closing Tags
1111
*/
1212

13-
import { prepareExecutable } from './javaServerStarter';
14-
import {
15-
LanguageClientOptions,
16-
RevealOutputChannelOn,
17-
LanguageClient,
18-
DidChangeConfigurationNotification,
19-
RequestType,
20-
TextDocumentPositionParams,
21-
ReferencesRequest,
22-
NotificationType,
23-
MessageType,
24-
ConfigurationRequest,
25-
ConfigurationParams,
26-
ExecuteCommandParams,
27-
CancellationToken,
28-
ExecuteCommandRequest
29-
} from 'vscode-languageclient';
30-
import * as requirements from './requirements';
31-
import { languages, IndentAction, workspace, window, commands, ExtensionContext, TextDocument, Position, LanguageConfiguration, Uri, extensions, Command, TextEditor } from "vscode";
32-
import * as path from 'path';
3313
import * as os from 'os';
34-
import { activateTagClosing, AutoCloseResult } from './tagClosing';
14+
import * as path from 'path';
15+
import { Command, commands, ExtensionContext, extensions, IndentAction, LanguageConfiguration, languages, Position, TextDocument, TextEditor, Uri, window, workspace, WorkspaceFolder } from "vscode";
16+
import { CancellationToken, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, ExecuteCommandParams, ExecuteCommandRequest, LanguageClient, LanguageClientOptions, MessageType, NotificationType, ReferencesRequest, RequestType, RevealOutputChannelOn, TextDocumentPositionParams } from 'vscode-languageclient';
3517
import { Commands } from './commands';
36-
import { getXMLConfiguration, onConfigurationChange, subscribeJDKChangeConfiguration } from './settings';
37-
import { collectXmlJavaExtensions, onExtensionChange } from './plugin';
18+
import { prepareExecutable } from './javaServerStarter';
3819
import { markdownPreviewProvider } from "./markdownPreviewProvider";
20+
import { collectXmlJavaExtensions, onExtensionChange } from './plugin';
21+
import * as requirements from './requirements';
22+
import { getXMLConfiguration, onConfigurationChange, subscribeJDKChangeConfiguration } from './settings';
23+
import { activateTagClosing, AutoCloseResult } from './tagClosing';
3924

4025
export interface ScopeInfo {
4126
scope: "default" | "global" | "workspace" | "folder";
@@ -163,9 +148,9 @@ export function activate(context: ExtensionContext) {
163148
const sectionId = '';
164149
markdownPreviewProvider.show(context.asAbsolutePath(path.join('docs', uri)), title, sectionId, context);
165150
}));
166-
context.subscriptions.push(commands.registerCommand(Commands.OPEN_DOCS, async (params: {page: string, section: string}) => {
151+
context.subscriptions.push(commands.registerCommand(Commands.OPEN_DOCS, async (params: { page: string, section: string }) => {
167152
const page = params.page.endsWith('.md') ? params.page.substr(0, params.page.length - 3) : params.page;
168-
const uri = page + '.md';
153+
const uri = page + '.md';
169154
const sectionId = params.section || '';
170155
const title = 'XML ' + page;
171156
markdownPreviewProvider.show(context.asAbsolutePath(path.join('docs', uri)), title, sectionId, context);
@@ -262,25 +247,25 @@ export function activate(context: ExtensionContext) {
262247
// Handler for 'xml/executeClientCommand` request message that executes a command on the client
263248
languageClient.onRequest(ExecuteClientCommandRequest.type, async (params: ExecuteCommandParams) => {
264249
return await commands.executeCommand(params.command, ...params.arguments);
265-
});
250+
});
266251

267252
// Register client command to execute custom XML Language Server command
268253
context.subscriptions.push(commands.registerCommand(Commands.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
269-
let token: CancellationToken;
270-
let commandArgs: any[] = rest;
271-
if (rest && rest.length && CancellationToken.is(rest[rest.length - 1])) {
272-
token = rest[rest.length - 1];
273-
commandArgs = rest.slice(0, rest.length - 1);
274-
}
275-
const params: ExecuteCommandParams = {
276-
command,
277-
arguments: commandArgs
278-
};
279-
if (token) {
280-
return languageClient.sendRequest(ExecuteCommandRequest.type, params, token);
281-
} else {
282-
return languageClient.sendRequest(ExecuteCommandRequest.type, params);
283-
}
254+
let token: CancellationToken;
255+
let commandArgs: any[] = rest;
256+
if (rest && rest.length && CancellationToken.is(rest[rest.length - 1])) {
257+
token = rest[rest.length - 1];
258+
commandArgs = rest.slice(0, rest.length - 1);
259+
}
260+
const params: ExecuteCommandParams = {
261+
command,
262+
arguments: commandArgs
263+
};
264+
if (token) {
265+
return languageClient.sendRequest(ExecuteCommandRequest.type, params, token);
266+
} else {
267+
return languageClient.sendRequest(ExecuteCommandRequest.type, params);
268+
}
284269
}));
285270

286271
context.subscriptions.push(commands.registerCommand(Commands.OPEN_SETTINGS, async (settingId?: string) => {
@@ -319,6 +304,15 @@ export function activate(context: ExtensionContext) {
319304
}
320305
return result;
321306
});
307+
// When the current document changes, update ${fileDirname} and ${fileBasenameNoExtension}
308+
// for the file associations, (but only if these variables are referenced),
309+
// and send the updated settings to the server
310+
context.subscriptions.push(window.onDidChangeActiveTextEditor(() => {
311+
if (fileAssocReferencesCurrentFile()) {
312+
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirements.java_home) });
313+
onConfigurationChange();
314+
}
315+
}));
322316

323317
const api: XMLExtensionApi = {
324318
// add API set catalogs to internal memory
@@ -420,14 +414,70 @@ export function activate(context: ExtensionContext) {
420414
xml['xml']['catalogs'].push(catalog);
421415
}
422416
})
423-
externalXmlSettings.xmlFileAssociations.forEach(element => {
424-
if (!xml['xml']['fileAssociations'].some(fileAssociation => fileAssociation.systemId === element.systemId)) {
425-
xml['xml']['fileAssociations'].push(element);
426-
}
427-
});
417+
const variableSubstitutedAssociations: XMLFileAssociation[] =
418+
xml['xml']['fileAssociations'].map((association: XMLFileAssociation): XMLFileAssociation => {
419+
420+
const currentFile: TextDocument = window.activeTextEditor.document;
421+
const currentFileUri: string = currentFile && currentFile.uri.fsPath;
422+
const currentWorkspace: WorkspaceFolder = workspace.getWorkspaceFolder(currentFile && currentFile.uri);
423+
const currentWorkspaceUri: string = (currentWorkspace && currentWorkspace.uri.fsPath)
424+
|| (workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath);
425+
426+
if (!currentWorkspaceUri
427+
&& (association.pattern.indexOf('&{workspaceFolder}') >= 0
428+
|| association.systemId.indexOf('&{workspaceFolder}') >= 0)) {
429+
return;
430+
}
431+
432+
if (!currentFileUri
433+
&& (association.pattern.indexOf('&{fileDirname}') >= 0
434+
|| association.systemId.indexOf('&{fileDirname}') >= 0
435+
|| association.pattern.indexOf('&{fileBasenameNoExtension}') >= 0
436+
|| association.systemId.indexOf('&{fileBasenameNoExtension}') >= 0)) {
437+
return;
438+
}
439+
440+
/**
441+
* Returns the string with the values for:
442+
* * ${workspaceFolder}
443+
* * ${fileDirname}
444+
* * ${fileBasenameNoExtension}
445+
* substituted into the string
446+
*
447+
* @param val the value to substitute the variables into
448+
* @return the string with values for the variables substituted into the string
449+
*/
450+
const subVars = (val: string): string => {
451+
let newVal: string = val.replace(/\$\{workspaceFolder\}/g, currentWorkspaceUri);
452+
newVal = newVal.replace(/\$\{fileDirname\}/g, path.dirname(currentFileUri));
453+
newVal = newVal.replace(/\$\{fileBasenameNoExtension\}/g, path.basename(currentFileUri, path.extname(currentFileUri)));
454+
return newVal;
455+
}
456+
457+
return {
458+
pattern: subVars(association.pattern),
459+
systemId: subVars(association.systemId)
460+
};
461+
});
462+
xml['xml']['fileAssociations'] = [...variableSubstitutedAssociations];
428463

429464
return xml;
430465
}
466+
467+
/**
468+
* Returns true if the the XML settings contain a file association with a variable reference to either ${fileDirname} or ${fileBasenameNoExtension} and false otherwise
469+
*
470+
* @return true if the the XML settings contain a file association with a variable reference to either ${fileDirname} or ${fileBasenameNoExtension} and false otherwise
471+
*/
472+
function fileAssocReferencesCurrentFile(): boolean {
473+
for (const assoc of getXMLConfiguration().get('fileAssociations') as XMLFileAssociation[]) {
474+
if (assoc.pattern.indexOf('${fileDirname}') >= 0 || assoc.pattern.indexOf('${fileBasenameNoExtension}') >= 0
475+
|| assoc.systemId.indexOf('${fileDirname}') >= 0 || assoc.systemId.indexOf('${fileBasenameNoExtension}') >= 0) {
476+
return true;
477+
}
478+
}
479+
return false;
480+
}
431481
}
432482

433483
export function deactivate(): void {

0 commit comments

Comments
 (0)