[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}>