diff --git a/docs/Features.md b/docs/Features.md
index f59a0534..6eefb0ec 100644
--- a/docs/Features.md
+++ b/docs/Features.md
@@ -9,4 +9,5 @@
- [XInclude features](Features/XIncludeFeatures.md#xinclude-features)
- [XML Catalog features](Features/XMLCatalogFeatures.md#xml-catalog-features)
- [XML References features](Features/XMLReferencesFeatures.md#xml-references-features)
-- [XML Colors features](Features/XMLColorsFeatures.md#xml-colors-features)
\ No newline at end of file
+- [XML Colors features](Features/XMLColorsFeatures.md#xml-colors-features)
+- [XML Embedded Languages features](Features/XMLEmbeddedLanguagesFeatures.md#xml-embedded-languages-features)
\ No newline at end of file
diff --git a/docs/Features/XMLEmbeddedLanguagesFeatures.md b/docs/Features/XMLEmbeddedLanguagesFeatures.md
new file mode 100644
index 00000000..d0e7648c
--- /dev/null
+++ b/docs/Features/XMLEmbeddedLanguagesFeatures.md
@@ -0,0 +1,204 @@
+# XML Embedded Languages Features
+
+XML embedded languages support provides syntax highlighting for embedded language content (e.g., Groovy, SQL, JavaScript) inside XML text and CDATA nodes using TextMate grammars.
+
+This feature uses the `xml.embeddedLanguages` setting with XPath expressions to identify which XML nodes contain embedded code, and the `languageId` to specify which language grammar to use for tokenization.
+
+## How it works
+
+1. You define XPath expressions that match text/CDATA nodes containing embedded code
+2. You specify the `languageId` (e.g., `groovy`, `sql`, `javascript`)
+3. The TextMate grammar is automatically resolved from installed VS Code extensions
+4. LSP semantic tokens are used to provide syntax highlighting
+
+## Groovy in XML workflow files
+
+Given this XML workflow file with Groovy code inside CDATA sections:
+
+```xml
+
+
+
+
+
+
+```
+
+You can enable Groovy syntax highlighting in these CDATA sections with the following setting:
+
+```json
+"xml.embeddedLanguages": [
+ {
+ "pattern": "**/workflow.xml",
+ "expressions": [
+ {
+ "xpath": "//scriptsInclude/text()",
+ "languageId": "groovy"
+ },
+ {
+ "xpath": "//handlerScript/text()",
+ "languageId": "groovy"
+ }
+ ]
+ }
+]
+```
+
+## SQL in MyBatis mapper files
+
+Given a MyBatis mapper XML file with SQL inside text nodes:
+
+```xml
+
+
+
+ INSERT INTO users (name, email)
+ VALUES (#{name}, #{email})
+
+
+```
+
+You can enable SQL syntax highlighting with:
+
+```json
+"xml.embeddedLanguages": [
+ {
+ "pattern": "**/*Mapper.xml",
+ "expressions": [
+ {
+ "xpath": "mapper/select/text()",
+ "languageId": "sql"
+ },
+ {
+ "xpath": "mapper/insert/text()",
+ "languageId": "sql"
+ },
+ {
+ "xpath": "mapper/update/text()",
+ "languageId": "sql"
+ },
+ {
+ "xpath": "mapper/delete/text()",
+ "languageId": "sql"
+ }
+ ]
+ }
+]
+```
+
+## JavaScript and CSS in XHTML
+
+Given an XHTML file with embedded JavaScript and CSS:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+You can enable syntax highlighting for both languages:
+
+```json
+"xml.embeddedLanguages": [
+ {
+ "pattern": "**/*.xhtml",
+ "expressions": [
+ {
+ "xpath": "//script/text()",
+ "languageId": "javascript"
+ },
+ {
+ "xpath": "//style/text()",
+ "languageId": "css"
+ }
+ ]
+ }
+]
+```
+
+## Using Document Matcher
+
+Like `xml.colors` and `xml.references`, the `xml.embeddedLanguages` setting supports [Document Matcher](../DocumentMatcher.md) criteria for more precise document matching beyond file patterns:
+
+```json
+"xml.embeddedLanguages": [
+ {
+ "pattern": "**/*.xml",
+ "rootElement": ["workflow"],
+ "expressions": [
+ {
+ "xpath": "//handlerScript/text()",
+ "languageId": "groovy"
+ }
+ ]
+ }
+]
+```
+
+Available Document Matcher properties:
+
+| Property | Description |
+|---|---|
+| `pattern` | File name pattern (glob syntax) |
+| `namespaceURI` | Root element namespace URI patterns |
+| `rootElement` | Root element name patterns |
+| `publicId` | DOCTYPE public ID patterns |
+| `systemId` | DOCTYPE system ID patterns |
+| `grammarURI` | Resolved grammar URI patterns |
+
+## Custom TextMate grammar
+
+By default, the TextMate grammar is automatically resolved from installed VS Code extensions based on the `languageId`. If a language is not available as a VS Code extension, you can specify the grammar path manually:
+
+```json
+"xml.embeddedLanguages": [
+ {
+ "pattern": "**/*.xml",
+ "expressions": [
+ {
+ "xpath": "//code/text()",
+ "languageId": "mylang",
+ "grammarPath": "/path/to/mylang.tmLanguage.json"
+ }
+ ]
+ }
+]
+```
+
+## Supported token types
+
+The following LSP semantic token types are used for embedded language highlighting:
+
+| Token Type | TextMate Scope |
+|---|---|
+| Comment | `comment.*` |
+| Keyword | `keyword.*`, `storage.type`, `storage.modifier` |
+| String | `string.*` |
+| Number | `constant.numeric` |
+| Function | `entity.name.function`, `support.function` |
+| Class | `entity.name.type`, `entity.name.class`, `support.class` |
+| Variable | `variable.*`, `constant.character`, `constant.language` |
+| Operator | `keyword.operator`, `punctuation.*` |
+| Namespace | `entity.name.namespace`, `entity.name.module` |
+| Parameter | `variable.parameter` |
+| Regexp | `string.regexp` |
diff --git a/docs/Features/XMLFeatures.md b/docs/Features/XMLFeatures.md
index 2fc21848..23c3f453 100644
--- a/docs/Features/XMLFeatures.md
+++ b/docs/Features/XMLFeatures.md
@@ -58,6 +58,22 @@ When placing the cursor on an XML tag, the corresponding opening/closing tag wil

+## Semantic Tokens
+
+LemMinX provides LSP semantic tokens for XML documents to enhance syntax highlighting beyond what TextMate grammars offer. Semantic tokens provide precise, context-aware highlighting for:
+
+| Token Type | Description |
+|---|---|
+| Property | Attribute names (e.g., `id`, `name`) |
+| Variable | Entity references (e.g., `&`) |
+| Type | Element and notation names |
+
+Semantic tokens work alongside the TextMate grammar-based highlighting and provide additional semantic information that the editor theme can use for more accurate coloring.
+
+### Embedded Language Highlighting
+
+LemMinX also supports syntax highlighting for embedded language content inside XML text and CDATA nodes (e.g., Groovy, SQL, JavaScript) using TextMate grammars. See [XML Embedded Languages features](XMLEmbeddedLanguagesFeatures.md#xml-embedded-languages-features) for more information.
+
## Symbols from Outline and Breadcrumbs
The `OUTLINE` dropdown under `EXPLORER` details the XML tags in the document and their children. The `Breadcrumbs` bar also shows the hierarchy of the tag that the cursor is on. Both features permit jumping to the tag(s) displayed.
diff --git a/gulpfile.js b/gulpfile.js
index c6a019e6..b4b0887b 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -8,14 +8,14 @@ const server_dir = '../lemminx';
gulp.task('build_server', function (done) {
cp.execSync(mvnw() + " clean verify -DskipTests", { cwd: server_dir, stdio: [0, 1, 2] });
- gulp.src(server_dir + '/org.eclipse.lemminx/target/org.eclipse.lemminx-uber.jar', { encoding: false })
+ gulp.src(server_dir + '/org.eclipse.lemminx.uber/target/org.eclipse.lemminx-uber.jar', { encoding: false })
.pipe(gulp.dest('./server'));
done();
});
gulp.task('build_server_with_binary', function (done) {
cp.execSync(mvnw() + " clean verify -DskipTests -Dnative", { cwd: server_dir, stdio: [0, 1, 2] });
- gulp.src([server_dir + '/org.eclipse.lemminx/target/org.eclipse.lemminx-uber.jar', server_dir + '/org.eclipse.lemminx/target/lemminx-*'], { encoding: false })
+ gulp.src([server_dir + '/org.eclipse.lemminx.uber/target/org.eclipse.lemminx-uber.jar', server_dir + '/org.eclipse.lemminx/target/lemminx-*'], { encoding: false })
.pipe(gulp.dest('./server'))
.on('end', function () {
glob.Glob('./server/lemminx-*.txt', (_er, txtfiles) => {
diff --git a/package.json b/package.json
index fc08d0f4..0fe211b3 100644
--- a/package.json
+++ b/package.json
@@ -1072,6 +1072,70 @@
"markdownDescription": "Allows colors for the given file name patterns or [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22documentmatcher%22%7D%5D) criteria. See [here](command:xml.open.docs?%5B%7B%22page%22%3A%22Features/XMLColorsFeatures%22%2C%22section%22%3A%22xmlcolorsfeatures%22%7D%5D) for more information.",
"scope": "window"
},
+ "xml.embeddedLanguages": {
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "object",
+ "properties": {
+ "pattern": {
+ "type": "string",
+ "markdownDescription": "Matches the files that embedded language expressions declared with `expressions` applies to.\n\nMore information on the glob syntax: https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22documentmatcher%22%7D%5D) for more information."
+ },
+ "namespaceURI": {
+ "type": "array",
+ "items": { "type": "string" },
+ "markdownDescription": "Root element namespace URI patterns (glob supported).\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22namespaceuri%22%7D%5D) for more information."
+ },
+ "rootElement": {
+ "type": "array",
+ "items": { "type": "string" },
+ "markdownDescription": "Root element name patterns (glob supported). Example: `mapper` matches ``. Uses local name (without prefix).\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22rootelement%22%7D%5D) for more information."
+ },
+ "publicId": {
+ "type": "array",
+ "items": { "type": "string" },
+ "markdownDescription": "DOCTYPE public ID patterns (glob supported).\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22publicid%22%7D%5D) for more information."
+ },
+ "systemId": {
+ "type": "array",
+ "items": { "type": "string" },
+ "markdownDescription": "DOCTYPE system ID patterns (glob supported).\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22systemid%22%7D%5D) for more information."
+ },
+ "grammarURI": {
+ "type": "array",
+ "items": { "type": "string" },
+ "markdownDescription": "Resolved grammar URI patterns (glob supported).\n\nSee [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22grammaruri%22%7D%5D) for more information."
+ },
+ "expressions": {
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "object",
+ "properties": {
+ "xpath": {
+ "type": "string",
+ "description": "The XPath expression to match text/CDATA nodes containing embedded language content (e.g., select/text(), //![CDATA[]])."
+ },
+ "languageId": {
+ "type": "string",
+ "description": "The language identifier of the embedded content (e.g., java, sql, javascript). Used to resolve the TextMate grammar from installed VS Code extensions."
+ }
+ },
+ "required": [
+ "xpath",
+ "languageId"
+ ]
+ }
+ }
+ },
+ "required": [
+ "expressions"
+ ]
+ },
+ "markdownDescription": "Allows embedded language syntax highlighting for the given file name patterns or [Document Matcher](command:xml.open.docs?%5B%7B%22page%22%3A%22DocumentMatcher%22%2C%22section%22%3A%22documentmatcher%22%7D%5D) criteria. Uses TextMate grammars from installed VS Code extensions to tokenize embedded content in XML text/CDATA nodes.\n\nExample:\n```json\n[\n {\n \"pattern\": \"**/*.mybatis.xml\",\n \"expressions\": [\n {\n \"xpath\": \"select/text()\",\n \"languageId\": \"sql\"\n }\n ]\n }\n]\n```",
+ "scope": "window"
+ },
"xml.filePathSupport.mappings": {
"type": "array",
"default": [],
diff --git a/src/commands/clientCommandConstants.ts b/src/commands/clientCommandConstants.ts
index 427e1454..48c03ba3 100644
--- a/src/commands/clientCommandConstants.ts
+++ b/src/commands/clientCommandConstants.ts
@@ -86,4 +86,10 @@ export const EXECUTE_WORKSPACE_COMMAND = 'xml.workspace.executeCommand';
/**
* VSCode client command to generate XML from a grammar file.
*/
-export const GENERATE_XML_FROM_GRAMMAR = 'xml.generate.fromGrammar';
\ No newline at end of file
+export const GENERATE_XML_FROM_GRAMMAR = 'xml.generate.fromGrammar';
+
+/**
+ * Resolve a TextMate grammar path for a given language ID.
+ * Called by the XML language server to find grammars from installed VS Code extensions.
+ */
+export const TEXT_MATE_GRAMMAR = 'xml.textMateGrammar';
\ No newline at end of file
diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts
index 794304d0..c65d0759 100644
--- a/src/commands/registerCommands.ts
+++ b/src/commands/registerCommands.ts
@@ -38,6 +38,7 @@ export async function registerClientServerCommands(context: ExtensionContext, la
registerGenerateXMLCommands(context, languageClient);
registerRestartLanguageServerCommand(context, languageClient);
registerConfigurationUpdateCommand();
+ registerTextMateGrammarCommand(context);
// Register client command to execute custom XML Language Server command
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND, (command, ...rest) => {
@@ -702,3 +703,25 @@ async function generateXMLFromGrammarCommand(grammarFileUri: Uri | undefined, co
}
}
+/**
+ * Register the command to resolve TextMate grammar paths for embedded languages.
+ * Called by the XML language server via xml/executeClientCommand.
+ */
+function registerTextMateGrammarCommand(context: ExtensionContext) {
+ context.subscriptions.push(commands.registerCommand(ClientCommandConstants.TEXT_MATE_GRAMMAR, (languageId: string) => {
+ for (const ext of vscode.extensions.all) {
+ const grammars = ext.packageJSON?.contributes?.grammars;
+ if (!Array.isArray(grammars)) {
+ continue;
+ }
+ for (const grammar of grammars) {
+ if (grammar.language === languageId && grammar.path) {
+ const grammarPath = path.join(ext.extensionPath, grammar.path);
+ return { grammarPath: grammarPath, scopeName: grammar.scopeName || '' };
+ }
+ }
+ }
+ return null;
+ }));
+}
+