From 517a90593b61b388b9e1d58ebc3a37a067923eb6 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 2 Oct 2020 16:19:06 -0400 Subject: [PATCH] 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 --- docs/Validation.md | 22 +++++++++++ src/extension.ts | 91 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/docs/Validation.md b/docs/Validation.md index 69502257..cd993c82 100644 --- a/docs/Validation.md +++ b/docs/Validation.md @@ -199,6 +199,18 @@ Please note that you can use wildcards in the pattern (ex: `foo*.xml`): 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) +You can also use the following three variables in either the `pattern` or `systemId`: + + | Variable | Meaning | + | --------------------------- | ------------------------------------------------------------------------ | + | ${workspaceFolder} | The absolute path to root folder of the workspace that is currently open | + | ${fileDirname} | The absolute path to the folder of the file that is currently opened | + | ${fileBasenameNoExtension} | The current opened file's basename with no file extension | + +If one of the variables for an association can't be expanded (eg. because vscode is opened in rootless mode), +the association is ignored. +This feature is specific to the VSCode client. + ## Validation with DTD grammar 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`): 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) +You can also use the following three variables in either the `pattern` or `systemId`: + + | Variable | Meaning | + | --------------------------- | ------------------------------------------------------------------------ | + | ${workspaceFolder} | The absolute path to root folder of the workspace that is currently open | + | ${fileDirname} | The absolute path to the folder of the file that is currently opened | + | ${fileBasenameNoExtension} | The current opened file's basename with no file extension | +If one of the variables for an association can't be expanded (eg. because vscode is opened in rootless mode), +the association is ignored. +This feature is specific to the VSCode client. # Other Validation Settings diff --git a/src/extension.ts b/src/extension.ts index 4bf4c9b1..c122ad0c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,17 +10,17 @@ * Microsoft Corporation - Auto Closing Tags */ -import { prepareExecutable } from './javaServerStarter'; -import { LanguageClientOptions, RevealOutputChannelOn, LanguageClient, DidChangeConfigurationNotification, RequestType, TextDocumentPositionParams, ReferencesRequest, NotificationType, MessageType, ConfigurationRequest, ConfigurationParams } from 'vscode-languageclient'; -import * as requirements from './requirements'; -import { languages, IndentAction, workspace, window, commands, ExtensionContext, TextDocument, Position, LanguageConfiguration, Uri, extensions, Command, TextEditor } from "vscode"; -import * as path from 'path'; import * as os from 'os'; -import { activateTagClosing, AutoCloseResult } from './tagClosing'; +import * as path from 'path'; +import { Command, commands, ExtensionContext, extensions, IndentAction, LanguageConfiguration, languages, Position, TextDocument, TextEditor, Uri, window, workspace, WorkspaceFolder } from "vscode"; +import { ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, LanguageClient, LanguageClientOptions, MessageType, NotificationType, ReferencesRequest, RequestType, RevealOutputChannelOn, TextDocumentPositionParams } from 'vscode-languageclient'; import { Commands } from './commands'; -import { getXMLConfiguration, onConfigurationChange, subscribeJDKChangeConfiguration } from './settings'; -import { collectXmlJavaExtensions, onExtensionChange } from './plugin'; +import { prepareExecutable } from './javaServerStarter'; import { markdownPreviewProvider } from "./markdownPreviewProvider"; +import { collectXmlJavaExtensions, onExtensionChange } from './plugin'; +import * as requirements from './requirements'; +import { getXMLConfiguration, onConfigurationChange, subscribeJDKChangeConfiguration } from './settings'; +import { activateTagClosing, AutoCloseResult } from './tagClosing'; export interface ScopeInfo { scope: "default" | "global" | "workspace" | "folder"; @@ -276,6 +276,15 @@ export function activate(context: ExtensionContext) { } return result; }); + // When the current document changes, update ${fileDirname} and ${fileBasenameNoExtension} + // for the file associations, (but only if these variables are referenced), + // and send the updated settings to the server + context.subscriptions.push(window.onDidChangeActiveTextEditor(() => { + if (fileAssocReferencesCurrentFile()) { + languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirements.java_home) }); + onConfigurationChange(); + } + })); const api: XMLExtensionApi = { // add API set catalogs to internal memory @@ -377,14 +386,70 @@ export function activate(context: ExtensionContext) { xml['xml']['catalogs'].push(catalog); } }) - externalXmlSettings.xmlFileAssociations.forEach(element => { - if (!xml['xml']['fileAssociations'].some(fileAssociation => fileAssociation.systemId === element.systemId)) { - xml['xml']['fileAssociations'].push(element); - } - }); + const variableSubstitutedAssociations: XMLFileAssociation[] = + xml['xml']['fileAssociations'].map((association: XMLFileAssociation): XMLFileAssociation => { + + const currentFile: TextDocument = window.activeTextEditor.document; + const currentFileUri: string = currentFile && currentFile.uri.fsPath; + const currentWorkspace: WorkspaceFolder = workspace.getWorkspaceFolder(currentFile && currentFile.uri); + const currentWorkspaceUri: string = (currentWorkspace && currentWorkspace.uri.fsPath) + || (workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath); + + if (!currentWorkspaceUri + && (association.pattern.indexOf('&{workspaceFolder}') >= 0 + || association.systemId.indexOf('&{workspaceFolder}') >= 0)) { + return; + } + + if (!currentFileUri + && (association.pattern.indexOf('&{fileDirname}') >= 0 + || association.systemId.indexOf('&{fileDirname}') >= 0 + || association.pattern.indexOf('&{fileBasenameNoExtension}') >= 0 + || association.systemId.indexOf('&{fileBasenameNoExtension}') >= 0)) { + return; + } + + /** + * Returns the string with the values for: + * * ${workspaceFolder} + * * ${fileDirname} + * * ${fileBasenameNoExtension} + * substituted into the string + * + * @param val the value to substitute the variables into + * @return the string with values for the variables substituted into the string + */ + const subVars = (val: string): string => { + let newVal: string = val.replace(/\$\{workspaceFolder\}/g, currentWorkspaceUri); + newVal = newVal.replace(/\$\{fileDirname\}/g, path.dirname(currentFileUri)); + newVal = newVal.replace(/\$\{fileBasenameNoExtension\}/g, path.basename(currentFileUri, path.extname(currentFileUri))); + return newVal; + } + + return { + pattern: subVars(association.pattern), + systemId: subVars(association.systemId) + }; + }); + xml['xml']['fileAssociations'] = [...variableSubstitutedAssociations]; return xml; } + + /** + * Returns true if the the XML settings contain a file association with a variable reference to either ${fileDirname} or ${fileBasenameNoExtension} and false otherwise + * + * @return true if the the XML settings contain a file association with a variable reference to either ${fileDirname} or ${fileBasenameNoExtension} and false otherwise + */ + function fileAssocReferencesCurrentFile(): boolean { + for (const assoc of getXMLConfiguration().get('fileAssociations') as XMLFileAssociation[]) { + if (assoc.pattern.indexOf('${fileDirname}') >= 0 || assoc.pattern.indexOf('${fileBasenameNoExtension}') >= 0 + || assoc.systemId.indexOf('${fileDirname}') >= 0 || assoc.systemId.indexOf('${fileBasenameNoExtension}') >= 0) { + return true; + } + } + return false; + } } export function deactivate(): void {