Skip to content

Commit cb6eff6

Browse files
authored
Merge Diff components (asm-differ & objdiff) (#1333)
1 parent b16ee88 commit cb6eff6

File tree

7 files changed

+362
-721
lines changed

7 files changed

+362
-721
lines changed

frontend/src/components/Diff/CompilationPanel.tsx

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { ThreeWayDiffBase, useThreeWayDiffBase } from "@/lib/settings"
1111
import GhostButton from "../GhostButton"
1212

1313
import Diff from "./Diff"
14-
import NewDiff from "./NewDiff"
1514

1615
function getProblemState(compilation: api.Compilation): ProblemState {
1716
if (!compilation.success) {
@@ -105,25 +104,16 @@ export default function CompilationPanel({ scratch, compilation, isCompiling, is
105104
}}
106105
>
107106
<Allotment.Pane>
108-
{objdiffResult ? (
109-
<NewDiff
110-
diff={objdiffResult}
111-
diffLabel={scratch.diff_label}
112-
isCompiling={isCompiling}
113-
isCurrentOutdated={isCompilationOld || problemState == ProblemState.ERRORS}
114-
selectedSourceLine={selectedSourceLine}
115-
/>
116-
) : (
117-
<Diff
118-
diff={diff}
119-
isCompiling={isCompiling}
120-
isCurrentOutdated={isCompilationOld || problemState == ProblemState.ERRORS}
121-
threeWayDiffEnabled={threeWayDiffEnabled}
122-
setThreeWayDiffEnabled={setThreeWayDiffEnabled}
123-
threeWayDiffBase={threeWayDiffBase}
124-
selectedSourceLine={selectedSourceLine}
125-
/>
126-
)}
107+
<Diff
108+
diff={diff || objdiffResult}
109+
diffLabel={scratch.diff_label}
110+
isCompiling={isCompiling}
111+
isCurrentOutdated={isCompilationOld || problemState == ProblemState.ERRORS}
112+
threeWayDiffEnabled={threeWayDiffEnabled}
113+
setThreeWayDiffEnabled={setThreeWayDiffEnabled}
114+
threeWayDiffBase={threeWayDiffBase}
115+
selectedSourceLine={selectedSourceLine}
116+
/>
127117
</Allotment.Pane>
128118
<Allotment.Pane
129119
minSize={problemsCollapsedHeight}

frontend/src/components/Diff/Diff.module.scss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,11 @@
9393

9494
align-items: stretch;
9595

96+
color: var(--g1600);
97+
9698
> .cell {
9799
overflow: hidden;
98100

99-
color: var(--g1600);
100-
101101
padding: 0 1em;
102102

103103
cursor: default;
@@ -131,12 +131,14 @@
131131
.immediate { color: #6d6dff; }
132132
.stack { color: #abc501; }
133133
.register { color: #aa8b00; }
134+
.symbol { color: #fff; }
134135

135136
.delay_slot {
136137
font-weight: bold;
137138
color: #969896;
138139
}
139140

141+
.diff_any { background-color: rgba(255, 255, 255, 0.02); }
140142
.diff_change { color: #6d6dff; }
141143
.diff_add { color: #45bd00; }
142144
.diff_remove { color: #c82829; }
Lines changed: 36 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
/* eslint css-modules/no-unused-class: off */
22

3-
import { createContext, CSSProperties, forwardRef, HTMLAttributes, memo, useContext, useRef, useState } from "react"
3+
import { createContext, CSSProperties, forwardRef, HTMLAttributes, useRef, useState } from "react"
44

55
import { VersionsIcon } from "@primer/octicons-react"
6-
import classNames from "classnames"
7-
import memoize from "memoize-one"
6+
import { DiffResult } from "objdiff-wasm"
87
import AutoSizer from "react-virtualized-auto-sizer"
9-
import { FixedSizeList, areEqual } from "react-window"
8+
import { FixedSizeList } from "react-window"
109

1110
import * as api from "@/lib/api"
1211
import { useSize } from "@/lib/hooks"
@@ -15,93 +14,10 @@ import { ThreeWayDiffBase, useCodeFontSize } from "@/lib/settings"
1514
import Loading from "../loading.svg"
1615

1716
import styles from "./Diff.module.scss"
17+
import * as AsmDiffer from "./DiffRowAsmDiffer"
18+
import * as Objdiff from "./DiffRowObjdiff"
1819
import DragBar from "./DragBar"
19-
import { Highlighter, useHighlighers } from "./Highlighter"
20-
21-
const PADDING_TOP = 8
22-
const PADDING_BOTTOM = 8
23-
24-
// Regex for tokenizing lines for click-to-highlight purposes.
25-
// Strings matched by the first regex group (spaces, punctuation)
26-
// are treated as non-highlightable.
27-
const RE_TOKEN = /([ \t,()[\]:]+|~>)|%(?:lo|hi)\([^)]+\)|[^ \t,()[\]:]+/g
28-
29-
const SelectedSourceLineContext = createContext<number | null>(null)
30-
31-
function FormatDiffText({ texts, highlighter }: {
32-
texts: api.DiffText[]
33-
highlighter: Highlighter
34-
}) {
35-
return <> {
36-
texts.map((t, index1) =>
37-
Array.from(t.text.matchAll(RE_TOKEN)).map((match, index2) => {
38-
const text = match[0]
39-
const isToken = !match[1]
40-
const key = index1 + "," + index2
41-
42-
let className: string
43-
if (t.format == "rotation") {
44-
className = styles[`rotation${t.index % 9}`]
45-
} else if (t.format) {
46-
className = styles[t.format]
47-
}
48-
49-
return <span
50-
key={key}
51-
className={classNames(className, {
52-
[styles.highlightable]: isToken,
53-
[styles.highlighted]: (highlighter.value === text),
54-
})}
55-
onClick={e => {
56-
if (isToken) {
57-
highlighter.select(text)
58-
e.stopPropagation()
59-
}
60-
}}
61-
>
62-
{text}
63-
</span>
64-
})
65-
)
66-
}</>
67-
}
68-
69-
function DiffCell({ cell, className, highlighter }: {
70-
cell: api.DiffCell | undefined
71-
className?: string
72-
highlighter: Highlighter
73-
}) {
74-
const selectedSourceLine = useContext(SelectedSourceLineContext)
75-
const hasLineNo = typeof cell?.src_line != "undefined"
76-
77-
if (!cell)
78-
return <div className={classNames(styles.cell, className)} />
79-
80-
return <div
81-
className={classNames(styles.cell, className, {
82-
[styles.highlight]: hasLineNo && cell.src_line == selectedSourceLine,
83-
})}
84-
>
85-
{hasLineNo && <span className={styles.lineNumber}>{cell.src_line}</span>}
86-
<FormatDiffText texts={cell.text} highlighter={highlighter} />
87-
</div>
88-
}
89-
90-
const DiffRow = memo(function DiffRow({ data, index, style }: { data: DiffListData, index: number, style: CSSProperties }) {
91-
const row = data.diff?.rows?.[index]
92-
return <li
93-
className={styles.row}
94-
style={{
95-
...style,
96-
top: `${parseFloat(style.top.toString()) + PADDING_TOP}px`,
97-
lineHeight: `${style.height.toString()}px`,
98-
}}
99-
>
100-
<DiffCell cell={row.base} highlighter={data.highlighters[0]} />
101-
<DiffCell cell={row.current} highlighter={data.highlighters[1]} />
102-
<DiffCell cell={row.previous} highlighter={data.highlighters[2]} />
103-
</li>
104-
}, areEqual)
20+
import { useHighlighers } from "./Highlighter"
10521

10622
// https://github.com/bvaughn/react-window#can-i-add-padding-to-the-top-and-bottom-of-a-list
10723
const innerElementType = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(({ style, ...rest }, ref) => {
@@ -116,21 +32,26 @@ const innerElementType = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListEl
11632
})
11733
innerElementType.displayName = "innerElementType"
11834

119-
interface DiffListData {
120-
diff: api.DiffOutput | null
121-
highlighters: Highlighter[]
35+
const isAsmDifferOutput = (diff: api.DiffOutput | DiffResult): diff is api.DiffOutput => {
36+
return Object.prototype.hasOwnProperty.call(diff, "arch_str")
12237
}
12338

124-
const createDiffListData = memoize((
125-
diff: api.DiffOutput | null,
126-
highlighters: Highlighter[]
127-
): DiffListData => {
128-
return { diff, highlighters }
129-
})
130-
131-
function DiffBody({ diff, fontSize }: { diff: api.DiffOutput | null, fontSize: number | undefined }) {
39+
function DiffBody({ diff, diffLabel, fontSize }: { diff: api.DiffOutput | DiffResult | null, diffLabel: string | null, fontSize: number | undefined }) {
13240
const { highlighters, setHighlightAll } = useHighlighers(3)
133-
const itemData = createDiffListData(diff, highlighters)
41+
42+
if (!diff) {
43+
return <div className={styles.bodyContainer} />
44+
}
45+
46+
let itemData: AsmDiffer.DiffListData | Objdiff.DiffListData
47+
let DiffRow: typeof AsmDiffer.DiffRow | typeof Objdiff.DiffRow
48+
if (isAsmDifferOutput(diff)) {
49+
itemData = AsmDiffer.createDiffListData(diff, diffLabel, highlighters)
50+
DiffRow = AsmDiffer.DiffRow
51+
} else {
52+
itemData = Objdiff.createDiffListData(diff, diffLabel, highlighters)
53+
DiffRow = Objdiff.DiffRow
54+
}
13455

13556
return <div
13657
className={styles.bodyContainer}
@@ -139,22 +60,22 @@ function DiffBody({ diff, fontSize }: { diff: api.DiffOutput | null, fontSize: n
13960
setHighlightAll(null)
14061
}}
14162
>
142-
{diff?.rows && <AutoSizer>
63+
<AutoSizer>
14364
{({ height, width }: {height: number|undefined, width:number|undefined}) => (
14465
<FixedSizeList
14566
className={styles.body}
146-
itemCount={diff.rows.length}
67+
itemCount={itemData.itemCount}
14768
itemData={itemData}
14869
itemSize={(fontSize ?? 12) * 1.33}
14970
overscanCount={40}
15071
width={width}
15172
height={height}
15273
innerElementType={innerElementType}
15374
>
154-
{DiffRow}
75+
{DiffRow as any}
15576
</FixedSizeList>
15677
)}
157-
</AutoSizer>}
78+
</AutoSizer>
15879
</div>
15980
}
16081

@@ -173,8 +94,14 @@ function ThreeWayToggleButton({ enabled, setEnabled }: { enabled: boolean, setEn
17394
</button>
17495
}
17596

97+
export const PADDING_TOP = 8
98+
export const PADDING_BOTTOM = 8
99+
100+
export const SelectedSourceLineContext = createContext<number | null>(null)
101+
176102
export type Props = {
177-
diff: api.DiffOutput | null
103+
diff: api.DiffOutput | DiffResult | null
104+
diffLabel: string | null
178105
isCompiling: boolean
179106
isCurrentOutdated: boolean
180107
threeWayDiffEnabled: boolean
@@ -183,7 +110,7 @@ export type Props = {
183110
selectedSourceLine: number | null
184111
}
185112

186-
export default function Diff({ diff, isCompiling, isCurrentOutdated, threeWayDiffEnabled, setThreeWayDiffEnabled, threeWayDiffBase, selectedSourceLine }: Props) {
113+
export default function Diff({ diff, diffLabel, isCompiling, isCurrentOutdated, threeWayDiffEnabled, setThreeWayDiffEnabled, threeWayDiffBase, selectedSourceLine }: Props) {
187114
const [fontSize] = useCodeFontSize()
188115

189116
const container = useSize<HTMLDivElement>()
@@ -245,7 +172,7 @@ export default function Diff({ diff, isCompiling, isCurrentOutdated, threeWayDif
245172
</div>}
246173
</div>
247174
<SelectedSourceLineContext.Provider value={selectedSourceLine}>
248-
<DiffBody diff={diff} fontSize={fontSize} />
175+
<DiffBody diff={diff} diffLabel={diffLabel} fontSize={fontSize} />
249176
</SelectedSourceLineContext.Provider>
250177
</div>
251178
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/* eslint css-modules/no-unused-class: off */
2+
3+
import { CSSProperties, memo, useContext } from "react"
4+
5+
import classNames from "classnames"
6+
import memoize from "memoize-one"
7+
import { areEqual } from "react-window"
8+
9+
import * as api from "@/lib/api"
10+
11+
import { PADDING_TOP, SelectedSourceLineContext } from "./Diff"
12+
import styles from "./Diff.module.scss"
13+
import { Highlighter } from "./Highlighter"
14+
15+
// Regex for tokenizing lines for click-to-highlight purposes.
16+
// Strings matched by the first regex group (spaces, punctuation)
17+
// are treated as non-highlightable.
18+
const RE_TOKEN = /([ \t,()[\]:]+|~>)|%(?:lo|hi)\([^)]+\)|[^ \t,()[\]:]+/g
19+
20+
function FormatDiffText({ texts, highlighter }: {
21+
texts: api.DiffText[]
22+
highlighter: Highlighter
23+
}) {
24+
return <> {
25+
texts.map((t, index1) =>
26+
Array.from(t.text.matchAll(RE_TOKEN)).map((match, index2) => {
27+
const text = match[0]
28+
const isToken = !match[1]
29+
const key = index1 + "," + index2
30+
31+
let className: string
32+
if (t.format == "rotation") {
33+
className = styles[`rotation${t.index % 9}`]
34+
} else if (t.format) {
35+
className = styles[t.format]
36+
}
37+
38+
return <span
39+
key={key}
40+
className={classNames(className, {
41+
[styles.highlightable]: isToken,
42+
[styles.highlighted]: (highlighter.value === text),
43+
})}
44+
onClick={e => {
45+
if (isToken) {
46+
highlighter.select(text)
47+
e.stopPropagation()
48+
}
49+
}}
50+
>
51+
{text}
52+
</span>
53+
})
54+
)
55+
}</>
56+
}
57+
58+
function DiffCell({ cell, className, highlighter }: {
59+
cell: api.DiffCell | undefined
60+
className?: string
61+
highlighter: Highlighter
62+
}) {
63+
const selectedSourceLine = useContext(SelectedSourceLineContext)
64+
const hasLineNo = typeof cell?.src_line != "undefined"
65+
66+
if (!cell)
67+
return <div className={classNames(styles.cell, className)} />
68+
69+
return <div
70+
className={classNames(styles.cell, className, {
71+
[styles.highlight]: hasLineNo && cell.src_line == selectedSourceLine,
72+
})}
73+
>
74+
{hasLineNo && <span className={styles.lineNumber}>{cell.src_line}</span>}
75+
<FormatDiffText texts={cell.text} highlighter={highlighter} />
76+
</div>
77+
}
78+
79+
export type DiffListData = {
80+
diff: api.DiffOutput | null
81+
itemCount: number
82+
highlighters: Highlighter[]
83+
}
84+
85+
export const createDiffListData = memoize((
86+
diff: api.DiffOutput | null,
87+
_diffLabel: string,
88+
highlighters: Highlighter[]
89+
): DiffListData => {
90+
return { diff, highlighters, itemCount: diff?.rows?.length ?? 0 }
91+
})
92+
93+
export const DiffRow = memo(function DiffRow({ data, index, style }: { data: DiffListData, index: number, style: CSSProperties }) {
94+
const row = data.diff?.rows?.[index]
95+
return <li
96+
className={styles.row}
97+
style={{
98+
...style,
99+
top: `${parseFloat(style.top.toString()) + PADDING_TOP}px`,
100+
lineHeight: `${style.height.toString()}px`,
101+
}}
102+
>
103+
<DiffCell cell={row.base} highlighter={data.highlighters[0]} />
104+
<DiffCell cell={row.current} highlighter={data.highlighters[1]} />
105+
<DiffCell cell={row.previous} highlighter={data.highlighters[2]} />
106+
</li>
107+
}, areEqual)

0 commit comments

Comments
 (0)