Skip to content
5 changes: 5 additions & 0 deletions .changeset/silent-shoes-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"codehike": patch
---

More flexible `!from` directive
1 change: 1 addition & 0 deletions apps/web/content/docs/concepts/assets/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!from ./assets/index.js
2 changes: 1 addition & 1 deletion apps/web/content/docs/concepts/code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ You can use the [Theme Editor](https://themes.codehike.org/editor) to customize
To include code from a file in your markdown codeblocks, you can use the `!from` directive followed by the path to the file (relative to the markdown file).

````txt
```js index.js
```js
!from ./assets/index.js
```
````
Expand Down
26 changes: 15 additions & 11 deletions packages/codehike/src/mdx/0.import-code-from-path.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { Code, Root } from "mdast"
import { visit } from "unist-util-visit"

/**
* Find all codeblocks like:
*
* ```jsx
* !from ./foo/bar.js
* ```
* and replace the value with the content of the referenced file.
* Find all codeblocks that contain lines starting with !from
* and replace those lines with the content from the referenced files.
*/
export async function transformImportedCode(
tree: Root,
file?: { history?: string[] },
) {
const nodes: Code[] = []
visit(tree, "code", (node) => {
if (node.value?.startsWith("!from ")) {
if (node.value?.includes("\n!from ") || node.value?.startsWith("!from ")) {
nodes.push(node)
}
})
Expand All @@ -27,9 +22,18 @@ export async function transformImportedCode(
const mdxPath = file?.history ? file.history[file.history.length - 1] : null
await Promise.all(
nodes.map(async (code) => {
const fromData = code.value.slice(6).trim()
const [codepath, range] = fromData?.split(/\s+/) || []
code.value = await readFile(codepath, mdxPath, range)
const lines = code.value.split("\n")
const newLines = await Promise.all(
lines.map(async (line) => {
if (line.startsWith("!from ")) {
const fromData = line.slice(6).trim()
const [codepath, range] = fromData?.split(/\s+/) || []
return await readFile(codepath, mdxPath, range)
}
return line
}),
)
code.value = newLines.join("\n")
}),
)

Expand Down
7 changes: 7 additions & 0 deletions packages/codehike/tests/md-suite/_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ snapshots:
- after-rehype
- before-recma-compiled-js
- before-recma-compiled-jsx
- before-recma-compiled-function
- before-recma-js
- before-recma-js-dev
- before-recma-jsx
- after-recma-js
- after-recma-js-dev
- after-recma-jsx
- compiled-js
- compiled-js-dev
- compiled-jsx
- compiled-function
- parsed-jsx
- rendered
- rendered-dev
---
```

Expand Down
5 changes: 2 additions & 3 deletions packages/codehike/tests/md-suite/assets/test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# !mark inside
import random

my_list = [1, 'a', 32, 'c', 'd', 31]
print(random.choice(my_list))
my_list = []
11 changes: 11 additions & 0 deletions packages/codehike/tests/md-suite/import-code.0.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
snapshots:
- compiled-jsx
- rendered
---

hello
Expand All @@ -12,3 +13,13 @@ hello
```py !
!from ./assets/test.py
```

```py
# !mark(2) bar
!from ./assets/test.py

def hello():
print("hello")

!from ./assets/test.py
```
21 changes: 21 additions & 0 deletions packages/codehike/tests/md-suite/import-code.0.render.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MDXContent } from "mdx/types"
import { AnnotationHandler, highlight, Pre, RawCode } from "../../src/code"
import React from "react"

export function render(Content: MDXContent) {
// @ts-ignore
return <Content components={{ MyCode }} />
}

async function MyCode({ codeblock }: { codeblock: RawCode }) {
const highlighted = await highlight(codeblock, "github-dark")
return <Pre code={highlighted} handlers={[mark]} />
}

const mark: AnnotationHandler = {
name: "mark",
Pre: ({ _stack, ...props }) => <section {...props} />,
Block: ({ children, annotation }) => (
<mark className={annotation.query}>{children}</mark>
),
}
12 changes: 9 additions & 3 deletions packages/codehike/tests/md-suite/import-code.7.compiled-jsx.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ function _createMdxContent(props) {
children: (
<>
<_components.p>{"hello"}</_components.p>
<MyCode
codeblock={{
value: "# !mark inside\r\nimport random\r\nmy_list = []",
lang: "py",
meta: "",
}}
/>
<MyCode
codeblock={{
value:
"import random\r\n\r\nmy_list = [1, 'a', 32, 'c', 'd', 31]\r\nprint(random.choice(my_list))",
'# !mark(2) bar\r\n# !mark inside\r\nimport random\r\nmy_list = []\n\r\ndef hello():\r\n print("hello")\r\n\r\n# !mark inside\r\nimport random\r\nmy_list = []',
lang: "py",
meta: "",
}}
Expand All @@ -26,8 +33,7 @@ function _createMdxContent(props) {
header: "",
},
code: {
value:
"import random\r\n\r\nmy_list = [1, 'a', 32, 'c', 'd', 31]\r\nprint(random.choice(my_list))",
value: "# !mark inside\r\nimport random\r\nmy_list = []",
lang: "py",
meta: "",
},
Expand Down
53 changes: 53 additions & 0 deletions packages/codehike/tests/md-suite/import-code.9.rendered.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<p>hello</p>
<section data-theme="github-dark" data-lang="python">
<mark class="inside">
<div>
<span style="color: #ff7b72">import</span>
<span style="color: #c9d1d9">random</span>
</div>
</mark>
<div>
<span style="color: #c9d1d9">my_list</span>
<span style="color: #ff7b72">=</span>
<span style="color: #c9d1d9">[]</span>
</div>
</section>
<section data-theme="github-dark" data-lang="python">
<mark class="inside">
<div>
<span style="color: #ff7b72">import</span>
<span style="color: #c9d1d9">random</span>
</div>
</mark>
<mark class="bar">
<div>
<span style="color: #c9d1d9">my_list</span>
<span style="color: #ff7b72">=</span>
<span style="color: #c9d1d9">[]</span>
</div>
</mark>
<div></div>
<div>
<span style="color: #ff7b72">def</span>
<span style="color: #d2a8ff">hello</span>
<span style="color: #c9d1d9">():</span>
</div>
<div>
<span style="color: #79c0ff">print</span>
<span style="color: #c9d1d9">(</span>
<span style="color: #a5d6ff">&quot;hello&quot;</span>
<span style="color: #c9d1d9">)</span>
</div>
<div></div>
<mark class="inside">
<div>
<span style="color: #ff7b72">import</span>
<span style="color: #c9d1d9">random</span>
</div>
</mark>
<div>
<span style="color: #c9d1d9">my_list</span>
<span style="color: #ff7b72">=</span>
<span style="color: #c9d1d9">[]</span>
</div>
</section>