Download packages/cli/src/test-utils/svg.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 6.46 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/test-utils/svg.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/test-utils/svg.ts
-
curl -L -o svg.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/test-utils/svg.ts
6.46 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import type { Terminal } from '@xterm/headless'; | |
| export const generateSvgForTerminal = (terminal: Terminal): string => { | |
| const activeBuffer = terminal.buffer.active; | |
| const getHexColor = ( | |
| isRGB: boolean, | |
| isPalette: boolean, | |
| isDefault: boolean, | |
| colorCode: number, | |
| ): string | null => { | |
| if (isDefault) return null; | |
| if (isRGB) { | |
| return `#${colorCode.toString(16).padStart(6, '0')}`; | |
| } | |
| if (isPalette) { | |
| if (colorCode >= 0 && colorCode <= 15) { | |
| return ( | |
| [ | |
| '#000000', | |
| '#cd0000', | |
| '#00cd00', | |
| '#cdcd00', | |
| '#0000ee', | |
| '#cd00cd', | |
| '#00cdcd', | |
| '#e5e5e5', | |
| '#7f7f7f', | |
| '#ff0000', | |
| '#00ff00', | |
| '#ffff00', | |
| '#5c5cff', | |
| '#ff00ff', | |
| '#00ffff', | |
| '#ffffff', | |
| ][colorCode] || null | |
| ); | |
| } else if (colorCode >= 16 && colorCode <= 231) { | |
| const v = [0, 95, 135, 175, 215, 255]; | |
| const c = colorCode - 16; | |
| const b = v[c % 6]; | |
| const g = v[Math.floor(c / 6) % 6]; | |
| const r = v[Math.floor(c / 36) % 6]; | |
| return `#${[r, g, b].map((x) => x?.toString(16).padStart(2, '0')).join('')}`; | |
| } else if (colorCode >= 232 && colorCode <= 255) { | |
| const gray = 8 + (colorCode - 232) * 10; | |
| const hex = gray.toString(16).padStart(2, '0'); | |
| return `#${hex}${hex}${hex}`; | |
| } | |
| } | |
| return null; | |
| }; | |
| const escapeXml = (unsafe: string): string => | |
| // eslint-disable-next-line no-control-regex | |
| unsafe.replace(/[<>&'"\x00-\x08\x0B-\x0C\x0E-\x1F]/g, (c) => { | |
| switch (c) { | |
| case '<': | |
| return '<'; | |
| case '>': | |
| return '>'; | |
| case '&': | |
| return '&'; | |
| case "'": | |
| return '''; | |
| case '"': | |
| return '"'; | |
| default: | |
| return ''; | |
| } | |
| }); | |
| const charWidth = 9; | |
| const charHeight = 17; | |
| const padding = 10; | |
| // Find the actual number of rows with content to avoid rendering trailing blank space. | |
| let contentRows = terminal.rows; | |
| for (let y = terminal.rows - 1; y >= 0; y--) { | |
| const line = activeBuffer.getLine(y); | |
| if (line && line.translateToString(true).trim().length > 0) { | |
| contentRows = y + 1; | |
| break; | |
| } | |
| } | |
| if (contentRows === 0) contentRows = 1; // Minimum 1 row | |
| const width = terminal.cols * charWidth + padding * 2; | |
| const height = contentRows * charHeight + padding * 2; | |
| let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"> | |
| `; | |
| svg += ` <style> | |
| `; | |
| svg += ` text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; } | |
| `; | |
| svg += ` </style> | |
| `; | |
| svg += ` <rect width="${width}" height="${height}" fill="#000000" /> | |
| `; // Terminal background | |
| svg += ` <g transform="translate(${padding}, ${padding})"> | |
| `; | |
| for (let y = 0; y < contentRows; y++) { | |
| const line = activeBuffer.getLine(y); | |
| if (!line) continue; | |
| let currentFgHex: string | null = null; | |
| let currentBgHex: string | null = null; | |
| let currentIsBold = false; | |
| let currentIsItalic = false; | |
| let currentIsUnderline = false; | |
| let currentBlockStartCol = -1; | |
| let currentBlockText = ''; | |
| let currentBlockNumCells = 0; | |
| const finalizeBlock = (_endCol: number) => { | |
| if (currentBlockStartCol !== -1) { | |
| if (currentBlockText.length > 0) { | |
| const xPos = currentBlockStartCol * charWidth; | |
| const yPos = y * charHeight; | |
| if (currentBgHex) { | |
| const rectWidth = currentBlockNumCells * charWidth; | |
| svg += ` <rect x="${xPos}" y="${yPos}" width="${rectWidth}" height="${charHeight}" fill="${currentBgHex}" /> | |
| `; | |
| } | |
| if (currentBlockText.trim().length > 0 || currentIsUnderline) { | |
| const fill = currentFgHex || '#ffffff'; // Default text color | |
| const textWidth = currentBlockNumCells * charWidth; | |
| let extraAttrs = ''; | |
| if (currentIsBold) extraAttrs += ' font-weight="bold"'; | |
| if (currentIsItalic) extraAttrs += ' font-style="italic"'; | |
| if (currentIsUnderline) | |
| extraAttrs += ' text-decoration="underline"'; | |
| // Use textLength to ensure the block fits exactly into its designated cells | |
| const textElement = `<text x="${xPos}" y="${yPos + 2}" fill="${fill}" textLength="${textWidth}" lengthAdjust="spacingAndGlyphs"${extraAttrs}>${escapeXml(currentBlockText)}</text>`; | |
| svg += ` ${textElement}\n`; | |
| } | |
| } | |
| } | |
| }; | |
| for (let x = 0; x < line.length; x++) { | |
| const cell = line.getCell(x); | |
| if (!cell) continue; | |
| const cellWidth = cell.getWidth(); | |
| if (cellWidth === 0) continue; // Skip continuation cells of wide characters | |
| let fgHex = getHexColor( | |
| cell.isFgRGB(), | |
| cell.isFgPalette(), | |
| cell.isFgDefault(), | |
| cell.getFgColor(), | |
| ); | |
| let bgHex = getHexColor( | |
| cell.isBgRGB(), | |
| cell.isBgPalette(), | |
| cell.isBgDefault(), | |
| cell.getBgColor(), | |
| ); | |
| if (cell.isInverse()) { | |
| const tempFgHex = fgHex; | |
| fgHex = bgHex || '#000000'; | |
| bgHex = tempFgHex || '#ffffff'; | |
| } | |
| const isBold = !!cell.isBold(); | |
| const isItalic = !!cell.isItalic(); | |
| const isUnderline = !!cell.isUnderline(); | |
| let chars = cell.getChars(); | |
| if (chars === '') chars = ' '.repeat(cellWidth); | |
| if ( | |
| fgHex !== currentFgHex || | |
| bgHex !== currentBgHex || | |
| isBold !== currentIsBold || | |
| isItalic !== currentIsItalic || | |
| isUnderline !== currentIsUnderline || | |
| currentBlockStartCol === -1 | |
| ) { | |
| finalizeBlock(x); | |
| currentFgHex = fgHex; | |
| currentBgHex = bgHex; | |
| currentIsBold = isBold; | |
| currentIsItalic = isItalic; | |
| currentIsUnderline = isUnderline; | |
| currentBlockStartCol = x; | |
| currentBlockText = chars; | |
| currentBlockNumCells = cellWidth; | |
| } else { | |
| currentBlockText += chars; | |
| currentBlockNumCells += cellWidth; | |
| } | |
| } | |
| finalizeBlock(line.length); | |
| } | |
| svg += ` </g>\n</svg>`; | |
| return svg; | |
| }; | |