Skip to content

Commit

Permalink
added file association option to binding wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Chen authored and angelozerr committed Aug 20, 2021
1 parent 27687c6 commit 5f56d3a
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 31 deletions.
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 81 additions & 13 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as path from 'path';
import { commands, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, Uri, window, workspace, WorkspaceEdit, Disposable } from "vscode";
import { commands, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, Uri, window, workspace, WorkspaceEdit, Disposable, ConfigurationTarget, TextDocument, WorkspaceFolder } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentIdentifier, TextDocumentEdit } from "vscode-languageclient";
import { LanguageClient } from 'vscode-languageclient/node';
import { markdownPreviewProvider } from "../markdownPreviewProvider";
import { ClientCommandConstants, ServerCommandConstants } from "./commandConstants";
import { getRelativePath, getFileName, getDirectoryPath, getWorkspaceUri } from '../utils/fileUtils';

/**
* Register the commands for vscode-xml that don't require communication with the language server
Expand Down Expand Up @@ -143,7 +144,8 @@ function registerValidationCommands(context: ExtensionContext) {

export const bindingTypes = new Map<string, string>([
["Standard (xsi, DOCTYPE)", "standard"],
["XML Model association", "xml-model"]
["XML Model association", "xml-model"],
["File association", "fileAssociation"]
]);

const bindingTypeOptions: QuickPickItem[] = [];
Expand All @@ -154,13 +156,13 @@ for (const label of bindingTypes.keys()) {
/**
* The function passed to context subscriptions for grammar association
*
* @param uri the uri of the XML file path
* @param documentURI the uri of the XML file path
* @param languageClient the language server client
*/
async function grammarAssociationCommand(documentURI: Uri, languageClient: LanguageClient) {
// A click on Bind to grammar/schema... has been processed in the XML document which is not bound to a grammar

// Step 1 : open a combo to select the binding type ("standard", "xml-model")
// Step 1 : open a combo to select the binding type ("standard", "xml-model", "fileAssociation")
const pickedBindingTypeOption = await window.showQuickPick(bindingTypeOptions, { placeHolder: "Binding type" });
if (!pickedBindingTypeOption) {
return;
Expand All @@ -179,24 +181,90 @@ async function grammarAssociationCommand(documentURI: Uri, languageClient: Langu
const fileUri = await window.showOpenDialog(options);
if (fileUri && fileUri[0]) {
// The XSD, DTD has been selected, get the proper syntax for binding this grammar file in the XML document.
const identifier = TextDocumentIdentifier.create(documentURI.toString());
const grammarURI = fileUri[0];
try {
const result = await commands.executeCommand(ServerCommandConstants.ASSOCIATE_GRAMMAR_INSERT, identifier, grammarURI.toString(), bindingType);
// Insert the proper syntax for binding
const lspTextDocumentEdit = <TextDocumentEdit>result;
const workEdits = new WorkspaceEdit();
for (const edit of lspTextDocumentEdit.edits) {
workEdits.replace(documentURI, languageClient.protocol2CodeConverter.asRange(edit.range), edit.newText);
const currentFile = (window.activeTextEditor && window.activeTextEditor.document && window.activeTextEditor.document.languageId === 'xml') ? window.activeTextEditor.document : undefined;
if (bindingType == 'fileAssociation') {
// Bind grammar using file association
await bindWithFileAssociation(documentURI, grammarURI, currentFile);
} else {
// Bind grammar using standard binding
await bindWithStandard(documentURI, grammarURI, bindingType, languageClient);
}
workspace.applyEdit(workEdits); // apply the edits

} catch (error) {
window.showErrorMessage('Error during grammar binding: ' + error.message);
};
}
}

/**
* Perform grammar binding using file association through settings.json
*
* @param documentURI the URI of the current XML document
* @param grammarURI the URI of the user selected grammar file
* @param document the opened TextDocument
*/
async function bindWithFileAssociation(documentURI: Uri, grammarURI: Uri, document: TextDocument) {
const absoluteGrammarFilePath = grammarURI.toString();
const currentFilename = getFileName(documentURI.toString());
const currentWorkspaceUri = getWorkspaceUri(document);
// If the grammar file is in the same workspace, use the relative path, otherwise use the absolute path
const grammarFilePath = getDirectoryPath(absoluteGrammarFilePath).includes(currentWorkspaceUri.toString()) ? getRelativePath(currentWorkspaceUri.toString(), absoluteGrammarFilePath) : absoluteGrammarFilePath;

const defaultPattern = `**/${currentFilename}`;
const inputBoxOptions = {
title: "File Association Pattern",
value: defaultPattern,
placeHolder: defaultPattern,
prompt: "Enter the pattern of the XML document(s) to be bound."
}
const inputPattern = (await window.showInputBox(inputBoxOptions));
if (!inputPattern) {
// User closed the input box with Esc
return;
}
const fileAssociation = {
"pattern": inputPattern,
"systemId": grammarFilePath
}
addToValueToSettingArray("xml.fileAssociations", fileAssociation);
}

/**
* Bind grammar file using standard XML document grammar
*
* @param documentURI the URI of the XML file path
* @param grammarURI the URI of the user selected grammar file
* @param bindingType the selected grammar binding type
* @param languageClient the language server client
*/
async function bindWithStandard(documentURI: Uri, grammarURI: Uri, bindingType: string, languageClient: LanguageClient) {
const identifier = TextDocumentIdentifier.create(documentURI.toString());
const result = await commands.executeCommand(ServerCommandConstants.ASSOCIATE_GRAMMAR_INSERT, identifier, grammarURI.toString(), bindingType);
// Insert the proper syntax for binding
const lspTextDocumentEdit = <TextDocumentEdit>result;
const workEdits = new WorkspaceEdit();
for (const edit of lspTextDocumentEdit.edits) {
workEdits.replace(documentURI, languageClient.protocol2CodeConverter.asRange(edit.range), edit.newText);
}
workspace.applyEdit(workEdits); // apply the edits
}

/**
* Add an entry, value, to the setting.json field, key
*
* @param key the filename/path of the xml document
* @param value the object to add to the config
*/
function addToValueToSettingArray<T>(key: string, value: T): void {
const settingArray: T[] = workspace.getConfiguration().get<T[]>(key, []);
if (settingArray.includes(value)) {
return;
}
settingArray.push(value);
workspace.getConfiguration().update(key, settingArray, ConfigurationTarget.Workspace);
}

/**
* Register commands used for associating grammar file (XSD,DTD) to a given XML file for command menu and CodeLens
*
Expand Down
13 changes: 6 additions & 7 deletions src/settings/variableSubstitution.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from "path";
import { TextDocument, window, workspace, WorkspaceFolder } from "vscode";
import { window } from "vscode";
import { getFilePath, getWorkspaceUri } from "../utils/fileUtils";
import { XMLFileAssociation } from "../api/xmlExtensionApi";

/**
Expand Down Expand Up @@ -99,11 +100,9 @@ const VARIABLE_SUBSTITUTIONS: VariableSubstitution[] = [
export function getVariableSubstitutedAssociations(associations: XMLFileAssociation[]): XMLFileAssociation[] {

// Collect properties needed to resolve variables
const currentFile: TextDocument = (window.activeTextEditor && window.activeTextEditor.document && window.activeTextEditor.document.languageId === 'xml') ? window.activeTextEditor.document : undefined;
const currentFileUri: string = (currentFile && currentFile.uri) ? currentFile.uri.fsPath : undefined;
const currentWorkspace: WorkspaceFolder = (currentFile && currentFile.uri) ? workspace.getWorkspaceFolder(currentFile.uri) : undefined;
const currentWorkspaceUri: string = (currentWorkspace && currentWorkspace.uri.fsPath)
|| (workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath);
const currentFile = (window.activeTextEditor && window.activeTextEditor.document && window.activeTextEditor.document.languageId === 'xml') ? window.activeTextEditor.document : undefined;
const currentFileUri = getFilePath(currentFile);
const currentWorkspaceUri = getWorkspaceUri(currentFile);

// Remove variables that can't be resolved
let variablesToSubstitute = VARIABLE_SUBSTITUTIONS;
Expand All @@ -123,7 +122,7 @@ export function getVariableSubstitutedAssociations(associations: XMLFileAssociat
const subVars = (val: string): string => {
let newVal = val;
for (const settingVariable of variablesToSubstitute) {
newVal = settingVariable.substituteString(newVal, currentFileUri, currentWorkspaceUri);
newVal = settingVariable.substituteString(newVal, currentFileUri, currentWorkspaceUri.toString());
}
return newVal;
}
Expand Down
55 changes: 55 additions & 0 deletions src/utils/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as path from "path";
import { TextDocument, Uri, workspace, WorkspaceFolder } from "vscode";

/**
* Return the current workspace uri from root
*
* @param document the opened TextDocument
* @returns the path from root of the current workspace
*/
export function getWorkspaceUri(document: TextDocument): Uri | undefined {
const currentWorkspace: WorkspaceFolder = (document && document.uri) ? workspace.getWorkspaceFolder(document.uri) : undefined;
return ((currentWorkspace && currentWorkspace.uri) || (workspace.workspaceFolders && workspace.workspaceFolders[0].uri));
}

/**
* Return the uri of the current file
*
* @param document the opened TextDocument
* @returns the uri of the current file
*/
export function getFilePath(document: TextDocument): string {
return (document && document.uri) ? document.uri.fsPath : undefined;
}

/**
* Uses path to return a basename from a uri
*
* @param filePath the uri of the file
* @return the filename
*/
export function getFileName(filePath: string): string {
return path.basename(filePath);
}

/**
* Return the relative file path between a start and destination uri
*
* @param startPath the absolute path of the beginning directory
* @param destinationPath the absolute path of destination file
* @returns the path to the destination relative to the start
*/
export function getRelativePath(startPath: string, destinationPath: string): string {
return path.relative(path.normalize(startPath), path.normalize(destinationPath)).replace(/\\/g, '/');
}

/**
* Uses path to return the directory name from a uri
*
* @param filePath the uri of the file
* @return the directory path
*/
export function getDirectoryPath(filePath: string): string {
return path.dirname(filePath);

}

0 comments on commit 5f56d3a

Please sign in to comment.