Skip to content

Commit

Permalink
Add variables for xml.fileAssociations
Browse files Browse the repository at this point in the history
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 redhat-developer#307

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 committed Oct 27, 2020
1 parent c79e830 commit 64a4ec1
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 46 deletions.
22 changes: 22 additions & 0 deletions docs/Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
151 changes: 105 additions & 46 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,18 @@
* Microsoft Corporation - Auto Closing Tags
*/

import { prepareExecutable } from './javaServerStarter';
import {
LanguageClientOptions,
RevealOutputChannelOn,
LanguageClient,
DidChangeConfigurationNotification,
RequestType,
TextDocumentPositionParams,
ReferencesRequest,
NotificationType,
MessageType,
ConfigurationRequest,
ConfigurationParams,
ExecuteCommandParams,
CancellationToken,
ExecuteCommandRequest
} 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 { CancellationToken, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, ExecuteCommandParams, ExecuteCommandRequest, 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';
import { FILE_BASENAME_NO_EXTENSION, FILE_DIRNAME, SETTINGS_VARIABLES, VariableReferenceKind, WORKSPACE_FOLDER } from './variableReference';

export interface ScopeInfo {
scope: "default" | "global" | "workspace" | "folder";
Expand Down Expand Up @@ -163,9 +149,9 @@ export function activate(context: ExtensionContext) {
const sectionId = '';
markdownPreviewProvider.show(context.asAbsolutePath(path.join('docs', uri)), title, sectionId, context);
}));
context.subscriptions.push(commands.registerCommand(Commands.OPEN_DOCS, async (params: {page: string, section: string}) => {
context.subscriptions.push(commands.registerCommand(Commands.OPEN_DOCS, async (params: { page: string, section: string }) => {
const page = params.page.endsWith('.md') ? params.page.substr(0, params.page.length - 3) : params.page;
const uri = page + '.md';
const uri = page + '.md';
const sectionId = params.section || '';
const title = 'XML ' + page;
markdownPreviewProvider.show(context.asAbsolutePath(path.join('docs', uri)), title, sectionId, context);
Expand Down Expand Up @@ -262,25 +248,25 @@ export function activate(context: ExtensionContext) {
// Handler for 'xml/executeClientCommand` request message that executes a command on the client
languageClient.onRequest(ExecuteClientCommandRequest.type, async (params: ExecuteCommandParams) => {
return await commands.executeCommand(params.command, ...params.arguments);
});
});

// Register client command to execute custom XML Language Server command
context.subscriptions.push(commands.registerCommand(Commands.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
let token: CancellationToken;
let commandArgs: any[] = rest;
if (rest && rest.length && CancellationToken.is(rest[rest.length - 1])) {
token = rest[rest.length - 1];
commandArgs = rest.slice(0, rest.length - 1);
}
const params: ExecuteCommandParams = {
command,
arguments: commandArgs
};
if (token) {
return languageClient.sendRequest(ExecuteCommandRequest.type, params, token);
} else {
return languageClient.sendRequest(ExecuteCommandRequest.type, params);
}
let token: CancellationToken;
let commandArgs: any[] = rest;
if (rest && rest.length && CancellationToken.is(rest[rest.length - 1])) {
token = rest[rest.length - 1];
commandArgs = rest.slice(0, rest.length - 1);
}
const params: ExecuteCommandParams = {
command,
arguments: commandArgs
};
if (token) {
return languageClient.sendRequest(ExecuteCommandRequest.type, params, token);
} else {
return languageClient.sendRequest(ExecuteCommandRequest.type, params);
}
}));

context.subscriptions.push(commands.registerCommand(Commands.OPEN_SETTINGS, async (settingId?: string) => {
Expand Down Expand Up @@ -319,6 +305,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
Expand Down Expand Up @@ -420,14 +415,78 @@ 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
&& SETTINGS_VARIABLES
.filter(variable => { variable.kind === VariableReferenceKind.Workspace })
.map(variable => {
return association.pattern.indexOf(`&{${variable.name}}`) >= 0
|| association.systemId.indexOf(`&{${variable.name}}`) >= 0;
})
.reduce((a, b) => { return a || b; }, false)) {
return;
}

if (!currentFileUri
&& SETTINGS_VARIABLES
.filter(variable => { variable.kind === VariableReferenceKind.File })
.map(variable => {
return association.pattern.indexOf(`&{${variable.name}}`) >= 0
|| association.systemId.indexOf(`&{${variable.name}}`) >= 0;
})
.reduce((a, b) => { return a || b; }, false)) {
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(WORKSPACE_FOLDER.getReplaceRegExp(), currentWorkspaceUri);
newVal = newVal.replace(FILE_DIRNAME.getReplaceRegExp(), path.dirname(currentFileUri));
newVal = newVal.replace(FILE_BASENAME_NO_EXTENSION.getReplaceRegExp(), 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 {
Expand Down
34 changes: 34 additions & 0 deletions src/variableReference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export class VariableReference {
public name: string;
public kind: VariableReferenceKind;
private replaceRegExp: RegExp | undefined;

constructor(name: string, kind: VariableReferenceKind) {
this.name = name;
this.kind = kind;
}

public getReplaceRegExp(): RegExp {
if (!this.replaceRegExp) {
this.replaceRegExp = new RegExp('\\$\\{' + `${this.name}` + '\\}', 'g');
}
return this.replaceRegExp;
}
}

export enum VariableReferenceKind {
Workspace,
File
}

export const WORKSPACE_FOLDER: VariableReference = new VariableReference("workspaceFolder", VariableReferenceKind.Workspace);

export const FILE_DIRNAME: VariableReference = new VariableReference("fileDirname", VariableReferenceKind.File);

export const FILE_BASENAME_NO_EXTENSION: VariableReference = new VariableReference("fileBasenameNoExtension", VariableReferenceKind.File);

export const SETTINGS_VARIABLES: VariableReference[] = [
WORKSPACE_FOLDER,
FILE_DIRNAME,
FILE_BASENAME_NO_EXTENSION
];

0 comments on commit 64a4ec1

Please sign in to comment.