diff --git a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml index d132284..3070792 100644 --- a/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml +++ b/grammars/textmate/python-markdown/src/python-markdown.tmLanguage.yml @@ -190,6 +190,7 @@ repository: flow: patterns: - include: "#table" + - include: "#directive" - include: "#admonition" - include: "#details" - include: "#tab" @@ -204,6 +205,73 @@ repository: - include: "#list_item" - include: "#paragraph" + directive: + patterns: + - name: meta.directive.condition.block.markdown + match: >- + (?:^|\G)([ \t]*)(@)(if|elif)([ \t]+)(.*?)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.directive.condition.markdown } + 3: { name: keyword.control.directive.condition.markdown } + 4: { name: punctuation.whitespace.markdown } + 5: + name: meta.directive.expression.markdown + patterns: + - match: (?i:\b(?:and|or|not)\b) + name: keyword.operator.logical.directive.markdown + - match: >- + (==|!=|<=|>=|=|<|>)([ \t]+)(\")([^\"\\r\\n]+)(\") + captures: + 1: { name: keyword.operator.comparison.directive.markdown } + 2: { name: punctuation.whitespace.markdown } + 3: { name: punctuation.definition.string.begin.directive.markdown } + 4: { name: string.quoted.double.directive.markdown } + 5: { name: punctuation.definition.string.end.directive.markdown } + - match: >- + (==|!=|<=|>=|=|<|>)([ \t]+)([a-zA-Z_][a-zA-Z0-9_.-]*) + captures: + 1: { name: keyword.operator.comparison.directive.markdown } + 2: { name: punctuation.whitespace.markdown } + 3: { name: string.unquoted.directive.markdown } + - match: ==|!=|<=|>=|=|<|> + name: keyword.operator.comparison.directive.markdown + - match: "([\"])([^\"\\r\\n]*)([\"])" + captures: + 1: { name: punctuation.definition.string.begin.directive.markdown } + 2: { name: string.quoted.double.directive.markdown } + 3: { name: punctuation.definition.string.end.directive.markdown } + - match: \b[a-zA-Z_][a-zA-Z0-9_.-]*\b + name: variable.other.directive.markdown + 6: { name: punctuation.whitespace.trailing.markdown } + - name: meta.directive.condition.block.markdown + match: >- + (?:^|\G)([ \t]*)(@)(else)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.directive.condition.markdown } + 3: { name: keyword.control.directive.condition.markdown } + 4: { name: punctuation.whitespace.trailing.markdown } + - name: meta.directive.use.block.markdown + match: >- + (?:^|\G)([ \t]*)(@)(use)([ \t]+)(.*?)([ \t]*)$ + captures: + 1: { name: punctuation.whitespace.leading.markdown } + 2: { name: keyword.control.directive.use.markdown } + 3: { name: keyword.control.directive.use.markdown } + 4: { name: punctuation.whitespace.markdown } + 5: + name: meta.directive.path.markdown + patterns: + - match: (\")(.+)(\") + captures: + 1: { name: punctuation.definition.string.begin.directive.markdown } + 2: { name: string.quoted.double.directive-path.markdown } + 3: { name: punctuation.definition.string.end.directive.markdown } + - match: \S+ + name: string.unquoted.directive-path.markdown + 6: { name: punctuation.whitespace.trailing.markdown } + table: patterns: - name: markup.table.markdown @@ -597,6 +665,7 @@ repository: inline_flow: patterns: + - include: "#directive_variable" - include: "#inline_code" - include: "#inline_math" - include: "#escape" @@ -613,6 +682,17 @@ repository: - include: "#autolink" - include: text.html.derivative + directive_variable: + patterns: + - name: meta.directive.variable.inline.markdown + match: >- + (@var)(\{)([_a-zA-Z][_a-zA-Z0-9]*)(\}) + captures: + 1: { name: keyword.control.directive.variable.markdown } + 2: { name: punctuation.definition.directive.variable.begin.markdown } + 3: { name: variable.other.directive.markdown } + 4: { name: punctuation.definition.directive.variable.end.markdown } + inline_code: patterns: # @each languages diff --git a/grammars/textmate/python-markdown/test/snapshots/flow/directive.scopes.json b/grammars/textmate/python-markdown/test/snapshots/flow/directive.scopes.json new file mode 100644 index 0000000..c6f5a51 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/flow/directive.scopes.json @@ -0,0 +1,439 @@ +{ + "conditions": { + "if_elif_else": [ + { + "line": "@if product = studio OR not platform = \"windows\"", + "tokens": [ + { + "text": "@", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + }, + { + "text": "if", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "product", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "variable.other.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "=", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "keyword.operator.comparison.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "studio", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "string.unquoted.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "OR", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "keyword.operator.logical.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "not", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "keyword.operator.logical.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "platform", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "variable.other.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "=", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "keyword.operator.comparison.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "punctuation.definition.string.begin.directive.markdown" + ] + }, + { + "text": "windows", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "string.quoted.double.directive.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "punctuation.definition.string.end.directive.markdown" + ] + } + ] + }, + { + "line": " Studio", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "Studio", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "@elif product = spark", + "tokens": [ + { + "text": "@", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + }, + { + "text": "elif", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "product", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "variable.other.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown" + ] + }, + { + "text": "=", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "keyword.operator.comparison.directive.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "spark", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "meta.directive.expression.markdown", + "string.unquoted.directive.markdown" + ] + } + ] + }, + { + "line": " Spark", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "Spark", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "@else", + "tokens": [ + { + "text": "@", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + }, + { + "text": "else", + "scopes": [ + "text.html.markdown.python", + "meta.directive.condition.block.markdown", + "keyword.control.directive.condition.markdown" + ] + } + ] + }, + { + "line": " Other", + "tokens": [ + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "Other", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + }, + "use": { + "quoted_and_bare_targets": [ + { + "line": "@use \"shared/guide.md\"", + "tokens": [ + { + "text": "@", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "keyword.control.directive.use.markdown" + ] + }, + { + "text": "use", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "keyword.control.directive.use.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "meta.directive.path.markdown", + "punctuation.definition.string.begin.directive.markdown" + ] + }, + { + "text": "shared/guide.md", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "meta.directive.path.markdown", + "string.quoted.double.directive-path.markdown" + ] + }, + { + "text": "\"", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "meta.directive.path.markdown", + "punctuation.definition.string.end.directive.markdown" + ] + } + ] + }, + { + "line": "@use shared/other.md", + "tokens": [ + { + "text": "@", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "keyword.control.directive.use.markdown" + ] + }, + { + "text": "use", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "keyword.control.directive.use.markdown" + ] + }, + { + "text": " ", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "punctuation.whitespace.markdown" + ] + }, + { + "text": "shared/other.md", + "scopes": [ + "text.html.markdown.python", + "meta.directive.use.block.markdown", + "meta.directive.path.markdown", + "string.unquoted.directive-path.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/snapshots/inline_flow/directive.scopes.json b/grammars/textmate/python-markdown/test/snapshots/inline_flow/directive.scopes.json new file mode 100644 index 0000000..834ecc3 --- /dev/null +++ b/grammars/textmate/python-markdown/test/snapshots/inline_flow/directive.scopes.json @@ -0,0 +1,108 @@ +{ + "variables": { + "named_variables": [ + { + "line": "Use @var{product} with @var{_variant}.", + "tokens": [ + { + "text": "Use ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "@var", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "keyword.control.directive.variable.markdown" + ] + }, + { + "text": "{", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "punctuation.definition.directive.variable.begin.markdown" + ] + }, + { + "text": "product", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "variable.other.directive.markdown" + ] + }, + { + "text": "}", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "punctuation.definition.directive.variable.end.markdown" + ] + }, + { + "text": " with ", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + }, + { + "text": "@var", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "keyword.control.directive.variable.markdown" + ] + }, + { + "text": "{", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "punctuation.definition.directive.variable.begin.markdown" + ] + }, + { + "text": "_variant", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "variable.other.directive.markdown" + ] + }, + { + "text": "}", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown", + "meta.directive.variable.inline.markdown", + "punctuation.definition.directive.variable.end.markdown" + ] + }, + { + "text": ".", + "scopes": [ + "text.html.markdown.python", + "meta.paragraph.markdown" + ] + } + ] + }, + { + "line": "", + "tokens": [] + } + ] + } +} diff --git a/grammars/textmate/python-markdown/test/specs/flow/directive.md b/grammars/textmate/python-markdown/test/specs/flow/directive.md new file mode 100644 index 0000000..61f0514 --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/flow/directive.md @@ -0,0 +1,21 @@ +## conditions + +### if_elif_else + +``` md +@if product = studio OR not platform = "windows" + Studio +@elif product = spark + Spark +@else + Other +``` + +## use + +### quoted_and_bare_targets + +``` md +@use "shared/guide.md" +@use shared/other.md +``` diff --git a/grammars/textmate/python-markdown/test/specs/inline_flow/directive.md b/grammars/textmate/python-markdown/test/specs/inline_flow/directive.md new file mode 100644 index 0000000..5e2c171 --- /dev/null +++ b/grammars/textmate/python-markdown/test/specs/inline_flow/directive.md @@ -0,0 +1,7 @@ +## variables + +### named_variables + +``` md +Use @var{product} with @var{_variant}. +``` diff --git a/integrations/code/media/preview.html b/integrations/code/media/preview.html index c3957c7..28b754f 100644 --- a/integrations/code/media/preview.html +++ b/integrations/code/media/preview.html @@ -42,6 +42,10 @@ vertical-align: super; } + .md-typeset { + font-size: 0.9rem; + } + .md-typeset .mermaid { display: flex; justify-content: center; @@ -54,6 +58,42 @@ height: auto; } + #preview-toolbar { + align-items: center; + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + background-color: color-mix( + in srgb, var(--md-default-bg-color) 82%, transparent + ); + font-size: 16px; + display: none; + gap: 0.45rem; + padding: 0.55rem 0.8rem; + position: sticky; + top: 0; + z-index: 1; + } + + #preview-toolbar strong { + font-weight: 600; + } + + #preview-variant-control { + align-items: center; + display: none; + gap: 0.3rem; + } + + #preview-variant { + background: transparent; + border: 1px solid color-mix(in srgb, currentColor 25%, transparent); + border-radius: 3px; + color: inherit; + font: inherit; + max-width: 14rem; + padding: 0.15rem 1.4rem 0.15rem 0.35rem; + } + @@ -64,6 +104,13 @@ data-md-color-primary="indigo" data-md-color-accent="indigo" > +
+ Preview + +
@@ -72,6 +119,8 @@ let latestVersion = -1; let latestSession = null; const preview = document.getElementById("preview"); + const variantControl = document.getElementById("preview-variant-control"); + const variantSelect = document.getElementById("preview-variant"); const vscode = acquireVsCodeApi(); let lastAnchor = null; let activeSourceOffset = null; @@ -84,6 +133,31 @@ let mermaidId = 0; const uiStateByUri = new Map(); + variantSelect.addEventListener("change", () => { + vscode.postMessage({ + type: "preview/select-variant", + variant: variantSelect.value, + }); + }); + + function updateVariants(message) { + const variants = Array.isArray(message.variants) ? message.variants : []; + const selected = message.selectedVariant; + variantSelect.replaceChildren( + ...variants.map((variant) => { + const option = document.createElement("option"); + option.value = variant; + option.textContent = variant; + option.selected = variant === selected; + return option; + }), + ); + const hasVariants = variants.length > 1; + variantControl.style.display = hasVariants ? "flex" : "none"; + document.getElementById("preview-toolbar").style.display = + hasVariants ? "flex" : "none"; + } + function updateColorScheme() { document.body.dataset.mdColorScheme = document.body.classList.contains("vscode-dark") || @@ -503,6 +577,10 @@ window.addEventListener("message", (event) => { const message = event.data; + if (message.type === "preview/variants") { + updateVariants(message); + return; + } if (message.type === "preview/highlight") { activeSourceOffset = message.sourceOffset; activeLineStart = message.sourceLineStart; diff --git a/integrations/code/src/preview.ts b/integrations/code/src/preview.ts index bf22018..2fc5e8d 100644 --- a/integrations/code/src/preview.ts +++ b/integrations/code/src/preview.ts @@ -107,6 +107,14 @@ interface ResolvedLocalLink { fragment?: string; } +/** + * Variants available for a preview document. + */ +interface PreviewVariants { + defaultVariant?: string; + variants: string[]; +} + /* ---------------------------------------------------------------------------- * Constants * ------------------------------------------------------------------------- */ @@ -115,6 +123,8 @@ interface ResolvedLocalLink { const previewStart = "zensical/preview/start"; const previewStop = "zensical/preview/stop"; const previewUpdate = "zensical/preview/update"; +const previewVariants = "zensical/preview/variants"; +const previewVariantsChanged = "zensical/preview/variantsChanged"; /* ---------------------------------------------------------------------------- * Data @@ -191,6 +201,78 @@ export function registerPreviewCommand( let latestVersion = -1; let latestUpdate: PreviewUpdate | undefined; let updateTimer: ReturnType | undefined; + let selectedVariant: string | undefined; + let availableVariants: string[] = []; + let variantUpdateGeneration = 0; + + // Generate a unique key for storing the selected variant + const variantStateKey = (uri: string) => `preview.variant.${uri}`; + + // Post the available variants and selected variant to the webview + const postVariants = () => { + void panel.webview.postMessage({ + type: "preview/variants", + variants: availableVariants, + selectedVariant, + }); + }; + + // Query the available variants for a document from the language server + const queryVariants = async ( + document: vscode.TextDocument, + ): Promise => { + try { + return await client.sendRequest( + previewVariants, + { uri: document.uri.toString() }, + ); + } catch { + // Older servers and projects without a catalog have no variants. + return undefined; + } + }; + + // Set the available variants and selected variant for a document + const setVariants = async ( + document: vscode.TextDocument, + updateSession: boolean, + ): Promise => { + const uri = document.uri.toString(); + const generation = ++variantUpdateGeneration; + const result = await queryVariants(document); + if (disposed || generation !== variantUpdateGeneration || uri !== activeUri) { + return; + } + + // Update the selected variant and persist it in the workspace state + const previous = selectedVariant; + availableVariants = result?.variants ?? []; + const persisted = context.workspaceState.get( + variantStateKey(uri), + ); + selectedVariant = availableVariants.includes(persisted ?? "") + ? persisted + : result?.defaultVariant; + if (selectedVariant && availableVariants.includes(selectedVariant)) { + void context.workspaceState.update( + variantStateKey(uri), selectedVariant, + ); + } + postVariants(); + + // If the selected variant has changed, send a preview update + if ( + updateSession && + activeSession && + selectedVariant !== previous + ) { + await client.sendRequest(previewUpdate, { + session: activeSession, + variant: selectedVariant, + projections: {}, + }); + } + }; // Accept only updates for the active session, then debounce the web view // replacement so rapid editor changes do not trigger a DOM rebuild each @@ -199,7 +281,7 @@ export function registerPreviewCommand( // Ignore updates for inactive sessions or older versions if ( update.session !== activeSession || - update.version <= latestVersion + update.version < latestVersion ) { return; } @@ -221,6 +303,27 @@ export function registerPreviewCommand( }, ); + // Listen for variant changes from the language server and update preview + const variantsChangedDisposable = client.onNotification( + previewVariantsChanged, + async () => { + if (!activeUri) return; + try { + const document = await vscode.workspace.openTextDocument( + vscode.Uri.parse(activeUri), + ); + await setVariants(document, true); + } catch (error) { + if (!disposed) { + const detail = error instanceof Error ? error.message : String(error); + void vscode.window.showErrorMessage( + `Unable to update Zensical Preview variants: ${detail}`, + ); + } + } + }, + ); + // Dispose of the panel and clean up resources when it is closed panel.onDidDispose(() => { disposed = true; @@ -232,6 +335,7 @@ export function registerPreviewCommand( activeUri = undefined; notificationDisposable?.dispose(); notificationDisposable = undefined; + variantsChangedDisposable.dispose(); if (updateTimer) clearTimeout(updateTimer); if (session) void client.sendNotification(previewStop, { session }); }, undefined, context.subscriptions); @@ -273,6 +377,7 @@ export function registerPreviewCommand( href?: string; uri?: string; end?: number; + variant?: string; }) => { if (message.type === "preview/reveal-source") { if ( @@ -309,6 +414,39 @@ export function registerPreviewCommand( } return; } + if (message.type === "preview/select-variant") { + if ( + !activeUri || + !activeSession || + !message.variant || + !availableVariants.includes(message.variant) || + message.variant === selectedVariant + ) { + return; + } + const session = activeSession; + const uri = activeUri; + selectedVariant = message.variant; + void context.workspaceState.update( + variantStateKey(uri), selectedVariant, + ); + postVariants(); + try { + await client.sendRequest(previewUpdate, { + session, + variant: selectedVariant, + projections: {}, + }); + } catch (error) { + if (session === activeSession && uri === activeUri) { + const detail = error instanceof Error ? error.message : String(error); + void vscode.window.showErrorMessage( + `Unable to update Zensical Preview: ${detail}`, + ); + } + } + return; + } if ( message.type !== "preview/open-link" || !message.href || !activeUri @@ -421,6 +559,9 @@ export function registerPreviewCommand( const previousSession = activeSession; activeSession = undefined; activeUri = undefined; + variantUpdateGeneration += 1; + availableVariants = []; + selectedVariant = undefined; latestVersion = -1; latestUpdate = undefined; if (updateTimer) { @@ -436,9 +577,26 @@ export function registerPreviewCommand( // Start a new preview session for the requested document let result: PreviewStartResult; try { + const variants = await queryVariants(document); + const persisted = context.workspaceState.get( + variantStateKey(uri), + ); + availableVariants = variants?.variants ?? []; + selectedVariant = availableVariants.includes(persisted ?? "") + ? persisted + : variants?.defaultVariant; + if (selectedVariant && availableVariants.includes(selectedVariant)) { + void context.workspaceState.update( + variantStateKey(uri), selectedVariant, + ); + } result = await client.sendRequest( previewStart, - { uri, projections: {} }, + { + uri, + projections: {}, + ...(selectedVariant ? { variant: selectedVariant } : {}), + }, ); } catch (error) { startingUri = undefined; @@ -474,6 +632,7 @@ export function registerPreviewCommand( type: "preview/update", update: withPreviewBase(panel.webview, result.initialUpdate), }); + postVariants(); postEditorPosition(vscode.window.activeTextEditor, true); } } finally { diff --git a/integrations/code/syntaxes/python-markdown.tmLanguage.json b/integrations/code/syntaxes/python-markdown.tmLanguage.json index 237b6a4..38c5df1 100644 --- a/integrations/code/syntaxes/python-markdown.tmLanguage.json +++ b/integrations/code/syntaxes/python-markdown.tmLanguage.json @@ -400,6 +400,9 @@ { "include": "#table" }, + { + "include": "#directive" + }, { "include": "#admonition" }, @@ -441,6 +444,158 @@ } ] }, + "directive": { + "patterns": [ + { + "name": "meta.directive.condition.block.markdown", + "match": "(?:^|\\G)([ \\t]*)(@)(if|elif)([ \\t]+)(.*?)([ \\t]*)$", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.directive.condition.markdown" + }, + "3": { + "name": "keyword.control.directive.condition.markdown" + }, + "4": { + "name": "punctuation.whitespace.markdown" + }, + "5": { + "name": "meta.directive.expression.markdown", + "patterns": [ + { + "match": "(?i:\\b(?:and|or|not)\\b)", + "name": "keyword.operator.logical.directive.markdown" + }, + { + "match": "(==|!=|<=|>=|=|<|>)([ \\t]+)(\\\")([^\\\"\\\\r\\\\n]+)(\\\")", + "captures": { + "1": { + "name": "keyword.operator.comparison.directive.markdown" + }, + "2": { + "name": "punctuation.whitespace.markdown" + }, + "3": { + "name": "punctuation.definition.string.begin.directive.markdown" + }, + "4": { + "name": "string.quoted.double.directive.markdown" + }, + "5": { + "name": "punctuation.definition.string.end.directive.markdown" + } + } + }, + { + "match": "(==|!=|<=|>=|=|<|>)([ \\t]+)([a-zA-Z_][a-zA-Z0-9_.-]*)", + "captures": { + "1": { + "name": "keyword.operator.comparison.directive.markdown" + }, + "2": { + "name": "punctuation.whitespace.markdown" + }, + "3": { + "name": "string.unquoted.directive.markdown" + } + } + }, + { + "match": "==|!=|<=|>=|=|<|>", + "name": "keyword.operator.comparison.directive.markdown" + }, + { + "match": "([\"])([^\"\\r\\n]*)([\"])", + "captures": { + "1": { + "name": "punctuation.definition.string.begin.directive.markdown" + }, + "2": { + "name": "string.quoted.double.directive.markdown" + }, + "3": { + "name": "punctuation.definition.string.end.directive.markdown" + } + } + }, + { + "match": "\\b[a-zA-Z_][a-zA-Z0-9_.-]*\\b", + "name": "variable.other.directive.markdown" + } + ] + }, + "6": { + "name": "punctuation.whitespace.trailing.markdown" + } + } + }, + { + "name": "meta.directive.condition.block.markdown", + "match": "(?:^|\\G)([ \\t]*)(@)(else)([ \\t]*)$", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.directive.condition.markdown" + }, + "3": { + "name": "keyword.control.directive.condition.markdown" + }, + "4": { + "name": "punctuation.whitespace.trailing.markdown" + } + } + }, + { + "name": "meta.directive.use.block.markdown", + "match": "(?:^|\\G)([ \\t]*)(@)(use)([ \\t]+)(.*?)([ \\t]*)$", + "captures": { + "1": { + "name": "punctuation.whitespace.leading.markdown" + }, + "2": { + "name": "keyword.control.directive.use.markdown" + }, + "3": { + "name": "keyword.control.directive.use.markdown" + }, + "4": { + "name": "punctuation.whitespace.markdown" + }, + "5": { + "name": "meta.directive.path.markdown", + "patterns": [ + { + "match": "(\\\")(.+)(\\\")", + "captures": { + "1": { + "name": "punctuation.definition.string.begin.directive.markdown" + }, + "2": { + "name": "string.quoted.double.directive-path.markdown" + }, + "3": { + "name": "punctuation.definition.string.end.directive.markdown" + } + } + }, + { + "match": "\\S+", + "name": "string.unquoted.directive-path.markdown" + } + ] + }, + "6": { + "name": "punctuation.whitespace.trailing.markdown" + } + } + } + ] + }, "table": { "patterns": [ { @@ -6129,6 +6284,9 @@ }, "inline_flow": { "patterns": [ + { + "include": "#directive_variable" + }, { "include": "#inline_code" }, @@ -6176,6 +6334,28 @@ } ] }, + "directive_variable": { + "patterns": [ + { + "name": "meta.directive.variable.inline.markdown", + "match": "(@var)(\\{)([_a-zA-Z][_a-zA-Z0-9]*)(\\})", + "captures": { + "1": { + "name": "keyword.control.directive.variable.markdown" + }, + "2": { + "name": "punctuation.definition.directive.variable.begin.markdown" + }, + "3": { + "name": "variable.other.directive.markdown" + }, + "4": { + "name": "punctuation.definition.directive.variable.end.markdown" + } + } + } + ] + }, "inline_code": { "patterns": [ {