Skip to content

Commit

Permalink
Bind XSD, DTD with CodeLens
Browse files Browse the repository at this point in the history
Fixes redhat-developer#395

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Jun 14, 2021
1 parent dd99f92 commit 48da324
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 60 deletions.
7 changes: 4 additions & 3 deletions src/client/xmlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { commands, ExtensionContext, extensions, Position, TextDocument, TextEdi
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, TextDocumentPositionParams } from "vscode-languageclient";
import { Executable, LanguageClient } from 'vscode-languageclient/node';
import { XMLFileAssociation } from '../api/xmlExtensionApi';
import { CommandConstants } from '../commands/commandConstants';
import { ClientCommandConstants, ServerCommandConstants } from '../commands/commandConstants';
import { registerCommands } from '../commands/registerCommands';
import { onExtensionChange } from '../plugin';
import { RequirementsData } from "../server/requirements";
Expand Down Expand Up @@ -67,7 +67,7 @@ export async function startLanguageClient(context: ExtensionContext, executable:
let text = languageClient.sendRequest(TagCloseRequest.type, param);
return text;
};
context.subscriptions.push(activateTagClosing(tagProvider, { xml: true, xsl: true }, CommandConstants.AUTO_CLOSE_TAGS));
context.subscriptions.push(activateTagClosing(tagProvider, { xml: true, xsl: true }, ServerCommandConstants.AUTO_CLOSE_TAGS));

if (extensions.onDidChange) {// Theia doesn't support this API yet
context.subscriptions.push(extensions.onDidChange(() => {
Expand Down Expand Up @@ -123,7 +123,8 @@ function getLanguageClientOptions(logfile: string, externalXmlSettings: External
codeLens: {
codeLensKind: {
valueSet: [
'references'
'references',
'association'
]
}
},
Expand Down
98 changes: 62 additions & 36 deletions src/commands/commandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,78 @@
'use strict';

/**
* Commonly used commands
* VScode client commands.
*/
export namespace CommandConstants {
export namespace ClientCommandConstants {

/**
* Auto close tags
*/
export const AUTO_CLOSE_TAGS = 'xml.completion.autoCloseTags';
/**
* Show XML references
*/
export const SHOW_REFERENCES = 'xml.show.references';

/**
* Show XML references
*/
export const SHOW_REFERENCES = 'xml.show.references';
/**
* Show editor references
*/
export const EDITOR_SHOW_REFERENCES = 'editor.action.showReferences';

/**
* Show editor references
*/
export const EDITOR_SHOW_REFERENCES = 'editor.action.showReferences';
/**
* Reload VS Code window
*/
export const RELOAD_WINDOW = 'workbench.action.reloadWindow';

/**
* Reload VS Code window
*/
export const RELOAD_WINDOW = 'workbench.action.reloadWindow';
/**
* Open settings command
*
* A `settingId: string` parameter can be optionally provided
*/
export const OPEN_SETTINGS = 'xml.open.settings';

/**
* Open settings command
*
* A `settingId: string` parameter can be optionally provided
*/
export const OPEN_SETTINGS = 'xml.open.settings';
/**
* Render markdown string to html string
*/
export const MARKDOWN_API_RENDER = 'markdown.api.render';

/**
* Render markdown string to html string
*/
export const MARKDOWN_API_RENDER = 'markdown.api.render';
export const OPEN_DOCS = 'xml.open.docs';

export const OPEN_DOCS = "xml.open.docs";
/**
* Commands to revalidate files with an LSP command on the XML Language Server
*/
export const VALIDATE_CURRENT_FILE = 'xml.validation.current.file';

export const OPEN_DOCS_HOME = "xml.open.docs.home";
export const VALIDATE_ALL_FILES = 'xml.validation.all.files';

/**
* VSCode client command to executes an LSP command on the XML Language Server
*/
export const EXECUTE_WORKSPACE_COMMAND = "xml.workspace.executeCommand";
export const OPEN_DOCS_HOME = 'xml.open.docs.home';

export const VALIDATE_CURRENT_FILE = "xml.validation.current.file";
/**
* VSCode client commands to open a folder dialog and select a file.
*/
export const SELECT_FILE = 'xml.select.file';

export const VALIDATE_ALL_FILES = "xml.validation.all.files";
/**
* Client command to execute an XML command on XML Language Server side.
*/
export const EXECUTE_WORKSPACE_COMMAND = 'xml.workspace.executeCommand';
}

/**
* XML Language Server commands.
*/
export namespace ServerCommandConstants {

/**
* Auto close tags
*/
export const AUTO_CLOSE_TAGS = 'xml.completion.autoCloseTags';

/**
* Commands to revalidate files with an LSP command on the XML Language Server
*/
export const VALIDATE_CURRENT_FILE = ClientCommandConstants.VALIDATE_CURRENT_FILE;

export const VALIDATE_ALL_FILES = ClientCommandConstants.VALIDATE_ALL_FILES;

/**
* Command to associate a grammar in a XML document
*/
export const ASSOCIATE_GRAMMAR_INSERT = "xml.associate.grammar.insert";
}
76 changes: 59 additions & 17 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import * as path from 'path';
import { commands, ExtensionContext, Position, Uri, window, workspace } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentIdentifier } from "vscode-languageclient";
import { commands, ExtensionContext, OpenDialogOptions, Position, Uri, window, workspace, WorkspaceEdit } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentIdentifier, TextDocumentEdit } from "vscode-languageclient";
import { LanguageClient } from 'vscode-languageclient/node';
import { markdownPreviewProvider } from "../markdownPreviewProvider";
import { CommandConstants } from "./commandConstants";
import { ClientCommandConstants, ServerCommandConstants } from "./commandConstants";

/**
* Register the commands for vscode-xml
*
* @param context the extension context
*/
export async function registerCommands(context: ExtensionContext, languageClient: LanguageClient) {
export function registerCommands(context: ExtensionContext, languageClient: LanguageClient) {

registerDocsCommands(context);
registerCodeLensCommands(context, languageClient);
registerValidationCommands(context);
registerAssociateGrammarCommands(context, languageClient);

// Register client command to execute custom XML Language Server command
context.subscriptions.push(commands.registerCommand(CommandConstants.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
let token: CancellationToken;
let commandArgs: any[] = rest;
if (rest && rest.length && CancellationToken.is(rest[rest.length - 1])) {
Expand All @@ -36,7 +37,7 @@ export async function registerCommands(context: ExtensionContext, languageClient
}));

// Register command that open settings to a given setting
context.subscriptions.push(commands.registerCommand(CommandConstants.OPEN_SETTINGS, async (settingId?: string) => {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.OPEN_SETTINGS, async (settingId?: string) => {
commands.executeCommand('workbench.action.openSettings', settingId);
}));
}
Expand All @@ -46,15 +47,15 @@ export async function registerCommands(context: ExtensionContext, languageClient
*
* @param context the extension context
*/
async function registerDocsCommands(context: ExtensionContext): Promise<void> {
function registerDocsCommands(context: ExtensionContext) {
context.subscriptions.push(markdownPreviewProvider);
context.subscriptions.push(commands.registerCommand(CommandConstants.OPEN_DOCS_HOME, async () => {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.OPEN_DOCS_HOME, async () => {
const uri = 'README.md';
const title = 'XML Documentation';
const sectionId = '';
markdownPreviewProvider.show(context.asAbsolutePath(path.join('docs', uri)), title, sectionId, context);
}));
context.subscriptions.push(commands.registerCommand(CommandConstants.OPEN_DOCS, async (params: { page: string, section: string }) => {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.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 sectionId = params.section || '';
Expand All @@ -69,14 +70,14 @@ async function registerDocsCommands(context: ExtensionContext): Promise<void> {
* @param context the extension context
* @param languageClient the language server client
*/
async function registerCodeLensCommands(context: ExtensionContext, languageClient: LanguageClient): Promise<void> {
context.subscriptions.push(commands.registerCommand(CommandConstants.SHOW_REFERENCES, (uriString: string, position: Position) => {
function registerCodeLensCommands(context: ExtensionContext, languageClient: LanguageClient) {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.SHOW_REFERENCES, (uriString: string, position: Position) => {
const uri = Uri.parse(uriString);
workspace.openTextDocument(uri).then(document => {
// Consume references service from the XML Language Server
let param = languageClient.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
languageClient.sendRequest(ReferencesRequest.type, param).then(locations => {
commands.executeCommand(CommandConstants.EDITOR_SHOW_REFERENCES, uri, languageClient.protocol2CodeConverter.asPosition(position), locations.map(languageClient.protocol2CodeConverter.asLocation));
commands.executeCommand(ClientCommandConstants.EDITOR_SHOW_REFERENCES, uri, languageClient.protocol2CodeConverter.asPosition(position), locations.map(languageClient.protocol2CodeConverter.asLocation));
})
})
}));
Expand All @@ -87,25 +88,66 @@ async function registerCodeLensCommands(context: ExtensionContext, languageClien
*
* @param context the extension context
*/
async function registerValidationCommands(context: ExtensionContext): Promise<void> {
function registerValidationCommands(context: ExtensionContext) {
// Revalidate current file
context.subscriptions.push(commands.registerCommand(CommandConstants.VALIDATE_CURRENT_FILE, async (params) => {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.VALIDATE_CURRENT_FILE, async (params) => {
const uri = window.activeTextEditor.document.uri;
const identifier = TextDocumentIdentifier.create(uri.toString());
commands.executeCommand(CommandConstants.EXECUTE_WORKSPACE_COMMAND, CommandConstants.VALIDATE_CURRENT_FILE, identifier).
commands.executeCommand(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, ServerCommandConstants.VALIDATE_CURRENT_FILE, identifier).
then(() => {
window.showInformationMessage('The current XML file was successfully validated.');
}, error => {
window.showErrorMessage('Error during XML validation ' + error.message);
});
}));
// Revalidate all open files
context.subscriptions.push(commands.registerCommand(CommandConstants.VALIDATE_ALL_FILES, async () => {
commands.executeCommand(CommandConstants.EXECUTE_WORKSPACE_COMMAND, CommandConstants.VALIDATE_ALL_FILES).
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.VALIDATE_ALL_FILES, async () => {
commands.executeCommand(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, ServerCommandConstants.VALIDATE_ALL_FILES).
then(() => {
window.showInformationMessage('All open XML files were successfully validated.');
}, error => {
window.showErrorMessage('Error during XML validation: ' + error.message);
});
}));
}

/**
* Register commands used for associating grammar file (XSD,DTD) to a given XML file
*
* @param context the extension context
*/
function registerAssociateGrammarCommands(context: ExtensionContext, languageClient: LanguageClient) {
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.SELECT_FILE, async (uriString: string, bindingType: string) => {
// A click on Bind with... has been processed in the XML document which is not bound to a grammar
const documentURI = Uri.parse(uriString);

// 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(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, 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);
};
}
}));

}
4 changes: 2 additions & 2 deletions src/markdownPreviewProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Disposable, WebviewPanel, window, ViewColumn, commands, Uri, Webview, ExtensionContext, env } from "vscode";
import * as fse from 'fs-extra';
import * as path from 'path';
import { CommandConstants } from "./commands/commandConstants";
import { ClientCommandConstants } from "./commands/commandConstants";

class MarkdownPreviewProvider implements Disposable {
private panel: WebviewPanel | undefined;
Expand Down Expand Up @@ -57,7 +57,7 @@ class MarkdownPreviewProvider implements Disposable {
(_match: string, linkText: string, page: string, section: string) => {
return `<a href="command:xml.open.docs?%5B%7B%22page%22%3A%22${page}%22%2C%22section%22%3A%22${section}%22%7D%5D">${linkText}</a>`
});
body = await commands.executeCommand(CommandConstants.MARKDOWN_API_RENDER, markdownString);
body = await commands.executeCommand(ClientCommandConstants.MARKDOWN_API_RENDER, markdownString);
this.documentCache.set(markdownFilePath, body);
}
return `
Expand Down
4 changes: 2 additions & 2 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as vscode from 'vscode';
import * as path from 'path';
import { CommandConstants } from './commands/commandConstants';
import { ClientCommandConstants } from './commands/commandConstants';
const glob = require('glob');

let existingExtensions: Array<string>;
Expand Down Expand Up @@ -49,7 +49,7 @@ export function onExtensionChange(extensions: readonly vscode.Extension<any>[],
if (hasChanged) {
const msg = `Extensions to the XML Language Server changed, reloading ${vscode.env.appName} is required for the changes to take effect.`;
const action = 'Reload';
const restartId = CommandConstants.RELOAD_WINDOW;
const restartId = ClientCommandConstants.RELOAD_WINDOW;
vscode.window.showWarningMessage(msg, action).then((selection) => {
if (action === selection) {
vscode.commands.executeCommand(restartId);
Expand Down

0 comments on commit 48da324

Please sign in to comment.