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/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 65259c1edba8c0..a54f2c3a775ed2 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorMessageHeader.js @@ -14,6 +14,7 @@ import type {Message} from '../Data/parseLogBoxLog'; 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'; @@ -24,6 +25,7 @@ type Props = $ReadOnly<{ level: LogLevel, title: string, onPress: () => void, + onCopy: () => void, }>; const SHOW_MORE_MESSAGE_LENGTH = 300; @@ -47,12 +49,25 @@ function LogBoxInspectorMessageHeader(props: Props): React.Node { {props.title} + + 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;