diff --git a/mdsvex.config.js b/mdsvex.config.js index dc569dac..602dc6b6 100644 --- a/mdsvex.config.js +++ b/mdsvex.config.js @@ -1,8 +1,9 @@ -import shiki from 'shiki' import rehypeSlug from 'rehype-slug' import rehypeAutolinkHeadings from 'rehype-autolink-headings' import rehypeExternalLinks from 'rehype-external-links' import { escapeSvelte } from 'mdsvex' +import { lex, parse as parseFence } from 'fenceparser' +import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from 'shiki-twoslash' import { statSync } from 'fs' import { parse, join } from 'path' import { visit } from 'unist-util-visit' @@ -59,10 +60,13 @@ export const mdsvexConfig = { _: './src/lib/components/layout_post.svelte' }, highlight: { - highlighter: async (code, lang) => - `{@html \`${escapeSvelte( - await shiki.getHighlighter({ theme: 'material-default' }).then(highlighter => highlighter.codeToHtml(code, { lang })) - )}\` }` + highlighter: async (code, lang, meta) => { + let fence, twoslash + try { fence = parseFence(lex([lang, meta].filter(Boolean).join(' '))) } + catch (error) { throw new Error(`Could not parse the codefence for this code sample \n${code}`) } + if (fence?.twoslash === true) twoslash = runTwoSlash(code, lang) + return `{@html \`${escapeSvelte(renderCodeToHTML(code, lang, fence ?? {}, {}, await createShikiHighlighter({ theme: 'material-default' }), twoslash))}\` }` + } }, remarkPlugins: [remarkUraraFm, remarkUraraSpoiler], rehypePlugins: [ diff --git a/package.json b/package.json index 8859e457..a2c32653 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte3": "^3.4.1", + "fenceparser": "^2.0.0", "github-slugger": "^1.4.0", "mdast-util-to-string": "^3.1.0", "mdsvex": "^0.9.8", @@ -47,7 +48,7 @@ "rehype-external-links": "^1.0.1", "rehype-slug": "^5.0.1", "remark": "^14.0.2", - "shiki": "^0.9.15", + "shiki-twoslash": "^3.0.2", "svelte": "^3.46.4", "svelte-check": "^2.4.5", "svelte-preprocess": "^4.10.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05dcf135..fb789aee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,7 @@ specifiers: eslint: ^7.32.0 eslint-config-prettier: ^8.5.0 eslint-plugin-svelte3: ^3.4.1 + fenceparser: ^2.0.0 github-slugger: ^1.4.0 mdast-util-to-string: ^3.1.0 mdsvex: ^0.9.8 @@ -30,7 +31,7 @@ specifiers: rehype-external-links: ^1.0.1 rehype-slug: ^5.0.1 remark: ^14.0.2 - shiki: ^0.9.15 + shiki-twoslash: ^3.0.2 svelte: ^3.46.4 svelte-check: ^2.4.5 svelte-preprocess: ^4.10.4 @@ -63,6 +64,7 @@ devDependencies: eslint: 7.32.0 eslint-config-prettier: 8.5.0_eslint@7.32.0 eslint-plugin-svelte3: 3.4.1_eslint@7.32.0+svelte@3.46.4 + fenceparser: 2.0.0 github-slugger: 1.4.0 mdast-util-to-string: 3.1.0 mdsvex: 0.9.8_svelte@3.46.4 @@ -77,7 +79,7 @@ devDependencies: rehype-external-links: 1.0.1 rehype-slug: 5.0.1 remark: 14.0.2 - shiki: 0.9.15 + shiki-twoslash: 3.0.2 svelte: 3.46.4 svelte-check: 2.4.5_267bdea6a4c44ec9b77b315a8750f243 svelte-preprocess: 4.10.4_8406790e2dd5d7d978d865e722004497 @@ -566,6 +568,32 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /@typescript/twoslash/3.1.0: + resolution: {integrity: sha512-kTwMUQ8xtAZaC4wb2XuLkPqFVBj2dNBueMQ89NWEuw87k2nLBbuafeG5cob/QEr6YduxIdTVUjix0MtC7mPlmg==} + dependencies: + '@typescript/vfs': 1.3.5 + debug: 4.3.3 + lz-string: 1.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript/vfs/1.3.4: + resolution: {integrity: sha512-RbyJiaAGQPIcAGWFa3jAXSuAexU4BFiDRF1g3hy7LmRqfNpYlTQWGXjcrOaVZjJ8YkkpuwG0FcsYvtWQpd9igQ==} + dependencies: + debug: 4.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript/vfs/1.3.5: + resolution: {integrity: sha512-pI8Saqjupf9MfLw7w2+og+fmb0fZS0J6vsKXXrp4/PDXEFvntgzXmChCXC/KefZZS0YGS6AT8e0hGAJcTsdJlg==} + dependencies: + debug: 4.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /acorn-jsx/5.3.2_acorn@7.4.1: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1646,6 +1674,10 @@ packages: reusify: 1.0.4 dev: true + /fenceparser/2.0.0: + resolution: {integrity: sha512-E9VwROet683ky1b3RuuLbfSaUD5lFlr5oT3dZr7eObA08zzhh+FFwacGPqsvPhW0nm175bkmpNHWwDDkFMNXMg==} + dev: true + /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2146,6 +2178,12 @@ packages: resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==} dev: true + /lru-cache/5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2153,6 +2191,11 @@ packages: yallist: 4.0.0 dev: true + /lz-string/1.4.4: + resolution: {integrity: sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=} + hasBin: true + dev: true + /magic-string/0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: @@ -2552,6 +2595,12 @@ packages: mimic-fn: 2.1.0 dev: true + /onigasm/2.2.5: + resolution: {integrity: sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==} + dependencies: + lru-cache: 5.1.1 + dev: true + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -3567,11 +3616,22 @@ packages: resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} dev: true - /shiki/0.9.15: - resolution: {integrity: sha512-/Y0z9IzhJ8nD9nbceORCqu6NgT9X6I8Fk8c3SICHI5NbZRLdZYFaB233gwct9sU0vvSypyaL/qaKvzyQGJBZSw==} + /shiki-twoslash/3.0.2: + resolution: {integrity: sha512-2VxUykJ0ETWxageixkfKcMjK5mpMYxJU3uhAoscTZnfX/iq+bWnEpLZnsrcErMJrD85orxABMw5w/GoO4nUiqg==} + dependencies: + '@typescript/twoslash': 3.1.0 + '@typescript/vfs': 1.3.4 + shiki: 0.9.11 + typescript: 4.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /shiki/0.9.11: + resolution: {integrity: sha512-tjruNTLFhU0hruCPoJP0y+B9LKOmcqUhTpxn7pcJB3fa+04gFChuEmxmrUfOJ7ZO6Jd+HwMnDHgY3lv3Tqonuw==} dependencies: jsonc-parser: 3.0.0 - vscode-oniguruma: 1.6.2 + onigasm: 2.2.5 vscode-textmate: 5.2.0 dev: true @@ -4160,10 +4220,6 @@ packages: fsevents: 2.3.2 dev: true - /vscode-oniguruma/1.6.2: - resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==} - dev: true - /vscode-textmate/5.2.0: resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} dev: true @@ -4254,6 +4310,10 @@ packages: engines: {node: '>=0.4'} dev: true + /yallist/3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true diff --git a/src/app.css b/src/app.css index cb9680ee..622bb737 100644 --- a/src/app.css +++ b/src/app.css @@ -47,20 +47,6 @@ html { @apply !relative; } -/* .urara-prose pre */ - -.urara-prose pre { - @apply mockup-code !bg-neutral min-w-0; -} - -.urara-prose pre.shiki::before { - @apply sticky -left-5 -ml-5; -} - -.urara-prose pre:not(.shiki) { - @apply bg-neutral text-neutral-content; -} - /* .urara-prose a */ .urara-prose :is(p, li) > a { @@ -101,4 +87,76 @@ footer a { @apply shadow-xl transition-shadow ease-in-out hover:shadow-2xl; } +/* .prose pre */ + +.prose pre { + @apply mockup-code !bg-neutral min-w-0; +} + +.prose pre:not(.shiki) { + @apply bg-neutral text-neutral-content; +} + +.prose pre:not(.shiki)::before { + @apply sticky -left-5 -ml-5 +} + +/* shiki */ + +pre.shiki { + @apply px-0 +} + +pre.shiki::before { + @apply sticky; +} + +pre.shiki > div.code-title { + @apply absolute -mt-10 ml-20 pt-1.5 pl-1.5 opacity-50 +} + +pre.shiki .language-id { + @apply hidden +} + +pre .code-container { + @apply overflow-auto +} + +pre .code-container > code > div.line > span:first-child { + @apply pl-5 +} + +pre .code-container > code > div.line > span:last-child { + @apply pr-5 +} + +pre.shiki div.dim { + @apply opacity-50 transition-opacity +} + +pre.shiki:hover div.dim { + @apply opacity-100 +} + +pre.shiki div.highlight { + @apply bg-warning/20 +} + +pre.twoslash { + @apply shadow-inner +} + +pre.twoslash data-lsp { + @apply border-b border-dashed border-transparent transition-all +} + +pre.twoslash:hover data-lsp { + @apply border-neutral-content/30 +} + +pre.twoslash data-lsp:hover::before { + @apply content-[attr(lsp)] absolute rounded translate-y-5 bg-neutral-focus text-neutral-content font-mono whitespace-pre-wrap transition-all px-2 py-1 z-50 +} + /* your code here */