From 36c2bf5c3e7cd2f804c04d261b675c14d09dcf14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 25 Jul 2025 10:27:09 -0400 Subject: [PATCH] [DevTools] Allow all file links in Chrome DevTools to open in external editor (#33985) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacked on #33983. Allow React to be configured as the default handler of all links in Chrome DevTools. To do this you need to configure the Chrome DevTools setting for "Link Handling:" to be set to "React Developer Tools". By default this doesn't do anything but if you then check the box added in #33983 it starts open local files directly in the external editor. This needs docs to show how to enable that option. (As far as I can tell this broke in Chrome Canary 🙄 but hopefully fixed before stable.) --- .../src/main/index.js | 36 ++++++++++++++++++- .../src/devtools/views/Editor/EditorPane.js | 33 ++++++++++++++++- .../views/Settings/CodeEditorByDefault.js | 15 +++++--- .../views/Settings/GeneralSettings.js | 17 ++++++++- 4 files changed, 94 insertions(+), 7 deletions(-) diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index 70e2dc4567..f21bdc7a9a 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -18,7 +18,12 @@ import { LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY, } from 'react-devtools-shared/src/constants'; import {logEvent} from 'react-devtools-shared/src/Logger'; -import {normalizeUrlIfValid} from 'react-devtools-shared/src/utils'; +import { + getAlwaysOpenInEditor, + getOpenInEditorURL, + normalizeUrlIfValid, +} from 'react-devtools-shared/src/utils'; +import {checkConditions} from 'react-devtools-shared/src/devtools/views/Editor/utils'; import { setBrowserSelectionFromReact, @@ -543,3 +548,32 @@ if (chrome.devtools.panels.setThemeChangeHandler) { // Firefox chrome.devtools.panels.onThemeChanged.addListener(onThemeChanged); } + +// Firefox doesn't support resources handlers yet. +if (chrome.devtools.panels.setOpenResourceHandler) { + chrome.devtools.panels.setOpenResourceHandler( + ( + resource, + lineNumber = 1, + // The column is a new feature so we have to specify a default if it doesn't exist + columnNumber = 1, + ) => { + const alwaysOpenInEditor = getAlwaysOpenInEditor(); + const editorURL = getOpenInEditorURL(); + if (alwaysOpenInEditor && editorURL) { + const location = ['', resource.url, lineNumber, columnNumber]; + const {url, shouldDisableButton} = checkConditions(editorURL, location); + if (!shouldDisableButton) { + window.open(url); + return; + } + } + // Otherwise fallback to the built-in behavior. + chrome.devtools.panels.openResource( + resource.url, + lineNumber - 1, + columnNumber - 1, + ); + }, + ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js b/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js index 09c76e1d26..eb759748ca 100644 --- a/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js +++ b/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js @@ -37,6 +37,7 @@ export type Props = {selectedSource: ?SourceSelection}; function EditorPane({selectedSource}: Props) { const [showSettings, setShowSettings] = useState(false); + const [showLinkInfo, setShowLinkInfo] = useState(false); const editorURL = useSyncExternalStore( function subscribe(callback) { @@ -50,6 +51,30 @@ function EditorPane({selectedSource}: Props) { }, ); + if (showLinkInfo) { + return ( +
+
+
+ To enable link handling in your browser's DevTools settings, look + for the option Extension -> Link Handling. Select "React Developer + Tools". +
+
+ +
+
+ ); + } + let editorToolbar; if (showSettings) { editorToolbar = ( @@ -87,7 +112,13 @@ function EditorPane({selectedSource}: Props) { {editorToolbar}
{editorURL ? ( - + { + if (alwaysOpenInEditor) { + startTransition(() => setShowLinkInfo(true)); + } + }} + /> ) : ( 'Configure an external editor to open local files.' )} diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js b/packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js index 74dba4947b..cabc3d6584 100644 --- a/packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js +++ b/packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js @@ -13,7 +13,11 @@ import {useLocalStorage} from '../hooks'; import styles from './SettingsShared.css'; -export default function CodeEditorByDefault(_: {}): React.Node { +export default function CodeEditorByDefault({ + onChange, +}: { + onChange?: boolean => void, +}): React.Node { const [alwaysOpenInEditor, setAlwaysOpenInEditor] = useLocalStorage( LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, false, @@ -24,9 +28,12 @@ export default function CodeEditorByDefault(_: {}): React.Node { - setAlwaysOpenInEditor(currentTarget.checked) - } + onChange={({currentTarget}) => { + setAlwaysOpenInEditor(currentTarget.checked); + if (onChange) { + onChange(currentTarget.checked); + } + }} className={styles.SettingRowCheckbox} /> Open local files directly in your code editor diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js b/packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js index c6db4e8932..6823dd9f61 100644 --- a/packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js +++ b/packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js @@ -13,10 +13,13 @@ import {SettingsContext} from './SettingsContext'; import {StoreContext} from '../context'; import {CHANGE_LOG_URL} from 'react-devtools-shared/src/devtools/constants'; import {isInternalFacebookBuild} from 'react-devtools-feature-flags'; -import CodeEditorOptions from './CodeEditorOptions'; import styles from './SettingsShared.css'; + +import CodeEditorOptions from './CodeEditorOptions'; import CodeEditorByDefault from './CodeEditorByDefault'; +import {LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR} from '../../../constants'; +import {useLocalStorage} from '../hooks'; function getChangeLogUrl(version: ?string): string | null { if (!version) { @@ -47,6 +50,11 @@ export default function GeneralSettings(_: {}): React.Node { const showBackendVersion = backendVersion && backendVersion !== frontendVersion; + const [alwaysOpenInEditor] = useLocalStorage( + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, + false, + ); + return (
{isInternalFacebookBuild && ( @@ -87,6 +95,13 @@ export default function GeneralSettings(_: {}): React.Node {
+ {alwaysOpenInEditor && (__IS_CHROME__ || __IS_EDGE__) ? ( +
+ To enable link handling in your browser's DevTools settings, look + for the option Extension -> Link Handling. Select "React Developer + Tools". +
+ ) : null}