Skip to content

Commit 00e3a1a

Browse files
authored
Merge pull request #214 from code-hike/from-annotation
Add from annotation
2 parents ad20260 + f73c87d commit 00e3a1a

File tree

10 files changed

+142
-24
lines changed

10 files changed

+142
-24
lines changed
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("hello foo")
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("Hello foo")

packages/mdx/dev/content/external.mdx

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
```js foo.js
2+
// from ./assets/foo.js
3+
```
4+
5+
```py foo.py
6+
# from ./assets/foo.py
7+
```
8+
9+
```py foo.py
10+
print("not external")
11+
# from ./assets/foo.py
12+
```
13+
14+
<CH.Code>
15+
16+
```js foo.js
17+
// from ./assets/foo.js
18+
```
19+
20+
```py foo.py
21+
# from ./assets/foo.py
22+
```
23+
24+
```html index.html
25+
<h1>Hello</h1>
26+
```
27+
28+
---
29+
30+
```py another.py
31+
# from ./assets/foo.py
32+
```
33+
34+
</CH.Code>

packages/mdx/dev/files.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@ export async function getFiles() {
1010
.filter(file => file.endsWith(".mdx"))
1111
.map(filename => filename.slice(0, -4))
1212
}
13-
export async function getContent(filename: string) {
14-
const file = await fs.promises.readFile(
15-
`./dev/content/${filename}.mdx`,
16-
"utf8"
17-
)
13+
export async function getFile(filename: string) {
14+
const path = `./dev/content/${filename}.mdx`
15+
const file = await fs.promises.readFile(path, "utf8")
1816

19-
return file
17+
return { value: file, path }
2018
}
2119

22-
export async function getCode(file: string, config = {}) {
20+
export async function getCode(file: any, config = {}) {
2321
let debugLink = ""
2422

2523
const debugCompile = withDebugger(compile, {

packages/mdx/pages/[name].tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { runSync } from "@mdx-js/mdx"
22
import * as runtime from "react/jsx-runtime.js"
33
import { CH } from "../src/components"
4-
import { getCode, getContent, getFiles } from "../dev/files"
4+
import { getCode, getFile, getFiles } from "../dev/files"
55
import { ClickToComponent } from "click-to-react-component"
66
import { Layout } from "../dev/layout"
77

@@ -17,8 +17,8 @@ export async function getStaticProps({ params }) {
1717
const { name = "test" } = params
1818

1919
const files = await getFiles()
20-
const content = await getContent(name)
21-
const { code, debugLink } = await getCode(content)
20+
const file = await getFile(name)
21+
const { code, debugLink } = await getCode(file)
2222
return {
2323
props: {
2424
tests: files,

packages/mdx/src/remark/code.ts

+78-8
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
extractAnnotationsFromCode,
99
extractJSXAnnotations,
1010
} from "./annotations"
11-
import { mergeFocus } from "../utils"
11+
import { Code, mergeFocus } from "../utils"
1212
import { CodeNode, SuperNode } from "./nodes"
1313
import { CodeHikeConfig } from "./config"
14+
import { getCommentData } from "./comment-data"
1415

1516
export function isEditorNode(
1617
node: SuperNode,
@@ -119,12 +120,15 @@ async function mapFile(
119120

120121
const lang = (node.lang as string) || "text"
121122

122-
const code = await highlight({
123+
let code = await highlight({
123124
code: node.value as string,
124125
lang,
125126
theme,
126127
})
127128

129+
// if the code is a single line with a "from" annotation
130+
code = await getCodeFromExternalFileIfNeeded(code, config)
131+
128132
const [commentAnnotations, commentFocus] =
129133
extractAnnotationsFromCode(code)
130134

@@ -136,12 +140,12 @@ async function mapFile(
136140
options as any
137141
)
138142

139-
const linkAnnotations = extractLinks(
140-
node,
141-
index,
142-
parent,
143-
node.value as string
144-
)
143+
// const linkAnnotations = extractLinks(
144+
// node,
145+
// index,
146+
// parent,
147+
// nodeValue as string
148+
// )
145149

146150
const jsxAnnotations = extractJSXAnnotations(
147151
node,
@@ -188,3 +192,69 @@ function parseMetastring(
188192
})
189193
return { name: name || "", ...options }
190194
}
195+
196+
async function getCodeFromExternalFileIfNeeded(
197+
code: Code,
198+
config: CodeHikeConfig
199+
) {
200+
if (code?.lines?.length != 1) {
201+
return code
202+
}
203+
204+
const firstLine = code.lines[0]
205+
const commentData = getCommentData(firstLine, code.lang)
206+
207+
if (!commentData || commentData.key != "from") {
208+
return code
209+
}
210+
211+
const fileText = firstLine.tokens
212+
.map(t => t.content)
213+
.join("")
214+
215+
const codepath = commentData.data
216+
217+
let fs, path
218+
219+
try {
220+
fs = (await import("fs")).default
221+
path = (await import("path")).default
222+
if (!fs || !fs.readFileSync || !path || !path.resolve) {
223+
throw new Error("fs or path not found")
224+
}
225+
} catch (e) {
226+
e.message = `Code Hike couldn't resolve this annotation:
227+
${fileText}
228+
Looks like node "fs" and "path" modules are not available.`
229+
throw e
230+
}
231+
232+
// if we don't know the path of the mdx file:
233+
if (config.filepath === undefined) {
234+
throw new Error(
235+
`Code Hike couldn't resolve this annotation:
236+
${fileText}
237+
Someone is calling the mdx compile function without setting the path.
238+
Open an issue on CodeHike's repo for help.`
239+
)
240+
}
241+
242+
const dir = path.dirname(config.filepath)
243+
const absoluteCodepath = path.resolve(dir, codepath)
244+
245+
let nodeValue
246+
try {
247+
nodeValue = fs.readFileSync(absoluteCodepath, "utf8")
248+
} catch (e) {
249+
e.message = `Code Hike couldn't resolve this annotation:
250+
${fileText}
251+
${absoluteCodepath} doesn't exist.`
252+
throw e
253+
}
254+
255+
return await highlight({
256+
code: nodeValue,
257+
lang: code.lang,
258+
theme: config.theme,
259+
})
260+
}

packages/mdx/src/remark/comment-data.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Code } from "../utils"
22
import { annotationsMap } from "../mdx-client/annotations"
33

4-
const validKeys = ["focus", ...Object.keys(annotationsMap)]
4+
const validKeys = [
5+
"focus",
6+
"from",
7+
...Object.keys(annotationsMap),
8+
]
59

610
export function getCommentData(
711
line: Code["lines"][0],

packages/mdx/src/remark/config.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ export type CodeHikeConfig = {
55
skipLanguages: string[]
66
showExpandButton?: boolean
77
showCopyButton?: boolean
8+
// path to the current file, internal use only
9+
filepath?: string
810
}
911

1012
/**
1113
* Add defaults and normalize config
1214
*/
1315
export function addConfigDefaults(
14-
config: Partial<CodeHikeConfig> | undefined
16+
config: Partial<CodeHikeConfig> | undefined,
17+
cwd?: string,
18+
filepath?: string
1519
): CodeHikeConfig {
1620
// TODO warn when config looks weird
1721
return {
1822
...config,
1923
theme: config?.theme || {},
2024
autoImport: config?.autoImport === false ? false : true,
2125
skipLanguages: config?.skipLanguages || [],
26+
filepath,
2227
}
2328
}

packages/mdx/src/remark/transform.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ const transforms = [
2020
transformInlineCodes,
2121
transformCodes,
2222
]
23-
2423
export function transform(unsafeConfig: CodeHikeConfig) {
25-
return async (tree: SuperNode) => {
26-
const config = addConfigDefaults(unsafeConfig)
24+
return async (tree: SuperNode, file: any) => {
25+
const config = addConfigDefaults(
26+
unsafeConfig,
27+
file?.cwd,
28+
file?.history
29+
? file.history[file.history.length - 1]
30+
: undefined
31+
)
2732

2833
try {
2934
for (const transform of transforms) {

playground/src/index.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ main {
182182

183183
.compile-error pre {
184184
color: #111;
185-
white-space: normal;
185+
white-space: pre-wrap;
186186
}
187187

188188
.with-error {

0 commit comments

Comments
 (0)