diff --git a/README.md b/README.md index 7dd2da0..2c774cd 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,17 @@ - [Example: line numbers for multiline codeblock](#example-line-numbers-for-multiline-codeblock) - [Example: show prompts](#example-show-prompts) - [Example: highlight lines](#example-highlight-lines) - - [Example: add a caption to a codeblock](#example-add-a-caption-to-a-codeblock) - - [Example: configure aliases](#example-configure-aliases) + - [Example: codeblock with a caption](#example-codeblock-with-a-caption) + - [Example: aliases](#example-aliases) - [Example: custom header extension](#example-custom-header-extension) + - [Example: custom classname prefix](#example-custom-classname-prefix) + - [Example: custom marker](#example-custom-marker) - [Related](#related) - [License](#license) ## What’s this? -This package is a [unified](https://github.com/unifiedjs/unified) ([rehype](https://github.com/rehypejs/rehype)) plugin to highlight code with [Starry Night](https://github.com/wooorm/starry-night) in a markdown document. This results into syntax highlighting like what GitHub uses to highlight code. +This package is a [unified](https://github.com/unifiedjs/unified) ([rehype](https://github.com/rehypejs/rehype)) plugin to highlight code with [Starry Night](https://github.com/wooorm/starry-night) in a markdown document. The syntax highlighting mimics what GitHub's syntax highlighting color schemes. ## When should I use this? @@ -38,7 +40,7 @@ The following additonal features are also available: - line highlights - support for prompt - captions and language information -- highlighting inline `code` elements (through a custom directive) +- highlighting inline `code` elements ## Install @@ -84,7 +86,7 @@ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import rehypeStringify from "rehype-stringify"; -import rehypeStarryNight from "https://esm.sh/@microflash/rehype-starry-night"; +import rehypeStarryNight from "@microflash/rehype-starry-night"; main(); @@ -103,11 +105,11 @@ async function main() { Running that with `node example.js` yields: ```html -
-
-
css
+
+
+
css
-
html {
+
html {
   box-sizing: border-box;
   text-size-adjust: 100%;
   /* allow percentage based heights for the children */
@@ -121,12 +123,12 @@ Running that with `node example.js` yields:
 
 ### Support for inline `code` elements
 
-To highlight inline `code` elements, you can import [`rehype-starry-night-inline`](./src/rehype-starry-night-inline.js) plugin. This plugin relies on a custom directive which requires importing [`remark-directive`](https://github.com/remarkjs/remark-directive) and [`remark-code-directive`](./src/remark-code-directive.js).
+To highlight inline `code` elements, import [`rehype-starry-night-inline`](./src/rehype-starry-night-inline/index.js) plugin. This plugin relies on the language information injected by the [`remark-inline-code-lang`](./src/remark-inline-code-lang/index.js) plugin.
 
 Say we have the following file `example.md`:
 
 ```md
-To print a greeting, use :code[console.log("Hello, world!");]{syntax=js}. When executed, it prints `Hello, world!`.
+To print a greeting, use `js> console.log("Hello, world!");`. When executed, it prints `Hello, world!`.
 ```
 
 And our module `example.js` looks as follows:
@@ -134,19 +136,17 @@ And our module `example.js` looks as follows:
 ```js
 import { unified } from "unified";
 import remarkParse from "remark-parse";
-import remarkDirective from "remark-directive";
-import remarkCodeDirective from "https://esm.sh/@microflash/rehype-starry-night/remark-code-directive";
+import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang";
 import remarkRehype from "remark-rehype";
 import rehypeStringify from "rehype-stringify";
-import rehypeStarryNightInline from "https://esm.sh/@microflash/rehype-starry-night/rehype-starry-night-inline";
+import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline";
 
 main();
 
 async function main() {
   const file = await unified()
     .use(remarkParse)
-    .use(remarkDirective)
-    .use(remarkCodeDirective)
+    .use(remarkInlineCodeLang)
     .use(remarkRehype, { allowDangerousHtml: true })
     .use(rehypeStarryNightInline)
     .use(rehypeStringify, { allowDangerousHtml: true })
@@ -159,7 +159,7 @@ async function main() {
 Running that with `node example.js` yields:
 
 ```html
-

To print a greeting, use console.log("Hello, world!");. When executed, it prints Hello, world!.

+

To print a greeting, use console.log("Hello, world!");. When executed, it prints Hello, world!.

``` ![Highlighting inline code element](./samples/sample-2.png) @@ -170,9 +170,11 @@ The default export is `rehypeStarryNight`. The following options are available. - `aliases`: an object to alias languages to force syntax highlighting. By default, unknown languages are highlighted as plain text. Applicable to both `rehype-starry-night` and `rehype-starry-night-inline`. - `grammars`: a list of [Starry Night](https://github.com/wooorm/starry-night) compatible grammar definitions. By default, all grammars provided by Starry Night are used. Applicable to both `rehype-starry-night` and `rehype-starry-night-inline`. -- `headerExtensions`: a list of functions to customize the header. Applicable to only `rehype-starry-night`. By default, [language-extension](./src/hast-util-starry-night-header-language-extension.js) and [caption-extension](./src/hast-util-starry-night-header-caption-extension.js) are used. A header extension has access to the following arguments. - - `headerOptions`: an object with a randomly generated `id` attached to the `pre` element, `metadata` containing the caption, list of highlighted lines, etc., and `language` tag specified on the code +- `classNamePrefix` (default: `hl`): a prefix for the classNames for different elements of HTML generated by `rehype-starry-night` and `rehype-starry-night-inline`. +- `headerExtensions`: a list of functions to customize the header. Applicable to only `rehype-starry-night`. By default, [language-extension](./src/rehype-starry-night/hast-util-starry-night-header-language-extension.js) and [caption-extension](./src/rehype-starry-night/hast-util-starry-night-header-caption-extension.js) are used. A header extension has access to the following arguments. + - `headerOptions`: an object with a randomly generated `id` attached to the `pre` element, `metadata` containing the caption, list of highlighted lines, etc., the `language` tag specified on the code, and the `classNamePrefix` - `children`: an array of nodes contained in the header +- `marker` (default: `> `): the marker for inline `code` element before which the language information is specified. Applicable to `remark-inline-code-lang`. ### Themes @@ -187,17 +189,17 @@ There are multiple ways to support light and dark themes. Here's one way to do t ```css :root { /* light theme variables specific to rehype-starry-night plugin */ - --highlight-background-color: hsl(0, 0%, 100%); - --highlight-border-color: hsl(208, 21%, 86%); - --highlight-code-highlight: hsl(208, 19%, 82%); + --hl-background-color: hsl(0, 0%, 100%); + --hl-border-color: hsl(208, 21%, 86%); + --hl-code-highlight: hsl(208, 19%, 82%); } @media (prefers-color-scheme: dark) { :root { /* dark theme variables specific to rehype-starry-night plugin */ - --highlight-background-color: hsl(240, 20%, 2%); - --highlight-border-color: hsl(208, 21%, 12%); - --highlight-code-highlight: hsl(208, 19%, 13%); + --hl-background-color: hsl(240, 20%, 2%); + --hl-border-color: hsl(208, 21%, 12%); + --hl-code-highlight: hsl(208, 19%, 13%); } } @@ -205,7 +207,7 @@ There are multiple ways to support light and dark themes. Here's one way to do t @import "https://raw.githubusercontent.com/wooorm/starry-night/main/style/both.css"; /* import CSS specific to rehype-starry-night plugin */ -@import "https://raw.githubusercontent.com/Microflash/rehype-starry-night/main/index.css"; +@import "https://raw.githubusercontent.com/Microflash/rehype-starry-night/main/src/index.css"; ``` > [!WARNING] @@ -222,11 +224,11 @@ There are multiple ways to support light and dark themes. Here's one way to do t The above codeblock will yield: ```html -
-
-
sh
+
+
+
sh
-
docker ps -a
+
docker ps -a
 
``` @@ -244,11 +246,11 @@ The above codeblock will yield: The above codeblock will yield: ```html -
-
-
css
+
+
+
css
-
* {
+
* {
   display: revert;
 }
 
@@ -273,11 +275,11 @@ Sometimes you may want to show a prompt while displaying a command-line instruct The above codeblock will yield: ```html -
-
-
sh
+
+
+
sh
-
curl localhost:8080/actuator/health
+
curl localhost:8080/actuator/health
 {"status":"UP"}
 curl localhost:8080/greeter?name=Anya
 Hello, Anya!
@@ -293,7 +295,7 @@ The above codeblock will yield:
 
 You can highlight multiple lines by specifying the line numbers (or even, range of line numbers) between curly braces in the codeblock metadata.
 
-	```sh {4-7} prompt{1}
+	```sh {4..7} prompt{1}
 	aws --endpoint-url http://localhost:4566 s3api list-buckets
 	{
 	  "Buckets": [
@@ -312,11 +314,11 @@ You can highlight multiple lines by specifying the line numbers (or even, range
 The above codeblock will yield:
 
 ```html
-
-
-
sh
+
+
+
sh
-
aws --endpoint-url http://localhost:4566 s3api list-buckets
+
aws --endpoint-url http://localhost:4566 s3api list-buckets
 {
   "Buckets": [
     {
@@ -337,7 +339,7 @@ The above codeblock will yield:
 
 Refer to the documentation of [fenceparser](https://github.com/Microflash/fenceparser) to learn about the additional ways in which you can specify the information about highlighted lines.
 
-### Example: add a caption to a codeblock
+### Example: codeblock with a caption
 
 Captions are useful to describe the context of a piece of code.
 
@@ -352,12 +354,12 @@ Captions are useful to describe the context of a piece of code.
 The above codeblock will yield:
 
 ```html
-
-
-
sh
-
Configuring the AWS account
+
+
+
sh
+
Configuring the AWS account
-
aws configure
+
aws configure
 AWS Access Key ID [None]: gwen
 AWS Secret Access Key [None]: stacy
 Default region name [None]: us-east-1
@@ -368,9 +370,9 @@ The above codeblock will yield:
 
 ![Syntax Highlighting add a caption to a codeblock](./samples/sample-7.png)
 
-### Example: configure aliases
+### Example: aliases
 
-Although Starry Night [supports](https://github.com/wooorm/starry-night#languages=) a huge number of languages, it is not all encompassing. In such cases, you can configure aliases to force syntax highlighting a codeblock containing code in a language not yet supported by Starry Night.
+Although Starry Night [supports](https://github.com/wooorm/starry-night#languages=) a large number of languages, it is not all encompassing. In such cases, you can configure aliases to force the syntax highlighting on a codeblock containing code in a language not yet supported by Starry Night.
 
 Say we have the following file `example.md`:
 
@@ -406,11 +408,11 @@ async function main() {
 Running that with `node example.js` yields:
 
 ```html
-
-
-
xjm
+
+
+
xjm
-
language = "en"
+
language = "en"
 customization = false
 features = [ "io", "graphics", "compute" ]
 
@@ -436,7 +438,7 @@ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import rehypeStringify from "rehype-stringify"; -import rehypeStarryNight from "https://esm.sh/@microflash/rehype-starry-night"; +import rehypeStarryNight from "@microflash/rehype-starry-night"; import rehypeStarryNightHeaderCaptionExtension from "@microflash/rehype-starry-night/header-caption-extension"; import rehypeStarryNightHeaderLanguageExtension from "@microflash/rehype-starry-night/header-language-extension"; @@ -454,7 +456,7 @@ async function main() { children.push({ type: "element", tagName: "button", - properties: { className: ["highlight-copy"], for: headerOptions.id }, + properties: { className: [`${headerOptions.classNamePrefix}-copy`], for: headerOptions.id }, children: [ { type: "text", @@ -475,18 +477,141 @@ async function main() { Running that with `node example.js` yields: ```html -
-
-
html
- +
+
+
html
+
-
<mark>highlighted</mark>
+
<mark>highlighted</mark>
 
``` ![Syntax Highlighting with custom header extension](./samples/sample-9.png) +### Example: custom classname prefix + +You can attach your own prefix on the classNames of HTML elements generated by the `rehype-starry-night` and `rehype-starry-night-inline` plugins. + +Say we have the following file `example.md`: + + ```java + System.out.println("Hello, world!"); + ``` + +You can customize the className prefix as follows with `example.js`: + +```js +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import rehypeStringify from "rehype-stringify"; +import rehypeStarryNight from "@microflash/rehype-starry-night"; + +main() + +async function main() { + const file = await unified() + .use(remarkParse) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeStarryNight, { classNamePrefix: "syntax" }) + .use(rehypeStringify, { allowDangerousHtml: true }) + .process(markdown); + + console.log(String(file)); +} +``` + +Running that with `node example.js` yields: + +```html +
+
+
java
+
+
System.out.println("Hello, world!");
+
+
+``` + +Similarly for inline `code` element, say we have the following file `example.md`: + +```md +To remove the whitespace around a string, try `java> str.strip()`. +``` + +You can customize the className prefix as follows with `example.js`: + +```js +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang"; +import remarkRehype from "remark-rehype"; +import rehypeStringify from "rehype-stringify"; +import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline"; + +main(); + +async function main() { + const file = await unified() + .use(remarkParse) + .use(remarkInlineCodeLang) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeStarryNightInline, { classNamePrefix: "syntax" }) + .use(rehypeStringify, { allowDangerousHtml: true }) + .process(markdown); + + console.log(String(file)); +} +``` + +Running that with `node example.js` yields: + +```html +

To remove the whitespace around a string, try str.strip().

+``` + +### Example: custom marker + +You can configure a custom marker for inline `code` element to inject the language information. For example, say you want to annotate your inline `code` element with `: ` instead of the default `> ` marker, as shown in the following file `example.md`: + +```md +To specify the language direction, use `html: مرحبا`. +``` + +You can customize the marker as follows with `example.js`: + +```js +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang"; +import remarkRehype from "remark-rehype"; +import rehypeStringify from "rehype-stringify"; +import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline"; + +main(); + +async function main() { + const file = await unified() + .use(remarkParse) + .use(remarkInlineCodeLang, { marker: ": " }) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeStarryNightInline) + .use(rehypeStringify, { allowDangerousHtml: true }) + .process(markdown); + + console.log(String(file)); +} +``` + +Running that with `node example.js` yields: + +```html +

To specify the language direction, use <span dir="rtl">مرحبا</span>.

+``` + +![Syntax Highlighting through language information using custom marker](./samples/sample-10.png) + ## Related - [`rehype-highlight`](https://github.com/rehypejs/rehype-highlight) — highlight code with [highlight.js](https://github.com/isagalaev/highlight.js) (through [lowlight](https://github.com/wooorm/lowlight)) diff --git a/samples/sample-1.png b/samples/sample-1.png index e41eba3..43e1a3c 100644 Binary files a/samples/sample-1.png and b/samples/sample-1.png differ diff --git a/samples/sample-10.png b/samples/sample-10.png new file mode 100644 index 0000000..09db0f0 Binary files /dev/null and b/samples/sample-10.png differ diff --git a/samples/sample-2.png b/samples/sample-2.png index b72bee8..c862a4e 100644 Binary files a/samples/sample-2.png and b/samples/sample-2.png differ diff --git a/samples/sample-3.png b/samples/sample-3.png index 904df94..effc0b7 100644 Binary files a/samples/sample-3.png and b/samples/sample-3.png differ diff --git a/samples/sample-4.png b/samples/sample-4.png index 1899751..3130faf 100644 Binary files a/samples/sample-4.png and b/samples/sample-4.png differ diff --git a/samples/sample-5.png b/samples/sample-5.png index 3c6c1d0..cacbe1a 100644 Binary files a/samples/sample-5.png and b/samples/sample-5.png differ diff --git a/samples/sample-6.png b/samples/sample-6.png index ff8ccab..41d08c4 100644 Binary files a/samples/sample-6.png and b/samples/sample-6.png differ diff --git a/samples/sample-7.png b/samples/sample-7.png index 050c651..afc54dc 100644 Binary files a/samples/sample-7.png and b/samples/sample-7.png differ diff --git a/samples/sample-8.png b/samples/sample-8.png index 21093a6..7e1be68 100644 Binary files a/samples/sample-8.png and b/samples/sample-8.png differ diff --git a/samples/sample-9.png b/samples/sample-9.png index 0e57a36..4e5a07a 100644 Binary files a/samples/sample-9.png and b/samples/sample-9.png differ diff --git a/src/index.css b/src/index.css index 8b75bb2..7c6ae64 100644 --- a/src/index.css +++ b/src/index.css @@ -1,27 +1,27 @@ -.highlight { - --hlBackgroundColor: var(--highlight-background-color, hsl(240, 20%, 2%)); - --hlBorderThickness: var(--highlight-border-thickness, 1px); - --hlBorderColor: var(--highlight-border-color, hsl(208, 21%, 12%)); - --hlBorderRadius: var(--highlight-border-radius, 12px); +.hl { + --hlBackgroundColor: var(--hl-background-color, hsl(240, 20%, 2%)); + --hlBorderThickness: var(--hl-border-thickness, 1px); + --hlBorderColor: var(--hl-border-color, hsl(208, 21%, 12%)); + --hlBorderRadius: var(--hl-border-radius, 12px); --hlFontFamilyMono: ui-monospace, SFMono-Regular, "Roboto Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --hlFontFamilyCode: var(--highlight-font-mono, var(--hlFontFamilyMono)); - --hlCodeHighlight: var(--highlight-code-highlight, hsl(208, 19%, 13%)); - --hlLineNumberColor: var(--highlight-color-line-number, var(--color-prettylights-syntax-sublimelinter-gutter-mark)); - --hlLineNumberColorHighlighted: var(--highlight-color-line-number-highlighted, var(--color-prettylights-syntax-comment)); + --hlFontFamilyCode: var(--hl-font-mono, var(--hlFontFamilyMono)); + --hlCodeHighlight: var(--hl-code-highlight, hsl(208, 19%, 13%)); + --hlLineNumberColor: var(--hl-color-line-number, var(--color-prettylights-syntax-sublimelinter-gutter-mark)); + --hlLineNumberColorHighlighted: var(--hl-color-line-number-highlighted, var(--color-prettylights-syntax-comment)); background-color: var(--hlBackgroundColor); border: var(--hlBorderThickness) solid var(--hlBorderColor); border-radius: var(--hlBorderRadius); } -.highlight:focus-within { +.hl:focus-within { outline-color: var(--hlBorderColor); outline-style: solid; outline-width: var(--hlBorderThickness); outline-offset: -0.125em; } -.highlight-header { +.hl-header { display: flex; align-items: center; gap: 1ch; @@ -30,59 +30,59 @@ font-size: 0.75em; } -.highlight-language { +.hl-language { font-family: var(--hlFontFamilyCode); background-color: var(--hlCodeHighlight); padding: 0.75ch 1ch; border-radius: calc(var(--hlBorderRadius) / 1.5); } -.highlight pre { +.hl pre { background-color: transparent; border: none; overflow-x: auto; } -.highlight pre>code { +.hl pre>code { display: grid; padding: 1em 0; cursor: auto; touch-action: auto; } -.highlight pre .line { +.hl pre .line { border-left: 2px solid transparent; padding-left: 1ch; padding-right: 1ch; } -.highlight pre .line:focus:not(:focus-visible), -.highlight pre .line:hover { +.hl pre .line:focus:not(:focus-visible), +.hl pre .line:hover { background-color: var(--hlCodeHighlight); } -.highlight pre .line:focus:not(:focus-visible) .line-number, -.highlight pre .line:hover .line-number { +.hl pre .line:focus:not(:focus-visible) .line-number, +.hl pre .line:hover .line-number { color: var(--hlLineNumberColorHighlighted); } -.highlight pre .line .line-number { +.hl pre .line .line-number { user-select: none; margin-right: 1em; text-align: right; color: var(--hlLineNumberColor); } -.highlight pre .line[data-highlighted] { +.hl pre .line[data-highlighted] { background-color: var(--hlCodeHighlight); border-left-color: var(--hlLineNumberColorHighlighted); } -.highlight pre .line[data-highlighted] .line-number { +.hl pre .line[data-highlighted] .line-number { color: var(--hlLineNumberColorHighlighted); } -.highlight pre .line .line-prompt::before { +.hl pre .line .line-prompt::before { margin-right: 1ch; vertical-align: middle; content: ""; @@ -102,9 +102,9 @@ } code.hl-inline { - --hlBackgroundColorInline: var(--highlight-background-color-inline, hsl(208, 21%, 14%)); + --hlBackgroundColorInline: var(--hl-background-color-inline, hsl(208, 21%, 14%)); - font-family: var(--highlight-font-mono); + font-family: var(--hl-font-mono); background-color: var(--hlBackgroundColorInline); padding-left: 0.5ch; padding-right: 0.5ch;