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 ![Highlight Corresponding XML](../images/Features/HighlightCorrespondingXML.gif) +## 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; + })); +} +