Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,22 @@
"command": "xml.validation.all.files",
"title": "Revalidate all opened XML files",
"category": "XML"
},
{
"command": "xml.command.bind.grammar",
"title": "Bind to grammar/schema file",
"category": "XML"
}
],
"menus": {
"commandPalette": [
{
"command": "xml.validation.current.file",
"when": "editorLangId == xml"
},
{
"command": "xml.command.bind.grammar",
"when": "resourceFilename =~ /xml/ && editorIsOpen"
}
]
},
Expand Down
10 changes: 10 additions & 0 deletions src/commands/commandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export namespace ClientCommandConstants {
*/
export const OPEN_BINDING_WIZARD = 'xml.open.binding.wizard';

/**
* VSCode client command to open the grammar/schema binding wizard from command menu.
*/
export const COMMAND_PALETTE_BINDING_WIZARD = 'xml.command.bind.grammar';

/**
* Client command to execute an XML command on XML Language Server side.
*/
Expand Down Expand Up @@ -85,4 +90,9 @@ export namespace ServerCommandConstants {
* Command to associate a grammar in a XML document
*/
export const ASSOCIATE_GRAMMAR_INSERT = "xml.associate.grammar.insert";

/**
* Command to check if the current XML document is bound to a grammar
*/
export const CHECK_BOUND_GRAMMAR = "xml.check.bound.grammar"
}
123 changes: 84 additions & 39 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { commands, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, Uri, window, workspace, WorkspaceEdit } from "vscode";
import { commands, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, Uri, window, workspace, WorkspaceEdit, Disposable } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentIdentifier, TextDocumentEdit } from "vscode-languageclient";
import { LanguageClient } from 'vscode-languageclient/node';
import { markdownPreviewProvider } from "../markdownPreviewProvider";
Expand All @@ -25,7 +25,7 @@ export async function registerClientServerCommands(context: ExtensionContext, la

registerCodeLensReferencesCommands(context, languageClient);
registerValidationCommands(context);
registerCodeLensAssociationCommands(context, languageClient);
registerAssociationCommands(context, languageClient);

// Register client command to execute custom XML Language Server command
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
Expand Down Expand Up @@ -139,49 +139,94 @@ for (const label of bindingTypes.keys()) {
}

/**
* Register commands used for associating grammar file (XSD,DTD) to a given XML file
* The function passed to context subscriptions for grammar association
*
* @param context the extension context
* @param uri the uri of the XML file path
* @param languageClient the language server client
*/
function registerCodeLensAssociationCommands(context: ExtensionContext, languageClient: LanguageClient) {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.OPEN_BINDING_WIZARD, async (uriString: string) => {
// A click on Bind to grammar/schema... has been processed in the XML document which is not bound to a grammar
const documentURI = Uri.parse(uriString);

// Step 1 : open a combo to select the binding type ("standard", "xml-model")
const pickedBindingTypeOption = await window.showQuickPick(bindingTypeOptions, { placeHolder: "Binding type" });
if(!pickedBindingTypeOption) {
return;
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")
const pickedBindingTypeOption = await window.showQuickPick(bindingTypeOptions, { placeHolder: "Binding type" });
if(!pickedBindingTypeOption) {
return;
}
const bindingType = bindingTypes.get(pickedBindingTypeOption.label);

// Open a dialog to select the XSD, DTD to bind.
const options: OpenDialogOptions = {
canSelectMany: false,
openLabel: 'Select XSD or DTD file',
filters: {
'Grammar files': ['xsd', 'dtd']
}
const bindingType = bindingTypes.get(pickedBindingTypeOption.label);

// Open a dialog to select the XSD, DTD to bind.
const options: OpenDialogOptions = {
canSelectMany: false,
openLabel: 'Select XSD or DTD file',
filters: {
'Grammar files': ['xsd', 'dtd']
};

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);
}
workspace.applyEdit(workEdits); // apply the edits

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

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);
}
workspace.applyEdit(workEdits); // apply the edits
} catch (error) {
window.showErrorMessage('Error during grammar binding: ' + error.message);
};
/**
* Register commands used for associating grammar file (XSD,DTD) to a given XML file for command menu and CodeLens
*
* @param context the extension context
* @param languageClient the language server client
*/
function registerAssociationCommands(context: ExtensionContext, languageClient: LanguageClient) {
// For CodeLens
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.OPEN_BINDING_WIZARD, async (uriString: string) => {
const uri = Uri.parse(uriString);
grammarAssociationCommand(uri, languageClient)
}));
// For command menu
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.COMMAND_PALETTE_BINDING_WIZARD, async () => {
const uri = window.activeTextEditor.document.uri;
// Run check to ensure available grammar binding command should be executed, or if error is thrown
const canBind = await checkCanBindGrammar(uri);
if (canBind) {
grammarAssociationCommand(uri, languageClient)
} else {
window.showErrorMessage(`The document ${uri.toString()} is already bound with a grammar`);
}
}));

}
}

/**
* Change value of 'canBindGrammar' to determine if grammar/schema can be bound
*
* @param document the text document
* @returns the `hasGrammar` check result from server
*/
async function checkCanBindGrammar(documentURI: Uri) {
// Retrieve the document uri and identifier
const identifier = TextDocumentIdentifier.create(documentURI.toString());

// Set the custom condition to watch if file already has bound grammar
var result = false;
try {
result = await commands.executeCommand(ServerCommandConstants.CHECK_BOUND_GRAMMAR, identifier);
} catch(error) {
console.log(`Error while checking bound grammar : ${error}`);
}

return result
}