Skip to content

Commit

Permalink
Contribute to vscode-xml with a custom language
Browse files Browse the repository at this point in the history
Fixes redhat-developer#589

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Dec 14, 2022
1 parent 8270daa commit d6371e3
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 30 deletions.
32 changes: 31 additions & 1 deletion docs/Extensions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
# Extensions

## Custom XML Extensions
## Contribution in package.json

### Custom language

If your vscode extension defines a custom language `myxml` in package.json:

```json
"contributes": {
"languages": [
{
"id": "myxml",
"extensions": [
".myxml"
]
}
]
```

and you want that `myxml` language contributesto vscode XML language server, you need to declare it with `xmlLanguageParticipants`:

```json
"contributes": {
"xmlLanguageParticipants": [
{
"languageId": "myxml"
}
]
}
```

## Custom XML Extensions (written in Java)

The [LemMinX - XML Language Server](https://github.com/eclipse/lemminx) can be extended to support custom completion, hover, validation, rename, etc by using the [Java Service Provider Interface (SPI)](https://www.baeldung.com/java-spi) mechanism.
vscode-xml provides the ability to use your custom XML support provider, by adding external jars to the XML language server's classpath.
Expand Down
49 changes: 31 additions & 18 deletions schemas/package.schema.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "XML Language server contributions to package.json",
"type": "object",
"properties": {
"contributes": {
"type": "object",
"properties": {
"xml.javaExtensions": {
"type": "array",
"markdownDescription": "XML language server extensions",
"items": {
"type": "string",
"description": "Relative path to a XML language server extension JAR file"
}
}
}
}
}
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "XML Language server contributions to package.json",
"type": "object",
"properties": {
"contributes": {
"type": "object",
"properties": {
"xml.javaExtensions": {
"type": "array",
"markdownDescription": "XML language server extensions",
"items": {
"type": "string",
"description": "Relative path to a XML language server extension JAR file"
}
},
"xmlLanguageParticipants": {
"type": "array",
"description": "A list of languages that participate with the XML language server.",
"items": {
"type": "object",
"properties": {
"languageId": {
"type": "string",
"description": "The id of the language that participates with XML language server."
}
}
}
}
}
}
}
}
78 changes: 78 additions & 0 deletions src/client/languageParticipants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DocumentSelector } from 'vscode-languageclient';
import { Event, EventEmitter, extensions } from 'vscode';

/**
* XML language participant contribution.
*/
interface LanguageParticipantContribution {
/**
* The id of the language which participates with the XML language server.
*/
languageId: string;
}

export interface LanguageParticipants {
readonly onDidChange: Event<void>;
readonly documentSelector: DocumentSelector;
hasLanguage(languageId: string): boolean;
dispose(): void;
}

export function getLanguageParticipants(): LanguageParticipants {
const onDidChangeEmmiter = new EventEmitter<void>();
let languages = new Set<string>();

function update() {
const oldLanguages = languages;

languages = new Set();
languages.add('xml');
languages.add('xsl');
languages.add('dtd');
languages.add('svg');

for (const extension of extensions.all /*extensions.allAcrossExtensionHosts*/) {
const xmlLanguageParticipants = extension.packageJSON?.contributes?.xmlLanguageParticipants as LanguageParticipantContribution[];
if (Array.isArray(xmlLanguageParticipants)) {
for (const xmlLanguageParticipant of xmlLanguageParticipants) {
const languageId = xmlLanguageParticipant.languageId;
if (typeof languageId === 'string') {
languages.add(languageId);
}
}
}
}
return !isEqualSet(languages, oldLanguages);
}
update();

const changeListener = extensions.onDidChange(_ => {
if (update()) {
onDidChangeEmmiter.fire();
}
});

return {
onDidChange: onDidChangeEmmiter.event,
get documentSelector() { return Array.from(languages); },
hasLanguage(languageId: string) { return languages.has(languageId); },
dispose: () => changeListener.dispose()
};
}

function isEqualSet<T>(s1: Set<T>, s2: Set<T>) {
if (s1.size !== s2.size) {
return false;
}
for (const e of s1) {
if (!s2.has(e)) {
return false;
}
}
return true;
}
15 changes: 4 additions & 11 deletions src/client/xmlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,11 @@ import { getXMLConfiguration, getXMLSettings, onConfigurationChange, subscribeJD
import { containsVariableReferenceToCurrentFile } from '../settings/variableSubstitution';
import * as Telemetry from '../telemetry';
import { ClientErrorHandler } from './clientErrorHandler';
import { getLanguageParticipants } from './languageParticipants';
import { activateTagClosing, AutoCloseResult } from './tagClosing';

export const XML_SUPPORTED_LANGUAGE_IDS = ['xml', 'xsl', 'dtd', 'svg'];

const XML_DOCUMENT_SELECTOR: DocumentSelector =
XML_SUPPORTED_LANGUAGE_IDS
.map(langId => ({ scheme: 'file', language: langId }))
.concat(
XML_SUPPORTED_LANGUAGE_IDS
.map(langId => ({ scheme: 'untitled', language: langId }))
);

const languageParticipants = getLanguageParticipants();
export const XML_SUPPORTED_LANGUAGE_IDS = languageParticipants.documentSelector;

const ExecuteClientCommandRequest: RequestType<ExecuteCommandParams, any, void> = new RequestType('xml/executeClientCommand');

Expand Down Expand Up @@ -136,7 +129,7 @@ function getLanguageClientOptions(
context: ExtensionContext): LanguageClientOptions {
return {
// Register the server for xml, xsl, dtd, svg
documentSelector: XML_DOCUMENT_SELECTOR,
documentSelector: XML_SUPPORTED_LANGUAGE_IDS,
revealOutputChannelOn: RevealOutputChannelOn.Never,
//wrap with key 'settings' so it can be handled same a DidChangeConfiguration
initializationOptions: {
Expand Down

0 comments on commit d6371e3

Please sign in to comment.