diff --git a/.gitignore b/.gitignore index 5feb907..9be5e4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target -node_modules \ No newline at end of file +node_modules +assets/syntax_cache.packdump \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2d7ace4..a2d9a43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,16 @@ dependencies = [ "regex", ] +[[package]] +name = "file-exists-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89c0a483302ef815dfa9f8615b9fc98946eb7345b11c59d9fe7381e0ae2a95" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.1.0" @@ -290,6 +300,7 @@ name = "mdsvexrs" version = "0.1.0" dependencies = [ "clap", + "file-exists-macro", "itertools", "markdown", "regex", @@ -393,9 +404,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -503,9 +514,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.99" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 248e864..02e5539 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [features] fancy = ["syntect/default-fancy"] onig = ["syntect/default-onig"] -default = ["onig"] +default = ["onig", "fancy"] [dependencies] markdown = { version = "1.0.0-alpha.21", features = ["serde"]} @@ -16,4 +16,8 @@ serde_yaml = "0.9.34" serde = { version = "1.0.215", features = ["derive"] } regex = "1.11.1" clap = { version = "4.5.21", features = ["derive"] } -syntect = { version = "5.0", default-features = false } \ No newline at end of file +syntect = { version = "5.0", default-features = false } +file-exists-macro = "0.1" + +[build-dependencies] +syntect = { version = "5.0", default-features = false, features = ["dump-create"] } \ No newline at end of file diff --git a/README.md b/README.md index 62bed30..8bad7b8 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,8 @@ A faster markdown preprocessor for svelte. Compiles `.md` files into `.svelte` w Note that this, like the original MDSvex, trusts it's input and doesn't escape HTML or script files. -This version is not yet tested and published. \ No newline at end of file +This version is not yet tested and published. + +Note that not all svelte syntax is supported yet. Notably, only HTML-like content is handled. If you get invalid syntax, try moving it into a component and just referencing that component. Templates, Ifs etc are not supported. + +Not all languages may be highlighted as syntect doesn't include support for all languages. Sublime syntax is supported and can be added on `Context.syntax_set`, but WASM don't have a way to set it - make an issue to embed the language instead. diff --git a/assets/svelte.sublime-syntax b/assets/svelte.sublime-syntax new file mode 100644 index 0000000..2dbe26c --- /dev/null +++ b/assets/svelte.sublime-syntax @@ -0,0 +1,1181 @@ +%YAML 1.2 +--- +name: Svelte +first_line_match: (?i)<(!DOCTYPE\s*)?html +file_extensions: + - svlt + - svelte +variables: + ascii_space: '\t\n\f ' + + # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attribute_name_char: '[{{ascii_space}}=/>]' + attribute_name_start: (?=[^{{attribute_name_char}}]) + attribute_name_break: (?={{attribute_name_char}}) + + # https://html.spec.whatwg.org/multipage/syntax.html#syntax-attribute-value + unquoted_attribute_start: (?=[^{{ascii_space}}=>]) + unquoted_attribute_break: (?=[{{ascii_space}}]|/?>) + + # https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state + tag_name_char: '[^{{ascii_space}}/<>]' + tag_name_break: (?=[^{{tag_name_char}}]) + tag_name: '[A-Za-z]{{tag_name_char}}*' + + block_tag_name: |- + (?ix: + address|applet|article|aside|blockquote|center|dd|dir|div|dl|dt|figcaption|figure|footer|frame|frameset|h1|h2|h3|h4|h5|h6|header|iframe|menu|nav|noframes|object|ol|p|pre|section|ul + ){{tag_name_break}} + + inline_tag_name: |- + (?ix: + abbr|acronym|area|audio|b|base|basefont|bdi|bdo|big|br|canvas|caption|cite|code|del|details|dfn|dialog|em|font|head|html|i|img|ins|isindex|kbd|li|link|map|mark|menu|menuitem|meta|noscript|param|picture|q|rp|rt|rtc|ruby|s|samp|script|small|source|span|strike|strong|style|sub|summary|sup|time|title|track|tt|u|var|video|wbr + ){{tag_name_break}} + + form_tag_name: |- + (?ix: + button|datalist|input|label|legend|meter|optgroup|option|output|progress|select|template|textarea + ){{tag_name_break}} + + javascript_mime_type: |- + (?ix: + # https://mimesniff.spec.whatwg.org/#javascript-mime-type + (?:application|text)/(?:x-)?(?:java|ecma)script + | text/javascript1\.[0-5] + | text/jscript + | text/livescript + ) + + custom_element_char: |- + (?x: + # https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts + [-_a-z0-9\x{00B7}] + | \\\. + | [\x{00C0}-\x{00D6}] + | [\x{00D8}-\x{00F6}] + | [\x{00F8}-\x{02FF}] + | [\x{0300}-\x{037D}] + | [\x{037F}-\x{1FFF}] + | [\x{200C}-\x{200D}] + | [\x{203F}-\x{2040}] + | [\x{2070}-\x{218F}] + | [\x{2C00}-\x{2FEF}] + | [\x{3001}-\x{D7FF}] + | [\x{F900}-\x{FDCF}] + | [\x{FDF0}-\x{FFFD}] + | [\x{10000}-\x{EFFFF}] + ) + + script_close_lookahead: (?i:(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: (?=\S) + embed: scope:source.css + embed_scope: source.css.embedded.html + escape: (?i)(?=(?:-->\s*)? + scope: punctuation.definition.tag.end.html + pop: true + + script-type-decider: + - match: (?i)(?={{javascript_mime_type}}{{unquoted_attribute_break}}|'{{javascript_mime_type}}'|"{{javascript_mime_type}}") + set: + - script-javascript + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?i)(?=module{{unquoted_attribute_break}}|'module'|"module") + set: + - script-javascript + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?i)(?=>|''|"") + set: + - script-javascript + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?i)(?=text/html{{unquoted_attribute_break}}|'text/html'|"text/html") + set: + - script-html + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?=\S) + set: + - script-other + - tag-generic-attribute-meta + - tag-generic-attribute-value + + style-lang-attribute: + - match: (?i)\btype\b + scope: meta.attribute-with-value.html entity.other.attribute-name.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - match: = + scope: punctuation.separator.key-value.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - include: style-lang-decider-type + - match: (?=\S) + set: style-css + - match: (?i)\blang\b + scope: meta.attribute-with-value.html entity.other.attribute-name.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - match: = + scope: punctuation.separator.key-value.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - include: style-lang-decider-lang + - match: (?=\S) + set: style-css + + tag-end: + - match: '>' + scope: punctuation.definition.tag.end.html + pop: true + + tag-class-attribute-value: + - include: set-interpolation + + - match: '"' + scope: punctuation.definition.string.begin.html + set: + - meta_scope: string.quoted.double.html + - meta_content_scope: meta.class-name.html + - match: '"' + scope: punctuation.definition.string.end.html + pop: true + - include: entities + - match: "'" + scope: punctuation.definition.string.begin.html + set: + - meta_scope: string.quoted.single.html + - meta_content_scope: meta.class-name.html + - match: "'" + scope: punctuation.definition.string.end.html + pop: true + - include: entities + - match: '{{unquoted_attribute_start}}' + set: + - meta_scope: string.unquoted.html meta.class-name.html + - match: '{{unquoted_attribute_break}}' + pop: true + - match: "[\"'`<]" + scope: invalid.illegal.attribute-value.html + - include: else-pop + + style-common: + - include: style-lang-attribute + + - include: style-type-attribute + - include: tag-attributes + - include: tag-end-self-closing + + cdata: + - match: (' + scope: punctuation.definition.tag.end.html + pop: true + + style-other: + - meta_content_scope: meta.tag.style.begin.html + - include: style-common + - match: '>' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + + tag-other-body: + - meta_scope: meta.tag.other.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + + tag-generic-attribute-name: + - meta_scope: entity.other.attribute-name.html + - match: '{{attribute_name_break}}' + pop: true + - match: "[\"'`<]" + scope: invalid.illegal.attribute-name.html + + script-lang-decider-type: + - match: (?i)(?=text/(?:ts|typescript)(?!{unquoted_attribute_value})|\'text/(?:ts|typescript)\'|"text/(?:ts|typescript)") + set: + - - meta_content_scope: meta.tag.script.begin.html + - include: script-common + - match: '>' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.ts.embedded.html + embed: scope:source.ts + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.coffee.embedded.html + embed: scope:source.coffee + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.livescript.embedded.html + embed: scope:source.livescript + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.js.embedded.html + embed: Packages/Babel/JavaScript (Babel).sublime-syntax + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.scss.embedded.html + embed: scope:source.scss + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.sass.embedded.html + embed: scope:source.sass + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.less.embedded.html + embed: scope:source.less + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.stylus.embedded.html + embed: scope:source.stylus + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.postcss.embedded.html + embed: scope:source.postcss + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.scss.embedded.html + embed: scope:source.scss + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.sass.embedded.html + embed: scope:source.sass + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.less.embedded.html + embed: scope:source.less + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.stylus.embedded.html + embed: scope:source.stylus + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: style-close-tag + - match: '' + embed_scope: source.postcss.embedded.html + embed: scope:source.postcss + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.ts.embedded.html + embed: scope:source.ts + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.coffee.embedded.html + embed: scope:source.coffee + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.livescript.embedded.html + embed: scope:source.livescript + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: '' + embed_scope: source.js.embedded.html + embed: Packages/Babel/JavaScript (Babel).sublime-syntax + escape: (?i)(?=(?:-->\s*)?' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + + svelte-attributes-string: + - match: (?i)\b(on|bind|class)(:)([A-z0-9_-]+)\b + scope: entity.other.attribute-name.svelte + captures: + 1: support.function.svelte + 2: punctuation.separator.svelte + 3: string.unquoted.svelte + push: tag-generic-attribute-equals + with_prototype: + - include: svelte-attribute-modifiers + + main: + - include: control + + - include: comment + - include: cdata + - include: doctype + - match: (<\?)(xml) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.xml.html + push: + - meta_scope: meta.tag.preprocessor.xml.html + - match: \?> + scope: punctuation.definition.tag.end.html + pop: true + - include: tag-generic-attribute + - include: string-double-quoted + - include: string-single-quoted + - match: (<)((?i:style)){{tag_name_break}} + captures: + 0: meta.tag.style.begin.html + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.style.html + push: style-css + - match: (<)((?i:script)){{tag_name_break}} + captures: + 0: meta.tag.script.begin.html + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.script.html + push: script-javascript + - match: (?)((?i:body|head|html){{tag_name_break}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.structure.any.html + push: + - meta_scope: meta.tag.structure.any.html + - include: tag-end + - include: tag-attributes + - match: (?)({{block_tag_name}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.block.any.html + push: + - meta_scope: meta.tag.block.any.html + - include: tag-end + - include: tag-attributes + - match: (?)((?i:hr){{tag_name_break}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.block.any.html + push: + - meta_scope: meta.tag.block.any.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + - match: (?)((?i:form|fieldset){{tag_name_break}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.block.form.html + push: + - meta_scope: meta.tag.block.form.html + - include: tag-end + - include: tag-attributes + - match: (?)({{inline_tag_name}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.inline.any.html + push: + - meta_scope: meta.tag.inline.any.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + - match: (?)({{form_tag_name}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.inline.form.html + push: + - meta_scope: meta.tag.inline.form.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + - match: (?)((?i:a){{tag_name_break}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.inline.a.html + push: + - meta_scope: meta.tag.inline.a.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + - match: (?)((?i:col|colgroup|table|tbody|td|tfoot|th|thead|tr){{tag_name_break}}) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.inline.table.html + push: + - meta_scope: meta.tag.inline.table.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + - match: ?(?=[A-Za-z]{{tag_name_char}}*?-) + scope: punctuation.definition.tag.begin.html + push: + - tag-custom-body + - tag-custom-name + - match: ?(?=[A-Za-z]) + scope: punctuation.definition.tag.begin.html + push: + - tag-other-body + - tag-other-name + - include: entities + - match: <> + scope: invalid.illegal.incomplete.html + + tag-event-attribute-equals: + - match: '=' + scope: punctuation.separator.key-value.html + set: tag-event-attribute-value + - include: else-pop + + tag-event-attribute: + - match: |- + (?x)\bon( + abort|autocomplete|autocompleteerror|auxclick|blur|cancel|canplay + |canplaythrough|change|click|close|contextmenu|cuechange|dblclick|drag + |dragend|dragenter|dragexit|dragleave|dragover|dragstart|drop + |durationchange|emptied|ended|error|focus|input|invalid|keydown + |keypress|keyup|load|loadeddata|loadedmetadata|loadstart|mousedown + |mouseenter|mouseleave|mousemove|mouseout|mouseover|mouseup|mousewheel + |pause|play|playing|progress|ratechange|reset|resize|scroll|seeked + |seeking|select|show|sort|stalled|submit|suspend|timeupdate|toggle + |volumechange|waiting + )\b + scope: entity.other.attribute-name.event.html + push: + - tag-event-attribute-meta + - tag-event-attribute-equals + + control: + - match: \{@(html|debug) + scope: punctuation.section.embedded.begin.svelte + push: embed-scope + + - match: (\{)(#each)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.control.loop.svelte + push: embed-scope + with_prototype: + - match: (?i)\bas\b + scope: keyword.control.loop.svelte + - match: (\b[_$\p{L}\p{Nl}][_$\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*\b)\s+ + captures: + 1: variable.other.readwrite.svelte + - match: (,)\s*(\b[_$\p{L}\p{Nl}][_$\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*\b)? + captures: + 1: keyword.operator.comma.svelte + 2: variable.other.readwrite.svelte + + - match: (\{)(\/each)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.control.loop.svelte + push: embed-scope + + - match: (\{)(#if|\/if|:else(?:\s+if)?)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.control.conditional.svelte + push: embed-scope + + - match: (\{)(#await)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.control.await.svelte + push: embed-scope + with_prototype: + - match: (?i)\b(then|catch)\b + scope: keyword.control.await.svelte + + - match: (\{)(\/await|:then|:catch)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.control.await.svelte + scope: keyword.control.await.svelte + push: embed-scope + + - match: (\{)(#key|\/key)\b + captures: + 1: punctuation.section.embedded.begin.svelte + 2: keyword.other.svelte + push: embed-scope + + tag-style-attribute-meta: + - meta_scope: meta.attribute-with-value.style.html + - include: immediately-pop + + style-close-tag: + - match: (?i)()(style)(>) + scope: meta.tag.style.end.html + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.style.html + 3: punctuation.definition.tag.end.html + pop: true + + tag-attributes: + - include: push-interpolation + - include: svelte-attributes-string + - include: svelte-attributes-variable + - include: svelte-attributes-storage + + - include: tag-id-attribute + - include: tag-class-attribute + - include: tag-style-attribute + - include: tag-event-attribute + - include: tag-generic-attribute + + tag-class-attribute-meta: + - meta_scope: meta.attribute-with-value.class.html + - include: immediately-pop + + else-pop: + - match: (?=\S) + pop: true + + comment: + - match: ( + scope: invalid.illegal.bad-comments-or-CDATA.html + + tag-custom-name: + - meta_content_scope: entity.name.tag.custom.html + - match: '{{tag_name_break}}' + pop: true + - match: '{{custom_element_char}}+' + # no scope + - match: '{{tag_name_char}}' + scope: invalid.illegal.custom-tag-name.html + + script-javascript: + - meta_content_scope: meta.tag.script.begin.html + - include: script-common + - match: '>' + scope: punctuation.definition.tag.end.html + set: + - include: script-close-tag + - match: (?=\S) + embed: scope:source.js + embed_scope: source.js.embedded.html + escape: '{{script_close_lookahead}}' + + string-single-quoted: + - match: "'" + scope: punctuation.definition.string.begin.html + push: + - meta_scope: string.quoted.single.html + - match: "'" + scope: punctuation.definition.string.end.html + pop: true + - include: entities + + svelte-attributes-variable: + - match: (?i)\b(in|out|transition|animate|use)(:)([A-z0-9_-]+)\b + scope: entity.other.attribute-name.svelte + captures: + 1: support.function.svelte + 2: punctuation.separator.svelte + 3: variable.other.readwrite.svelte + push: tag-generic-attribute-equals + with_prototype: + - include: svelte-attribute-modifiers + + style-type-decider: + - match: (?i)(?=text/css{{unquoted_attribute_break}}|'text/css'|"text/css") + set: + - style-css + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?i)(?=>|''|"") + set: + - style-css + - tag-generic-attribute-meta + - tag-generic-attribute-value + - match: (?=\S) + set: + - style-other + - tag-generic-attribute-meta + - tag-generic-attribute-value + + doctype-content: + - match: \[ + scope: punctuation.section.brackets.begin.html + set: + - meta_scope: meta.brackets.html meta.internal-subset.xml.html + - match: \] + scope: punctuation.section.brackets.end.html + pop: true + - include: comment + - include: string-double-quoted + - include: string-single-quoted + - include: else-pop + + tag-style-attribute: + - match: \bstyle\b + scope: entity.other.attribute-name.style.html + push: + - tag-style-attribute-meta + - tag-style-attribute-equals + + tag-style-attribute-equals: + - match: '=' + scope: punctuation.separator.key-value.html + set: tag-style-attribute-value + - include: else-pop + + doctype-meta: + - meta_scope: meta.tag.sgml.doctype.html + - match: '>' + scope: punctuation.definition.tag.end.html + pop: true + + tag-generic-attribute-equals: + - match: '=' + scope: punctuation.separator.key-value.html + set: tag-generic-attribute-value + - include: else-pop + + tag-id-attribute-equals: + - match: '=' + scope: punctuation.separator.key-value.html + set: tag-id-attribute-value + - include: else-pop + + immediately-pop: + - match: '' + pop: true + + tag-end-maybe-self-closing: + - match: /?> + scope: punctuation.definition.tag.end.html + pop: true + tag-id-attribute: + - match: \bid\b + scope: entity.other.attribute-name.id.html + push: + - tag-id-attribute-meta + - tag-id-attribute-equals + + tag-generic-attribute: + - match: '{{attribute_name_start}}' + push: + - tag-generic-attribute-meta + - tag-generic-attribute-equals + - tag-generic-attribute-name + + string-double-quoted: + - match: '"' + scope: punctuation.definition.string.begin.html + push: + - meta_scope: string.quoted.double.html + - match: '"' + scope: punctuation.definition.string.end.html + pop: true + - include: entities + tag-custom-body: + - meta_scope: meta.tag.custom.html + - include: tag-end-maybe-self-closing + - include: tag-attributes + + tag-id-attribute-meta: + - meta_scope: meta.attribute-with-value.id.html + - include: immediately-pop + + push-interpolation: + - match: \{ + scope: punctuation.section.embedded.begin.svelte + push: embed-scope + with_prototype: + - match: \$\$(restProps|props) + scope: variable.language.dollar.js + + tag-generic-attribute-value: + - include: set-interpolation + + - match: '"' + scope: punctuation.definition.string.begin.html + set: + - meta_scope: string.quoted.double.html + - match: '"' + scope: punctuation.definition.string.end.html + pop: true + - include: entities + - match: "'" + scope: punctuation.definition.string.begin.html + set: + - meta_scope: string.quoted.single.html + - match: "'" + scope: punctuation.definition.string.end.html + pop: true + - include: entities + - match: '{{unquoted_attribute_start}}' + set: + - meta_scope: string.unquoted.html + - match: '{{unquoted_attribute_break}}' + pop: true + - match: "[\"'`<]" + scope: invalid.illegal.attribute-value.html + - include: else-pop + + tag-event-attribute-value: + - include: set-interpolation + + - match: '"' + scope: string.quoted.double punctuation.definition.string.begin.html + embed: scope:source.js + embed_scope: meta.attribute-with-value.event.html + escape: '"' + escape_captures: + 0: string.quoted.double punctuation.definition.string.end.html + - match: "'" + scope: string.quoted.single punctuation.definition.string.begin.html meta.attribute-with-value.event.html + embed: scope:source.js + embed_scope: meta.attribute-with-value.event.html + escape: "'" + escape_captures: + 0: string.quoted.single punctuation.definition.string.end.html + - include: else-pop + + tag-other-name: + - meta_content_scope: entity.name.tag.other.html + - match: '{{tag_name_break}}' + pop: true + + svelte-attributes-storage: + - match: (?i)\b(let)(:)([A-z0-9_-]+)\b + scope: entity.other.attribute-name.svelte + captures: + 1: storage.type.svelte + 2: punctuation.separator.svelte + 3: variable.other.readwrite.svelte + push: tag-generic-attribute-equals + + entities: + - include: push-interpolation + + - match: ([xX])\h+(;) + scope: constant.character.entity.hexadecimal.html + captures: + 1: punctuation.definition.entity.html + 2: punctuation.terminator.entity.html + - match: ()[0-9]+(;) + scope: constant.character.entity.decimal.html + captures: + 1: punctuation.definition.entity.html + 2: punctuation.terminator.entity.html + - match: (&)[a-zA-Z0-9]+(;) + scope: constant.character.entity.named.html + captures: + 1: punctuation.definition.entity.html + 2: punctuation.terminator.entity.html + + script-type-attribute: + - match: (?i)\btype\b + scope: meta.attribute-with-value.html entity.other.attribute-name.html + set: + - meta_content_scope: meta.tag.script.begin.html meta.attribute-with-value.html + - match: = + scope: punctuation.separator.key-value.html + set: + - meta_content_scope: meta.tag.script.begin.html meta.attribute-with-value.html + - include: script-type-decider + - match: (?=\S) + set: script-javascript + + script-html: + - meta_content_scope: meta.tag.script.begin.html + - include: script-common + - match: '>' + scope: punctuation.definition.tag.end.html + set: + - meta_content_scope: text.html.embedded.html + - include: comment + - include: script-close-tag + - include: main + + style-type-attribute: + - match: (?i)\btype\b + scope: meta.attribute-with-value.html entity.other.attribute-name.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - match: = + scope: punctuation.separator.key-value.html + set: + - meta_content_scope: meta.tag.style.begin.html meta.attribute-with-value.html + - include: style-type-decider + - match: (?=\S) + set: style-css + + script-close-tag: + - match: + scope: comment.block.html punctuation.definition.comment.end.html + - match: (?i:()(script)) + captures: + 1: punctuation.definition.tag.begin.html + 2: entity.name.tag.script.html + set: + - meta_scope: meta.tag.script.end.html + - include: tag-end + - include: tag-attributes diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..639a38c --- /dev/null +++ b/build.rs @@ -0,0 +1,10 @@ +use syntect::{dumps::dump_to_uncompressed_file, parsing::SyntaxSet}; + +fn main() { + println!("cargo:rerun-if-changed=assets"); + let mut ss = SyntaxSet::load_defaults_newlines().into_builder(); + ss.add_from_folder("assets", true).unwrap(); + let ss = ss.build(); + + dump_to_uncompressed_file(&ss, "target/syntax_cache.packdump").expect("Failed to dump syntax cache"); +} \ No newline at end of file diff --git a/packages/vite/index.d.ts b/packages/vite/index.d.ts index d86d452..9bb72fd 100644 --- a/packages/vite/index.d.ts +++ b/packages/vite/index.d.ts @@ -1,5 +1,5 @@ -interface Options { +export interface Options { layout: string; } @@ -8,4 +8,4 @@ interface Plugin { markup: (opts: { content: string, filename: string }) => { code: string } | undefined; } -export function mdsvexrs(options): Plugin; \ No newline at end of file +export function mdsvexrs(options: Options): Plugin; \ No newline at end of file diff --git a/packages/vite/index.js b/packages/vite/index.js index e6d8cdd..8bf8e7c 100644 --- a/packages/vite/index.js +++ b/packages/vite/index.js @@ -1,4 +1,4 @@ -import { render } from "mdsvexrs-wasm" +import * as wasm from "mdsvexrs-wasm" export function mdsvexrs(options) { return { @@ -7,7 +7,7 @@ export function mdsvexrs(options) { if(!filename || !filename.endsWith('.md')) return return { - code: render(content, options) + code: wasm.render(content, options.layout) } } } diff --git a/packages/vite/package.json b/packages/vite/package.json index d59a32a..211745b 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -3,11 +3,12 @@ "version": "0.1.0", "description": "", "main": "index.js", + "types": "index.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "mdsvexrs-wasm": "0.1.0" + "mdsvexrs-wasm": "file:../wasm/pkg" }, "keywords": [], "author": "", diff --git a/packages/wasm/Cargo.lock b/packages/wasm/Cargo.lock index 8da1ca8..7b63cf4 100644 --- a/packages/wasm/Cargo.lock +++ b/packages/wasm/Cargo.lock @@ -201,6 +201,16 @@ dependencies = [ "regex", ] +[[package]] +name = "file-exists-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89c0a483302ef815dfa9f8615b9fc98946eb7345b11c59d9fe7381e0ae2a95" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.1.0" @@ -287,6 +297,7 @@ name = "mdsvexrs" version = "0.1.0" dependencies = [ "clap", + "file-exists-macro", "itertools", "markdown", "regex", @@ -370,9 +381,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -480,9 +491,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.99" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/packages/wasm/package.json b/packages/wasm/package.json new file mode 100644 index 0000000..a3cb75f --- /dev/null +++ b/packages/wasm/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "build": "wasm-pack build --target nodejs" + }, + "packageManager": "pnpm@9.5.0+sha1.8c155dc114e1689d18937974f6571e0ceee66f1d" +} diff --git a/packages/wasm/src/lib.rs b/packages/wasm/src/lib.rs index f8945f0..b48dbfe 100644 --- a/packages/wasm/src/lib.rs +++ b/packages/wasm/src/lib.rs @@ -5,30 +5,36 @@ use mdsvexrs::Context; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -#[wasm_bindgen] -pub struct Options { - layout: String, - // path: String, -} +// #[wasm_bindgen] +// pub struct Options { +// layout: String, +// // path: String, +// } + +// #[wasm_bindgen] +// impl Options { +// #[wasm_bindgen(getter)] +// pub fn layout(&self) -> String { +// self.layout.clone() +// } + +// #[wasm_bindgen(setter)] +// pub fn set_layout(&mut self, layout: String) { +// self.layout = layout; +// } +// } + +// #[wasm_bindgen] +// pub fn get_default_options() -> Options { +// Options { +// layout: String::new(), +// } +// } #[wasm_bindgen] -impl Options { - #[wasm_bindgen(getter)] - pub fn layout(&self) -> String { - self.layout.clone() - } - - #[wasm_bindgen(setter)] - pub fn set_layout(&mut self, layout: String) { - self.layout = layout; - } -} - - -#[wasm_bindgen] -pub fn render(contents: &str, options: Options) -> String { +pub fn render(contents: &str, layout: &str) -> String { Context::new(mdsvexrs::MdsvexrsOptions { - layout: options.layout.to_string(), + layout: layout.to_string(), // path: options.path, }) .convert(contents) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..5004879 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - packages/vite + - packages/wasm/pkg \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3158579..d17c292 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ use std::{ sync::LazyLock, - time::{Duration, Instant}, + time::{Duration/*, Instant*/}, }; use itertools::Itertools; @@ -18,11 +18,7 @@ use markdown::{ use serde::Serialize; use serde_json::Value; use syntect::{ - easy::HighlightLines, - highlighting::ThemeSet, - html::{append_highlighted_html_for_styled_line, IncludeBackground}, - parsing::SyntaxSet, - util::LinesWithEndings, + dumps::from_uncompressed_data, easy::HighlightLines, highlighting::ThemeSet, html::{append_highlighted_html_for_styled_line, IncludeBackground}, parsing::{SyntaxDefinition, SyntaxSet}, util::LinesWithEndings }; #[derive(Debug)] @@ -174,7 +170,7 @@ impl ToHtml for InlineCode { impl ToHtml for InlineMath { fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult { - todo!() + ToHtmlResult::new(self.value.clone(), false) } } @@ -542,13 +538,13 @@ pub struct Context { pub options: MdsvexrsOptions, pub titles: Vec
{}", html_encode(&code.code));
+ return format!("{}", html_encode(lang), html_encode(&code.code));
}
};
let mut highlighter = HighlightLines::new(syntax, theme);
@@ -615,7 +612,7 @@ impl Context {
let regions = highlighter
.highlight_line(&code.code, &self.syntax_set)
.unwrap();
- string += "";
+ string += &format!("", html_encode(lang));
append_highlighted_html_for_styled_line(
®ions[..],
IncludeBackground::No,
@@ -625,7 +622,7 @@ impl Context {
string += "";
}
false => {
- string += "\n";
+ string += &format!("", html_encode(lang));
for line in LinesWithEndings::from(&code.code) {
let regions = highlighter.highlight_line(line, &self.syntax_set).unwrap();
append_highlighted_html_for_styled_line(
@@ -639,7 +636,9 @@ impl Context {
}
};
- self.highlight_times += start.elapsed();
+ // #[cfg(not(target_arch = "wasm32"))] {
+ // self.highlight_times += start.elapsed();
+ // }
string
}
@@ -648,12 +647,18 @@ impl Context {
}
pub fn convert(&mut self, input: &str) -> String {
- let start = Instant::now();
+ // #[cfg(not(target_arch = "wasm32"))]
+ // let start = Instant::now();
let ast = markdown::to_mdast(input, &DEFAULT_MD_OPTIONS).unwrap();
- self.parse_time = start.elapsed();
- let start = Instant::now();
+ // #[cfg(not(target_arch = "wasm32"))] {
+ // self.parse_time = start.elapsed();
+ // }
+ // #[cfg(not(target_arch = "wasm32"))]
+ // let start = Instant::now();
ast.visit(self);
- self.visit_time = start.elapsed();
+ // #[cfg(not(target_arch = "wasm32"))] {
+ // self.visit_time = start.elapsed();
+ // }
if let Some(yaml) = &self.yaml {
if let Some(val) = yaml.get("defaultLang") {
@@ -661,10 +666,13 @@ impl Context {
}
}
- let start = Instant::now();
+ // #[cfg(not(target_arch = "wasm32"))]
+ // let start = Instant::now();
let res = ast.to_html(self);
let html = finish(res);
- self.convert_time = start.elapsed();
+ // #[cfg(not(target_arch = "wasm32"))] {
+ // self.convert_time = start.elapsed();
+ // }
if let Some(yaml) = &mut self.yaml {
yaml.insert(
@@ -695,17 +703,22 @@ impl Context {
format!(
"
{script}
-
+
{html}
"
)
}
+ // #[cfg(not(target_arch = "wasm32"))]
+ // pub fn print_timings(&self) {
+ // println!("Parse: {:?}", self.parse_time);
+ // println!("Visit: {:?}", self.visit_time);
+ // println!("Convert: {:?}", self.convert_time);
+ // println!("Highlight: {:?}", self.highlight_times);
+ // }
+ // #[cfg(target_arch = "wasm32")]
pub fn print_timings(&self) {
- println!("Parse: {:?}", self.parse_time);
- println!("Visit: {:?}", self.visit_time);
- println!("Convert: {:?}", self.convert_time);
- println!("Highlight: {:?}", self.highlight_times);
+ println!("wasm timings are not available");
}
}
diff --git a/src/main.rs b/src/main.rs
index d8a7b16..0f41d7f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,10 @@
use std::io::{stdin, Read};
-use clap::Parser;
+use clap::{Parser, Subcommand};
use mdsvexrs::Context;
#[derive(Parser)]
+#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
layout: String,
@@ -13,8 +14,10 @@ struct Args {
timings: bool,
}
+
fn main() {
let args = Args::parse();
+
let mut ctx = Context::new(mdsvexrs::MdsvexrsOptions {
layout: args.layout,
// path: args.path,