Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/components/ansi-to-react.ts
Views: 687
/*1This is a fork of the BSD-licensed https://github.com/nteract/ansi-to-react23It should stay BSD licensed, NOT switched to cocalc's less open license.4*/56import Anser, { AnserJsonEntry } from "anser";7import { escapeCarriageReturn } from "escape-carriage";8import * as React from "react";910/**11* Converts ANSI strings into JSON output.12* @name ansiToJSON13* @function14* @param {String} input The input string.15* @param {boolean} use_classes If `true`, HTML classes will be appended16* to the HTML output.17* @return {Array} The parsed input.18*/19function ansiToJSON(20input: string,21use_classes: boolean = false22): AnserJsonEntry[] {23input = escapeCarriageReturn(fixBackspace(input));24return Anser.ansiToJson(input, {25json: true,26remove_empty: true,27use_classes,28});29}3031/**32* Create a class string.33* @name createClass34* @function35* @param {AnserJsonEntry} bundle36* @return {String} class name(s)37*/38function createClass(bundle: AnserJsonEntry): string | null {39let classNames: string = "";4041if (bundle.bg) {42classNames += `${bundle.bg}-bg `;43}44if (bundle.fg) {45classNames += `${bundle.fg}-fg `;46}47if (bundle.decoration) {48classNames += `ansi-${bundle.decoration} `;49}5051if (classNames === "") {52return null;53}5455classNames = classNames.substring(0, classNames.length - 1);56return classNames;57}5859/**60* Create the style attribute.61* @name createStyle62* @function63* @param {AnserJsonEntry} bundle64* @return {Object} returns the style object65*/66function createStyle(bundle: AnserJsonEntry): React.CSSProperties {67const style: React.CSSProperties = {};68if (bundle.bg) {69style.backgroundColor = `rgb(${bundle.bg})`;70}71if (bundle.fg) {72style.color = `rgb(${bundle.fg})`;73}74switch (bundle.decoration) {75case 'bold':76style.fontWeight = 'bold';77break;78case 'dim':79style.opacity = '0.5';80break;81case 'italic':82style.fontStyle = 'italic';83break;84case 'hidden':85style.visibility = 'hidden';86break;87case 'strikethrough':88style.textDecoration = 'line-through';89break;90case 'underline':91style.textDecoration = 'underline';92break;93case 'blink':94style.textDecoration = 'blink';95break;96default:97break;98}99return style;100}101102/**103* Converts an Anser bundle into a React Node.104* @param linkify whether links should be converting into clickable anchor tags.105* @param useClasses should render the span with a class instead of style.106* @param bundle Anser output.107* @param key108*/109110function convertBundleIntoReact(111linkify: boolean,112useClasses: boolean,113bundle: AnserJsonEntry,114key: number115): JSX.Element {116const style = useClasses ? null : createStyle(bundle);117const className = useClasses ? createClass(bundle) : null;118119if (!linkify) {120return React.createElement(121"span",122{ style, key, className },123bundle.content124);125}126127const content: React.ReactNode[] = [];128const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g;129130let index = 0;131let match: RegExpExecArray | null;132while ((match = linkRegex.exec(bundle.content)) !== null) {133const [, pre, url] = match;134135const startIndex = match.index + pre.length;136if (startIndex > index) {137content.push(bundle.content.substring(index, startIndex));138}139140// Make sure the href we generate from the link is fully qualified. We assume http141// if it starts with a www because many sites don't support https142const href = url.startsWith("www.") ? `http://${url}` : url;143content.push(144React.createElement(145"a",146{147key: index,148href,149target: "_blank",150},151`${url}`152)153);154155index = linkRegex.lastIndex;156}157158if (index < bundle.content.length) {159content.push(bundle.content.substring(index));160}161162return React.createElement("span", { style, key, className }, content);163}164165declare interface Props {166children?: string;167linkify?: boolean;168className?: string;169useClasses?: boolean;170}171172export default function Ansi(props: Props): JSX.Element {173const { className, useClasses, children, linkify } = props;174return React.createElement(175"code",176{ className },177ansiToJSON(children ?? "", useClasses ?? false).map(178convertBundleIntoReact.bind(null, linkify ?? false, useClasses ?? false)179)180);181}182183// This is copied from the Jupyter Classic source code184// notebook/static/base/js/utils.js to handle \b in a way185// that is **compatible with Jupyter classic**. One can186// argue that this behavior is questionable:187// https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter#188function fixBackspace(txt: string) {189let tmp = txt;190do {191txt = tmp;192// Cancel out anything-but-newline followed by backspace193tmp = txt.replace(/[^\n]\x08/gm, "");194} while (tmp.length < txt.length);195return txt;196}197198199200