@tanishqkancharla/diffmap 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -1
- package/index.html +6 -0
- package/package.json +3 -2
- package/src/changes.ts +15 -0
- package/src/codeNotes.ts +123 -0
- package/src/errors.ts +5 -0
- package/src/git.test.ts +13 -1
- package/src/git.ts +15 -2
- package/src/host/HostHome.tsx +22 -6
- package/src/host/home.md +148 -102
- package/src/host/home.test.ts +6 -7
- package/src/parseFence.ts +57 -17
- package/src/parseViewer.test.ts +216 -2
- package/src/parseViewer.ts +85 -19
- package/src/services.ts +289 -0
- package/src/share.ts +1 -1
- package/src/viewer/CallStackDiff.tsx +90 -24
- package/src/viewer/ChangesCard.tsx +133 -43
- package/src/viewer/CodeBlock.tsx +131 -0
- package/src/viewer/Fence.tsx +22 -2
- package/src/viewer/ServiceDiagramBlock.tsx +323 -0
- package/src/viewer/ServiceStage.tsx +988 -0
- package/src/viewer/SourceDiffPanel.tsx +11 -8
- package/src/viewer/TableOfContents.tsx +3 -0
- package/src/viewer/ViewerApp.tsx +18 -3
- package/src/viewer/notes.tsx +126 -0
- package/src/viewer/serviceLayout.ts +189 -0
- package/vite.config.ts +1 -0
- package/vite.gist.config.ts +1 -0
package/src/host/home.md
CHANGED
|
@@ -1,156 +1,202 @@
|
|
|
1
1
|
# diffmap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
diffmap turns a Markdown file into a page like this one. Your coding agent writes it to show you a change, before and after building it: diagrams you can play, call stacks of what changes, and links that open the code.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Install the skill
|
|
5
|
+
Install the skill, then ask your agent to write a diffmap for a change you are planning or for a branch it just built:
|
|
8
6
|
|
|
9
7
|
```sh
|
|
10
8
|
npx skills add tanishqkancharla/diffmap --skill generate-diffmap
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Serve locally
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
npx @tanishqkancharla/diffmap serve specs/<name>.md
|
|
19
|
-
```
|
|
11
|
+
The agent keeps the spec in `specs/<name>.md`, serves it on your machine, and can share it as a link like this page.
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
Everything below is an example spec: diffmap's own features, one per phase. Each phase uses the feature it describes. Click a call stack row, a diagram node, or anything marked ↗ to open its code.
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
## Show a change, not a wall of text
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
A coding agent explains a change in a long message. You read it, then open each file it names to check that the code says the same thing.
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
A diffmap puts the explanation next to the code. Each phase says what changes for you, then shows the change as call stacks and diagrams whose parts open the code they name.
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
npx @tanishqkancharla/diffmap share specs/<name>.md
|
|
31
|
-
```
|
|
21
|
+
### ✅ Phase 1: Call stacks that open the code
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
**Today**
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
The agent describes the call path in a paragraph. You search for each function it names and guess how they connect.
|
|
36
26
|
|
|
37
|
-
|
|
27
|
+
**Proposed**
|
|
38
28
|
|
|
39
|
-
`
|
|
29
|
+
The agent draws the path as data flowing through calls: each row reads `result = call(inputs)`, `+` rows are new, `−` rows go away, and each row opens its code. This is how diffmap draws the tree below:
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
```callstack
|
|
32
|
+
fence = parseFence(lang, source) [[src/parseFence.ts#parseFence]]
|
|
33
|
+
+└── lines = parseCallStack(source) # one row per line, with its links [[src/annotations.ts#parseCallStack]]
|
|
34
|
+
view = Fence({ fence }) [[src/viewer/Fence.tsx#Fence]]
|
|
35
|
+
-└── return CodeBlock(fence.source) # the tree as plain text
|
|
36
|
+
+├── if fence.kind === "callstack":
|
|
37
|
+
+│ └── return CallStackDiff(fence.lines) # rows with + and − marks [[src/viewer/CallStackDiff.tsx#CallStackDiff]]
|
|
38
|
+
+└── return CodeBlock(fence.source) # other languages stay pseudocode
|
|
39
|
+
```
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
### ✅ Phase 2: Diagrams you can click
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
**Today**
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
The agent draws a flow in ASCII boxes. The boxes do not link to anything, and they break when a label gets long.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
**Proposed**
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
Mermaid diagrams render in the page, and each node or arrow can open its code. Here is what happens when you click a reference:
|
|
52
50
|
|
|
53
51
|
```mermaid
|
|
54
52
|
flowchart LR
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
%% ref node:
|
|
61
|
-
%% ref node:
|
|
62
|
-
%% ref
|
|
53
|
+
Click[Click a reference] --> Find[useSourceReference]
|
|
54
|
+
Find -->|diffmap serve| Local[Read the repo with git]
|
|
55
|
+
Find -->|diffmap.dev| GitHub[fetchPinnedBlob]
|
|
56
|
+
Local --> Panel[SourceDiffPanel]
|
|
57
|
+
GitHub --> Panel
|
|
58
|
+
%% ref node:Find [[src/viewer/sourceNavigation.ts#useSourceReference]]
|
|
59
|
+
%% ref node:GitHub [[src/github/fetchRepo.ts#fetchPinnedBlob]]
|
|
60
|
+
%% ref node:Panel [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
61
|
+
%% ref edge:1 [[src/viewer/sourceNavigation.ts#requestSource]]
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
### ✅ Phase 3: Services you can play
|
|
65
|
+
|
|
66
|
+
**Today**
|
|
67
|
+
|
|
68
|
+
A sequence diagram shows the messages between services, but not what each message changes. You keep the state of every service in your head.
|
|
69
|
+
|
|
70
|
+
**Proposed**
|
|
71
|
+
|
|
72
|
+
A service diagram shows each service with its state, then steps through the events. Press **Play** to watch `diffmap serve` open a spec, or hover a dotted name to read what it is:
|
|
73
|
+
|
|
74
|
+
```services
|
|
75
|
+
service Laptop
|
|
76
|
+
> Your machine. serve reads files here and uploads nothing.
|
|
77
|
+
service Agent "coding agent"
|
|
78
|
+
> Writes the spec and runs diffmap for you.
|
|
79
|
+
service CLI "diffmap serve" [[src/serve.ts#startServer]]
|
|
80
|
+
> A small server that reads your repo with git.
|
|
81
|
+
state url = none
|
|
82
|
+
service Repo "your repo"
|
|
83
|
+
> Holds the spec and the commits it links to.
|
|
84
|
+
service Browser
|
|
85
|
+
service Page "the spec" [[src/viewer/ViewerApp.tsx#ViewerApp]]
|
|
86
|
+
state page = empty
|
|
87
|
+
service Panel "Diff panel" [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
88
|
+
> Opens beside the spec and shows the code a reference names.
|
|
89
|
+
state shows = none
|
|
90
|
+
|
|
91
|
+
step "The agent serves the spec"
|
|
92
|
+
Agent -> CLI: serve specs/search.md
|
|
93
|
+
step "serve checks every commit the spec links to"
|
|
94
|
+
CLI -> Repo: readDocumentChanges [[src/git.ts#readDocumentChanges]]
|
|
95
|
+
> A wrong SHA or line range fails here, so you never meet a broken link.
|
|
96
|
+
step "serve prints a URL for you"
|
|
97
|
+
CLI -> Agent: localhost URL
|
|
98
|
+
CLI.url = localhost:4321
|
|
99
|
+
step "You open the page"
|
|
100
|
+
Page -> CLI: GET /
|
|
101
|
+
CLI -> Page: spec
|
|
102
|
+
Page.page = rendered
|
|
103
|
+
step "You click a call stack row"
|
|
104
|
+
Panel -> CLI: /__diffmap/source
|
|
105
|
+
CLI -> Repo: git show
|
|
106
|
+
> Reads the file at the commit the row names, not the working tree.
|
|
107
|
+
step "The Diff panel shows the code"
|
|
108
|
+
CLI -> Panel: lines
|
|
109
|
+
Panel.shows = code
|
|
70
110
|
```
|
|
71
|
-
````
|
|
72
111
|
|
|
73
|
-
###
|
|
112
|
+
### ✅ Phase 4: Open code from prose
|
|
74
113
|
|
|
75
|
-
|
|
114
|
+
**Today**
|
|
76
115
|
|
|
77
|
-
|
|
78
|
-
ViewerApp
|
|
79
|
-
-└── DoneButton # a loud Done control
|
|
80
|
-
+└── CloseServerButton # quiet, local mode only [[dae6b98:src/viewer/ViewerApp.tsx#L219-L227]] [[dae6b98:src/viewer/CloseServerButton.tsx#L8-L16]]
|
|
81
|
-
└── DiffButton
|
|
82
|
-
```
|
|
116
|
+
A paragraph names a function as plain code, like `shareMarkdownFile`. You search the repo to find it.
|
|
83
117
|
|
|
84
|
-
|
|
85
|
-
shareMarkdownFile [[src/share.ts#shareMarkdownFile]]
|
|
86
|
-
-└── print the gist URL
|
|
87
|
-
+└── gistViewerUrl # https://diffmap.dev/g/<id> [[d14ae1a:src/share.ts#L55-L59]]
|
|
88
|
-
```
|
|
118
|
+
**Proposed**
|
|
89
119
|
|
|
90
|
-
|
|
120
|
+
A function named in a paragraph opens its code too: [[src/share.ts#shareMarkdownFile]].
|
|
91
121
|
|
|
92
|
-
|
|
122
|
+
This phase is done, so it ends the way a finished phase does: with a card for the commit that built it, and references pinned to that commit. Pinned links show the code as the commit left it, even after the file moves on.
|
|
93
123
|
|
|
94
124
|
```changes
|
|
95
|
-
|
|
125
|
+
6e4b5d3
|
|
96
126
|
```
|
|
97
127
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
version: packageVersion,
|
|
108
|
-
args: serveArgs,
|
|
109
|
-
options: serveOptions,
|
|
110
|
-
output: serveOutput,
|
|
111
|
-
hint: "`serve <spec.md>` runs the local viewer. `share <spec.md>` publishes a secret gist. `update` (or `--update`) installs the latest diffmap.",
|
|
112
|
-
examples: [
|
|
113
|
-
{
|
|
114
|
-
args: { file: "spec.md" },
|
|
115
|
-
description: "Same as `diffmap serve spec.md`",
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
run: runServe,
|
|
119
|
-
})
|
|
128
|
+
```callstack
|
|
129
|
+
nodes = fromNode(text) [[6e4b5d3:src/parseViewer.ts#L318-L326]]
|
|
130
|
+
-└── return [{ type: "text", value: text }] # prose stays plain text
|
|
131
|
+
+└── return proseText(text) [[6e4b5d3:src/parseViewer.ts#L329-L353]]
|
|
132
|
+
+ ├── reference = parseReference(match) # each link in double brackets
|
|
133
|
+
+ └── return [...textBefore, { type: "reference", annotation }]
|
|
134
|
+
element = renderNode(node) [[6e4b5d3:src/viewer/ComarkView.tsx#L64-L68]]
|
|
135
|
+
+└── if node.type === "reference":
|
|
136
|
+
+ └── return InlineReference(node.annotation) # a code button that opens the Diff panel [[6e4b5d3:src/viewer/ComarkView.tsx#L173-L189]]
|
|
120
137
|
```
|
|
121
138
|
|
|
122
|
-
###
|
|
139
|
+
### ✅ Phase 5: Share a spec as a link
|
|
123
140
|
|
|
124
|
-
|
|
141
|
+
**Today**
|
|
125
142
|
|
|
126
|
-
|
|
127
|
-
<aside>If this renders, the host leaked HTML.</aside>
|
|
128
|
-
```
|
|
143
|
+
To read a spec, a teammate needs diffmap and a checkout of your repo.
|
|
129
144
|
|
|
130
|
-
|
|
145
|
+
**Proposed**
|
|
131
146
|
|
|
132
|
-
|
|
147
|
+
One command uploads the spec as a secret GitHub gist and prints a link anyone can open:
|
|
133
148
|
|
|
134
|
-
|
|
149
|
+
```sh
|
|
150
|
+
npx @tanishqkancharla/diffmap share specs/search.md
|
|
151
|
+
# prints https://diffmap.dev/g/<gistId>
|
|
152
|
+
```
|
|
135
153
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
The page that opens has no git and no checkout, so the gist carries what the spec reads: a snapshot of each commit it links, and the current contents of each file it quotes. That is why this page can show code at all:
|
|
155
|
+
|
|
156
|
+
```mermaid
|
|
157
|
+
flowchart LR
|
|
158
|
+
subgraph Gist
|
|
159
|
+
Spec[spec.md]
|
|
160
|
+
Changes[diffmap-changes.json]
|
|
161
|
+
Files[diffmap-files.json]
|
|
162
|
+
end
|
|
163
|
+
Spec -->|Markdown| Page[The spec page]
|
|
164
|
+
Changes -->|commits| Cards[Changes cards and commit links]
|
|
165
|
+
Files -->|file contents| Excerpts[Excerpts and plain links]
|
|
166
|
+
%% ref node:Changes [[2f96051:src/share.ts#L44-L53]]
|
|
167
|
+
%% ref node:Files [[2f96051:src/share.ts#L105-L125]]
|
|
168
|
+
%% ref node:Excerpts [[src/host/files.ts#HostFilesContext]]
|
|
140
169
|
```
|
|
141
170
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
```changes
|
|
172
|
+
2f96051
|
|
173
|
+
```
|
|
145
174
|
|
|
146
|
-
|
|
175
|
+
`share` builds both snapshots before it calls `gh`, so diffmap never stores a GitHub token:
|
|
147
176
|
|
|
148
177
|
```callstack
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
result = shareMarkdownFile({ filePath, workspaceRoot }) [[2f96051:src/share.ts#L26-L103]]
|
|
179
|
+
├── parsed = parseViewerDocument(source)
|
|
180
|
+
├── if parsed is an error: return parsed # a broken diagram stops here
|
|
181
|
+
├── changes = readDocumentChanges(workspaceRoot, parsed) # each linked commit
|
|
182
|
+
+├── files = readDocumentFiles(workspaceRoot, parsed) # each quoted file [[2f96051:src/share.ts#L105-L125]]
|
|
183
|
+
├── await gh("auth", "status")
|
|
184
|
+
-├── created = await gh("gist", "create", spec, changes)
|
|
185
|
+
+├── created = await gh("gist", "create", spec, changes, files)
|
|
186
|
+
└── return { gistId, viewerUrl: gistViewerUrl(gistId) }
|
|
153
187
|
```
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
This excerpt is one of the quoted files, so a shared copy of this page shows it too:
|
|
190
|
+
|
|
191
|
+
```54:63:src/share.ts
|
|
192
|
+
// It has no files either, so excerpts and references without a commit read these.
|
|
193
|
+
const files = readDocumentFiles(input.workspaceRoot, parsed);
|
|
194
|
+
if (files instanceof Error) return files;
|
|
195
|
+
|
|
196
|
+
const auth = await runGh(["auth", "status"]);
|
|
197
|
+
if (auth instanceof DiffmapShareError) return auth;
|
|
198
|
+
|
|
199
|
+
const args = ["gist", "create"];
|
|
200
|
+
if (input.isPublic) args.push("--public");
|
|
201
|
+
args.push("--desc", extractTitle(source), filePath);
|
|
202
|
+
```
|
package/src/host/home.test.ts
CHANGED
|
@@ -32,7 +32,7 @@ test("homepage markdown is a real viewer document", async () => {
|
|
|
32
32
|
assert.equal(kinds.has("callstack"), true);
|
|
33
33
|
assert.equal(kinds.has("changes"), true);
|
|
34
34
|
assert.equal(kinds.has("file"), true);
|
|
35
|
-
assert.equal(kinds.has("
|
|
35
|
+
assert.equal(kinds.has("services"), true);
|
|
36
36
|
|
|
37
37
|
const mermaid = fences.find((fence) => fence.kind === "mermaid");
|
|
38
38
|
assert.notEqual(mermaid, undefined);
|
|
@@ -68,16 +68,15 @@ test("homepage markdown is a real viewer document", async () => {
|
|
|
68
68
|
);
|
|
69
69
|
|
|
70
70
|
const excerpt = fences.find(
|
|
71
|
-
(fence) => fence.kind === "file" && fence.path === "src/
|
|
71
|
+
(fence) => fence.kind === "file" && fence.path === "src/share.ts",
|
|
72
72
|
);
|
|
73
73
|
assert.equal(excerpt?.kind, "file");
|
|
74
74
|
if (excerpt?.kind !== "file") return;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const lines =
|
|
75
|
+
const sharePath = path.join(path.dirname(homePath), "..", "share.ts");
|
|
76
|
+
const share = await fs.readFile(sharePath, "utf8");
|
|
77
|
+
const lines = share.split(/\n/);
|
|
78
78
|
const slice = lines.slice(excerpt.start - 1, excerpt.end).join("\n");
|
|
79
79
|
assert.equal(lines[excerpt.start - 1]?.trim().length !== 0, true);
|
|
80
|
-
assert.match(slice, /
|
|
81
|
-
assert.match(slice, /\}\)\s*$/);
|
|
80
|
+
assert.match(slice, /"gist", "create"/);
|
|
82
81
|
assert.equal(excerpt.source, slice);
|
|
83
82
|
});
|
package/src/parseFence.ts
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
import { parseCallStack, type SourceAnnotation } from "./annotations.js";
|
|
2
2
|
import { WORKING, type Rev } from "./changes.js";
|
|
3
|
+
import { parseCodeNotes, type CodeNote } from "./codeNotes.js";
|
|
3
4
|
import {
|
|
4
5
|
parseDiagramAnnotations,
|
|
5
6
|
type DiagramAnnotation,
|
|
6
7
|
} from "./diagramAnnotations.js";
|
|
8
|
+
import { parseServiceDiagram, type ServicesParse } from "./services.js";
|
|
7
9
|
|
|
8
10
|
export type Fence =
|
|
9
11
|
| { kind: "mermaid"; source: string; annotations: DiagramAnnotation[] }
|
|
12
|
+
| { kind: "services"; source: string; parsed: ServicesParse }
|
|
10
13
|
| { kind: "html"; source: string }
|
|
11
|
-
| {
|
|
12
|
-
|
|
14
|
+
| {
|
|
15
|
+
kind: "callstack";
|
|
16
|
+
/** Rows with trailing note lines removed. */
|
|
17
|
+
source: string;
|
|
18
|
+
lines: SourceAnnotation[];
|
|
19
|
+
/** 1-based rows from the info string, as in ```callstack {2}. */
|
|
20
|
+
highlights: number[];
|
|
21
|
+
notes: CodeNote[];
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
kind: "changes";
|
|
25
|
+
rev: Rev;
|
|
26
|
+
/** Paths to review first, in order; other changed files fold away. */
|
|
27
|
+
review: string[];
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
13
30
|
| { kind: "file"; path: string; start: number; end: number; source: string }
|
|
14
31
|
| { kind: "diff"; path: string | undefined; source: string }
|
|
15
|
-
| {
|
|
32
|
+
| {
|
|
33
|
+
kind: "code";
|
|
34
|
+
lang: string;
|
|
35
|
+
/** Code with trailing note lines removed. */
|
|
36
|
+
source: string;
|
|
37
|
+
/** 1-based lines from the info string, as in ```ts {2,4-6}. */
|
|
38
|
+
highlights: number[];
|
|
39
|
+
notes: CodeNote[];
|
|
40
|
+
};
|
|
16
41
|
|
|
17
42
|
const fileRefPattern = /^(\d+):(\d+):(.+)$/;
|
|
18
43
|
const diffPathPattern = /^diff:(.+)$/;
|
|
@@ -31,7 +56,11 @@ export function pathFromDiffSource(source: string) {
|
|
|
31
56
|
return undefined;
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
export function parseFence(
|
|
59
|
+
export function parseFence(
|
|
60
|
+
lang: string,
|
|
61
|
+
source: string,
|
|
62
|
+
highlights: number[] = [],
|
|
63
|
+
): Fence {
|
|
35
64
|
const trimmed = source.replace(/\n$/, "");
|
|
36
65
|
if (lang === "mermaid")
|
|
37
66
|
return {
|
|
@@ -39,19 +68,22 @@ export function parseFence(lang: string, source: string): Fence {
|
|
|
39
68
|
source: trimmed,
|
|
40
69
|
annotations: parseDiagramAnnotations(trimmed),
|
|
41
70
|
};
|
|
42
|
-
if (lang === "
|
|
43
|
-
if (lang === "callstack")
|
|
71
|
+
if (lang === "services")
|
|
44
72
|
return {
|
|
45
|
-
kind: "
|
|
73
|
+
kind: "services",
|
|
46
74
|
source: trimmed,
|
|
47
|
-
|
|
75
|
+
parsed: parseServiceDiagram(trimmed),
|
|
48
76
|
};
|
|
77
|
+
if (lang === "html") return { kind: "html", source: trimmed };
|
|
78
|
+
if (lang === "callstack") return callStackFence(trimmed, highlights);
|
|
49
79
|
|
|
80
|
+
// The first line is the rev (blank for uncommitted); each later line is a path to review.
|
|
50
81
|
if (lang === "changes") {
|
|
51
|
-
const rev = trimmed.trim();
|
|
82
|
+
const [rev = "", ...paths] = trimmed.split("\n").map((line) => line.trim());
|
|
52
83
|
return {
|
|
53
84
|
kind: "changes",
|
|
54
85
|
rev: rev.length === 0 ? WORKING : rev,
|
|
86
|
+
review: paths.filter((line) => line.length > 0),
|
|
55
87
|
source: trimmed,
|
|
56
88
|
};
|
|
57
89
|
}
|
|
@@ -69,13 +101,7 @@ export function parseFence(lang: string, source: string): Fence {
|
|
|
69
101
|
|
|
70
102
|
const diffPath = diffPathPattern.exec(lang);
|
|
71
103
|
if (lang === "diff" || diffPath !== null) {
|
|
72
|
-
if (isCallStackSource(trimmed))
|
|
73
|
-
return {
|
|
74
|
-
kind: "callstack",
|
|
75
|
-
source: trimmed,
|
|
76
|
-
lines: parseCallStack(trimmed),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
104
|
+
if (isCallStackSource(trimmed)) return callStackFence(trimmed, highlights);
|
|
79
105
|
return {
|
|
80
106
|
kind: "diff",
|
|
81
107
|
path: diffPath === null ? pathFromDiffSource(trimmed) : diffPath[1],
|
|
@@ -83,9 +109,23 @@ export function parseFence(lang: string, source: string): Fence {
|
|
|
83
109
|
};
|
|
84
110
|
}
|
|
85
111
|
|
|
112
|
+
const { code, notes } = parseCodeNotes(trimmed);
|
|
86
113
|
return {
|
|
87
114
|
kind: "code",
|
|
88
115
|
lang: lang.length === 0 ? "text" : lang,
|
|
89
|
-
source:
|
|
116
|
+
source: code,
|
|
117
|
+
highlights,
|
|
118
|
+
notes,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function callStackFence(source: string, highlights: number[]): Fence {
|
|
123
|
+
const { code, notes } = parseCodeNotes(source);
|
|
124
|
+
return {
|
|
125
|
+
kind: "callstack",
|
|
126
|
+
source: code,
|
|
127
|
+
lines: parseCallStack(code),
|
|
128
|
+
highlights,
|
|
129
|
+
notes,
|
|
90
130
|
};
|
|
91
131
|
}
|