1/**
2 * Copyright (c) 650 Industries.
3 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8import Anser from 'anser';
9import React from 'react';
10import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
11
12// Afterglow theme from https://iterm2colorschemes.com/
13const COLORS: Record<string, string> = {
14  'ansi-black': 'rgb(27, 27, 27)',
15  'ansi-red': 'rgb(187, 86, 83)',
16  'ansi-green': 'rgb(144, 157, 98)',
17  'ansi-yellow': 'rgb(234, 193, 121)',
18  'ansi-blue': 'rgb(125, 169, 199)',
19  'ansi-magenta': 'rgb(176, 101, 151)',
20  'ansi-cyan': 'rgb(140, 220, 216)',
21  // Instead of white, use the default color provided to the component
22  // 'ansi-white': 'rgb(216, 216, 216)',
23  'ansi-bright-black': 'rgb(98, 98, 98)',
24  'ansi-bright-red': 'rgb(187, 86, 83)',
25  'ansi-bright-green': 'rgb(144, 157, 98)',
26  'ansi-bright-yellow': 'rgb(234, 193, 121)',
27  'ansi-bright-blue': 'rgb(125, 169, 199)',
28  'ansi-bright-magenta': 'rgb(176, 101, 151)',
29  'ansi-bright-cyan': 'rgb(140, 220, 216)',
30  'ansi-bright-white': 'rgb(247, 247, 247)',
31};
32
33export function Ansi({ text, style }: { text: string; style: StyleProp<TextStyle> }) {
34  let commonWhitespaceLength = Infinity;
35  const parsedLines = text.split(/\n/).map((line) =>
36    Anser.ansiToJson(line, {
37      json: true,
38      remove_empty: true,
39      use_classes: true,
40    })
41  );
42
43  parsedLines.map((lines) => {
44    // The third item on each line includes the whitespace of the source code.
45    // We are looking for the least amount of common whitespace to trim all lines.
46    // Example: Array [" ", " 96 |", "     text", ...]
47    const match = lines[2] && lines[2]?.content?.match(/^ +/);
48    const whitespaceLength = (match && match[0]?.length) || 0;
49    if (whitespaceLength < commonWhitespaceLength) {
50      commonWhitespaceLength = whitespaceLength;
51    }
52  });
53
54  const getText = (content: string, key: number) => {
55    if (key === 1) {
56      // Remove the vertical bar after line numbers
57      return content.replace(/\| $/, ' ');
58    } else if (key === 2 && commonWhitespaceLength < Infinity) {
59      // Remove common whitespace at the beginning of the line
60      return content.substr(commonWhitespaceLength);
61    } else {
62      return content;
63    }
64  };
65
66  return (
67    <View>
68      {parsedLines.map((items, i) => (
69        <View style={styles.line} key={i}>
70          {items.map((bundle, key) => {
71            const textStyle =
72              bundle.fg && COLORS[bundle.fg]
73                ? {
74                    backgroundColor: bundle.bg && COLORS[bundle.bg],
75                    color: bundle.fg && COLORS[bundle.fg],
76                  }
77                : {
78                    backgroundColor: bundle.bg && COLORS[bundle.bg],
79                  };
80            return (
81              <Text style={[style, textStyle]} key={key}>
82                {getText(bundle.content, key)}
83              </Text>
84            );
85          })}
86        </View>
87      ))}
88    </View>
89  );
90}
91
92const styles = StyleSheet.create({
93  line: {
94    flexDirection: 'row',
95  },
96});
97