Skip to content

Commit

Permalink
feat: add LineNumbers (#248)
Browse files Browse the repository at this point in the history
Closes #247
  • Loading branch information
metonym authored Dec 29, 2022
1 parent 80f391b commit 82f9366
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 4 deletions.
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,71 @@ Prefer to specify a language if possible.
<HighlightAuto {code} />
```

## Line Numbers

Use the `LineNumbers` component to render the highlighted code with line numbers.

```svelte
<script>
import Hightlight, { LineNumbers } from "svelte-highlight";
import typescript from "svelte-highlight/languages/typescript";
import atomOneDark from "svelte-highlight/styles/atom-one-dark";
const code = "const add = (a: number, b: number) => a + b";
</script>
<svelte:head>
{@html atomOneDark}
</svelte:head>
<Highlight language={typescript} {code} let:highlighted>
<LineNumbers {highlighted} />
</Highlight>
```

### Hidden Border

Set `hideBorder` to `true` to hide the border of the line numbers column.

```svelte
<Highlight language={typescript} {code} let:highlighted>
<LineNumbers {highlighted} hideBorder />
</Highlight>
```

### Wrapped Lines

By default, overflowing horizontal content is contained by a scrollbar.

Set `wrapLines` to `true` to hide the border of the line numbers column.

```svelte
<Highlight language={typescript} {code} let:highlighted>
<LineNumbers {highlighted} wrapLines />
</Highlight>
```

### Custom Styles

Use `--style-props` to customize the following visual properties:

- `--line-number-color`: text color of the line numbers
- `--border-color`: color of the line numbers column
- `--padding-left`: left padding for `td` elements
- `--padding-right`: right padding for `td` elements

```svelte
<Highlight language={typescript} {code} let:highlighted>
<LineNumbers
{highlighted}
--line-number-color="pink"
--border-color="rgba(255, 255, 255, 0.2)"
--padding-left="0"
--padding-right="3em"
/>
</Highlight>
```

## Language Targeting

All `Highlight` components apply a `data-language` attribute on the codeblock containing the language name.
Expand Down
32 changes: 32 additions & 0 deletions demo/lib/LineNumbers/Basic.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script>
// @ts-check
export let snippet = "<LineNumbers {highlighted} />";
import { HighlightSvelte } from "../../../src";
import LineNumbers from "../../../src/LineNumbers.svelte";
import atomOneDark from "../../../src/styles/atom-one-dark";
const code = `<script>
import Hightlight, { LineNumbers } from "svelte-highlight";
import typescript from "svelte-highlight/languages/typescript";
import atomOneDark from "svelte-highlight/styles/atom-one-dark";
const code = "const add = (a: number, b: number) => a + b";
<\/script>
<svelte:head>
{@html atomOneDark}
</svelte:head>
<Highlight language={typescript} {code} let:highlighted>
${snippet}
</Highlight>`;
</script>

<svelte:head>
{@html atomOneDark}
</svelte:head>

<HighlightSvelte {code} let:highlighted>
<LineNumbers {highlighted} {...$$restProps} />
</HighlightSvelte>
8 changes: 8 additions & 0 deletions demo/lib/LineNumbers/HideBorder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
// @ts-check
import Basic from "./Basic.svelte";
const snippet = "<LineNumbers {highlighted} hideBorder />";
</script>

<Basic {snippet} hideBorder />
20 changes: 20 additions & 0 deletions demo/lib/LineNumbers/StyleProps.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script>
// @ts-check
import Basic from "./Basic.svelte";
const snippet = `<LineNumbers
{highlighted}
--line-number-color="pink"
--border-color="rgba(255, 255, 255, 0.2)"
--padding-left={0}
--padding-right="1em"
/>`;
</script>

<Basic
{snippet}
--line-number-color="pink"
--border-color="rgba(255, 255, 255, 0.2)"
--padding-left={0}
--padding-right="1em"
/>
8 changes: 8 additions & 0 deletions demo/lib/LineNumbers/WrapLines.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
// @ts-check
import Basic from "./Basic.svelte";
const snippet = "<LineNumbers {highlighted} wrapLines />";
</script>

<Basic {snippet} wrapLines />
52 changes: 52 additions & 0 deletions demo/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import ScopedStyleAuto from "../lib/ScopedStyleAuto.svelte";
import HighlightSvelte from "../../src/HighlightSvelte.svelte";
import HighlightAuto from "../../src/HighlightAuto.svelte";
import Basic from "../lib/LineNumbers/Basic.svelte";
import HideBorder from "../lib/LineNumbers/HideBorder.svelte";
import WrapLines from "../lib/LineNumbers/WrapLines.svelte";
import StyleProps from "../lib/LineNumbers/StyleProps.svelte";
const NAME = process.env.NAME;
Expand Down Expand Up @@ -219,6 +223,54 @@
</Column>
</Row>

<Row>
<Column>
<h3>Line Numbers</h3>
</Column>
</Row>

<Row class="mb-7">
<Column xlg={9} lg={12}>
<p class="mb-5">
Use the <code class="code">LineNumbers</code> component to render the highlighted
code with line numbers.
</p>
</Column>
<Column xlg={12}>
<Basic />
</Column>
<Column xlg={9} lg={12}>
<p class="mb-5">
Set <code class="code">hideBorder</code> to <code class="code">true</code>
to hide the border of the line numbers column.
</p>
</Column>
<Column xlg={12}>
<HideBorder />
</Column>
<Column xlg={9} lg={12}>
<p class="mb-5">
By default, overflowing horizontal content is contained by a scrollbar.
</p>
<p class="mb-5">
Set <code class="code">wrapLines</code> to <code class="code">true</code>
to apply a <code class="code">white-space: pre-wrap</code> rule to the
<code class="code">pre</code> element.
</p>
</Column>
<Column xlg={8}>
<WrapLines />
</Column>
<Column xlg={9} lg={12}>
<p class="mb-5">
Use <code class="code">--style-props</code> to customize visual properties.
</p>
</Column>
<Column xlg={12}>
<StyleProps />
</Column>
</Row>

<Row>
<Column>
<h3>Language Targeting</h3>
Expand Down
124 changes: 124 additions & 0 deletions src/LineNumbers.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
interface $$Props extends HTMLAttributes<HTMLDivElement> {
/**
* Pass the highlighted `code` to `LineNumbers`.
* @example
* <Highlight language={typescript} {code} let:highlighted>
* <LineNumbers {highlighted} />
* </Highlight>
*/
highlighted: string;
/**
* Set to `true` to hide the border of the line numbers column.
* @default false
*/
hideBorder?: boolean;
/**
* Set to `true` for lines to wrap.
* @default false
*/
wrapLines?: boolean;
/**
* Specify the text color for line numbers.
* Defaults to the current theme color applied to `.hljs code`.
* @default currentColor
* @example "pink"
*/
"--line-number-color"?: string;
/**
* Specify the border color.
* Defaults to the current background color applied to `.hljs`.
* @default currentColor
* @example "#fff"
*/
"--border-color"?: string;
/**
* Specify the left padding for `td` elements.
* @default 1em
* @example 0
*/
"--padding-left"?: number | string;
/**
* Specify the right padding for `td` elements.
* @default 1em
* @example 0
*/
"--padding-right"?: number | string;
}
export let highlighted: string;
export let hideBorder = false;
export let wrapLines = false;
const DIGIT_WIDTH = 18;
const MIN_DIGITS = 3;
$: lines = highlighted.split("\n");
$: len_digits = lines.length.toString().length;
$: len = len_digits - MIN_DIGITS < 1 ? MIN_DIGITS : len_digits;
$: width = len * DIGIT_WIDTH;
</script>

<div style:overflow-x="auto" {...$$restProps}>
<table style:width="100%">
<tbody class:hljs={true}>
{#each lines as line, i}
{@const lineNumber = i + 1}
{@const isFirst = i === 0}
{@const isLast = i === lines.length - 1}
<tr>
<td
class:hljs={true}
class:hideBorder
style:position="sticky"
style:left="0"
style:text-align="right"
style:user-select="none"
style:padding-top={isFirst ? "1em" : undefined}
style:padding-bottom={isLast ? "1em" : undefined}
style:width={width + "px"}
style:min-width={width + "px"}
>
<code style:color="var(--line-number-color, currentColor)">
{lineNumber}
</code>
</td>
<td>
<pre class:wrapLines><code>{@html line || "\n"}</code></pre>
</td>
</tr>
{/each}
</tbody>
</table>
</div>

<style>
td {
padding-left: var(--padding-left, 1em);
padding-right: var(--padding-right, 1em);
}
td.hljs:not(.hideBorder):after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 1px;
height: 100%;
background: var(--border-color, currentColor);
}
.wrapLines {
white-space: pre-wrap;
}
</style>
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as default } from "./Highlight.svelte";
export { default as Highlight } from "./Highlight.svelte";
export { default as default, default as Highlight } from "./Highlight.svelte";
export { default as HighlightAuto } from "./HighlightAuto.svelte";
export { default as HighlightSvelte } from "./HighlightSvelte.svelte";
export { default as LineNumbers } from "./LineNumbers.svelte";

12 changes: 11 additions & 1 deletion tests/SvelteHighlight.test.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<script lang="ts">
import Highlight, { HighlightAuto, HighlightSvelte } from "../src";
import Highlight, {
HighlightAuto,
HighlightSvelte,
LineNumbers,
} from "../src";
import Highlight2 from "../src/Highlight.svelte";
import { typescript } from "../src/languages";
import javascript from "../src/languages/javascript";
Expand Down Expand Up @@ -51,3 +55,9 @@
<Highlight2 code="123" />

<div id="highlighted">{highlighted}</div>

<div id="line-numbers">
<Highlight language={typescript} code="" let:highlighted>
<LineNumbers {highlighted} />
</Highlight>
</div>
9 changes: 8 additions & 1 deletion tests/SvelteHighlight.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect, describe, beforeEach } from "vitest";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, test } from "vitest";
import SvelteHighlight from "./SvelteHighlight.test.svelte";

describe("SvelteHighlight", () => {
Expand Down Expand Up @@ -55,5 +55,12 @@ describe("SvelteHighlight", () => {
).toMatchInlineSnapshot(
'"<code class=\\"hljs\\">&lt;<span class=\\"hljs-keyword\\">button</span> <span class=\\"hljs-keyword\\">on</span>:click&gt;Click me&lt;/<span class=\\"hljs-keyword\\">button</span>&gt;</code>"'
);

expect(
target.querySelector("#line-numbers")?.innerHTML
).toMatchInlineSnapshot(`
"<div style=\\"overflow-x: auto;\\" class=\\"svelte-1gt16c9\\"><table style=\\"width: 100%;\\"><tbody class=\\"hljs\\"><tr><td class=\\"svelte-1gt16c9 hljs\\" style=\\"position: sticky; left: 0px; text-align: right; user-select: none; padding-top: 1em; padding-bottom: 1em; width: 54px; min-width: 54px;\\"><code>1</code></td> <td class=\\"svelte-1gt16c9\\"><pre class=\\"svelte-1gt16c9\\"><code>
</code></pre></td> </tr></tbody></table></div>"
`);
});
});
Loading

0 comments on commit 82f9366

Please sign in to comment.