From e01e02ece9afb18284a5da6f38feee11c1894d3a Mon Sep 17 00:00:00 2001 From: "Martin O. Reistadbakk" Date: Wed, 10 Dec 2025 21:19:02 +0100 Subject: [PATCH 1/2] Add selectable error message and copy button --- .../Libraries/LogBox/UI/AnsiHighlight.js | 2 +- .../LogBox/UI/LogBoxInspectorMessageHeader.js | 32 ++++++++++++++++++- .../LogBox/UI/LogBoxInspectorStackFrame.js | 2 ++ .../Libraries/LogBox/UI/LogBoxMessage.js | 8 +++-- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/LogBox/UI/AnsiHighlight.js b/packages/react-native/Libraries/LogBox/UI/AnsiHighlight.js index df3ae6565ae4a4..93bb8fd8e556c5 100644 --- a/packages/react-native/Libraries/LogBox/UI/AnsiHighlight.js +++ b/packages/react-native/Libraries/LogBox/UI/AnsiHighlight.js @@ -87,7 +87,7 @@ export default function Ansi({ {parsedLines.map((items, i) => ( - + {items.map((bundle, key) => { const textStyle = bundle.fg && COLORS[bundle.fg] diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js index 65259c1edba8c0..089032ff814868 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js @@ -11,9 +11,11 @@ import type {LogLevel} from '../Data/LogBoxLog'; import type {Message} from '../Data/parseLogBoxLog'; +import Clipboard from '../../Components/Clipboard/Clipboard'; import View from '../../Components/View/View'; import StyleSheet from '../../StyleSheet/StyleSheet'; import Text from '../../Text/Text'; +import LogBoxButton from './LogBoxButton'; import LogBoxMessage from './LogBoxMessage'; import * as LogBoxStyle from './LogBoxStyle'; import * as React from 'react'; @@ -47,12 +49,27 @@ function LogBoxInspectorMessageHeader(props: Props): React.Node { {props.title} + { + Clipboard.setString(props.message.content); + }} + style={messageStyles.copyButton}> + Copy + - + {frame.methodName} diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxMessage.js b/packages/react-native/Libraries/LogBox/UI/LogBoxMessage.js index 1e1fcc24e8ea6b..2886afa9f603c1 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxMessage.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxMessage.js @@ -97,7 +97,11 @@ function TappableLinks(props: { ); } - return {fragments}; + return ( + + {fragments} + + ); } const cleanContent = (content: string) => @@ -107,7 +111,7 @@ function LogBoxMessage(props: Props): React.Node { const {content, substitutions}: Message = props.message; if (props.plaintext === true) { - return {cleanContent(content)}; + return {cleanContent(content)}; } const maxLength = props.maxLength != null ? props.maxLength : Infinity; From e3723d5d569dc9badef41fc42c487460a556585b Mon Sep 17 00:00:00 2001 From: "Martin O. Reistadbakk" Date: Wed, 10 Dec 2025 22:01:33 +0100 Subject: [PATCH 2/2] copy full error --- .../LogBox/UI/LogBoxInspectorBody.js | 32 ++++++++++++++++++- .../LogBox/UI/LogBoxInspectorMessageHeader.js | 6 ++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorBody.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorBody.js index f9059fa82d1585..cbffc52c051f0e 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorBody.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorBody.js @@ -8,6 +8,7 @@ * @format */ +import Clipboard from '../../Components/Clipboard/Clipboard'; import ScrollView from '../../Components/ScrollView/ScrollView'; import StyleSheet from '../../StyleSheet/StyleSheet'; import LogBoxLog from '../Data/LogBoxLog'; @@ -17,7 +18,7 @@ import LogBoxInspectorReactFrames from './LogBoxInspectorReactFrames'; import LogBoxInspectorStackFrames from './LogBoxInspectorStackFrames'; import * as LogBoxStyle from './LogBoxStyle'; import * as React from 'react'; -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; const headerTitleMap = { warn: 'Console Warning', @@ -27,6 +28,19 @@ const headerTitleMap = { component: 'Render Error', }; +function formatStackFrameForCopy(frame: { + methodName: string, + file?: ?string, + lineNumber?: ?number, + column?: ?string | ?number, + ... +}): string { + const location = frame.file + ? ` (${frame.file}${frame.lineNumber != null ? ':' + frame.lineNumber : ''}${frame.column != null ? ':' + frame.column : ''})` + : ''; + return ` at ${frame.methodName}${location}`; +} + export default function LogBoxInspectorBody(props: { log: LogBoxLog, onRetry: () => void, @@ -41,12 +55,27 @@ export default function LogBoxInspectorBody(props: { props.log.type ?? headerTitleMap[props.log.isComponentError ? 'component' : props.log.level]; + const handleCopy = useCallback(() => { + const log = props.log; + const stack = log.getAvailableStack(); + + let text = `${headerTitle}\n\n${log.message.content}`; + + if (stack.length > 0) { + const stackText = stack.map(formatStackFrameForCopy).join('\n'); + text += `\n\n${stackText}`; + } + + Clipboard.setString(text); + }, [props.log, headerTitle]); + if (collapsed) { return ( <> setCollapsed(!collapsed)} + onCopy={handleCopy} message={props.log.message} level={props.log.level} title={headerTitle} @@ -67,6 +96,7 @@ export default function LogBoxInspectorBody(props: { setCollapsed(!collapsed)} + onCopy={handleCopy} message={props.log.message} level={props.log.level} title={headerTitle} diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js index 089032ff814868..a54f2c3a775ed2 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js @@ -11,7 +11,6 @@ import type {LogLevel} from '../Data/LogBoxLog'; import type {Message} from '../Data/parseLogBoxLog'; -import Clipboard from '../../Components/Clipboard/Clipboard'; import View from '../../Components/View/View'; import StyleSheet from '../../StyleSheet/StyleSheet'; import Text from '../../Text/Text'; @@ -26,6 +25,7 @@ type Props = $ReadOnly<{ level: LogLevel, title: string, onPress: () => void, + onCopy: () => void, }>; const SHOW_MORE_MESSAGE_LENGTH = 300; @@ -59,9 +59,7 @@ function LogBoxInspectorMessageHeader(props: Props): React.Node { default: 'transparent', pressed: LogBoxStyle.getBackgroundDarkColor(1), }} - onPress={() => { - Clipboard.setString(props.message.content); - }} + onPress={props.onCopy} style={messageStyles.copyButton}> Copy