mirror of
https://github.com/decompme/decomp.me.git
synced 2026-02-22 14:29:18 -06:00
Highlight tokens in the diff view on click (#472)
* Highlight tokens in the diff view on click * update style * fix types Co-authored-by: Alex Bates <alex@nanaian.town>
This commit is contained in:
@@ -72,6 +72,8 @@
|
||||
|
||||
padding: 0 1em;
|
||||
|
||||
cursor: default;
|
||||
|
||||
&.highlight {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
|
||||
@@ -128,3 +130,12 @@
|
||||
.rotation6 { color: lightcyan; }
|
||||
.rotation7 { color: lightgreen; }
|
||||
.rotation8 { color: grey; }
|
||||
|
||||
.highlighted {
|
||||
color: white;
|
||||
background-color: #aa8b00;
|
||||
}
|
||||
|
||||
.highlightable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint css-modules/no-unused-class: off */
|
||||
|
||||
import { createContext, CSSProperties, forwardRef, HTMLAttributes, useContext, useEffect, useState } from "react"
|
||||
import { createContext, CSSProperties, forwardRef, HTMLAttributes, Fragment, useContext, useEffect, useState } from "react"
|
||||
|
||||
import classNames from "classnames"
|
||||
import AutoSizer from "react-virtualized-auto-sizer"
|
||||
@@ -17,25 +17,78 @@ import DragBar from "./DragBar"
|
||||
const PADDING_TOP = 8
|
||||
const PADDING_BOTTOM = 8
|
||||
|
||||
// Regex for tokenizing lines for click-to-highlight purposes.
|
||||
// Strings matched by the first regex group (spaces, punctuation)
|
||||
// are treated as non-highlightable.
|
||||
const RE_TOKEN = /([ \t,():]+|~>)|%(?:lo|hi)\([^)]+\)|[^ \t,():]+/g
|
||||
|
||||
const SelectedSourceLineContext = createContext<number | null>(null)
|
||||
|
||||
function FormatDiffText({ texts }: { texts: api.DiffText[] }) {
|
||||
return <> {
|
||||
texts.map((t, i) => {
|
||||
if (t.format == "rotation") {
|
||||
return <span key={i} className={styles[`rotation${t.index % 9}`]}>{t.text}</span>
|
||||
} else if (t.format) {
|
||||
return <span key={i} className={styles[t.format]}>{t.text}</span>
|
||||
} else {
|
||||
return <span key={i}>{t.text}</span>
|
||||
}
|
||||
})
|
||||
} </>
|
||||
type Highlighter = {
|
||||
value: string | null
|
||||
setValue: (value: string | null) => void
|
||||
select: (value: string) => void
|
||||
}
|
||||
|
||||
function DiffCell({ cell, className }: {
|
||||
function useHighlighter(setAll: Highlighter["setValue"]): Highlighter {
|
||||
const [value, setValue] = useState(null)
|
||||
return {
|
||||
value,
|
||||
setValue,
|
||||
select: newValue => {
|
||||
// When selecting the same value twice (double-clicking), select it
|
||||
// in all diff columns
|
||||
if (value === newValue) {
|
||||
setAll(newValue)
|
||||
} else {
|
||||
setValue(newValue)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function FormatDiffText({ texts, highlighter }: {
|
||||
texts: api.DiffText[]
|
||||
highlighter: Highlighter
|
||||
}) {
|
||||
return <> {
|
||||
texts.map((t, index1) =>
|
||||
Array.from(t.text.matchAll(RE_TOKEN)).map((match, index2) => {
|
||||
const text = match[0]
|
||||
const isToken = !match[1]
|
||||
const key = index1 + "," + index2
|
||||
|
||||
let className: string
|
||||
if (t.format == "rotation") {
|
||||
className = styles[`rotation${t.index % 9}`]
|
||||
} else if (t.format) {
|
||||
className = styles[t.format]
|
||||
}
|
||||
|
||||
return <span
|
||||
key={key}
|
||||
className={classNames(className, {
|
||||
[styles.highlightable]: isToken,
|
||||
[styles.highlighted]: (highlighter.value === text),
|
||||
})}
|
||||
onClick={e => {
|
||||
if (isToken) {
|
||||
highlighter.select(text)
|
||||
e.stopPropagation()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
})
|
||||
)
|
||||
}</>
|
||||
}
|
||||
|
||||
function DiffCell({ cell, className, highlighter }: {
|
||||
cell: api.DiffCell | undefined
|
||||
className?: string
|
||||
highlighter: Highlighter
|
||||
}) {
|
||||
const selectedSourceLine = useContext(SelectedSourceLineContext)
|
||||
const hasLineNo = typeof cell?.src_line != "undefined"
|
||||
@@ -44,23 +97,22 @@ function DiffCell({ cell, className }: {
|
||||
return <div className={classNames(styles.cell, className)} />
|
||||
|
||||
return <div
|
||||
className={classNames(className, {
|
||||
[styles.cell]: true,
|
||||
className={classNames(styles.cell, className, {
|
||||
[styles.highlight]: hasLineNo && cell.src_line == selectedSourceLine,
|
||||
})}
|
||||
>
|
||||
{hasLineNo && <span className={styles.lineNumber}>{cell.src_line}</span>}
|
||||
<FormatDiffText texts={cell.text} />
|
||||
<FormatDiffText texts={cell.text} highlighter={highlighter} />
|
||||
</div>
|
||||
}
|
||||
|
||||
function DiffRow({ data, index, style }: {
|
||||
data: api.DiffRow[]
|
||||
index: number
|
||||
function DiffRow({ row, style, highlighter1, highlighter2, highlighter3 }: {
|
||||
row: api.DiffRow
|
||||
style: CSSProperties
|
||||
highlighter1: Highlighter
|
||||
highlighter2: Highlighter
|
||||
highlighter3: Highlighter
|
||||
}) {
|
||||
const row = data[index]
|
||||
|
||||
return <li
|
||||
className={styles.row}
|
||||
style={{
|
||||
@@ -69,9 +121,9 @@ function DiffRow({ data, index, style }: {
|
||||
lineHeight: `${style.height.toString()}px`,
|
||||
}}
|
||||
>
|
||||
<DiffCell cell={row.base} />
|
||||
<DiffCell cell={row.current} />
|
||||
<DiffCell cell={row.previous} />
|
||||
<DiffCell cell={row.base} highlighter={highlighter1} />
|
||||
<DiffCell cell={row.current} highlighter={highlighter2} />
|
||||
<DiffCell cell={row.previous} highlighter={highlighter3} />
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -89,7 +141,22 @@ const innerElementType = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListEl
|
||||
innerElementType.displayName = "innerElementType"
|
||||
|
||||
function DiffBody({ diff, fontSize }: { diff: api.DiffOutput, fontSize: number | undefined }) {
|
||||
return <div className={styles.bodyContainer}>
|
||||
const setHighlightAll: Highlighter["setValue"] = value => {
|
||||
highlighter1.setValue(value)
|
||||
highlighter2.setValue(value)
|
||||
highlighter3.setValue(value)
|
||||
}
|
||||
const highlighter1 = useHighlighter(setHighlightAll)
|
||||
const highlighter2 = useHighlighter(setHighlightAll)
|
||||
const highlighter3 = useHighlighter(setHighlightAll)
|
||||
|
||||
return <div
|
||||
className={styles.bodyContainer}
|
||||
onClick={() => {
|
||||
// If clicks propagate to the container, clear all
|
||||
setHighlightAll(null)
|
||||
}}
|
||||
>
|
||||
{diff?.rows && <AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
@@ -102,7 +169,15 @@ function DiffBody({ diff, fontSize }: { diff: api.DiffOutput, fontSize: number |
|
||||
height={height}
|
||||
innerElementType={innerElementType}
|
||||
>
|
||||
{DiffRow}
|
||||
{({ data, index, style }) =>
|
||||
<DiffRow
|
||||
row={data[index]}
|
||||
style={style}
|
||||
highlighter1={highlighter1}
|
||||
highlighter2={highlighter2}
|
||||
highlighter3={highlighter3}
|
||||
/>
|
||||
}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>}
|
||||
|
||||
Reference in New Issue
Block a user