From 7114cddce6a4b57be7ec2829cd0a94f7fcf65c6c Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 21 Sep 2024 17:09:20 +0100 Subject: [PATCH 1/5] Scroll to source line on diff click --- .../src/components/Diff/DiffRowAsmDiffer.tsx | 46 ++++++++++++++++- frontend/src/components/Scratch/Scratch.tsx | 51 ++++++++++--------- frontend/src/components/ScrollContext.tsx | 3 ++ 3 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/ScrollContext.tsx diff --git a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx index cc2d281d..7e5d5130 100644 --- a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx +++ b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx @@ -1,13 +1,16 @@ /* eslint css-modules/no-unused-class: off */ -import { CSSProperties, memo, useContext } from "react" +import { CSSProperties, MutableRefObject, memo, useContext } from "react" import classNames from "classnames" +import { EditorView } from "codemirror" import memoize from "memoize-one" import { areEqual } from "react-window" import * as api from "@/lib/api" +import { ScrollContext } from "../ScrollContext" + import { PADDING_TOP, SelectedSourceLineContext } from "./Diff" import styles from "./Diff.module.scss" import { Highlighter } from "./Highlighter" @@ -61,17 +64,56 @@ function DiffCell({ cell, className, highlighter }: { highlighter: Highlighter }) { const selectedSourceLine = useContext(SelectedSourceLineContext) + const editorView = useContext>(ScrollContext) const hasLineNo = typeof cell?.src_line != "undefined" if (!cell) return
+ const scrollToLineNumber = () => { + if (!editorView) { + return + } + + const lineNumbersEl = editorView.current.dom.querySelectorAll(".cm-gutter.cm-lineNumbers").item(0) + if (!lineNumbersEl || lineNumbersEl.children.length === 0) { + return + } + + // NOTE: Element 0 has textContent of "999" so ignore it + const firstLineNum = lineNumbersEl.children[1] as HTMLElement + const lastLineNum = lineNumbersEl.children[lineNumbersEl.children.length - 1] as HTMLElement + + const minLine = parseInt(firstLineNum.textContent) + const maxLine = parseInt(lastLineNum.textContent) + + const scrollerEl = editorView.current.dom.getElementsByClassName("cm-scroller").item(0) + + if ((minLine <= cell.src_line) && (cell.src_line <= maxLine)) { + // smoothly scroll to the desired line number + const lineNumberEl = lineNumbersEl.children[cell.src_line - minLine + 1] as HTMLElement + scrollerEl.scroll({ + left: lineNumberEl.offsetLeft, + top: lineNumberEl.offsetTop, + behavior: "smooth", + }) + } else { + // instantly scroll to the start/end and recurse. + scrollerEl.scroll({ + left: cell.src_line < minLine ? firstLineNum.offsetLeft : lastLineNum.offsetLeft, + top: cell.src_line < minLine ? firstLineNum.offsetTop : lastLineNum.offsetTop, + behavior: "instant", + }) + window.setTimeout(scrollToLineNumber, 0) + } + } + return
- {hasLineNo && {cell.src_line}} + {hasLineNo && }
} diff --git a/frontend/src/components/Scratch/Scratch.tsx b/frontend/src/components/Scratch/Scratch.tsx index b2c2c8e2..48cac437 100644 --- a/frontend/src/components/Scratch/Scratch.tsx +++ b/frontend/src/components/Scratch/Scratch.tsx @@ -16,6 +16,7 @@ import CompilationPanel from "../Diff/CompilationPanel" import CodeMirror from "../Editor/CodeMirror" import ErrorBoundary from "../ErrorBoundary" import ScoreBadge, { calculateScorePercent } from "../ScoreBadge" +import { ScrollContext } from "../ScrollContext" import { Tab, TabCloseButton } from "../Tabs" import useLanguageServer from "./hooks/useLanguageServer" @@ -322,28 +323,30 @@ export default function Scratch({ const matchPercent = calculateScorePercent(lastGoodScore.current, lastGoodMaxScore.current) - return
- - - - - - {matchProgressBarEnabledSetting &&
} -
- - {layout && } - - {offlineOverlay} -
+ return +
+ + + + + + {matchProgressBarEnabledSetting &&
} +
+ + {layout && } + + {offlineOverlay} +
+
} diff --git a/frontend/src/components/ScrollContext.tsx b/frontend/src/components/ScrollContext.tsx new file mode 100644 index 00000000..4ffbc65f --- /dev/null +++ b/frontend/src/components/ScrollContext.tsx @@ -0,0 +1,3 @@ +import { createContext } from "react" + +export const ScrollContext = createContext(null) From 01e61208ff50e4560e333b80853ccba9fb2114b2 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 21 Sep 2024 21:50:38 +0100 Subject: [PATCH 2/5] use chippy's better code --- .../src/components/Diff/DiffRowAsmDiffer.tsx | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx index 7e5d5130..7f7e1751 100644 --- a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx +++ b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx @@ -64,47 +64,24 @@ function DiffCell({ cell, className, highlighter }: { highlighter: Highlighter }) { const selectedSourceLine = useContext(SelectedSourceLineContext) - const editorView = useContext>(ScrollContext) + const sourceEditor = useContext>(ScrollContext) const hasLineNo = typeof cell?.src_line != "undefined" if (!cell) return
const scrollToLineNumber = () => { - if (!editorView) { + if (!sourceEditor) { return } - - const lineNumbersEl = editorView.current.dom.querySelectorAll(".cm-gutter.cm-lineNumbers").item(0) - if (!lineNumbersEl || lineNumbersEl.children.length === 0) { - return - } - - // NOTE: Element 0 has textContent of "999" so ignore it - const firstLineNum = lineNumbersEl.children[1] as HTMLElement - const lastLineNum = lineNumbersEl.children[lineNumbersEl.children.length - 1] as HTMLElement - - const minLine = parseInt(firstLineNum.textContent) - const maxLine = parseInt(lastLineNum.textContent) - - const scrollerEl = editorView.current.dom.getElementsByClassName("cm-scroller").item(0) - - if ((minLine <= cell.src_line) && (cell.src_line <= maxLine)) { - // smoothly scroll to the desired line number - const lineNumberEl = lineNumbersEl.children[cell.src_line - minLine + 1] as HTMLElement - scrollerEl.scroll({ - left: lineNumberEl.offsetLeft, - top: lineNumberEl.offsetTop, - behavior: "smooth", - }) - } else { - // instantly scroll to the start/end and recurse. - scrollerEl.scroll({ - left: cell.src_line < minLine ? firstLineNum.offsetLeft : lastLineNum.offsetLeft, - top: cell.src_line < minLine ? firstLineNum.offsetTop : lastLineNum.offsetTop, - behavior: "instant", - }) - window.setTimeout(scrollToLineNumber, 0) + if (cell.src_line <= sourceEditor.current.state.doc.lines) { + // check if the source line <= number of lines + // which can be false if pragmas are used to force line numbers + const line = sourceEditor.current.state.doc.line(cell.src_line) + if (line) { + const { top } = sourceEditor.current.lineBlockAt(line.to) + sourceEditor.current.scrollDOM.scrollTo({ top, behavior: "smooth" }) + } } } From 5c72329b5db757f1f92c7d47be745bf23ad7e83a Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 21 Sep 2024 22:18:36 +0100 Subject: [PATCH 3/5] Add same for objdiff diff (untested) --- .../src/components/Diff/DiffRowObjdiff.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Diff/DiffRowObjdiff.tsx b/frontend/src/components/Diff/DiffRowObjdiff.tsx index 567ae372..4a411a20 100644 --- a/frontend/src/components/Diff/DiffRowObjdiff.tsx +++ b/frontend/src/components/Diff/DiffRowObjdiff.tsx @@ -1,12 +1,15 @@ /* eslint css-modules/no-unused-class: off */ -import { CSSProperties, memo, useContext } from "react" +import { CSSProperties, MutableRefObject, memo, useContext } from "react" import classNames from "classnames" +import { EditorView } from "codemirror" import memoize from "memoize-one" import { DiffKind, DiffResult, FunctionDiff, InstructionDiff, ObjectDiff, displayDiff, oneof } from "objdiff-wasm" import { areEqual } from "react-window" +import { ScrollContext } from "../ScrollContext" + import { PADDING_TOP, SelectedSourceLineContext } from "./Diff" import styles from "./Diff.module.scss" import { Highlighter } from "./Highlighter" @@ -123,6 +126,7 @@ function DiffCell({ cell, baseAddress, className, highlighter }: { highlighter?: Highlighter }) { const selectedSourceLine = useContext(SelectedSourceLineContext) + const sourceEditor = useContext>(ScrollContext) const hasLineNo = typeof cell?.instruction?.line_number != "undefined" if (!cell) @@ -144,12 +148,27 @@ function DiffCell({ cell, baseAddress, className, highlighter }: { break } + const scrollToLineNumber = () => { + if (!sourceEditor) { + return + } + if (cell.instruction.line_number <= sourceEditor.current.state.doc.lines) { + // check if the source line <= number of lines + // which can be false if pragmas are used to force line numbers + const line = sourceEditor.current.state.doc.line(cell.instruction.line_number) + if (line) { + const { top } = sourceEditor.current.lineBlockAt(line.to) + sourceEditor.current.scrollDOM.scrollTo({ top, behavior: "smooth" }) + } + } + } + return
- {hasLineNo && {cell.instruction.line_number}} + {hasLineNo && }
} From 7bb193007ae2e9c72eb85c70fecb4869092f9782 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 21 Sep 2024 22:18:56 +0100 Subject: [PATCH 4/5] move context a little lower --- frontend/src/components/Scratch/Scratch.tsx | 46 ++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/Scratch/Scratch.tsx b/frontend/src/components/Scratch/Scratch.tsx index 48cac437..1256581d 100644 --- a/frontend/src/components/Scratch/Scratch.tsx +++ b/frontend/src/components/Scratch/Scratch.tsx @@ -323,30 +323,30 @@ export default function Scratch({ const matchPercent = calculateScorePercent(lastGoodScore.current, lastGoodMaxScore.current) - return -
- - - - - - {matchProgressBarEnabledSetting &&
} -
- - {layout && + + + + + + {matchProgressBarEnabledSetting &&
} +
+ + {layout && + } - - {offlineOverlay} -
-
+ /> + } + + {offlineOverlay} +
} From df1ba2b723aca2bf86d7afe9daec3114fa730ac9 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 21 Sep 2024 22:41:59 +0100 Subject: [PATCH 5/5] de-dupe code --- frontend/src/components/Diff/Diff.tsx | 18 +++++++++++++++++- .../src/components/Diff/DiffRowAsmDiffer.tsx | 19 ++----------------- .../src/components/Diff/DiffRowObjdiff.tsx | 19 ++----------------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/Diff/Diff.tsx b/frontend/src/components/Diff/Diff.tsx index b2108402..d03bcf96 100644 --- a/frontend/src/components/Diff/Diff.tsx +++ b/frontend/src/components/Diff/Diff.tsx @@ -1,8 +1,9 @@ /* eslint css-modules/no-unused-class: off */ -import { createContext, CSSProperties, forwardRef, HTMLAttributes, useRef, useState } from "react" +import { createContext, CSSProperties, forwardRef, HTMLAttributes, MutableRefObject, useRef, useState } from "react" import { VersionsIcon } from "@primer/octicons-react" +import { EditorView } from "codemirror" import { DiffResult } from "objdiff-wasm" import AutoSizer from "react-virtualized-auto-sizer" import { FixedSizeList } from "react-window" @@ -94,6 +95,21 @@ function ThreeWayToggleButton({ enabled, setEnabled }: { enabled: boolean, setEn } +export function scrollToLineNumber(editorView: MutableRefObject, lineNumber: number) { + if (!editorView) { + return + } + if (lineNumber <= editorView.current.state.doc.lines) { + // check if the source line <= number of lines + // which can be false if pragmas are used to force line numbers + const line = editorView.current.state.doc.line(lineNumber) + if (line) { + const { top } = editorView.current.lineBlockAt(line.to) + editorView.current.scrollDOM.scrollTo({ top, behavior: "smooth" }) + } + } +} + export const PADDING_TOP = 8 export const PADDING_BOTTOM = 8 diff --git a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx index 7f7e1751..45aa3799 100644 --- a/frontend/src/components/Diff/DiffRowAsmDiffer.tsx +++ b/frontend/src/components/Diff/DiffRowAsmDiffer.tsx @@ -11,7 +11,7 @@ import * as api from "@/lib/api" import { ScrollContext } from "../ScrollContext" -import { PADDING_TOP, SelectedSourceLineContext } from "./Diff" +import { PADDING_TOP, SelectedSourceLineContext, scrollToLineNumber } from "./Diff" import styles from "./Diff.module.scss" import { Highlighter } from "./Highlighter" @@ -70,27 +70,12 @@ function DiffCell({ cell, className, highlighter }: { if (!cell) return
- const scrollToLineNumber = () => { - if (!sourceEditor) { - return - } - if (cell.src_line <= sourceEditor.current.state.doc.lines) { - // check if the source line <= number of lines - // which can be false if pragmas are used to force line numbers - const line = sourceEditor.current.state.doc.line(cell.src_line) - if (line) { - const { top } = sourceEditor.current.lineBlockAt(line.to) - sourceEditor.current.scrollDOM.scrollTo({ top, behavior: "smooth" }) - } - } - } - return
- {hasLineNo && } + {hasLineNo && }
} diff --git a/frontend/src/components/Diff/DiffRowObjdiff.tsx b/frontend/src/components/Diff/DiffRowObjdiff.tsx index 4a411a20..a0a9fffa 100644 --- a/frontend/src/components/Diff/DiffRowObjdiff.tsx +++ b/frontend/src/components/Diff/DiffRowObjdiff.tsx @@ -10,7 +10,7 @@ import { areEqual } from "react-window" import { ScrollContext } from "../ScrollContext" -import { PADDING_TOP, SelectedSourceLineContext } from "./Diff" +import { PADDING_TOP, SelectedSourceLineContext, scrollToLineNumber } from "./Diff" import styles from "./Diff.module.scss" import { Highlighter } from "./Highlighter" @@ -148,27 +148,12 @@ function DiffCell({ cell, baseAddress, className, highlighter }: { break } - const scrollToLineNumber = () => { - if (!sourceEditor) { - return - } - if (cell.instruction.line_number <= sourceEditor.current.state.doc.lines) { - // check if the source line <= number of lines - // which can be false if pragmas are used to force line numbers - const line = sourceEditor.current.state.doc.line(cell.instruction.line_number) - if (line) { - const { top } = sourceEditor.current.lineBlockAt(line.to) - sourceEditor.current.scrollDOM.scrollTo({ top, behavior: "smooth" }) - } - } - } - return
- {hasLineNo && } + {hasLineNo && }
}