From 2f0aae2d552f70ad1d732deef86a49cef9011d09 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Mon, 22 Jun 2026 20:49:28 +0200 Subject: [PATCH 1/9] fix: start not waiting for file association prompt Signed-off-by: squidfunk --- integrations/code/src/extension.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/code/src/extension.ts b/integrations/code/src/extension.ts index 5e3eeb2..7bdc9d9 100644 --- a/integrations/code/src/extension.ts +++ b/integrations/code/src/extension.ts @@ -54,9 +54,6 @@ export async function activate(extension: ExtensionContext): Promise { return; } - // Prompt user to associate Markdown files with Zensical Studio - await promptFileAssociation(context); - // Register commands registerCommands(extension); @@ -69,6 +66,9 @@ export async function activate(extension: ExtensionContext): Promise { const message = error instanceof Error ? error.message : String(error); context.log(`Failed to start Zensical Studio: ${message}`); } + + // Prompt user to associate Markdown files with Zensical Studio + void promptFileAssociation(context); } /** From 84bed8840179faffdc1ec0076eed13d23f23578d Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 12:01:23 +0200 Subject: [PATCH 2/9] feature: add highlighting for snippets Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 127 ++ .../snapshots/preprocess/snippet.scopes.json | 1095 +++++++++++++++++ .../test/specs/preprocess/snippet.md | 137 +++ .../syntaxes/python-markdown.tmLanguage.json | 308 +++++ 4 files changed, 1667 insertions(+) create mode 100644 grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/preprocess/snippet.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index 152e3f3..1388589 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -26,6 +26,7 @@ scopeName: text.html.markdown.python patterns: - include: "#front_matter" + - include: "#snippet" - include: "#flow" repository: @@ -62,6 +63,132 @@ repository: - include: "#block_quote" - include: "#paragraph" + snippet: + patterns: + - name: meta.snippet.section.markdown + match: >- + (?:^|\G)(.*?)(?- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(")((?:\\"|[^"\r\n])+?)((?::-?[0-9]*){1,2}(?:(?:,(?=[-0-9:])-?[0-9]*)(?::-?[0-9]*)?)*)(")$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.double.snippet.markdown } + 6: { name: constant.numeric.snippet-selector.line.markdown } + 7: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.inline.markdown + match: >- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(")((?:\\"|[^"\r\n])+?)(:[a-z][-_0-9a-z]*)(")$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.double.snippet.markdown } + 6: { name: entity.name.section.snippet-selector.markdown } + 7: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.inline.markdown + match: >- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(")((?:\\"|[^"\r\n])+?)(")$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.double.snippet.markdown } + 6: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.inline.markdown + match: >- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)((?::-?[0-9]*){1,2}(?:(?:,(?=[-0-9:])-?[0-9]*)(?::-?[0-9]*)?)*)(')$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.single.snippet.markdown } + 6: { name: constant.numeric.snippet-selector.line.markdown } + 7: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.inline.markdown + match: >- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)(:[a-z][-_0-9a-z]*)(')$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.single.snippet.markdown } + 6: { name: entity.name.section.snippet-selector.markdown } + 7: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.inline.markdown + match: >- + (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)(')$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + 3: { name: comment.snippet.separator.markdown } + 4: { name: punctuation.definition.string.begin.snippet.markdown } + 5: { name: string.quoted.single.snippet.markdown } + 6: { name: punctuation.definition.string.end.snippet.markdown } + - name: meta.snippet.block.markdown + begin: (?:^|\G)([ \t]*)(-{1,}8<-{1,})(?![ \t])$ + beginCaptures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + end: (?:^|\G)([ \t]*)(-{1,}8<-{1,})(?![ \t])$ + endCaptures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.snippet.markdown } + patterns: + - match: (?:^|\G)([ \t]*)(;[^\r\n]*?)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: comment.line.snippet.markdown } + - match: >- + (?:^|\G)([ \t]*)(?=[^;\s\r\n])(.*?)(:[-0-9:,]+)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: string.unquoted.snippet.markdown } + 3: { name: constant.numeric.snippet-selector.line.markdown } + 4: { name: punctuation.whitespace.trailing.markdown } + - match: >- + (?:^|\G)([ \t]*)(?=[^;\s\r\n])(.*?)(:[a-z][-_0-9a-z]*)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: string.unquoted.snippet.markdown } + 3: { name: entity.name.section.snippet-selector.markdown } + 4: { name: punctuation.whitespace.trailing.markdown } + - match: (?:^|\G)([ \t]*)(?=[^;\s\r\n])([^\r\n]*?)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: string.unquoted.snippet.markdown } + 3: { name: punctuation.whitespace.trailing.markdown } + - match: (?:^|\G)([ \t]+)$ + captures: + 1: { name: punctuation.whitespace.markdown } + table: patterns: - name: markup.table.markdown diff --git a/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json new file mode 100644 index 0000000..1892664 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json @@ -0,0 +1,1095 @@ +{ + "inline": { + "accept_double_quoted": [ + { + "line": "--8<-- \"index.md\"", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.begin.snippet.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "string.quoted.double.snippet.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.end.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_single_quoted": [ + { + "line": "---8<--- 'index.md'", + "tokens": [ + { + "text": "---8<---", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "'", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.begin.snippet.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "string.quoted.single.snippet.markdown" + ] + }, + { + "text": "'", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.end.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_indented": [ + { + "line": " --8<-- \"index.md\"", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.begin.snippet.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "string.quoted.double.snippet.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.end.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_section_selector": [ + { + "line": "--8<-- \"index.md:example\"", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.begin.snippet.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "string.quoted.double.snippet.markdown" + ] + }, + { + "text": ":example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "entity.name.section.snippet-selector.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.end.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_line_selector": [ + { + "line": "--8<-- \"index.md:1:3,5:7\"", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.begin.snippet.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "string.quoted.double.snippet.markdown" + ] + }, + { + "text": ":1:3,5:7", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "constant.numeric.snippet-selector.line.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.inline.markdown", + "punctuation.definition.string.end.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_escaped": [ + { + "line": ";--8<-- \"index.md\"", + "tokens": [ + { + "text": ";--8<-- \"index.md\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_unquoted": [ + { + "line": "--8<-- index.md", + "tokens": [ + { + "text": "--8<-- index.md", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_trailing_space": [ + { + "line": "--8<-- \"index.md\" ", + "tokens": [ + { + "text": "--8<-- \"index.md\" ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "block": { + "accept_plain": [ + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "index.md", + "tokens": [ + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + } + ] + }, + { + "line": "about.md", + "tokens": [ + { + "text": "about.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + } + ] + }, + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_blank_lines": [ + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + }, + { + "line": " ", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "punctuation.whitespace.markdown" + ] + } + ] + }, + { + "line": "index.md", + "tokens": [ + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + } + ] + }, + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_selectors": [ + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "index.md:1:3", + "tokens": [ + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + }, + { + "text": ":1:3", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "constant.numeric.snippet-selector.line.markdown" + ] + } + ] + }, + { + "line": "about.md:section", + "tokens": [ + { + "text": "about.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + }, + { + "text": ":section", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "entity.name.section.snippet-selector.markdown" + ] + } + ] + }, + { + "line": "https://example.com/", + "tokens": [ + { + "text": "https://example.com/", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + } + ] + }, + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_line_selector_split": [ + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "snippet.md:1:3", + "tokens": [ + { + "text": "snippet.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + }, + { + "text": ":1:3", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "constant.numeric.snippet-selector.line.markdown" + ] + } + ] + }, + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_indented": [ + { + "line": " --8<--", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": " index.md ", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "punctuation.whitespace.trailing.markdown" + ] + } + ] + }, + { + "line": " --8<--", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_comment_line": [ + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "; comment", + "tokens": [ + { + "text": "; comment", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "comment.line.snippet.markdown" + ] + } + ] + }, + { + "line": "index.md", + "tokens": [ + { + "text": "index.md", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "string.unquoted.snippet.markdown" + ] + } + ] + }, + { + "line": "--8<--", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.block.markdown", + "keyword.control.snippet.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "section": { + "accept_start": [ + { + "line": "--8<-- [start:example]", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.begin.markdown" + ] + }, + { + "text": "start", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.section.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.separator.snippet-section.markdown" + ] + }, + { + "text": "example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "entity.name.section.snippet.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_end": [ + { + "line": "--8<-- [end:example]", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.begin.markdown" + ] + }, + { + "text": "end", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.section.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.separator.snippet-section.markdown" + ] + }, + { + "text": "example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "entity.name.section.snippet.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_prefix_and_suffix": [ + { + "line": "prefix --8<-- [start:example] suffix", + "tokens": [ + { + "text": "prefix ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "meta.snippet.section.prefix.markdown" + ] + }, + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.begin.markdown" + ] + }, + { + "text": "start", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.section.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.separator.snippet-section.markdown" + ] + }, + { + "text": "example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "entity.name.section.snippet.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.end.markdown" + ] + }, + { + "text": " suffix", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "meta.snippet.section.suffix.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_uppercase_keyword": [ + { + "line": "--8<-- [START:example]", + "tokens": [ + { + "text": "--8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.begin.markdown" + ] + }, + { + "text": "START", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.section.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.separator.snippet-section.markdown" + ] + }, + { + "text": "example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "entity.name.section.snippet.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_escaped": [ + { + "line": ";--8<-- [start:example]", + "tokens": [ + { + "text": ";-", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "meta.snippet.section.prefix.markdown" + ] + }, + { + "text": "-8<--", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "comment.snippet.separator.markdown" + ] + }, + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.begin.markdown" + ] + }, + { + "text": "start", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "keyword.control.snippet.section.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.separator.snippet-section.markdown" + ] + }, + { + "text": "example", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "entity.name.section.snippet.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.snippet.section.markdown", + "punctuation.definition.snippet-section.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/specs/preprocess/snippet.md b/grammars/textmate/python-markdown/test/specs/preprocess/snippet.md new file mode 100644 index 0000000..9a7869e --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/preprocess/snippet.md @@ -0,0 +1,137 @@ +## inline + +### accept_double_quoted + +``` md +--8<-- "index.md" +``` + +### accept_single_quoted + +``` md +---8<--- 'index.md' +``` + +### accept_indented + +``` md + --8<-- "index.md" +``` + +### accept_section_selector + +``` md +--8<-- "index.md:example" +``` + +### accept_line_selector + +``` md +--8<-- "index.md:1:3,5:7" +``` + +### reject_escaped + +``` md +;--8<-- "index.md" +``` + +### reject_unquoted + +``` md +--8<-- index.md +``` + +### reject_trailing_space + +``` md +--8<-- "index.md" +``` + +## block + +### accept_plain + +``` md +--8<-- +index.md +about.md +--8<-- +``` + +### accept_blank_lines + +``` md +--8<-- + + +index.md +--8<-- +``` + +### accept_selectors + +``` md +--8<-- +index.md:1:3 +about.md:section +https://example.com/ +--8<-- +``` + +### accept_line_selector_split + +``` md +--8<-- +snippet.md:1:3 +--8<-- +``` + +### accept_indented + +``` md + --8<-- + index.md + --8<-- +``` + +### accept_comment_line + +``` md +--8<-- +; comment +index.md +--8<-- +``` + +## section + +### accept_start + +``` md +--8<-- [start:example] +``` + +### accept_end + +``` md +--8<-- [end:example] +``` + +### accept_with_prefix_and_suffix + +``` md +prefix --8<-- [start:example] suffix +``` + +### accept_uppercase_keyword + +``` md +--8<-- [START:example] +``` + +### reject_escaped + +``` md +;--8<-- [start:example] +``` diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index 4ae3691..c1ccb41 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -5,6 +5,9 @@ { "include": "#front_matter" }, + { + "include": "#snippet" + }, { "include": "#flow" } @@ -86,6 +89,311 @@ } ] }, + "snippet": { + "patterns": [ + { + "name": "meta.snippet.section.markdown", + "match": "(?:^|\\G)(.*?)(? Date: Tue, 23 Jun 2026 13:13:24 +0200 Subject: [PATCH 3/9] feature: activate Zensical Studio when `mkdocs.yml` or `zensical.toml` present Signed-off-by: squidfunk --- integrations/code/src/extension.ts | 8 +- .../code/src/extension/association.ts | 144 -------------- integrations/code/src/extension/project.ts | 176 ++++++++++++++++++ 3 files changed, 180 insertions(+), 148 deletions(-) delete mode 100644 integrations/code/src/extension/association.ts create mode 100644 integrations/code/src/extension/project.ts diff --git a/integrations/code/src/extension.ts b/integrations/code/src/extension.ts index 7bdc9d9..8e06e86 100644 --- a/integrations/code/src/extension.ts +++ b/integrations/code/src/extension.ts @@ -24,9 +24,9 @@ import type { ExtensionContext } from "vscode"; import type { LanguageClient } from "vscode-languageclient/node"; import { registerCommands } from "./commands"; -import { promptFileAssociation } from "./extension/association"; import { createLanguageClient } from "./extension/client"; import { Context } from "./extension/context"; +import { activateProjectMarkdown } from "./extension/project"; import { getStudio } from "./extension/studio"; /* ---------------------------------------------------------------------------- @@ -49,6 +49,9 @@ let client: LanguageClient | undefined; */ export async function activate(extension: ExtensionContext): Promise { const context = new Context(extension); + void activateProjectMarkdown(extension, context); + + // Obtain Zensical studio configuration const studio = await getStudio(context); if (typeof studio === "undefined") { return; @@ -66,9 +69,6 @@ export async function activate(extension: ExtensionContext): Promise { const message = error instanceof Error ? error.message : String(error); context.log(`Failed to start Zensical Studio: ${message}`); } - - // Prompt user to associate Markdown files with Zensical Studio - void promptFileAssociation(context); } /** diff --git a/integrations/code/src/extension/association.ts b/integrations/code/src/extension/association.ts deleted file mode 100644 index 9e5244b..0000000 --- a/integrations/code/src/extension/association.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2026 Zensical and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -import * as vscode from "vscode"; -import { basename } from "node:path"; - -import type { Context } from "./context"; - -/* ---------------------------------------------------------------------------- - * Types - * ------------------------------------------------------------------------- */ - -/** - * Project type. - */ -type Project = "MkDocs" | "Zensical"; - -/* ---------------------------------------------------------------------------- - * Functions - * ------------------------------------------------------------------------- */ - -/** - * Prompt to associate Markdown files with Python Markdown. - * - * @param context - Extension context - */ -export async function promptFileAssociation(context: Context): Promise { - const folders = vscode.workspace.workspaceFolders ?? []; - if (folders.length !== 1) { - return; - } - - // If we have a single workspace folder, check if it contains a project - const [folder] = folders; - const project = await findProject(folder); - if (typeof project === "undefined") { - return; - } - - // Check, if we already prompted the user for this workspace - const key = `association:${folder.uri.toString()}`; - if (context.getState(key) === true) { - return; - } - - // Check, if we already have an association for Markdown files to the - // Python Markdown grammar in this workspace, and if so, skip - const config = vscode.workspace.getConfiguration("files"); - const effective = config.get>("associations") ?? {}; - if (effective["*.md"] === "python-markdown") { - context.setState(key, true); - return; - } - - // Prompt the user to associate Markdown files with Python Markdown - const result = await vscode.window.showInformationMessage( - `Detected ${project} project. ` + - `Associate Markdown files in '${folder.name}' with Python Markdown?`, - "Yes", - "Not now", - ); - - // If the user dismissed the prompt, don't ask again - if (typeof result === "undefined") { - return; - } - - if (result !== "Yes") { - context.setState(key, true); - return; - } - - // Update file associations for this workspace - const associations = - config.inspect>("associations")?.workspaceValue ?? - {}; - try { - await config.update( - "associations", - { - ...associations, - "*.md": "python-markdown", - }, - vscode.ConfigurationTarget.Workspace, - ); - context.setState(key, true); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - context.log( - `Failed to update file associations for '${folder.name}': ${message}`, - ); - } -} - -/* ---------------------------------------------------------------------------- - * Helper functions - * ------------------------------------------------------------------------- */ - -/** - * Return the first matching project type in a workspace folder. - * - * @param folder - Workspace folder - * - * @returns Project type or nothing - */ -async function findProject( - folder: vscode.WorkspaceFolder, -): Promise { - const [match] = await vscode.workspace.findFiles( - new vscode.RelativePattern(folder, "**/{mkdocs.yml,zensical.toml}"), - null, - 1, - ); - - // Return the project type based on the matching file name - switch (basename(match?.fsPath ?? "")) { - case "zensical.toml": - return "Zensical"; - case "mkdocs.yml": - return "MkDocs"; - } - - // No matching project type found - return; -} diff --git a/integrations/code/src/extension/project.ts b/integrations/code/src/extension/project.ts new file mode 100644 index 0000000..438ac7d --- /dev/null +++ b/integrations/code/src/extension/project.ts @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2026 Zensical and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +import * as vscode from "vscode"; +import { basename, extname } from "node:path"; + +import type { Context } from "./context"; +import type { ExtensionContext } from "vscode"; + +/* ---------------------------------------------------------------------------- + * Types + * ------------------------------------------------------------------------- */ + +/** + * Project type. + */ +type Project = "MkDocs" | "Zensical"; + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Activate project-aware Markdown tagging. + * + * If a workspace folder contains an `mkdocs.yml` or `zensical.toml` file, open + * Markdown files in that folder are treated as Python Markdown for the current + * session. This makes it easier to work with Python Markdown in a project + * context without requiring explicit configuration in the workspace settings. + * + * @param context - Extension context + */ +export async function activateProjectMarkdown( + extension: ExtensionContext, + context: Context, +): Promise { + let projects = await findProjects(); + await tagOpenDocuments(projects); + + // Tag newly opened Markdown files in detected project folders + extension.subscriptions.push( + vscode.workspace.onDidOpenTextDocument((document) => { + void tagDocument(document, projects); + }), + ); + + // Re-scan project folders when the workspace shape changes + extension.subscriptions.push( + vscode.workspace.onDidChangeWorkspaceFolders(async () => { + projects = await findProjects(); + await tagOpenDocuments(projects); + }), + ); + + // Helper to refresh the list of detected project folders + const refresh = async (): Promise => { + projects = await findProjects(); + await tagOpenDocuments(projects); + }; + + // Create a file system watcher to detect changes to project config files + const watcher = vscode.workspace.createFileSystemWatcher( + "**/{mkdocs.yml,zensical.toml}", + ); + watcher.onDidCreate(() => void refresh()); + watcher.onDidDelete(() => void refresh()); + watcher.onDidChange(() => void refresh()); + extension.subscriptions.push(watcher); + + // Log detected project folders + context.log( + projects.size > 0 + ? `Detected ${projects.size} Python Markdown project folder(s)` + : "No Python Markdown project folders detected", + ); +} + +/* ---------------------------------------------------------------------------- + * Helper functions + * ------------------------------------------------------------------------- */ + +/** + * Find project folders in the current workspace. + * + * @returns Project folders + */ +async function findProjects(): Promise> { + const projects = new Map(); + for (const folder of vscode.workspace.workspaceFolders ?? []) { + const [match] = await vscode.workspace.findFiles( + new vscode.RelativePattern(folder, "**/{mkdocs.yml,zensical.toml}"), + null, + 1, + ); + + // Skip folders without a project config file + switch (basename(match?.fsPath ?? "")) { + case "zensical.toml": + projects.set(folder.uri.toString(), "Zensical"); + break; + case "mkdocs.yml": + projects.set(folder.uri.toString(), "MkDocs"); + break; + } + } + + // Return detected project folders + return projects; +} + +/** + * Tag all currently open Markdown documents inside detected project folders. + * + * @param projects - Project folders + */ +async function tagOpenDocuments(projects: Map): Promise { + for (const document of vscode.workspace.textDocuments) { + await tagDocument(document, projects); + } +} + +/** + * Tag a Markdown document as Python Markdown when appropriate. + * + * @param document - Text document + * @param projects - Project folders + */ +async function tagDocument( + document: vscode.TextDocument, + projects: Map, +): Promise { + if (document.languageId !== "markdown") { + return; + } + + // Skip non-file documents and non-Markdown files + if ( + document.uri.scheme !== "file" || + extname(document.uri.fsPath) !== ".md" + ) { + return; + } + + // Skip documents outside of detected project folders + const folder = vscode.workspace.getWorkspaceFolder(document.uri); + if (typeof folder === "undefined") { + return; + } + + // Skip documents in non-project folders + if (!projects.has(folder.uri.toString())) { + return; + } + + // Retag the document as Python Markdown + await vscode.languages.setTextDocumentLanguage(document, "python-markdown"); +} From 78b2cd7ad925bf027fd3542752391a65a02525c8 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 13:51:57 +0200 Subject: [PATCH 4/9] feature: add highlighting for emojis and icons Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 36 +- .../snapshots/inline_flow/emoji.scopes.json | 726 ++++++++++++++++++ .../snapshots/preprocess/snippet.scopes.json | 20 +- .../test/specs/inline_flow/emoji.md | 103 +++ .../syntaxes/python-markdown.tmLanguage.json | 46 +- 5 files changed, 896 insertions(+), 35 deletions(-) create mode 100644 grammars/textmate/python-markdown/test/snapshots/inline_flow/emoji.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/inline_flow/emoji.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index 1388589..857172f 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -95,10 +95,10 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.double.snippet.markdown } 6: { name: constant.numeric.snippet-selector.line.markdown } - 7: { name: punctuation.definition.string.end.snippet.markdown } + 7: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.inline.markdown match: >- (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(")((?:\\"|[^"\r\n])+?)(:[a-z][-_0-9a-z]*)(")$ @@ -106,10 +106,10 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.double.snippet.markdown } 6: { name: entity.name.section.snippet-selector.markdown } - 7: { name: punctuation.definition.string.end.snippet.markdown } + 7: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.inline.markdown match: >- (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(")((?:\\"|[^"\r\n])+?)(")$ @@ -117,9 +117,9 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.double.snippet.markdown } - 6: { name: punctuation.definition.string.end.snippet.markdown } + 6: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.inline.markdown match: >- (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)((?::-?[0-9]*){1,2}(?:(?:,(?=[-0-9:])-?[0-9]*)(?::-?[0-9]*)?)*)(')$ @@ -127,10 +127,10 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.single.snippet.markdown } 6: { name: constant.numeric.snippet-selector.line.markdown } - 7: { name: punctuation.definition.string.end.snippet.markdown } + 7: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.inline.markdown match: >- (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)(:[a-z][-_0-9a-z]*)(')$ @@ -138,10 +138,10 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.single.snippet.markdown } 6: { name: entity.name.section.snippet-selector.markdown } - 7: { name: punctuation.definition.string.end.snippet.markdown } + 7: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.inline.markdown match: >- (?:^|\G)([ \t]*)(-{1,}8<-{1,})([ \t]+)(')((?:\\'|[^'\r\n])+?)(')$ @@ -149,9 +149,9 @@ repository: 1: { name: punctuation.whitespace.leading.markdown } 2: { name: keyword.control.snippet.markdown } 3: { name: comment.snippet.separator.markdown } - 4: { name: punctuation.definition.string.begin.snippet.markdown } + 4: { name: punctuation.definition.string.begin.markdown } 5: { name: string.quoted.single.snippet.markdown } - 6: { name: punctuation.definition.string.end.snippet.markdown } + 6: { name: punctuation.definition.string.end.markdown } - name: meta.snippet.block.markdown begin: (?:^|\G)([ \t]*)(-{1,}8<-{1,})(?![ \t])$ beginCaptures: @@ -531,13 +531,13 @@ repository: end: (?=$) patterns: - include: "#inline_flow" - - include: text.html.derivative # - include: "#heading_setext" inline_flow: patterns: - include: "#escape" - include: "#inline_code" + - include: "#emoji" - include: "#mark" - include: "#emphasis" - include: "#image" @@ -546,6 +546,7 @@ repository: - include: "#link" - include: "#link_reference" - include: "#autolink" + - include: text.html.derivative escape: patterns: @@ -620,6 +621,15 @@ repository: - include: "#attribute" 4: { name: punctuation.definition.attribute-list.end.markdown } + emoji: + patterns: + - name: meta.emoji.markdown + match: (:)([\p{L}\p{N}_+-]+)(:) + captures: + 1: { name: punctuation.definition.emoji.markdown } + 2: { name: constant.other.emoji-name.markdown } + 3: { name: punctuation.definition.emoji.markdown } + mark: patterns: - name: markup.highlight.markdown diff --git a/grammars/textmate/python-markdown/test/snapshots/inline_flow/emoji.scopes.json b/grammars/textmate/python-markdown/test/snapshots/inline_flow/emoji.scopes.json new file mode 100644 index 0000000..90890c0 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/inline_flow/emoji.scopes.json @@ -0,0 +1,726 @@ +{ + "name": { + "accept_plain": [ + { + "line": ":smile:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_plus": [ + { + "line": ":+1:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "+1", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_dash": [ + { + "line": ":-1:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "-1", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_underscore": [ + { + "line": ":foo_bar:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "foo_bar", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_icon_name": [ + { + "line": ":material-home:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "material-home", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_icon_name_with_digits": [ + { + "line": ":octicons-arrow-left-16:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "octicons-arrow-left-16", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_unicode": [ + { + "line": ":é:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "é", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "position": { + "accept_before_text": [ + { + "line": ":smile: Text", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": " Text", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_after_text": [ + { + "line": "Text :smile:", + "tokens": [ + { + "text": "Text ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_multiple": [ + { + "line": ":smile::heart:", + "tokens": [ + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "heart", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_inside_word": [ + { + "line": "Text:smile:Text", + "tokens": [ + { + "text": "Text", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "Text", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "nesting": { + "accept_inside_emphasis": [ + { + "line": "*:smile:*", + "tokens": [ + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_inside_strong": [ + { + "line": "**:smile:**", + "tokens": [ + { + "text": "**", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.bold.markdown", + "punctuation.definition.bold.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.bold.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.bold.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.bold.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "**", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.bold.markdown", + "punctuation.definition.bold.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_inside_link": [ + { + "line": "[:smile:](href)", + "tokens": [ + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.emoji.markdown", + "constant.other.emoji-name.markdown" + ] + }, + { + "text": ":", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.emoji.markdown", + "punctuation.definition.emoji.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + }, + { + "text": "(", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": "href", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "markup.underline.link.markdown" + ] + }, + { + "text": ")", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "rejection": { + "reject_empty": [ + { + "line": "::", + "tokens": [ + { + "text": "::", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_unterminated": [ + { + "line": ":smile", + "tokens": [ + { + "text": ":smile", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json index 1892664..045ddfd 100644 --- a/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json +++ b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json @@ -25,7 +25,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.begin.snippet.markdown" + "punctuation.definition.string.begin.markdown" ] }, { @@ -41,7 +41,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.end.snippet.markdown" + "punctuation.definition.string.end.markdown" ] } ] @@ -76,7 +76,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.begin.snippet.markdown" + "punctuation.definition.string.begin.markdown" ] }, { @@ -92,7 +92,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.end.snippet.markdown" + "punctuation.definition.string.end.markdown" ] } ] @@ -135,7 +135,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.begin.snippet.markdown" + "punctuation.definition.string.begin.markdown" ] }, { @@ -151,7 +151,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.end.snippet.markdown" + "punctuation.definition.string.end.markdown" ] } ] @@ -186,7 +186,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.begin.snippet.markdown" + "punctuation.definition.string.begin.markdown" ] }, { @@ -210,7 +210,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.end.snippet.markdown" + "punctuation.definition.string.end.markdown" ] } ] @@ -245,7 +245,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.begin.snippet.markdown" + "punctuation.definition.string.begin.markdown" ] }, { @@ -269,7 +269,7 @@ "scopes": [ "text.html.markdown.python", "meta.snippet.inline.markdown", - "punctuation.definition.string.end.snippet.markdown" + "punctuation.definition.string.end.markdown" ] } ] diff --git a/grammars/textmate/python-markdown/test/specs/inline_flow/emoji.md b/grammars/textmate/python-markdown/test/specs/inline_flow/emoji.md new file mode 100644 index 0000000..0e1c7d5 --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/inline_flow/emoji.md @@ -0,0 +1,103 @@ +## name + +### accept_plain + +``` md +:smile: +``` + +### accept_with_plus + +``` md +:+1: +``` + +### accept_with_dash + +``` md +:-1: +``` + +### accept_with_underscore + +``` md +:foo_bar: +``` + +### accept_icon_name + +``` md +:material-home: +``` + +### accept_icon_name_with_digits + +``` md +:octicons-arrow-left-16: +``` + +### accept_unicode + +``` md +:é: +``` + +## position + +### accept_before_text + +``` md +:smile: Text +``` + +### accept_after_text + +``` md +Text :smile: +``` + +### accept_multiple + +``` md +:smile::heart: +``` + +### accept_inside_word + +``` md +Text:smile:Text +``` + +## nesting + +### accept_inside_emphasis + +``` md +*:smile:* +``` + +### accept_inside_strong + +``` md +**:smile:** +``` + +### accept_inside_link + +``` md +[:smile:](href) +``` + +## rejection + +### reject_empty + +``` md +:: +``` + +### reject_unterminated + +``` md +:smile +``` diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index c1ccb41..a76f443 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -160,7 +160,7 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.double.snippet.markdown" @@ -169,7 +169,7 @@ "name": "constant.numeric.snippet-selector.line.markdown" }, "7": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -187,7 +187,7 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.double.snippet.markdown" @@ -196,7 +196,7 @@ "name": "entity.name.section.snippet-selector.markdown" }, "7": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -214,13 +214,13 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.double.snippet.markdown" }, "6": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -238,7 +238,7 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.single.snippet.markdown" @@ -247,7 +247,7 @@ "name": "constant.numeric.snippet-selector.line.markdown" }, "7": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -265,7 +265,7 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.single.snippet.markdown" @@ -274,7 +274,7 @@ "name": "entity.name.section.snippet-selector.markdown" }, "7": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -292,13 +292,13 @@ "name": "comment.snippet.separator.markdown" }, "4": { - "name": "punctuation.definition.string.begin.snippet.markdown" + "name": "punctuation.definition.string.begin.markdown" }, "5": { "name": "string.quoted.single.snippet.markdown" }, "6": { - "name": "punctuation.definition.string.end.snippet.markdown" + "name": "punctuation.definition.string.end.markdown" } } }, @@ -5699,6 +5699,9 @@ { "include": "#inline_code" }, + { + "include": "#emoji" + }, { "include": "#mark" }, @@ -10429,6 +10432,25 @@ } ] }, + "emoji": { + "patterns": [ + { + "name": "meta.emoji.markdown", + "match": "(:)([\\p{L}\\p{N}_+-]+)(:)", + "captures": { + "1": { + "name": "punctuation.definition.emoji.markdown" + }, + "2": { + "name": "constant.other.emoji-name.markdown" + }, + "3": { + "name": "punctuation.definition.emoji.markdown" + } + } + } + ] + }, "mark": { "patterns": [ { From cee44f8db4fc795e88cfeea40e665ed32677e1ff Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 13:53:02 +0200 Subject: [PATCH 5/9] fix: move HTML patterns into inline flow for nested matching Signed-off-by: squidfunk --- integrations/code/syntaxes/python-markdown.tmLanguage.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index a76f443..c9ec548 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -5685,9 +5685,6 @@ "patterns": [ { "include": "#inline_flow" - }, - { - "include": "text.html.derivative" } ] }, @@ -5725,6 +5722,9 @@ }, { "include": "#autolink" + }, + { + "include": "text.html.derivative" } ] }, From b4f211ca3904e10821a1fcd23de4ab2c5c67981f Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 14:08:58 +0200 Subject: [PATCH 6/9] feature: add highlighting for smart symbols Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 18 + .../inline_flow/smart_symbol.scopes.json | 751 ++++++++++++++++++ .../snapshots/preprocess/snippet.scopes.json | 51 +- .../test/specs/inline_flow/smart_symbol.md | 215 +++++ .../syntaxes/python-markdown.tmLanguage.json | 31 + 5 files changed, 1063 insertions(+), 3 deletions(-) create mode 100644 grammars/textmate/python-markdown/test/snapshots/inline_flow/smart_symbol.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/inline_flow/smart_symbol.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index 857172f..ca104fc 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -538,6 +538,7 @@ repository: - include: "#escape" - include: "#inline_code" - include: "#emoji" + - include: "#smart_symbol" - include: "#mark" - include: "#emphasis" - include: "#image" @@ -630,6 +631,23 @@ repository: 2: { name: constant.other.emoji-name.markdown } 3: { name: punctuation.definition.emoji.markdown } + smart_symbol: + patterns: + - name: constant.other.smart-symbol.markdown + match: \(tm\)|\(c\)|\(r\) + - name: constant.other.smart-symbol.markdown + match: \+/-|=/= + - name: constant.other.smart-symbol.markdown + match: <-->|(?|<--(?!-) + - name: constant.other.smart-symbol.markdown + match: (?- + (?- + (?", + "tokens": [ + { + "text": "-->", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_both": [ + { + "line": "<-->", + "tokens": [ + { + "text": "<-->", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "care_of": { + "accept_plain": [ + { + "line": "c/o", + "tokens": [ + { + "text": "c/o", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "fraction": { + "accept_half": [ + { + "line": "1/2", + "tokens": [ + { + "text": "1/2", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_five_eighths": [ + { + "line": "5/8", + "tokens": [ + { + "text": "5/8", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "ordinal": { + "accept_first": [ + { + "line": "1st", + "tokens": [ + { + "text": "1st", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_eleventh": [ + { + "line": "11th", + "tokens": [ + { + "text": "11th", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_twelfth": [ + { + "line": "12th", + "tokens": [ + { + "text": "12th", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_thirty_second": [ + { + "line": "32nd", + "tokens": [ + { + "text": "32nd", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_hundred_third": [ + { + "line": "103rd", + "tokens": [ + { + "text": "103rd", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "position": { + "accept_before_text": [ + { + "line": "(c) Text", + "tokens": [ + { + "text": "(c)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " Text", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_after_text": [ + { + "line": "Text (c)", + "tokens": [ + { + "text": "Text ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "(c)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_multiple": [ + { + "line": "(c) +/- 1/2", + "tokens": [ + { + "text": "(c)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "+/-", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "1/2", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "nesting": { + "accept_inside_emphasis": [ + { + "line": "*(c)*", + "tokens": [ + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + }, + { + "text": "(c)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_inside_link": [ + { + "line": "[(c)](href)", + "tokens": [ + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": "(c)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + }, + { + "text": "(", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": "href", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "markup.underline.link.markdown" + ] + }, + { + "text": ")", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "rejection": { + "reject_escaped_copyright": [ + { + "line": "\\(c)", + "tokens": [ + { + "text": "\\(", + "scopes": [ + "text.html.markdown.python", + "constant.character.escape.markdown" + ] + }, + { + "text": "c)", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_uppercase_trademark": [ + { + "line": "(TM)", + "tokens": [ + { + "text": "(TM)", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_uppercase_care_of": [ + { + "line": "C/O", + "tokens": [ + { + "text": "C/O", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_arrow_right_tail": [ + { + "line": "---", + "tokens": [ + { + "text": "---", + "scopes": [ + "text.html.markdown.python", + "punctuation.definition.begin.frontmatter" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_arrow_left_tail": [ + { + "line": "<---", + "tokens": [ + { + "text": "<---", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_wrong_ordinal_eleventh_second": [ + { + "line": "11nd", + "tokens": [ + { + "text": "11nd", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_wrong_ordinal_tenth": [ + { + "line": "10st", + "tokens": [ + { + "text": "10st", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_wrong_ordinal_twenty_second": [ + { + "line": "22th", + "tokens": [ + { + "text": "22th", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_leading_zero_ordinal": [ + { + "line": "01st", + "tokens": [ + { + "text": "01st", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_fraction_digit_before": [ + { + "line": "11/2", + "tokens": [ + { + "text": "11/2", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_fraction_digit_after": [ + { + "line": "1/22", + "tokens": [ + { + "text": "1/22", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_care_of_before_underscore": [ + { + "line": "c/o_", + "tokens": [ + { + "text": "c/o_", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json index 045ddfd..6be6d01 100644 --- a/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json +++ b/grammars/textmate/python-markdown/test/snapshots/preprocess/snippet.scopes.json @@ -284,7 +284,22 @@ "line": ";--8<-- \"index.md\"", "tokens": [ { - "text": ";--8<-- \"index.md\"", + "text": ";--8", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "<--", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " \"index.md\"", "scopes": [ "text.html.markdown.python", "meta.paragraph.markdown" @@ -302,7 +317,22 @@ "line": "--8<-- index.md", "tokens": [ { - "text": "--8<-- index.md", + "text": "--8", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "<--", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " index.md", "scopes": [ "text.html.markdown.python", "meta.paragraph.markdown" @@ -320,7 +350,22 @@ "line": "--8<-- \"index.md\" ", "tokens": [ { - "text": "--8<-- \"index.md\" ", + "text": "--8", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "<--", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "constant.other.smart-symbol.markdown" + ] + }, + { + "text": " \"index.md\" ", "scopes": [ "text.html.markdown.python", "meta.paragraph.markdown" diff --git a/grammars/textmate/python-markdown/test/specs/inline_flow/smart_symbol.md b/grammars/textmate/python-markdown/test/specs/inline_flow/smart_symbol.md new file mode 100644 index 0000000..f9d4009 --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/inline_flow/smart_symbol.md @@ -0,0 +1,215 @@ +## parenthesized + +### accept_trademark + +``` md +(tm) +``` + +### accept_copyright + +``` md +(c) +``` + +### accept_registered + +``` md +(r) +``` + +## operator + +### accept_plusminus + +``` md ++/- +``` + +### accept_notequal + +``` md +=/= +``` + +## arrow + +### accept_left + +``` md +<-- +``` + +### accept_right + +``` md +--> +``` + +### accept_both + +``` md +<--> +``` + +## care_of + +### accept_plain + +``` md +c/o +``` + +## fraction + +### accept_half + +``` md +1/2 +``` + +### accept_five_eighths + +``` md +5/8 +``` + +## ordinal + +### accept_first + +``` md +1st +``` + +### accept_eleventh + +``` md +11th +``` + +### accept_twelfth + +``` md +12th +``` + +### accept_thirty_second + +``` md +32nd +``` + +### accept_hundred_third + +``` md +103rd +``` + +## position + +### accept_before_text + +``` md +(c) Text +``` + +### accept_after_text + +``` md +Text (c) +``` + +### accept_multiple + +``` md +(c) +/- 1/2 +``` + +## nesting + +### accept_inside_emphasis + +``` md +*(c)* +``` + +### accept_inside_link + +``` md +[(c)](href) +``` + +## rejection + +### reject_escaped_copyright + +``` md +\(c) +``` + +### reject_uppercase_trademark + +``` md +(TM) +``` + +### reject_uppercase_care_of + +``` md +C/O +``` + +### reject_arrow_right_tail + +``` md +--- +``` + +### reject_arrow_left_tail + +``` md +<--- +``` + +### reject_wrong_ordinal_eleventh_second + +``` md +11nd +``` + +### reject_wrong_ordinal_tenth + +``` md +10st +``` + +### reject_wrong_ordinal_twenty_second + +``` md +22th +``` + +### reject_leading_zero_ordinal + +``` md +01st +``` + +### reject_fraction_digit_before + +``` md +11/2 +``` + +### reject_fraction_digit_after + +``` md +1/22 +``` + +### reject_care_of_before_underscore + +``` md +c/o_ +``` diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index c9ec548..0ba411f 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -5699,6 +5699,9 @@ { "include": "#emoji" }, + { + "include": "#smart_symbol" + }, { "include": "#mark" }, @@ -10451,6 +10454,34 @@ } ] }, + "smart_symbol": { + "patterns": [ + { + "name": "constant.other.smart-symbol.markdown", + "match": "\\(tm\\)|\\(c\\)|\\(r\\)" + }, + { + "name": "constant.other.smart-symbol.markdown", + "match": "\\+/-|=/=" + }, + { + "name": "constant.other.smart-symbol.markdown", + "match": "<-->|(?|<--(?!-)" + }, + { + "name": "constant.other.smart-symbol.markdown", + "match": "(? Date: Tue, 23 Jun 2026 14:21:42 +0200 Subject: [PATCH 7/9] feature: add highlighting for list and task lists Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 32 + .../test/snapshots/flow/list.scopes.json | 583 ++++++++++++++++++ .../inline_flow/emphasis.scopes.json | 17 +- .../python-markdown/test/specs/flow/list.md | 101 +++ .../syntaxes/python-markdown.tmLanguage.json | 75 +++ 5 files changed, 806 insertions(+), 2 deletions(-) create mode 100644 grammars/textmate/python-markdown/test/snapshots/flow/list.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/flow/list.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index ca104fc..ae53c99 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -61,6 +61,7 @@ repository: - include: "#heading_atx" - include: "#thematic_break" - include: "#block_quote" + - include: "#list_item" - include: "#paragraph" snippet: @@ -513,6 +514,37 @@ repository: - match: (?:^|\G)[ \t]*([\*\-\_])([ ]{0,2}\1){2,}[ \t]*$\n? name: meta.thematic-break.markdown + list_item: + patterns: + - match: >- + (?:^|\G)([ \t]*)([*+-])([ ]+)(\[(?: |x|X)\])([ \t]+) + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.list.unordered.markdown } + 3: { name: punctuation.whitespace.markdown } + 4: { name: keyword.control.task-checkbox.markdown } + 5: { name: punctuation.whitespace.markdown } + - match: >- + (?:^|\G)([ \t]*)(\d+\.)([ ]+)(\[(?: |x|X)\])([ \t]+) + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.list.ordered.markdown } + 3: { name: punctuation.whitespace.markdown } + 4: { name: keyword.control.task-checkbox.markdown } + 5: { name: punctuation.whitespace.markdown } + - match: >- + (?:^|\G)([ \t]*)([*+-])([ ]+)(?=\S) + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.list.unordered.markdown } + 3: { name: punctuation.whitespace.markdown } + - match: >- + (?:^|\G)([ \t]*)(\d+\.)([ ]+)(?=\S) + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.list.ordered.markdown } + 3: { name: punctuation.whitespace.markdown } + block_quote: patterns: - name: meta.block.quote.markdown diff --git a/grammars/textmate/python-markdown/test/snapshots/flow/list.scopes.json b/grammars/textmate/python-markdown/test/snapshots/flow/list.scopes.json new file mode 100644 index 0000000..fb672d7 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/flow/list.scopes.json @@ -0,0 +1,583 @@ +{ + "marker": { + "accept_unordered_dash": [ + { + "line": "- Item", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_unordered_asterisk": [ + { + "line": "* Item", + "tokens": [ + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_unordered_plus": [ + { + "line": "+ Item", + "tokens": [ + { + "text": "+", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_ordered": [ + { + "line": "1. Item", + "tokens": [ + { + "text": "1.", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.ordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_3_leading_spaces": [ + { + "line": " - Item", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "task": { + "accept_unchecked": [ + { + "line": "- [ ] Task", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_checked_uppercase": [ + { + "line": "* [X] Task", + "tokens": [ + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[X]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_ordered": [ + { + "line": "1. [ ] Task", + "tokens": [ + { + "text": "1.", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.ordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_tab_after_checkbox": [ + { + "line": "- [ ]\tTask", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": "\t", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_2_spaces_after_checkbox": [ + { + "line": "- [ ] Task", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_checkbox_without_text": [ + { + "line": "- [ ] ", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python", + "keyword.control.task-checkbox.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "rejection": { + "reject_marker_missing_space": [ + { + "line": "-Item", + "tokens": [ + { + "text": "-Item", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_checkbox_only": [ + { + "line": "- [ ]", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[ ]", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_checkbox_invalid_marker": [ + { + "line": "- [y] Task", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[y] Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_checkbox_without_space_after": [ + { + "line": "- [x]Task", + "tokens": [ + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "[x]Task", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_with_4_leading_spaces": [ + { + "line": " - Item", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.leading.markdown" + ] + }, + { + "text": "-", + "scopes": [ + "text.html.markdown.python", + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Item", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/snapshots/inline_flow/emphasis.scopes.json b/grammars/textmate/python-markdown/test/snapshots/inline_flow/emphasis.scopes.json index c2f0eb0..862e469 100644 --- a/grammars/textmate/python-markdown/test/snapshots/inline_flow/emphasis.scopes.json +++ b/grammars/textmate/python-markdown/test/snapshots/inline_flow/emphasis.scopes.json @@ -249,10 +249,23 @@ "line": "* Emphasis*", "tokens": [ { - "text": "* Emphasis*", + "text": "*", "scopes": [ "text.html.markdown.python", - "meta.paragraph.markdown" + "keyword.control.list.unordered.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "Emphasis*", + "scopes": [ + "text.html.markdown.python" ] } ] diff --git a/grammars/textmate/python-markdown/test/specs/flow/list.md b/grammars/textmate/python-markdown/test/specs/flow/list.md new file mode 100644 index 0000000..9b5111b --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/flow/list.md @@ -0,0 +1,101 @@ +## marker + +### accept_unordered_dash + +``` md +- Item +``` + +### accept_unordered_asterisk + +``` md +* Item +``` + +### accept_unordered_plus + +``` md ++ Item +``` + +### accept_ordered + +``` md +1. Item +``` + +### accept_with_3_leading_spaces + +``` md + - Item +``` + +## task + +### accept_unchecked + +``` md +- [ ] Task +``` + +### accept_checked_uppercase + +``` md +* [X] Task +``` + +### accept_ordered + +``` md +1. [ ] Task +``` + +### accept_tab_after_checkbox + +``` md +- [ ] Task +``` + +### accept_2_spaces_after_checkbox + +``` md +- [ ] Task +``` + +### accept_checkbox_without_text + +``` md +- [ ] +``` + +## rejection + +### reject_marker_missing_space + +``` md +-Item +``` + +### reject_checkbox_only + +``` md +- [ ] +``` + +### reject_checkbox_invalid_marker + +``` md +- [y] Task +``` + +### reject_checkbox_without_space_after + +``` md +- [x]Task +``` + +### accept_with_4_leading_spaces + +``` md + - Item +``` diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index 0ba411f..1b00f19 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -84,6 +84,9 @@ { "include": "#block_quote" }, + { + "include": "#list_item" + }, { "include": "#paragraph" } @@ -5654,6 +5657,78 @@ } ] }, + "list_item": { + "patterns": [ + { + "match": "(?:^|\\G)([ \\t]*)([*+-])([ ]+)(\\[(?: |x|X)\\])([ \\t]+)", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.list.unordered.markdown" + }, + "3": { + "name": "punctuation.whitespace.markdown" + }, + "4": { + "name": "keyword.control.task-checkbox.markdown" + }, + "5": { + "name": "punctuation.whitespace.markdown" + } + } + }, + { + "match": "(?:^|\\G)([ \\t]*)(\\d+\\.)([ ]+)(\\[(?: |x|X)\\])([ \\t]+)", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.list.ordered.markdown" + }, + "3": { + "name": "punctuation.whitespace.markdown" + }, + "4": { + "name": "keyword.control.task-checkbox.markdown" + }, + "5": { + "name": "punctuation.whitespace.markdown" + } + } + }, + { + "match": "(?:^|\\G)([ \\t]*)([*+-])([ ]+)(?=\\S)", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.list.unordered.markdown" + }, + "3": { + "name": "punctuation.whitespace.markdown" + } + } + }, + { + "match": "(?:^|\\G)([ \\t]*)(\\d+\\.)([ ]+)(?=\\S)", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.list.ordered.markdown" + }, + "3": { + "name": "punctuation.whitespace.markdown" + } + } + } + ] + }, "block_quote": { "patterns": [ { From 56dd84b21802cffbe3235b84fbf14c88646cb888 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 14:30:17 +0200 Subject: [PATCH 8/9] feature: add highlighting for keyboard keys Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 27 + .../snapshots/inline_flow/keys.scopes.json | 1038 +++++++++++++++++ .../test/specs/inline_flow/keys.md | 143 +++ .../syntaxes/python-markdown.tmLanguage.json | 60 + 4 files changed, 1268 insertions(+) create mode 100644 grammars/textmate/python-markdown/test/snapshots/inline_flow/keys.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/inline_flow/keys.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index ae53c99..92c5ebe 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -570,6 +570,7 @@ repository: - include: "#escape" - include: "#inline_code" - include: "#emoji" + - include: "#keys" - include: "#smart_symbol" - include: "#mark" - include: "#emphasis" @@ -663,6 +664,32 @@ repository: 2: { name: constant.other.emoji-name.markdown } 3: { name: punctuation.definition.emoji.markdown } + keys: + patterns: + - name: meta.keys.markdown + begin: >- + (\+{2})(?=(?:[\p{L}\p{N}_-]+|"(?:\\.|[^"\\+])+"|'(?:\\.|[^'\\+])+')(?:\+(?:[\p{L}\p{N}_-]+|"(?:\\.|[^"\\+])+"|'(?:\\.|[^'\\+])+'))*\+{2}) + beginCaptures: + 1: { name: punctuation.definition.keys.begin.markdown } + end: \+{2} + endCaptures: + 0: { name: punctuation.definition.keys.end.markdown } + patterns: + - match: \+ + name: punctuation.separator.key.markdown + - match: (")((?:\\.|[^"\\+])+)(") + captures: + 1: { name: punctuation.definition.string.begin.key.markdown } + 2: { name: constant.other.key.markdown } + 3: { name: punctuation.definition.string.end.key.markdown } + - match: (')((?:\\.|[^'\\+])+)(') + captures: + 1: { name: punctuation.definition.string.begin.key.markdown } + 2: { name: constant.other.key.markdown } + 3: { name: punctuation.definition.string.end.key.markdown } + - match: '[\p{L}\p{N}_-]+' + name: constant.other.key.markdown + smart_symbol: patterns: - name: constant.other.smart-symbol.markdown diff --git a/grammars/textmate/python-markdown/test/snapshots/inline_flow/keys.scopes.json b/grammars/textmate/python-markdown/test/snapshots/inline_flow/keys.scopes.json new file mode 100644 index 0000000..f10ee73 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/inline_flow/keys.scopes.json @@ -0,0 +1,1038 @@ +{ + "name": { + "accept_named_key": [ + { + "line": "++enter++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_key_alias": [ + { + "line": "++pg-up++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "pg-up", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_function_key": [ + { + "line": "++f12++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "f12", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_unicode_key": [ + { + "line": "++é++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "é", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "sequence": { + "accept_key_sequence": [ + { + "line": "++ctrl+alt+delete++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "ctrl", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "+", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.separator.key.markdown" + ] + }, + { + "text": "alt", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "+", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.separator.key.markdown" + ] + }, + { + "text": "delete", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_key_name_with_side": [ + { + "line": "++left-control++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "left-control", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_key_name_with_side_in_sequence": [ + { + "line": "++ctrl+left-control++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "ctrl", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "+", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.separator.key.markdown" + ] + }, + { + "text": "left-control", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "quoted": { + "accept_quoted_key": [ + { + "line": "++\"enter delete\"++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.begin.key.markdown" + ] + }, + { + "text": "enter delete", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.end.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_single_quoted_key": [ + { + "line": "++'enter delete'++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "'", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.begin.key.markdown" + ] + }, + { + "text": "enter delete", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "'", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.end.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_escaped_quoted_key": [ + { + "line": "++\"control\\\"alt\"++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.begin.key.markdown" + ] + }, + { + "text": "control\\\"alt", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.end.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_escaped_plus_in_quoted_key": [ + { + "line": "++\"control\\+alt\"++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.begin.key.markdown" + ] + }, + { + "text": "control\\+alt", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.end.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_mixed_named_and_quoted_keys": [ + { + "line": "++ctrl+\"alt delete\"++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "ctrl", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "+", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.separator.key.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.begin.key.markdown" + ] + }, + { + "text": "alt delete", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.string.end.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "position": { + "accept_before_text": [ + { + "line": "++enter++ Text", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + }, + { + "text": " Text", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_after_text": [ + { + "line": "Text ++enter++", + "tokens": [ + { + "text": "Text ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_multiple": [ + { + "line": "++ctrl++ ++alt++", + "tokens": [ + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "ctrl", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "alt", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "nesting": { + "accept_inside_emphasis": [ + { + "line": "*++enter++*", + "tokens": [ + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + }, + { + "text": "*", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "markup.italic.markdown", + "punctuation.definition.italic.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "accept_inside_link": [ + { + "line": "[++enter++](href)", + "tokens": [ + { + "text": "[", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.begin.markdown" + ] + }, + { + "text": "enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.keys.markdown", + "constant.other.key.markdown" + ] + }, + { + "text": "++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "string.other.link.title.markdown", + "meta.keys.markdown", + "punctuation.definition.keys.end.markdown" + ] + }, + { + "text": "]", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + }, + { + "text": "(", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.begin.markdown" + ] + }, + { + "text": "href", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "markup.underline.link.markdown" + ] + }, + { + "text": ")", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.link.inline.markdown", + "punctuation.definition.link.end.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "rejection": { + "reject_blank_key": [ + { + "line": "++++", + "tokens": [ + { + "text": "++++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_unterminated_key": [ + { + "line": "++enter", + "tokens": [ + { + "text": "++enter", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_escaped_key": [ + { + "line": "\\++enter++", + "tokens": [ + { + "text": "\\+", + "scopes": [ + "text.html.markdown.python", + "constant.character.escape.markdown" + ] + }, + { + "text": "+enter++", + "scopes": [ + "text.html.markdown.python" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_unescaped_plus_in_quoted_key": [ + { + "line": "++\"control+alt\"++", + "tokens": [ + { + "text": "++\"control+alt\"++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ], + "reject_empty_quoted_key": [ + { + "line": "+\"\"++", + "tokens": [ + { + "text": "+\"\"++", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/specs/inline_flow/keys.md b/grammars/textmate/python-markdown/test/specs/inline_flow/keys.md new file mode 100644 index 0000000..49daea6 --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/inline_flow/keys.md @@ -0,0 +1,143 @@ +## name + +### accept_named_key + +``` md +++enter++ +``` + +### accept_key_alias + +``` md +++pg-up++ +``` + +### accept_function_key + +``` md +++f12++ +``` + +### accept_unicode_key + +``` md +++é++ +``` + +## sequence + +### accept_key_sequence + +``` md +++ctrl+alt+delete++ +``` + +### accept_key_name_with_side + +``` md +++left-control++ +``` + +### accept_key_name_with_side_in_sequence + +``` md +++ctrl+left-control++ +``` + +## quoted + +### accept_quoted_key + +``` md +++"enter delete"++ +``` + +### accept_single_quoted_key + +``` md +++'enter delete'++ +``` + +### accept_escaped_quoted_key + +``` md +++"control\"alt"++ +``` + +### accept_escaped_plus_in_quoted_key + +``` md +++"control\+alt"++ +``` + +### accept_mixed_named_and_quoted_keys + +``` md +++ctrl+"alt delete"++ +``` + +## position + +### accept_before_text + +``` md +++enter++ Text +``` + +### accept_after_text + +``` md +Text ++enter++ +``` + +### accept_multiple + +``` md +++ctrl++ ++alt++ +``` + +## nesting + +### accept_inside_emphasis + +``` md +*++enter++* +``` + +### accept_inside_link + +``` md +[++enter++](href) +``` + +## rejection + +### reject_blank_key + +``` md +++++ +``` + +### reject_unterminated_key + +``` md +++enter +``` + +### reject_escaped_key + +``` md +\++enter++ +``` + +### reject_unescaped_plus_in_quoted_key + +``` md +++"control+alt"++ +``` + +### reject_empty_quoted_key + +``` md ++""++ +``` diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index 1b00f19..d90a587 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -5774,6 +5774,9 @@ { "include": "#emoji" }, + { + "include": "#keys" + }, { "include": "#smart_symbol" }, @@ -10529,6 +10532,63 @@ } ] }, + "keys": { + "patterns": [ + { + "name": "meta.keys.markdown", + "begin": "(\\+{2})(?=(?:[\\p{L}\\p{N}_-]+|\"(?:\\\\.|[^\"\\\\+])+\"|'(?:\\\\.|[^'\\\\+])+')(?:\\+(?:[\\p{L}\\p{N}_-]+|\"(?:\\\\.|[^\"\\\\+])+\"|'(?:\\\\.|[^'\\\\+])+'))*\\+{2})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.keys.begin.markdown" + } + }, + "end": "\\+{2}", + "endCaptures": { + "0": { + "name": "punctuation.definition.keys.end.markdown" + } + }, + "patterns": [ + { + "match": "\\+", + "name": "punctuation.separator.key.markdown" + }, + { + "match": "(\")((?:\\\\.|[^\"\\\\+])+)(\")", + "captures": { + "1": { + "name": "punctuation.definition.string.begin.key.markdown" + }, + "2": { + "name": "constant.other.key.markdown" + }, + "3": { + "name": "punctuation.definition.string.end.key.markdown" + } + } + }, + { + "match": "(')((?:\\\\.|[^'\\\\+])+)(')", + "captures": { + "1": { + "name": "punctuation.definition.string.begin.key.markdown" + }, + "2": { + "name": "constant.other.key.markdown" + }, + "3": { + "name": "punctuation.definition.string.end.key.markdown" + } + } + }, + { + "match": "[\\p{L}\\p{N}_-]+", + "name": "constant.other.key.markdown" + } + ] + } + ] + }, "smart_symbol": { "patterns": [ { From ce7d71126d5e4e1541fcbdc006c79fcdbca154ab Mon Sep 17 00:00:00 2001 From: squidfunk Date: Tue, 23 Jun 2026 14:37:22 +0200 Subject: [PATCH 9/9] feature: add highlighting for inline math Signed-off-by: squidfunk --- .../src/python-markdown.tmLanguage.yml | 36 +- .../snapshots/inline_flow/math.scopes.json | 888 ++++++++++++++++++ .../test/specs/inline_flow/math.md | 148 +++ .../syntaxes/python-markdown.tmLanguage.json | 69 +- 4 files changed, 1125 insertions(+), 16 deletions(-) create mode 100644 grammars/textmate/python-markdown/test/snapshots/inline_flow/math.scopes.json create mode 100644 grammars/textmate/python-markdown/test/specs/inline_flow/math.md diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index 92c5ebe..fd8d017 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -567,8 +567,9 @@ repository: inline_flow: patterns: - - include: "#escape" - include: "#inline_code" + - include: "#inline_math" + - include: "#escape" - include: "#emoji" - include: "#keys" - include: "#smart_symbol" @@ -582,11 +583,6 @@ repository: - include: "#autolink" - include: text.html.derivative - escape: - patterns: - - match: \\. - name: constant.character.escape.markdown - inline_code: patterns: # @each languages @@ -655,6 +651,34 @@ repository: - include: "#attribute" 4: { name: punctuation.definition.attribute-list.end.markdown } + inline_math: + patterns: + - name: markup.math.inline.markdown + begin: (\$)(?=[^\s$]) + beginCaptures: + 1: { name: punctuation.definition.math.begin.markdown } + contentName: meta.embedded.inline.latex + end: (?