From fe2e70d8b73b0a357410e98bf2926cb0ccab86ad Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Sun, 25 Dec 2022 00:28:26 +0000 Subject: [PATCH 1/3] feat(ui/frontend): add shortcuts for tools --- ui/frontend/Playground.tsx | 31 +++++++++++++++++-- ui/frontend/hooks/shortcuts.tsx | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 ui/frontend/hooks/shortcuts.tsx diff --git a/ui/frontend/Playground.tsx b/ui/frontend/Playground.tsx index a2da3023e..073070eec 100644 --- a/ui/frontend/Playground.tsx +++ b/ui/frontend/Playground.tsx @@ -11,6 +11,8 @@ import { Orientation } from './types'; import * as actions from './actions'; import styles from './Playground.module.css'; +import { useKeyDown } from './hooks/shortcuts'; +import { useAppDispatch } from './configureStore'; const TRACK_OPTION_NAME = { [Orientation.Horizontal]: 'rowGutters', @@ -88,7 +90,32 @@ const ResizableArea: React.FC = () => { }; const Playground: React.FC = () => { - const showNotifications = useSelector(selectors.anyNotificationsToShowSelector); + const showNotifications = useSelector( + selectors.anyNotificationsToShowSelector + ); + + const dispatch = useAppDispatch(); + const handleRustFmt = useCallback((_event) => { + dispatch(actions.performFormat()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleClippy = useCallback((_event) => { + dispatch(actions.performClippy()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleMiri = useCallback((_event) => { + dispatch(actions.performMiri()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleMacroExpansion = useCallback((_event) => { + dispatch(actions.performMacroExpansion()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useKeyDown(['Control', 'Alt', 'f'], handleRustFmt); + useKeyDown(['Control', 'Alt', 'c'], handleClippy); + useKeyDown(['Control', 'Alt', 'm'], handleMiri); + useKeyDown(['Control', 'Alt', 'e'], handleMacroExpansion); return ( <> @@ -96,7 +123,7 @@ const Playground: React.FC = () => {
- { showNotifications && } + {showNotifications && } ); } diff --git a/ui/frontend/hooks/shortcuts.tsx b/ui/frontend/hooks/shortcuts.tsx new file mode 100644 index 000000000..56af40f89 --- /dev/null +++ b/ui/frontend/hooks/shortcuts.tsx @@ -0,0 +1,55 @@ +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'react'; + +export const useKeyDown = (keys: string[], callback: Function, node = null) => { + const [currentShortcutKeys, setCurrentShortcutKeys] = useState([]); + + const callbackRef = useRef(callback); + useLayoutEffect(() => { + callbackRef.current = callback; + }); + + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + // If key is already depressed, return early + if (currentShortcutKeys.includes(event.key)) { + return; + } + const newShortcutKeys = currentShortcutKeys.concat([event.key]); + // Note: this implementation cares about order of keys pressed + if ( + keys.length === newShortcutKeys.length && + keys.every((val, i) => newShortcutKeys[i] === val) + ) { + callbackRef.current(event); + } + setCurrentShortcutKeys(newShortcutKeys); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [keys] + ); + + const handleKeyUp = (event: KeyboardEvent) => { + setCurrentShortcutKeys((prev) => { + const keyIndex = prev.indexOf(event.key); + if (keyIndex !== -1) { + return prev.slice(0, keyIndex).concat(prev.slice(keyIndex + 1)); + } + return prev; + }); + }; + + useEffect(() => { + // target is either the provided node or the whole document + const targetNode = node ?? document; + targetNode.addEventListener('keydown', handleKeyDown); + targetNode.addEventListener('keyup', handleKeyUp); + return () => + targetNode && targetNode.removeEventListener('keydown', handleKeyDown); + }, [handleKeyDown, node]); +}; From 9d2492136e2cbeac68bf0f5e58fb126dc3bb1493 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Sun, 25 Dec 2022 01:05:27 +0000 Subject: [PATCH 2/3] fix: use map to allow multiple shortcuts --- ui/frontend/Playground.tsx | 11 +++++--- ui/frontend/hooks/shortcuts.tsx | 46 ++++++++++++++------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/ui/frontend/Playground.tsx b/ui/frontend/Playground.tsx index 073070eec..e33fb8bd7 100644 --- a/ui/frontend/Playground.tsx +++ b/ui/frontend/Playground.tsx @@ -112,10 +112,13 @@ const Playground: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useKeyDown(['Control', 'Alt', 'f'], handleRustFmt); - useKeyDown(['Control', 'Alt', 'c'], handleClippy); - useKeyDown(['Control', 'Alt', 'm'], handleMiri); - useKeyDown(['Control', 'Alt', 'e'], handleMacroExpansion); + const shortcutMap = new Map([ + [['Control', 'Alt', 'f'], handleRustFmt], + [['Control', 'Alt', 'c'], handleClippy], + [['Control', 'Alt', 'm'], handleMiri], + [['Control', 'Alt', 'x'], handleMacroExpansion], + ]); + useKeyDown(shortcutMap); return ( <> diff --git a/ui/frontend/hooks/shortcuts.tsx b/ui/frontend/hooks/shortcuts.tsx index 56af40f89..3eac84b32 100644 --- a/ui/frontend/hooks/shortcuts.tsx +++ b/ui/frontend/hooks/shortcuts.tsx @@ -1,19 +1,11 @@ -import { - useCallback, - useEffect, - useLayoutEffect, - useRef, - useState, -} from 'react'; +import { useCallback, useEffect, useState } from 'react'; -export const useKeyDown = (keys: string[], callback: Function, node = null) => { +export const useKeyDown = ( + shortcutMap: Map, + node = document +) => { const [currentShortcutKeys, setCurrentShortcutKeys] = useState([]); - const callbackRef = useRef(callback); - useLayoutEffect(() => { - callbackRef.current = callback; - }); - const handleKeyDown = useCallback( (event: KeyboardEvent) => { // If key is already depressed, return early @@ -21,17 +13,19 @@ export const useKeyDown = (keys: string[], callback: Function, node = null) => { return; } const newShortcutKeys = currentShortcutKeys.concat([event.key]); - // Note: this implementation cares about order of keys pressed - if ( - keys.length === newShortcutKeys.length && - keys.every((val, i) => newShortcutKeys[i] === val) - ) { - callbackRef.current(event); + for (const [keys, cb] of shortcutMap.entries()) { + // Note: this implementation cares about order of keys pressed + if ( + keys.length === newShortcutKeys.length && + keys.every((val, i) => newShortcutKeys[i] === val) + ) { + cb(event); + } } setCurrentShortcutKeys(newShortcutKeys); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [keys] + [shortcutMap] ); const handleKeyUp = (event: KeyboardEvent) => { @@ -45,11 +39,11 @@ export const useKeyDown = (keys: string[], callback: Function, node = null) => { }; useEffect(() => { - // target is either the provided node or the whole document - const targetNode = node ?? document; - targetNode.addEventListener('keydown', handleKeyDown); - targetNode.addEventListener('keyup', handleKeyUp); - return () => - targetNode && targetNode.removeEventListener('keydown', handleKeyDown); + node.addEventListener('keydown', handleKeyDown); + node.addEventListener('keyup', handleKeyUp); + return () => { + node.removeEventListener('keydown', handleKeyDown); + node.removeEventListener('keydown', handleKeyUp); + }; }, [handleKeyDown, node]); }; From 8221080d427da9bcae7a2499d9688edc54950fec Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Sun, 25 Dec 2022 01:37:59 +0000 Subject: [PATCH 3/3] feat: add styles and shortcut hints --- ui/frontend/ToolsMenu.module.css | 5 +++++ ui/frontend/ToolsMenu.tsx | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 ui/frontend/ToolsMenu.module.css diff --git a/ui/frontend/ToolsMenu.module.css b/ui/frontend/ToolsMenu.module.css new file mode 100644 index 000000000..74d170acd --- /dev/null +++ b/ui/frontend/ToolsMenu.module.css @@ -0,0 +1,5 @@ +.shortcut { + float: right; + background-color: #a0ffa0; + border: 2px solid #a0ffa0; +} diff --git a/ui/frontend/ToolsMenu.tsx b/ui/frontend/ToolsMenu.tsx index a0ed22c74..f47940ea1 100644 --- a/ui/frontend/ToolsMenu.tsx +++ b/ui/frontend/ToolsMenu.tsx @@ -9,6 +9,8 @@ import * as selectors from './selectors'; import * as actions from './actions'; import { useAppDispatch } from './configureStore'; +import styles from './ToolsMenu.module.css'; + interface ToolsMenuProps { close: () => void; } @@ -46,18 +48,21 @@ const ToolsMenu: React.FC = props => { + ⌘/Ctrl + Alt + f
Format this code with Rustfmt.
{rustfmtVersion} ({rustfmtVersionDetails})
+ ⌘/Ctrl + Alt + c
Catch common mistakes and improve the code using the Clippy linter.
{clippyVersion} ({clippyVersionDetails})
+ ⌘/Ctrl + Alt + m
Execute this program in the Miri interpreter to detect certain cases of undefined behavior (like out-of-bounds memory access). @@ -67,6 +72,7 @@ const ToolsMenu: React.FC = props => { + ⌘/Ctrl + Alt + x
Expand macros in code using the nightly compiler.