addons/isl/src/dag/renderText.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Hash} from '../types';
b69ab319import type {Ancestor} from './render';
b69ab3110
b69ab3111import {LinkLine, NodeLine, PadLine, Renderer} from './render';
b69ab3112
b69ab3113/* eslint no-bitwise: 0 */
b69ab3114/* Translated from fbcode/eden/scm/lib/renderdag/src/box_drawing.rs */
b69ab3115
b69ab3116const GLYPHYS = {
b69ab3117 SPACE: ' ',
b69ab3118 HORIZONTAL: '──',
b69ab3119 PARENT: '│ ',
b69ab3120 ANCESTOR: ': ',
b69ab3121 MERGE_LEFT: '╯ ',
b69ab3122 MERGE_RIGHT: '╰─',
b69ab3123 MERGE_BOTH: '┴─',
b69ab3124 FORK_LEFT: '╮ ',
b69ab3125 FORK_RIGHT: '╭─',
b69ab3126 FORK_BOTH: '┬─',
b69ab3127 JOIN_LEFT: '┤ ',
b69ab3128 JOIN_RIGHT: '├─',
b69ab3129 JOIN_BOTH: '┼─',
b69ab3130 TERMINATION: '~ ',
b69ab3131};
b69ab3132
b69ab3133/** Render a graph to text (string) */
b69ab3134export class TextRenderer {
b69ab3135 private inner: Renderer;
b69ab3136 private extraPadLine: string | undefined = undefined;
b69ab3137
b69ab3138 constructor(private config?: {debugLinkLineFromNode: boolean}) {
b69ab3139 this.inner = new Renderer();
b69ab3140 }
b69ab3141
b69ab3142 reserve(hash: Hash) {
b69ab3143 this.inner.reserve(hash);
b69ab3144 }
b69ab3145
b69ab3146 nextRow(hash: Hash, parents: Array<Ancestor>, message: string, glyph = 'o'): string {
b69ab3147 const {debugLinkLineFromNode = false} = this.config ?? {};
b69ab3148 const line = this.inner.nextRow(hash, parents);
b69ab3149 const out: string[] = [];
b69ab3150
b69ab3151 let needExtraPadLine = false;
b69ab3152 const messageLines = message.split('\n');
b69ab3153 const messageIter = messageLines.values();
b69ab3154 const pushWithMessageLine = (lineBuf: string[], msg?: string) => {
b69ab3155 const msgLine = msg ?? messageIter.next()?.value;
b69ab3156 if (msgLine != null) {
b69ab3157 lineBuf.push(' ');
b69ab3158 lineBuf.push(msgLine);
b69ab3159 }
b69ab3160 out.push(lineBuf.join('').trimEnd());
b69ab3161 out.push('\n');
b69ab3162 };
b69ab3163
b69ab3164 // Render the previous extra pad line.
b69ab3165 if (this.extraPadLine != null) {
b69ab3166 out.push(this.extraPadLine);
b69ab3167 out.push('\n');
b69ab3168 this.extraPadLine = undefined;
b69ab3169 }
b69ab3170
b69ab3171 // Render the node line.
b69ab3172 const outNodeLine: string[] = [];
b69ab3173 line.nodeLine.forEach((entry, i) => {
b69ab3174 if (debugLinkLineFromNode && i !== line.nodeColumn) {
b69ab3175 outNodeLine.push(GLYPHYS.SPACE);
b69ab3176 } else if (entry === NodeLine.Node) {
b69ab3177 outNodeLine.push(glyph);
b69ab3178 outNodeLine.push(' ');
b69ab3179 } else if (entry === NodeLine.Parent) {
b69ab3180 outNodeLine.push(GLYPHYS.PARENT);
b69ab3181 } else if (entry === NodeLine.Ancestor) {
b69ab3182 outNodeLine.push(GLYPHYS.ANCESTOR);
b69ab3183 } else if (entry === NodeLine.Blank) {
b69ab3184 outNodeLine.push(GLYPHYS.SPACE);
b69ab3185 }
b69ab3186 });
b69ab3187 pushWithMessageLine(outNodeLine);
b69ab3188
b69ab3189 // Render the top pad lines.
b69ab3190 const outTopPadLine: string[] = [];
b69ab3191 line.postNodeLine.forEach((entry, i) => {
b69ab3192 if (debugLinkLineFromNode && i !== line.nodeColumn) {
b69ab3193 outTopPadLine.push(GLYPHYS.SPACE);
b69ab3194 } else {
b69ab3195 outTopPadLine.push(toGlyph(entry));
b69ab3196 }
b69ab3197 });
b69ab3198 pushWithMessageLine(outTopPadLine, debugLinkLineFromNode ? '# top pad' : undefined);
b69ab3199
b69ab31100 // Render the link line.
b69ab31101 const linkLine = debugLinkLineFromNode ? line.linkLineFromNode : line.linkLine;
b69ab31102 if (linkLine != null) {
b69ab31103 const outLinkLine = [];
b69ab31104 for (const cur of linkLine) {
b69ab31105 if (cur.intersects(LinkLine.HORIZONTAL)) {
b69ab31106 if (cur.intersects(LinkLine.CHILD)) {
b69ab31107 outLinkLine.push(GLYPHYS.JOIN_BOTH);
b69ab31108 } else if (cur.intersects(LinkLine.ANY_FORK) && cur.intersects(LinkLine.ANY_MERGE)) {
b69ab31109 outLinkLine.push(GLYPHYS.JOIN_BOTH);
b69ab31110 } else if (
b69ab31111 cur.intersects(LinkLine.ANY_FORK) &&
b69ab31112 cur.intersects(LinkLine.VERT_PARENT) &&
b69ab31113 !line.merge
b69ab31114 ) {
b69ab31115 outLinkLine.push(GLYPHYS.JOIN_BOTH);
b69ab31116 } else if (cur.intersects(LinkLine.ANY_FORK)) {
b69ab31117 outLinkLine.push(GLYPHYS.FORK_BOTH);
b69ab31118 } else if (cur.intersects(LinkLine.ANY_MERGE)) {
b69ab31119 outLinkLine.push(GLYPHYS.MERGE_BOTH);
b69ab31120 } else {
b69ab31121 outLinkLine.push(GLYPHYS.HORIZONTAL);
b69ab31122 }
b69ab31123 } else if (cur.intersects(LinkLine.VERT_PARENT) && !line.merge) {
b69ab31124 const left = cur.intersects(LinkLine.LEFT_MERGE | LinkLine.LEFT_FORK);
b69ab31125 const right = cur.intersects(LinkLine.RIGHT_MERGE | LinkLine.RIGHT_FORK);
b69ab31126 if (left && right) {
b69ab31127 outLinkLine.push(GLYPHYS.JOIN_BOTH);
b69ab31128 } else if (left) {
b69ab31129 outLinkLine.push(GLYPHYS.JOIN_LEFT);
b69ab31130 } else if (right) {
b69ab31131 outLinkLine.push(GLYPHYS.JOIN_RIGHT);
b69ab31132 } else {
b69ab31133 outLinkLine.push(GLYPHYS.PARENT);
b69ab31134 }
b69ab31135 } else if (
b69ab31136 cur.intersects(LinkLine.VERT_PARENT | LinkLine.VERT_ANCESTOR) &&
b69ab31137 !cur.intersects(LinkLine.LEFT_FORK | LinkLine.RIGHT_FORK)
b69ab31138 ) {
b69ab31139 const left = cur.intersects(LinkLine.LEFT_MERGE);
b69ab31140 const right = cur.intersects(LinkLine.RIGHT_MERGE);
b69ab31141 if (left && right) {
b69ab31142 outLinkLine.push(GLYPHYS.JOIN_BOTH);
b69ab31143 } else if (left) {
b69ab31144 outLinkLine.push(GLYPHYS.JOIN_LEFT);
b69ab31145 } else if (right) {
b69ab31146 outLinkLine.push(GLYPHYS.JOIN_RIGHT);
b69ab31147 } else if (cur.intersects(LinkLine.VERT_ANCESTOR)) {
b69ab31148 outLinkLine.push(GLYPHYS.ANCESTOR);
b69ab31149 } else {
b69ab31150 outLinkLine.push(GLYPHYS.PARENT);
b69ab31151 }
b69ab31152 } else if (
b69ab31153 cur.intersects(LinkLine.LEFT_FORK) &&
b69ab31154 cur.intersects(LinkLine.LEFT_MERGE | LinkLine.CHILD)
b69ab31155 ) {
b69ab31156 outLinkLine.push(GLYPHYS.JOIN_LEFT);
b69ab31157 } else if (
b69ab31158 cur.intersects(LinkLine.RIGHT_FORK) &&
b69ab31159 cur.intersects(LinkLine.RIGHT_MERGE | LinkLine.CHILD)
b69ab31160 ) {
b69ab31161 outLinkLine.push(GLYPHYS.JOIN_RIGHT);
b69ab31162 } else if (cur.intersects(LinkLine.LEFT_MERGE) && cur.intersects(LinkLine.RIGHT_MERGE)) {
b69ab31163 outLinkLine.push(GLYPHYS.MERGE_BOTH);
b69ab31164 } else if (cur.intersects(LinkLine.LEFT_FORK) && cur.intersects(LinkLine.RIGHT_FORK)) {
b69ab31165 outLinkLine.push(GLYPHYS.FORK_BOTH);
b69ab31166 } else if (cur.intersects(LinkLine.LEFT_FORK)) {
b69ab31167 outLinkLine.push(GLYPHYS.FORK_LEFT);
b69ab31168 } else if (cur.intersects(LinkLine.LEFT_MERGE)) {
b69ab31169 outLinkLine.push(GLYPHYS.MERGE_LEFT);
b69ab31170 } else if (cur.intersects(LinkLine.RIGHT_FORK)) {
b69ab31171 outLinkLine.push(GLYPHYS.FORK_RIGHT);
b69ab31172 } else if (cur.intersects(LinkLine.RIGHT_MERGE)) {
b69ab31173 outLinkLine.push(GLYPHYS.MERGE_RIGHT);
b69ab31174 } else {
b69ab31175 outLinkLine.push(GLYPHYS.SPACE);
b69ab31176 }
b69ab31177 }
b69ab31178 pushWithMessageLine(outLinkLine, debugLinkLineFromNode ? '# link line' : undefined);
b69ab31179 }
b69ab31180
b69ab31181 // Patch the "padLines" for debugLinkLineFromNode.
b69ab31182 const {ancestryLine: padLines} = line;
b69ab31183 if (debugLinkLineFromNode) {
b69ab31184 padLines.forEach((padLine, i) => {
b69ab31185 if (!line.parentColumns.includes(i)) {
b69ab31186 padLines[i] = PadLine.Blank;
b69ab31187 }
b69ab31188 });
b69ab31189 }
b69ab31190
b69ab31191 // Render the term lines.
b69ab31192 // For each column, if terminated, use "-" "~". Otherwise, use the pad line.
b69ab31193 const termLine = line.termLine;
b69ab31194 if (termLine != null) {
b69ab31195 const termStrs = [GLYPHYS.PARENT, GLYPHYS.TERMINATION];
b69ab31196 termStrs.forEach(termStr => {
b69ab31197 const termLineOut: string[] = [];
b69ab31198 termLine.forEach((term, i) => {
b69ab31199 if (term) {
b69ab31200 termLineOut.push(termStr);
b69ab31201 } else {
b69ab31202 termLineOut.push(toGlyph(padLines.at(i)));
b69ab31203 }
b69ab31204 });
b69ab31205 pushWithMessageLine(termLineOut, debugLinkLineFromNode ? '# term line' : undefined);
b69ab31206 });
b69ab31207 needExtraPadLine = true;
b69ab31208 }
b69ab31209
b69ab31210 // Render the pad lines for long messages.
b69ab31211 // basePadLine is the pad line columns, without text messages.
b69ab31212 const basePadLine: string[] = [];
b69ab31213 for (const entry of padLines) {
b69ab31214 basePadLine.push(toGlyph(entry));
b69ab31215 }
b69ab31216
b69ab31217 if (debugLinkLineFromNode) {
b69ab31218 // For debugLinkLineFromNode, show the pad line for investigation.
b69ab31219 pushWithMessageLine([...basePadLine], '# pad line');
b69ab31220 out.push('-'.repeat(20) + '\n');
b69ab31221 } else {
b69ab31222 const messageRest = [...messageIter];
b69ab31223 if (messageRest.length === 0 && basePadLine.includes(toGlyph(PadLine.Ancestor))) {
b69ab31224 messageRest.push('');
b69ab31225 }
b69ab31226 for (const msg of messageRest) {
b69ab31227 const padLine: string[] = [...basePadLine];
b69ab31228 pushWithMessageLine(padLine, msg);
b69ab31229 needExtraPadLine = false;
b69ab31230 }
b69ab31231 }
b69ab31232
b69ab31233 if (needExtraPadLine) {
b69ab31234 this.extraPadLine = basePadLine.join('').trimEnd();
b69ab31235 }
b69ab31236
b69ab31237 return out.join('');
b69ab31238 }
b69ab31239}
b69ab31240
b69ab31241function toGlyph(pad?: PadLine): string {
b69ab31242 if (pad === PadLine.Parent) {
b69ab31243 return GLYPHYS.PARENT;
b69ab31244 } else if (pad === PadLine.Ancestor) {
b69ab31245 return GLYPHYS.ANCESTOR;
b69ab31246 } else {
b69ab31247 return GLYPHYS.SPACE;
b69ab31248 }
b69ab31249}