Skip to content

Commit 20ee685

Browse files
angelozerrdatho7561
authored andcommitted
Contribute to vscode-xml with a custom language
Fixes #589 Signed-off-by: azerr <azerr@redhat.com>
1 parent 8270daa commit 20ee685

4 files changed

Lines changed: 144 additions & 30 deletions

File tree

docs/Extensions.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
# Extensions
22

3-
## Custom XML Extensions
3+
## Contribution in package.json
4+
5+
### Custom language
6+
7+
If your vscode extension defines a custom language `myxml` in package.json:
8+
9+
```json
10+
"contributes": {
11+
"languages": [
12+
{
13+
"id": "myxml",
14+
"extensions": [
15+
".myxml"
16+
]
17+
}
18+
]
19+
```
20+
21+
and you want that `myxml` language uses [vscode-xml](https://github.com/redhat-developer/vscode-xml) language server, you need to declare it with `xmlLanguageParticipants`:
22+
23+
```json
24+
"contributes": {
25+
"xmlLanguageParticipants": [
26+
{
27+
"languageId": "myxml"
28+
}
29+
]
30+
}
31+
```
32+
33+
## Custom XML Extensions (written in Java)
434

535
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.
636
vscode-xml provides the ability to use your custom XML support provider, by adding external jars to the XML language server's classpath.

schemas/package.schema.json

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
{
2-
"$schema": "http://json-schema.org/draft-04/schema#",
3-
"title": "XML Language server contributions to package.json",
4-
"type": "object",
5-
"properties": {
6-
"contributes": {
7-
"type": "object",
8-
"properties": {
9-
"xml.javaExtensions": {
10-
"type": "array",
11-
"markdownDescription": "XML language server extensions",
12-
"items": {
13-
"type": "string",
14-
"description": "Relative path to a XML language server extension JAR file"
15-
}
16-
}
17-
}
18-
}
19-
}
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "XML Language server contributions to package.json",
4+
"type": "object",
5+
"properties": {
6+
"contributes": {
7+
"type": "object",
8+
"properties": {
9+
"xml.javaExtensions": {
10+
"type": "array",
11+
"markdownDescription": "XML language server extensions",
12+
"items": {
13+
"type": "string",
14+
"description": "Relative path to a XML language server extension JAR file"
15+
}
16+
},
17+
"xmlLanguageParticipants": {
18+
"type": "array",
19+
"description": "A list of languages that participate with the XML language server.",
20+
"items": {
21+
"type": "object",
22+
"properties": {
23+
"languageId": {
24+
"type": "string",
25+
"description": "The id of the language that participates with XML language server."
26+
}
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
2033
}

src/client/languageParticipants.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { DocumentSelector } from 'vscode-languageclient';
7+
import { Event, EventEmitter, extensions } from 'vscode';
8+
9+
/**
10+
* XML language participant contribution.
11+
*/
12+
interface LanguageParticipantContribution {
13+
/**
14+
* The id of the language which participates with the XML language server.
15+
*/
16+
languageId: string;
17+
}
18+
19+
export interface LanguageParticipants {
20+
readonly onDidChange: Event<void>;
21+
readonly documentSelector: DocumentSelector;
22+
hasLanguage(languageId: string): boolean;
23+
dispose(): void;
24+
}
25+
26+
export function getLanguageParticipants(): LanguageParticipants {
27+
const onDidChangeEmmiter = new EventEmitter<void>();
28+
let languages = new Set<string>();
29+
30+
function update() {
31+
const oldLanguages = languages;
32+
33+
languages = new Set();
34+
languages.add('xml');
35+
languages.add('xsl');
36+
languages.add('dtd');
37+
languages.add('svg');
38+
39+
for (const extension of extensions.all /*extensions.allAcrossExtensionHosts*/) {
40+
const xmlLanguageParticipants = extension.packageJSON?.contributes?.xmlLanguageParticipants as LanguageParticipantContribution[];
41+
if (Array.isArray(xmlLanguageParticipants)) {
42+
for (const xmlLanguageParticipant of xmlLanguageParticipants) {
43+
const languageId = xmlLanguageParticipant.languageId;
44+
if (typeof languageId === 'string') {
45+
languages.add(languageId);
46+
}
47+
}
48+
}
49+
}
50+
return !isEqualSet(languages, oldLanguages);
51+
}
52+
update();
53+
54+
const changeListener = extensions.onDidChange(_ => {
55+
if (update()) {
56+
onDidChangeEmmiter.fire();
57+
}
58+
});
59+
60+
return {
61+
onDidChange: onDidChangeEmmiter.event,
62+
get documentSelector() { return Array.from(languages); },
63+
hasLanguage(languageId: string) { return languages.has(languageId); },
64+
dispose: () => changeListener.dispose()
65+
};
66+
}
67+
68+
function isEqualSet<T>(s1: Set<T>, s2: Set<T>) {
69+
if (s1.size !== s2.size) {
70+
return false;
71+
}
72+
for (const e of s1) {
73+
if (!s2.has(e)) {
74+
return false;
75+
}
76+
}
77+
return true;
78+
}

src/client/xmlClient.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,11 @@ import { getXMLConfiguration, getXMLSettings, onConfigurationChange, subscribeJD
1212
import { containsVariableReferenceToCurrentFile } from '../settings/variableSubstitution';
1313
import * as Telemetry from '../telemetry';
1414
import { ClientErrorHandler } from './clientErrorHandler';
15+
import { getLanguageParticipants } from './languageParticipants';
1516
import { activateTagClosing, AutoCloseResult } from './tagClosing';
1617

17-
export const XML_SUPPORTED_LANGUAGE_IDS = ['xml', 'xsl', 'dtd', 'svg'];
18-
19-
const XML_DOCUMENT_SELECTOR: DocumentSelector =
20-
XML_SUPPORTED_LANGUAGE_IDS
21-
.map(langId => ({ scheme: 'file', language: langId }))
22-
.concat(
23-
XML_SUPPORTED_LANGUAGE_IDS
24-
.map(langId => ({ scheme: 'untitled', language: langId }))
25-
);
26-
18+
const languageParticipants = getLanguageParticipants();
19+
export const XML_SUPPORTED_LANGUAGE_IDS = languageParticipants.documentSelector;
2720

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

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

0 commit comments

Comments
 (0)