shiki-twoslash
Advanced tools
Comparing version 0.6.1 to 0.6.2
import { Highlighter } from "shiki/dist/highlighter"; | ||
import { TwoSlashReturn } from "@typescript/twoslash"; | ||
/** Checks if it is available in shiki */ | ||
export declare const canHighlightLang: (lang: string) => boolean; | ||
export declare const createShikiHighlighter: (options: import("shiki/dist/highlighter").HighlighterOptions) => Highlighter | Promise<Highlighter>; | ||
import { TwoSlashOptions, TwoSlashReturn } from "@typescript/twoslash"; | ||
export declare type ShikiTwoslashSettings = { | ||
@@ -10,6 +7,18 @@ useNodeModules?: true; | ||
}; | ||
/** Checks if a particular lang is available in shiki */ | ||
export declare const canHighlightLang: (lang: string) => boolean; | ||
/** | ||
* Creates a Shiki highlighter, this is an async call because of the call to WASM to get the regex parser set up. | ||
* | ||
* In other functions, passing a the result of this highlighter function is kind of optional but it's the author's | ||
* opinion that you should be in control of the highlighter, and not this library. | ||
* | ||
*/ | ||
export declare const createShikiHighlighter: (options: import("shiki/dist/highlighter").HighlighterOptions) => Highlighter | Promise<Highlighter>; | ||
export declare const defaultShikiTwoslashSettings: ShikiTwoslashSettings; | ||
/** Uses Shiki to render the code to HTML */ | ||
export declare const renderCodeToHTML: (code: string, lang: string, highlighter?: Highlighter | undefined, twoslash?: TwoSlashReturn | undefined) => string; | ||
/** Runs Twoslash over the code in the */ | ||
export declare const runTwoSlash: (code: string, lang: string, settings?: ShikiTwoslashSettings) => TwoSlashReturn; | ||
/** | ||
* Runs Twoslash over the code passed in with a particular language as the default file. | ||
*/ | ||
export declare const runTwoSlash: (code: string, lang: string, settings?: ShikiTwoslashSettings, twoslashDefaults?: TwoSlashOptions) => TwoSlashReturn; |
@@ -10,2 +10,20 @@ 'use strict'; | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
var splice = function splice(str, idx, rem, newString) { | ||
@@ -288,3 +306,3 @@ return str.slice(0, idx) + newString + str.slice(idx + Math.abs(rem)); | ||
[].concat(shikiLanguages.commonLangIds, shikiLanguages.commonLangAliases, shikiLanguages.otherLangIds); | ||
/** Checks if it is available in shiki */ | ||
/** Checks if a particular lang is available in shiki */ | ||
@@ -301,2 +319,10 @@ var canHighlightLang = function canHighlightLang(lang) { | ||
var storedHighlighter = null; | ||
/** | ||
* Creates a Shiki highlighter, this is an async call because of the call to WASM to get the regex parser set up. | ||
* | ||
* In other functions, passing a the result of this highlighter function is kind of optional but it's the author's | ||
* opinion that you should be in control of the highlighter, and not this library. | ||
* | ||
*/ | ||
var createShikiHighlighter = function createShikiHighlighter(options) { | ||
@@ -340,8 +366,10 @@ if (storedHighlighter) return storedHighlighter; | ||
return results; | ||
}; // Basically so that we can store this once, then re-use it | ||
}; // Basically so that we can store this once, then re-use it in the same process | ||
var nodeModulesMap = undefined; | ||
/** Runs Twoslash over the code in the */ | ||
/** | ||
* Runs Twoslash over the code passed in with a particular language as the default file. | ||
*/ | ||
var runTwoSlash = function runTwoSlash(code, lang, settings) { | ||
var runTwoSlash = function runTwoSlash(code, lang, settings, twoslashDefaults) { | ||
if (settings === void 0) { | ||
@@ -351,2 +379,6 @@ settings = defaultShikiTwoslashSettings; | ||
if (twoslashDefaults === void 0) { | ||
twoslashDefaults = {}; | ||
} | ||
var map = undefined; // Shiki doesn't respect json5 as an input, so switch it | ||
@@ -379,5 +411,5 @@ // to json, which can handle comments in the syntax highlight | ||
var results = twoslash.twoslasher(code, lang, { | ||
var results = twoslash.twoslasher(code, lang, _extends({}, twoslashDefaults, { | ||
fsMap: map | ||
}); | ||
})); | ||
return results; | ||
@@ -384,0 +416,0 @@ }; |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("shiki"),n=require("shiki-languages"),t=require("@typescript/twoslash"),r=require("@typescript/vfs");function a(e){var n={"<":"lt",'"':"quot","'":"apos","&":"amp","\r":"#10","\n":"#13"};return e.toString().replace(/[<"'\r\n&]/g,(function(e){return"&"+n[e]+";"}))}function s(e){return e.replace(/</g,"<").replace(/>/g,">")}function o(e,n){var t=new Map;return e.forEach((function(e){var r=n(e),a=t.get(r);a?a.push(e):t.set(r,[e])})),t}var i=[].concat(n.commonLangIds,n.commonLangAliases,n.otherLangIds),c=null,l={},u=void 0;exports.canHighlightLang=function(e){return i.includes(e)},exports.createShikiHighlighter=function(n){if(c)return c;var t,r=(n||{}).theme||"nord";try{t=e.getTheme(r)}catch(n){try{t=e.loadTheme(r)}catch(e){throw new Error("Unable to load theme: "+r+" - "+e.message)}}return e.getHighlighter({theme:t,langs:i}).then((function(e){return c=e}))},exports.defaultShikiTwoslashSettings=l,exports.renderCodeToHTML=function(e,n,t,r){if(!t&&!c)throw new Error("The highlighter object hasn't been initialised via `setupHighLighter` yet in render-shiki-twoslash");return function(e,n,t){if(!t)return function(e,n){var t="";return t+='<pre class="shiki">',n.langId&&(t+='<div class="language-id">'+n.langId+"</div>"),t+="<div class='code-container'><code>",e.forEach((function(e){0===e.length?t+="\n":(e.forEach((function(e){t+='<span style="color: '+e.color+'">'+s(e.content)+"</span>"})),t+="\n")})),t=t.replace(/\n*$/,""),t+="</code></div></pre>"}(e,n);var r="";r+='<pre class="shiki twoslash">',n.langId&&(r+='<div class="language-id">'+n.langId+"</div>"),r+="<div class='code-container'><code>";var i,c=o(t.errors,(function(e){return e.line}))||new Map,l=o(t.staticQuickInfos,(function(e){return e.line}))||new Map,u=o(t.queries,(function(e){return e.line-1}))||new Map,p=0;return e.forEach((function(e,n){var t=c.get(n)||[],o=l.get(n)||[],i=u.get(n)||[];if(0===e.length&&0===n)p+=1;else if(0===e.length)p+=1,r+="\n";else{var d=0;e.forEach((function(e){var n="",s=function(n){return function(t){return n<=t.character&&n+e.content.length>=t.character+t.length}},c=t.filter(s(d)),l=o.filter(s(d)),u=i.filter(s(d)),h=[].concat(c,l,u).sort((function(e,n){return(e.start||0)-(n.start||0)}));n+=h.length?function(e,n){var t=[],r=!1;e.forEach((function(e){"lsp"===e.classes?(t.push({text:"⇍/data-lsp⇏",index:e.end}),t.push({text:"⇍data-lsp lsp=⇯"+(e.lsp||"")+"⇯⇏",index:e.begin})):"err"===e.classes?r=!0:"query"===e.classes&&(t.push({text:"⇍/data-highlight⇏",index:e.end}),t.push({text:"⇍data-highlight'⇏",index:e.begin}))}));var s=(" "+n).slice(1);return t.sort((function(e,n){return n.index-e.index})).forEach((function(e){var n,t,r;r=e.text,s=(n=s).slice(0,t=e.index)+r+n.slice(t+Math.abs(0))})),r&&(s="⇍data-err⇏"+s+"⇍/data-err⇏"),a(s).replace(/⇍/g,"<").replace(/⇏/g,">").replace(/⇯/g,"'")}(h.map((function(e){var n={begin:e.start-p,end:e.start+e.length-p};return"renderedMessage"in e&&(n.classes="err"),"kind"in e&&(n.classes=e.kind),"targetString"in e&&(n.classes="lsp",n.lsp=a(e.text)),n})),e.content):e.content.replace(/</g,"⇍").replace(/>/g,"⇏").replace(/'/g,"⇯"),r+='<span style="color: '+e.color+'">'+n+"</span>",d+=e.content.length,p+=e.content.length})),r+="\n",p+=1}if(t.length){var h=t.map((function(e){return s(e.renderedMessage)})).join("</br>"),g=t.map((function(e){return e.code})).join("<br/>");r+='<span class="error"><span>'+h+'</span><span class="code">'+g+"</span></span>",r+='<span class="error-behind">'+h+"</span>"}i.length&&(i.forEach((function(e){switch(e.kind){case"query":r+="<span class='query'>//"+"".padStart(e.offset-2)+"^ = "+e.text+"</span>";break;case"completions":if(e.completions){var n=e.completions.filter((function(n){return n.name.startsWith(e.completionsPrefix||"____")}));console.log("Prefix: ",e.completionsPrefix);var t=n.sort((function(e,n){return e.name.localeCompare(n.name)})).map((function(n){var t,r=n.name.substr((null===(t=e.completionsPrefix)||void 0===t?void 0:t.length)||0);return"<li><span><span class='result-found'>"+(e.completionsPrefix||"")+"</span>"+r+"<span></li>"})).join("");r+="".padStart(e.offset)+"<span class='inline-completions'><ul class='dropdown'>"+t+"</ul></span>"}else r+="<span class='query'>//"+"".padStart(e.offset-2)+"^ - No completions found</span>"}})),r+="\n")})),i=r.replace(/\n*$/,""),r=i.replace(/⇍/g,"<").replace(/⇏/g,">").replace(/⇯/g,"'"),r+="</code><a href='"+t.playgroundURL+"'>Try</a></div></pre>"}((t||c).codeToThemedTokens(e,n),{langId:n},r)},exports.runTwoSlash=function(e,n,a){void 0===a&&(a=l);var s=void 0,o={json5:"json"};return o[n]&&(n=o[n]),a.useNodeModules&&(u?s=new Map(u):(s=r.createDefaultMapFromNodeModules({target:6}),u=s),r.addAllFilesFromFolder(s,a.nodeModulesTypesPath||"node_modules/@types")),t.twoslasher(e,n,{fsMap:s})}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("shiki"),n=require("shiki-languages"),t=require("@typescript/twoslash"),r=require("@typescript/vfs");function a(){return(a=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var t=arguments[n];for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])}return e}).apply(this,arguments)}function s(e){var n={"<":"lt",'"':"quot","'":"apos","&":"amp","\r":"#10","\n":"#13"};return e.toString().replace(/[<"'\r\n&]/g,(function(e){return"&"+n[e]+";"}))}function o(e){return e.replace(/</g,"<").replace(/>/g,">")}function i(e,n){var t=new Map;return e.forEach((function(e){var r=n(e),a=t.get(r);a?a.push(e):t.set(r,[e])})),t}var c=[].concat(n.commonLangIds,n.commonLangAliases,n.otherLangIds),l=null,u={},p=void 0;exports.canHighlightLang=function(e){return c.includes(e)},exports.createShikiHighlighter=function(n){if(l)return l;var t,r=(n||{}).theme||"nord";try{t=e.getTheme(r)}catch(n){try{t=e.loadTheme(r)}catch(e){throw new Error("Unable to load theme: "+r+" - "+e.message)}}return e.getHighlighter({theme:t,langs:c}).then((function(e){return l=e}))},exports.defaultShikiTwoslashSettings=u,exports.renderCodeToHTML=function(e,n,t,r){if(!t&&!l)throw new Error("The highlighter object hasn't been initialised via `setupHighLighter` yet in render-shiki-twoslash");return function(e,n,t){if(!t)return function(e,n){var t="";return t+='<pre class="shiki">',n.langId&&(t+='<div class="language-id">'+n.langId+"</div>"),t+="<div class='code-container'><code>",e.forEach((function(e){0===e.length?t+="\n":(e.forEach((function(e){t+='<span style="color: '+e.color+'">'+o(e.content)+"</span>"})),t+="\n")})),t=t.replace(/\n*$/,""),t+="</code></div></pre>"}(e,n);var r="";r+='<pre class="shiki twoslash">',n.langId&&(r+='<div class="language-id">'+n.langId+"</div>"),r+="<div class='code-container'><code>";var a,c=i(t.errors,(function(e){return e.line}))||new Map,l=i(t.staticQuickInfos,(function(e){return e.line}))||new Map,u=i(t.queries,(function(e){return e.line-1}))||new Map,p=0;return e.forEach((function(e,n){var t=c.get(n)||[],a=l.get(n)||[],i=u.get(n)||[];if(0===e.length&&0===n)p+=1;else if(0===e.length)p+=1,r+="\n";else{var d=0;e.forEach((function(e){var n="",o=function(n){return function(t){return n<=t.character&&n+e.content.length>=t.character+t.length}},c=t.filter(o(d)),l=a.filter(o(d)),u=i.filter(o(d)),h=[].concat(c,l,u).sort((function(e,n){return(e.start||0)-(n.start||0)}));n+=h.length?function(e,n){var t=[],r=!1;e.forEach((function(e){"lsp"===e.classes?(t.push({text:"⇍/data-lsp⇏",index:e.end}),t.push({text:"⇍data-lsp lsp=⇯"+(e.lsp||"")+"⇯⇏",index:e.begin})):"err"===e.classes?r=!0:"query"===e.classes&&(t.push({text:"⇍/data-highlight⇏",index:e.end}),t.push({text:"⇍data-highlight'⇏",index:e.begin}))}));var a=(" "+n).slice(1);return t.sort((function(e,n){return n.index-e.index})).forEach((function(e){var n,t,r;r=e.text,a=(n=a).slice(0,t=e.index)+r+n.slice(t+Math.abs(0))})),r&&(a="⇍data-err⇏"+a+"⇍/data-err⇏"),s(a).replace(/⇍/g,"<").replace(/⇏/g,">").replace(/⇯/g,"'")}(h.map((function(e){var n={begin:e.start-p,end:e.start+e.length-p};return"renderedMessage"in e&&(n.classes="err"),"kind"in e&&(n.classes=e.kind),"targetString"in e&&(n.classes="lsp",n.lsp=s(e.text)),n})),e.content):e.content.replace(/</g,"⇍").replace(/>/g,"⇏").replace(/'/g,"⇯"),r+='<span style="color: '+e.color+'">'+n+"</span>",d+=e.content.length,p+=e.content.length})),r+="\n",p+=1}if(t.length){var h=t.map((function(e){return o(e.renderedMessage)})).join("</br>"),g=t.map((function(e){return e.code})).join("<br/>");r+='<span class="error"><span>'+h+'</span><span class="code">'+g+"</span></span>",r+='<span class="error-behind">'+h+"</span>"}i.length&&(i.forEach((function(e){switch(e.kind){case"query":r+="<span class='query'>//"+"".padStart(e.offset-2)+"^ = "+e.text+"</span>";break;case"completions":if(e.completions){var n=e.completions.filter((function(n){return n.name.startsWith(e.completionsPrefix||"____")}));console.log("Prefix: ",e.completionsPrefix);var t=n.sort((function(e,n){return e.name.localeCompare(n.name)})).map((function(n){var t,r=n.name.substr((null===(t=e.completionsPrefix)||void 0===t?void 0:t.length)||0);return"<li><span><span class='result-found'>"+(e.completionsPrefix||"")+"</span>"+r+"<span></li>"})).join("");r+="".padStart(e.offset)+"<span class='inline-completions'><ul class='dropdown'>"+t+"</ul></span>"}else r+="<span class='query'>//"+"".padStart(e.offset-2)+"^ - No completions found</span>"}})),r+="\n")})),a=r.replace(/\n*$/,""),r=a.replace(/⇍/g,"<").replace(/⇏/g,">").replace(/⇯/g,"'"),r+="</code><a href='"+t.playgroundURL+"'>Try</a></div></pre>"}((t||l).codeToThemedTokens(e,n),{langId:n},r)},exports.runTwoSlash=function(e,n,s,o){void 0===s&&(s=u),void 0===o&&(o={});var i=void 0,c={json5:"json"};return c[n]&&(n=c[n]),s.useNodeModules&&(p?i=new Map(p):(i=r.createDefaultMapFromNodeModules({target:6}),p=i),r.addAllFilesFromFolder(i,s.nodeModulesTypesPath||"node_modules/@types")),t.twoslasher(e,n,a({},o,{fsMap:i}))}; | ||
//# sourceMappingURL=shiki-twoslash.cjs.production.min.js.map |
@@ -6,2 +6,20 @@ import { getTheme, loadTheme, getHighlighter } from 'shiki'; | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
var splice = function splice(str, idx, rem, newString) { | ||
@@ -284,3 +302,3 @@ return str.slice(0, idx) + newString + str.slice(idx + Math.abs(rem)); | ||
[].concat(commonLangIds, commonLangAliases, otherLangIds); | ||
/** Checks if it is available in shiki */ | ||
/** Checks if a particular lang is available in shiki */ | ||
@@ -297,2 +315,10 @@ var canHighlightLang = function canHighlightLang(lang) { | ||
var storedHighlighter = null; | ||
/** | ||
* Creates a Shiki highlighter, this is an async call because of the call to WASM to get the regex parser set up. | ||
* | ||
* In other functions, passing a the result of this highlighter function is kind of optional but it's the author's | ||
* opinion that you should be in control of the highlighter, and not this library. | ||
* | ||
*/ | ||
var createShikiHighlighter = function createShikiHighlighter(options) { | ||
@@ -336,8 +362,10 @@ if (storedHighlighter) return storedHighlighter; | ||
return results; | ||
}; // Basically so that we can store this once, then re-use it | ||
}; // Basically so that we can store this once, then re-use it in the same process | ||
var nodeModulesMap = undefined; | ||
/** Runs Twoslash over the code in the */ | ||
/** | ||
* Runs Twoslash over the code passed in with a particular language as the default file. | ||
*/ | ||
var runTwoSlash = function runTwoSlash(code, lang, settings) { | ||
var runTwoSlash = function runTwoSlash(code, lang, settings, twoslashDefaults) { | ||
if (settings === void 0) { | ||
@@ -347,2 +375,6 @@ settings = defaultShikiTwoslashSettings; | ||
if (twoslashDefaults === void 0) { | ||
twoslashDefaults = {}; | ||
} | ||
var map = undefined; // Shiki doesn't respect json5 as an input, so switch it | ||
@@ -375,5 +407,5 @@ // to json, which can handle comments in the syntax highlight | ||
var results = twoslasher(code, lang, { | ||
var results = twoslasher(code, lang, _extends({}, twoslashDefaults, { | ||
fsMap: map | ||
}); | ||
})); | ||
return results; | ||
@@ -380,0 +412,0 @@ }; |
{ | ||
"name": "shiki-twoslash", | ||
"version": "0.6.1", | ||
"version": "0.6.2", | ||
"license": "MIT", | ||
"homepage": "https://github.com/microsoft/TypeScript-Website/", | ||
"description": "API primitives to mix Shiki with Twoslash", | ||
"author": "Orta Therox", | ||
@@ -7,0 +8,0 @@ "main": "./dist/index.js", |
232
README.md
### shiki-twoslash | ||
Sets up markdown code blocks to run through [shiki](https://shiki.matsu.io) which means it gets the VS Code quality | ||
syntax highlighting. This part is basically the same as [gatsby-remark-shiki](https://www.gatsbyjs.org/packages/gatsby-remark-shiki/). | ||
Provides the API primitives to mix [shiki](https://shiki.matsu.io) with [@typescript/twoslash](https://github.com/microsoft/TypeScript-Website/tree/v2/packages/ts-twoslasher). | ||
Why Shiki? Shiki uses the same syntax highlighter engine as VS Code, which means no matter how complex your code is - it will syntax highlight correctly. | ||
Things it handles: | ||
In addition to all the languages shiki handles ([it's a lot](https://github.com/octref/shiki/blob/master/packages/languages/README.md#literal-values)), this module adds opt-in [@typescript/twoslash](https://github.com/microsoft/TypeScript-Website/tree/v2/packages/ts-twoslasher) rendering for TypeScript code blocks. | ||
- Shiki bootstrapping: `createShikiHighlighter` | ||
- Checking if shiki can handle a code sample: `canHighlightLang` | ||
- Running Twoslash over code, with caching and DTS lookups: `runTwoSlash` | ||
- Rendering any code sample with Shiki: `renderCodeToHTML` | ||
This module powers the code samples on the TypeScript website. | ||
### API | ||
![](https://user-images.githubusercontent.com/49038/78996047-ca7be880-7b11-11ea-9e6e-fa7ea8854993.png) | ||
The user-exposed parts of the API is a single file, you might find it easier to just read that: [`src/index.ts`](https://github.com/microsoft/TypeScript-website/blob/v2/packages/shiki-twoslash/src/index.ts). | ||
With a bit of work you can explain complicated code in a way that lets people introspect at their own pace. | ||
##### `createShikiHighlighter` | ||
## Plugin Setup | ||
Sets up the highlighter for Shiki, accepts shiki options: | ||
#### Express Setup | ||
```ts | ||
async function visitor(highlighterOpts, shikiOpts) { | ||
const highlighter = await createShikiHighlighter(highlighterOpts) | ||
visit(markdownAST, "code", visitor(highlighter, shikiOpts)) | ||
} | ||
``` | ||
[Read this PR](https://github.com/orta/gatsby-twoslash-shiki-blog/pull/1) and apply the same to your project. | ||
##### `renderCodeToHTML` | ||
#### Setup | ||
Renders source code into HTML via Shiki: | ||
1. **Install the dependency**: `yarn add shiki-twoslash` | ||
1. **Include `"shiki-twoslash"` in the plugins section** of `gatsby-transformer-remark` | ||
```ts | ||
const shouldHighlight = lang && canHighlightLang(lang) | ||
```diff | ||
{ | ||
resolve: `gatsby-transformer-remark`, | ||
options: { | ||
plugins: [ | ||
"gatsby-remark-autolink-headers", | ||
+ { | ||
+ resolve: "shiki-twoslash", | ||
+ options: { | ||
+ theme: "nord", | ||
+ } | ||
+ }, | ||
"gatsby-remark-copy-linked-files", | ||
"gatsby-remark-smartypants", | ||
], | ||
}, | ||
} | ||
``` | ||
if (shouldHighlight) { | ||
const results = renderCodeToHTML(node.value, lang, highlighter) | ||
node.type = "html" | ||
node.children = [] | ||
} | ||
``` | ||
If you have `gatsby-remark-prismjs` in, delete it from the config and run `yarn remove gatsby-remark-prismjs`. | ||
To get access to the twoslash renderer, you'll need to pass in the results of a twoslash run to `renderCodeToHTML`: | ||
1. **Add the CSS** | ||
```ts | ||
const highlighter = await createShikiHighlighter(highlighterOpts) | ||
const twoslashResults = runTwoSlash(code, lang) | ||
const html = renderCodeToHTML(twoslashResults.code, twoslashResults.lang, highlighter) | ||
``` | ||
This CSS comes from the [TypeScript website's scss](https://github.com/microsoft/TypeScript-website/blob/v2/packages/typescriptlang-org/src/templates/markdown-twoslash.scss) | ||
#### `runTwoSlash` | ||
You should consider it a base to work from, rather than a perfect for every project reference. | ||
Used to run Twoslash on a code sample. In this case it's looking at a code AST node and switching out the HTML with the twoslash results: | ||
```css | ||
/* Code blocks look like: | ||
<pre class='shiki twoslash'> | ||
<div class='language-id>[lang-id]</div> | ||
<div class='code-container'> | ||
<code>[the code as a series of spans]</code> | ||
</div> | ||
</pre> | ||
*/ | ||
pre { | ||
/* In theory shiki will overwrite these, but this is to make sure there are defaults */ | ||
background-color: white; | ||
color: black; | ||
/* Give it some space to breathe */ | ||
padding: 12px; | ||
/* All code samples get a grey border, twoslash ones get a different color */ | ||
border-left: 1px solid #999; | ||
border-bottom: 1px solid #999; | ||
margin-bottom: 3rem; | ||
/* Important to allow the code to move horizontally; */ | ||
overflow: auto; | ||
position: relative; | ||
} | ||
pre.shiki { | ||
overflow: initial; | ||
} | ||
/* So that folks know you can highlight */ | ||
pre.twoslash { | ||
border-color: #719af4; | ||
} | ||
/* The code inside should scroll, but the overflow can't be on the shiki because it would not allow the relative positioning */ | ||
pre .code-container { | ||
overflow: auto; | ||
} | ||
/* Handle scrolling, and showing code correctly */ | ||
pre code { | ||
white-space: pre; | ||
-webkit-overflow-scrolling: touch; | ||
} | ||
/* Let errors use the outer shiki for their absolute sizing, and not be affected by the scrolling of the code */ | ||
pre data-err { | ||
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") | ||
repeat-x bottom left; | ||
padding-bottom: 3px; | ||
} | ||
/* In order to have the 'popped out' style design and to not break the layout | ||
/* we need to place a fake and un-selectable copy of the error which _isn't_ broken out | ||
/* behind the actual error message. | ||
/* This section keeps both of those in sync */ | ||
pre .error, | ||
pre .error-behind { | ||
margin-left: -20px; | ||
margin-top: 8px; | ||
margin-bottom: 4px; | ||
padding: 6px; | ||
padding-left: 14px; | ||
white-space: pre-wrap; | ||
display: block; | ||
} | ||
pre .error { | ||
position: absolute; | ||
background-color: #ffeeee; | ||
border-left: 2px solid #bf1818; | ||
width: calc(100% - 14px); | ||
/* Give the space to the error code */ | ||
display: flex; | ||
align-items: center; | ||
color: black; | ||
} | ||
pre .error .code { | ||
display: none; | ||
} | ||
pre .error-behind { | ||
user-select: none; | ||
color: #ffeeee; | ||
} | ||
data-lsp { | ||
/* Ensures there's no 1px jump when the hover happens above */ | ||
border-bottom: 1px dotted transparent; | ||
/* Fades in unobtrusively */ | ||
transition-timing-function: ease; | ||
transition: border-color 0.3s; | ||
} | ||
/* Respect people's wishes to not have animations */ | ||
@media (prefers-reduced-motion: reduce) { | ||
data-lsp { | ||
transition: none; | ||
} | ||
} | ||
/** When you mouse over the pre, show the underlines */ | ||
pre:hover data-lsp { | ||
border-color: #747474; | ||
} | ||
/** The tooltip-like which provides the LSP response */ | ||
#twoslash-mouse-hover-info { | ||
background-color: #3f3f3f; | ||
color: #fff; | ||
text-align: left; | ||
padding: 5px 8px; | ||
border-radius: 2px; | ||
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, Courier New, monospace; | ||
font-size: 14px; | ||
white-space: pre-wrap; | ||
} | ||
``` | ||
1. **Add the JS** for hover info to your component: | ||
```jsx | ||
import React, { useEffect } from "react" | ||
import { setupTwoslashHovers } from "shiki-twoslash/dom"; | ||
export default () => { | ||
// Add a the hovers | ||
useEffect(setupTwoslashHovers, []) | ||
// Normal JSX for your component | ||
return </> | ||
} | ||
``` | ||
### Verify | ||
With that set up, start up your server and add a codeblock to a markdown file to see if it renders with highlights: | ||
<pre>```json | ||
{ "json": true } | ||
```</pre> | ||
If that works, then add a twoslash example: | ||
<pre>```ts twoslash | ||
interface IdLabel {id: number, /* some fields */ } | ||
interface NameLabel {name: string, /* other fields */ } | ||
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel; | ||
// This comment should not be included | ||
// ---cut--- | ||
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> { | ||
throw "unimplemented" | ||
} | ||
let a = createLabel("typescript");` | ||
```</pre> | ||
If the code sample shows as | ||
```ts | ||
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> { | ||
throw "unimplemented" | ||
if (node.meta && node.meta.includes("twoslash")) { | ||
const results = runTwoSlash(node.value, node.lang, settings) | ||
node.value = results.code | ||
node.lang = results.extension | ||
node.twoslash = results | ||
} | ||
let a = createLabel("typescript") | ||
``` | ||
Then it worked, and you should be able to hover over `createLabel` to see it's types. | ||
### Used in: | ||
### Plugin Config | ||
This plugin passes the config options directly to Shiki. You probably will want to | ||
[set `theme`](https://github.com/octref/shiki/blob/master/packages/themes/README.md#shiki-themes). | ||
- [gatsby-remark-shiki-twoslash](https://www.npmjs.com/package/gatsby-remark-shiki-twoslash) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
888
115397
65