@samitouri / QOS-React-2 / commits / 36c2bf5c3e

[DevTools] Allow all file links in Chrome DevTools to open in external editor (#33985)

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.)

Sebastian Markbåge committed Jul 25, 2025 at 10:27 UTC 36c2bf5c3e7cd2f804c04d261b675c14d09dcf14
4 files changed +94 -7
packages/react-devtools-extensions/src/main/index.js
+35 -1
@@ -18,7 +18,12 @@ import {
18 LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
19 } from 'react-devtools-shared/src/constants';
20 import {logEvent} from 'react-devtools-shared/src/Logger';
21 -import {normalizeUrlIfValid} from 'react-devtools-shared/src/utils';
21 +import {
22 + getAlwaysOpenInEditor,
23 + getOpenInEditorURL,
24 + normalizeUrlIfValid,
25 +} from 'react-devtools-shared/src/utils';
26 +import {checkConditions} from 'react-devtools-shared/src/devtools/views/Editor/utils';
27
28 import {
29 setBrowserSelectionFromReact,
@@ -543,3 +548,32 @@ if (chrome.devtools.panels.setThemeChangeHandler) {
548 // Firefox
549 chrome.devtools.panels.onThemeChanged.addListener(onThemeChanged);
550 }
551 +
552 +// Firefox doesn't support resources handlers yet.
553 +if (chrome.devtools.panels.setOpenResourceHandler) {
554 + chrome.devtools.panels.setOpenResourceHandler(
555 + (
556 + resource,
557 + lineNumber = 1,
558 + // The column is a new feature so we have to specify a default if it doesn't exist
559 + columnNumber = 1,
560 + ) => {
561 + const alwaysOpenInEditor = getAlwaysOpenInEditor();
562 + const editorURL = getOpenInEditorURL();
563 + if (alwaysOpenInEditor && editorURL) {
564 + const location = ['', resource.url, lineNumber, columnNumber];
565 + const {url, shouldDisableButton} = checkConditions(editorURL, location);
566 + if (!shouldDisableButton) {
567 + window.open(url);
568 + return;
569 + }
570 + }
571 + // Otherwise fallback to the built-in behavior.
572 + chrome.devtools.panels.openResource(
573 + resource.url,
574 + lineNumber - 1,
575 + columnNumber - 1,
576 + );
577 + },
578 + );
579 +}
packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js
+32 -1
@@ -37,6 +37,7 @@ export type Props = {selectedSource: ?SourceSelection};
37
38 function EditorPane({selectedSource}: Props) {
39 const [showSettings, setShowSettings] = useState(false);
40 + const [showLinkInfo, setShowLinkInfo] = useState(false);
41
42 const editorURL = useSyncExternalStore(
43 function subscribe(callback) {
@@ -50,6 +51,30 @@ function EditorPane({selectedSource}: Props) {
51 },
52 );
53
54 + if (showLinkInfo) {
55 + return (
56 + <div className={styles.EditorPane}>
57 + <div className={styles.EditorToolbar}>
58 + <div style={{display: 'flex', flex: '1 1 auto'}}>
59 + To enable link handling in your browser's DevTools settings, look
60 + for the option Extension -> Link Handling. Select "React Developer
61 + Tools".
62 + </div>
63 + <div className={styles.VRule} />
64 + <Button
65 + onClick={() =>
66 + startTransition(() => {
67 + setShowLinkInfo(false);
68 + setShowSettings(false);
69 + })
70 + }>
71 + <ButtonIcon type="close" />
72 + </Button>
73 + </div>
74 + </div>
75 + );
76 + }
77 +
78 let editorToolbar;
79 if (showSettings) {
80 editorToolbar = (
@@ -87,7 +112,13 @@ function EditorPane({selectedSource}: Props) {
112 {editorToolbar}
113 <div className={styles.EditorInfo}>
114 {editorURL ? (
90 - <CodeEditorByDefault />
115 + <CodeEditorByDefault
116 + onChange={alwaysOpenInEditor => {
117 + if (alwaysOpenInEditor) {
118 + startTransition(() => setShowLinkInfo(true));
119 + }
120 + }}
121 + />
122 ) : (
123 'Configure an external editor to open local files.'
124 )}
packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js
+11 -4
@@ -13,7 +13,11 @@ import {useLocalStorage} from '../hooks';
13
14 import styles from './SettingsShared.css';
15
16 -export default function CodeEditorByDefault(_: {}): React.Node {
16 +export default function CodeEditorByDefault({
17 + onChange,
18 +}: {
19 + onChange?: boolean => void,
20 +}): React.Node {
21 const [alwaysOpenInEditor, setAlwaysOpenInEditor] = useLocalStorage<boolean>(
22 LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
23 false,
@@ -24,9 +28,12 @@ export default function CodeEditorByDefault(_: {}): React.Node {
28 <input
29 type="checkbox"
30 checked={alwaysOpenInEditor}
27 - onChange={({currentTarget}) =>
28 - setAlwaysOpenInEditor(currentTarget.checked)
29 - }
31 + onChange={({currentTarget}) => {
32 + setAlwaysOpenInEditor(currentTarget.checked);
33 + if (onChange) {
34 + onChange(currentTarget.checked);
35 + }
36 + }}
37 className={styles.SettingRowCheckbox}
38 />
39 Open local files directly in your code editor
packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js
+16 -1
@@ -13,10 +13,13 @@ import {SettingsContext} from './SettingsContext';
13 import {StoreContext} from '../context';
14 import {CHANGE_LOG_URL} from 'react-devtools-shared/src/devtools/constants';
15 import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
16 -import CodeEditorOptions from './CodeEditorOptions';
16
17 import styles from './SettingsShared.css';
18 +
19 +import CodeEditorOptions from './CodeEditorOptions';
20 import CodeEditorByDefault from './CodeEditorByDefault';
21 +import {LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR} from '../../../constants';
22 +import {useLocalStorage} from '../hooks';
23
24 function getChangeLogUrl(version: ?string): string | null {
25 if (!version) {
@@ -47,6 +50,11 @@ export default function GeneralSettings(_: {}): React.Node {
50 const showBackendVersion =
51 backendVersion && backendVersion !== frontendVersion;
52
53 + const [alwaysOpenInEditor] = useLocalStorage<boolean>(
54 + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
55 + false,
56 + );
57 +
58 return (
59 <div className={styles.SettingList}>
60 {isInternalFacebookBuild && (
@@ -87,6 +95,13 @@ export default function GeneralSettings(_: {}): React.Node {
95
96 <div className={styles.SettingWrapper}>
97 <CodeEditorByDefault />
98 + {alwaysOpenInEditor && (__IS_CHROME__ || __IS_EDGE__) ? (
99 + <div>
100 + To enable link handling in your browser's DevTools settings, look
101 + for the option Extension -> Link Handling. Select "React Developer
102 + Tools".
103 + </div>
104 + ) : null}
105 </div>
106
107 <div className={styles.SettingWrapper}>