diff --git a/pdl-live-react/src/view/masonry/MasonryCombo.tsx b/pdl-live-react/src/view/masonry/MasonryCombo.tsx index ea6954f77..55570360c 100644 --- a/pdl-live-react/src/view/masonry/MasonryCombo.tsx +++ b/pdl-live-react/src/view/masonry/MasonryCombo.tsx @@ -11,7 +11,6 @@ import { Button, BackToTop, Modal, - type ModalProps, ModalBody, ModalFooter, ModalHeader, @@ -28,6 +27,7 @@ import MasonryTileWrapper from "./MasonryTileWrapper" import Toolbar, { type SML } from "./Toolbar" import computeModel from "./model" +import ConditionVariable from "./condvar" import { hasContextInformation, hasTimingInformation, @@ -39,11 +39,7 @@ import RunningIcon from "@patternfly/react-icons/dist/esm/icons/running-icon" import "./Masonry.css" -export type Runner = ( - block: NonScalarPdlBlock, - onExit: () => void, - modalVariant?: ModalProps["variant"], -) => void +export type Runner = (block: NonScalarPdlBlock, onExit: () => void) => void type Props = { value: string @@ -60,9 +56,6 @@ function setSMLUserSetting(sml: SML) { /** Combines , , ... */ export default function MasonryCombo({ value, setValue }: Props) { - const [modalVariant, setModalVariant] = - useState("large") - const block = useMemo(() => { if (value) { try { @@ -83,11 +76,17 @@ export default function MasonryCombo({ value, setValue }: Props) { cmd: string args?: string[] onExit?: (exitCode: number) => void + cancelCondVar?: ConditionVariable }>(null) + const cancelModal = useCallback( + () => modalContent?.cancelCondVar?.signal(), + [modalContent?.cancelCondVar], + ) const closeModal = useCallback(() => { + modalContent?.cancelCondVar?.signal() setModalContent(null) setModalIsDone(-1) - }, [setModalContent, setModalIsDone]) + }, [modalContent?.cancelCondVar, setModalContent, setModalIsDone]) const onExit = useCallback( (exitCode: number) => { setModalIsDone(exitCode) @@ -97,19 +96,16 @@ export default function MasonryCombo({ value, setValue }: Props) { }, [setModalIsDone, modalContent], ) + useEffect(() => setModalIsDone(-2), [modalContent]) // special form of setModalContent for running a PDL program const run = useCallback( - async (runThisBlock, onExit, modalVariant) => { + async (runThisBlock, onExit) => { if (!isNonScalarPdlBlock(block)) { onExit() return } - if (modalVariant) { - setModalVariant(modalVariant) - } - const [cmd, input, output] = (await invoke("replay_prep", { trace: JSON.stringify(runThisBlock), name: @@ -132,6 +128,7 @@ export default function MasonryCombo({ value, setValue }: Props) { ...(!data ? [] : ["--data", JSON.stringify(data)]), input, ], + cancelCondVar: new ConditionVariable(), onExit: async () => { onExit() try { @@ -198,15 +195,11 @@ export default function MasonryCombo({ value, setValue }: Props) { - + @@ -228,10 +222,18 @@ export default function MasonryCombo({ value, setValue }: Props) { key="Close" variant={modalIsDone > 0 ? "danger" : "primary"} onClick={closeModal} - isDisabled={modalIsDone === -1} + isDisabled={modalIsDone < 0} > Close + diff --git a/pdl-live-react/src/view/masonry/MasonryTile.tsx b/pdl-live-react/src/view/masonry/MasonryTile.tsx index 223b75bc4..2bf949e34 100644 --- a/pdl-live-react/src/view/masonry/MasonryTile.tsx +++ b/pdl-live-react/src/view/masonry/MasonryTile.tsx @@ -53,7 +53,7 @@ export default function MasonryTile({ const myRun = useCallback(() => { if (block && run) { setIsRunning(true) - run(block, () => setIsRunning(false), "medium") + run(block, () => setIsRunning(false)) } }, [block, run, setIsRunning]) diff --git a/pdl-live-react/src/view/masonry/condvar.ts b/pdl-live-react/src/view/masonry/condvar.ts new file mode 100644 index 000000000..61baf0a7d --- /dev/null +++ b/pdl-live-react/src/view/masonry/condvar.ts @@ -0,0 +1,29 @@ +export default class ConditionVariable { + private condition: boolean = false + private waitingPromises: { + resolve: () => void + reject: (reason?: unknown) => void + }[] = [] + + public async wait(): Promise { + if (this.condition) { + return Promise.resolve() + } + + return new Promise((resolve, reject) => { + this.waitingPromises.push({ resolve, reject }) + }) + } + + public signal(): void { + if (this.waitingPromises.length > 0) { + const { resolve } = this.waitingPromises.shift()! + resolve() + this.condition = true + } + } + + public reset(): void { + this.condition = false + } +} diff --git a/pdl-live-react/src/view/term/RunTerminal.tsx b/pdl-live-react/src/view/term/RunTerminal.tsx index fcba32da8..f6fb50983 100644 --- a/pdl-live-react/src/view/term/RunTerminal.tsx +++ b/pdl-live-react/src/view/term/RunTerminal.tsx @@ -8,16 +8,25 @@ import { ClipboardAddon } from "@xterm/addon-clipboard" import "./RunTerminal.css" type Props = { + /** The cmd part of `cmd ...args` */ cmd: string + + /** The args part of `cmd ...args */ args?: string[] + + /** A callback provided by caller to be invoked upon pty completion */ onExit?: (exitCode: number) => void + + /** A condition variable used by caller to request pty cancellation */ + cancel?: import("../masonry/condvar").default } -export default function RunTerminal({ cmd, args = [], onExit }: Props) { +export default function RunTerminal({ cmd, args = [], onExit, cancel }: Props) { const ref = createRef() const [term, setTerm] = useState(null) const [exitCode, setExitCode] = useState(-1) + /** Schema adapter from our props.onExit to that of tauri-pty */ const onExit2 = useCallback( ({ exitCode }: { exitCode: number }) => { setExitCode(exitCode) @@ -28,7 +37,8 @@ export default function RunTerminal({ cmd, args = [], onExit }: Props) { [onExit, setExitCode], ) - useEffect(() => setExitCode(-1), [cmd, args, onExit]) + /** Re-initialization of exit code if props change */ + useEffect(() => setExitCode(-1), [cmd, args, onExit, cancel]) // Why a two-stage useEffect? Otherwise: cannot read properties of // undefined (reading 'dimensions') @@ -64,6 +74,11 @@ export default function RunTerminal({ cmd, args = [], onExit }: Props) { rows: term.rows, }) + /** Respond to cancellation request by killing the pty */ + cancel?.wait().then(() => { + pty.kill() + }) + pty.onData((data) => term.write(data)) term.onData((data) => pty.write(data)) @@ -78,7 +93,7 @@ export default function RunTerminal({ cmd, args = [], onExit }: Props) { } } } - }, [term, ref, exitCode, args, cmd, onExit2]) + }, [term, ref, exitCode, cmd, args, cancel, onExit2]) return (