diff --git a/packages/core/src/buffer.ts b/packages/core/src/buffer.ts index 36de88cb6..75c7fcb30 100644 --- a/packages/core/src/buffer.ts +++ b/packages/core/src/buffer.ts @@ -84,8 +84,8 @@ export class OptimizedBuffer { this._rawBuffers = { char: new Uint32Array(toArrayBuffer(charPtr, 0, size * 4)), - fg: new Float32Array(toArrayBuffer(fgPtr, 0, size * 4 * 4)), - bg: new Float32Array(toArrayBuffer(bgPtr, 0, size * 4 * 4)), + fg: new Float32Array(toArrayBuffer(fgPtr, 0, size * 5 * 4)), + bg: new Float32Array(toArrayBuffer(bgPtr, 0, size * 5 * 4)), attributes: new Uint32Array(toArrayBuffer(attributesPtr, 0, size * 4)), } } @@ -174,8 +174,8 @@ export class OptimizedBuffer { for (let x = 0; x < this._width; x++) { const i = y * this._width + x const cp = char[i] - const cellFg = RGBA.fromValues(fg[i * 4], fg[i * 4 + 1], fg[i * 4 + 2], fg[i * 4 + 3]) - const cellBg = RGBA.fromValues(bg[i * 4], bg[i * 4 + 1], bg[i * 4 + 2], bg[i * 4 + 3]) + const cellFg = RGBA.fromValues(fg[i * 5], fg[i * 5 + 1], fg[i * 5 + 2], fg[i * 5 + 3], fg[i * 5 + 4]) + const cellBg = RGBA.fromValues(bg[i * 5], bg[i * 5 + 1], bg[i * 5 + 2], bg[i * 5 + 3], bg[i * 5 + 4]) const cellAttrs = attributes[i] & 0xff // Continuation cells are placeholders for wide characters (emojis, CJK) diff --git a/packages/core/src/examples/terminal_ansi.ts b/packages/core/src/examples/terminal_ansi.ts new file mode 100644 index 000000000..feb652a38 --- /dev/null +++ b/packages/core/src/examples/terminal_ansi.ts @@ -0,0 +1,197 @@ +#!/usr/bin/env bun + +import { + CliRenderer, + createCliRenderer, + RGBA, + TextAttributes, + TextRenderable, + FrameBufferRenderable, + BoxRenderable, +} from "../index" +import { ScrollBoxRenderable } from "../renderables/ScrollBox" +import { setupCommonDemoKeys } from "./lib/standalone-keys" + +/** + * This demo showcases 256 indexed ANSI colors using RGBA.fromIndex(). + * It renders the full 0-255 palette as colored blocks in a grid. + */ + +let scrollBox: ScrollBoxRenderable | null = null + +export function run(renderer: CliRenderer): void { + renderer.start() + renderer.setBackgroundColor(RGBA.fromIndex(0)) // Use indexed black + + const mainContainer = new BoxRenderable(renderer, { + id: "main-container", + flexGrow: 1, + flexDirection: "column", + }) + renderer.root.add(mainContainer) + + scrollBox = new ScrollBoxRenderable(renderer, { + id: "ansi-scroll-box", + stickyScroll: false, + border: true, + borderColor: RGBA.fromIndex(1), + title: "256 ANSI Indexed Colors (Ctrl+C to exit)", + titleAlignment: "center", + contentOptions: { + paddingLeft: 2, + paddingRight: 2, + paddingTop: 1, + }, + }) + mainContainer.add(scrollBox) + + const contentContainer = new BoxRenderable(renderer, { + id: "ansi-content", + width: "auto", + flexDirection: "column", + }) + scrollBox.add(contentContainer) + + // --- Standard colors (0-7) --- + const standardLabel = new TextRenderable(renderer, { + id: "standard-label", + content: "Standard Colors (0-7)", + fg: RGBA.fromIndex(15), + }) + contentContainer.add(standardLabel) + + const standardBuffer = new FrameBufferRenderable(renderer, { + id: "standard-buffer", + width: 40, + height: 2, + marginTop: 1, + }) + contentContainer.add(standardBuffer) + drawColorRow(standardBuffer.frameBuffer, 0, 8, 40) + + // --- Bright colors (8-15) --- + const brightLabel = new TextRenderable(renderer, { + id: "bright-label", + content: "Bright Colors (8-15)", + fg: RGBA.fromIndex(15), + marginTop: 1, + }) + contentContainer.add(brightLabel) + + const brightBuffer = new FrameBufferRenderable(renderer, { + id: "bright-buffer", + width: 40, + height: 2, + marginTop: 1, + }) + contentContainer.add(brightBuffer) + drawColorRow(brightBuffer.frameBuffer, 8, 16, 40) + + // --- 6x6x6 Color Cube (16-231) --- + const cubeLabel = new TextRenderable(renderer, { + id: "cube-label", + content: "6x6x6 Color Cube (16-231)", + fg: RGBA.fromIndex(15), + marginTop: 1, + }) + contentContainer.add(cubeLabel) + + const cubeBuffer = new FrameBufferRenderable(renderer, { + id: "cube-buffer", + width: 72, + height: 12, + marginTop: 1, + }) + contentContainer.add(cubeBuffer) + drawColorCube(cubeBuffer.frameBuffer) + + // --- Grayscale Ramp (232-255) --- + const grayLabel = new TextRenderable(renderer, { + id: "gray-label", + content: "Grayscale Ramp (232-255)", + fg: RGBA.fromIndex(15), + marginTop: 1, + }) + contentContainer.add(grayLabel) + + const grayBuffer = new FrameBufferRenderable(renderer, { + id: "gray-buffer", + width: 72, + height: 2, + marginTop: 1, + }) + contentContainer.add(grayBuffer) + drawColorRow(grayBuffer.frameBuffer, 232, 256, 72) + + // --- Info text --- + const infoText = new TextRenderable(renderer, { + id: "info-text", + content: "All colors rendered using RGBA.fromIndex(n) — indexed color meta is packed into RGBA[4]", + fg: RGBA.fromIndex(244), + marginTop: 2, + }) + contentContainer.add(infoText) +} + +function drawColorRow( + buffer: FrameBufferRenderable["frameBuffer"], + startIndex: number, + endIndex: number, + width: number, +): void { + const count = endIndex - startIndex + const cellWidth = Math.floor(width / count) + + for (let i = 0; i < count; i++) { + const colorIndex = startIndex + i + const bg = RGBA.fromIndex(colorIndex) + const fg = colorIndex < 8 || (colorIndex >= 232 && colorIndex < 244) + ? RGBA.fromIndex(15) + : RGBA.fromIndex(0) + const label = colorIndex.toString().padStart(3, " ") + + for (let dy = 0; dy < 2; dy++) { + for (let dx = 0; dx < cellWidth; dx++) { + const x = i * cellWidth + dx + if (dy === 0 && dx < label.length) { + buffer.drawText(label[dx], x, dy, fg, bg, TextAttributes.NONE) + } else { + buffer.setCell(x, dy, " ", fg, bg) + } + } + } + } +} + +function drawColorCube(buffer: FrameBufferRenderable["frameBuffer"]): void { + // 6x6x6 cube: 6 rows of 36 colors, each cell 2 chars wide + for (let row = 0; row < 6; row++) { + for (let col = 0; col < 36; col++) { + const colorIndex = 16 + row * 36 + col + const bg = RGBA.fromIndex(colorIndex) + const x = col * 2 + const y = row * 2 + + for (let dy = 0; dy < 2; dy++) { + for (let dx = 0; dx < 2; dx++) { + buffer.setCell(x + dx, y + dy, " ", RGBA.fromIndex(0), bg) + } + } + } + } +} + +export function destroy(renderer: CliRenderer): void { + if (scrollBox) { + renderer.root.remove("main-container") + scrollBox = null + } +} + +if (import.meta.main) { + const renderer = await createCliRenderer({ + exitOnCtrlC: true, + }) + run(renderer) + setupCommonDemoKeys(renderer) +} diff --git a/packages/core/src/lib/RGBA.test.ts b/packages/core/src/lib/RGBA.test.ts index 3fe35b119..8dc50a7ca 100644 --- a/packages/core/src/lib/RGBA.test.ts +++ b/packages/core/src/lib/RGBA.test.ts @@ -27,11 +27,18 @@ describe("RGBA class", () => { expect(rgba.a).toBeCloseTo(0.4, 5) }) - test("uses same buffer reference", () => { - const array = new Float32Array([0.1, 0.2, 0.3, 0.4]) + test("uses same buffer reference for 5-element array", () => { + const array = new Float32Array([0.1, 0.2, 0.3, 0.4, 0.0]) const rgba = RGBA.fromArray(array) expect(rgba.buffer).toBe(array) }) + + test("creates new buffer for legacy 4-element array", () => { + const array = new Float32Array([0.1, 0.2, 0.3, 0.4]) + const rgba = RGBA.fromArray(array) + expect(rgba.buffer.length).toBe(5) + expect(rgba.buffer[4]).toBe(0) + }) }) describe("fromValues", () => { diff --git a/packages/core/src/lib/RGBA.ts b/packages/core/src/lib/RGBA.ts index 1dae109a9..065d23121 100644 --- a/packages/core/src/lib/RGBA.ts +++ b/packages/core/src/lib/RGBA.ts @@ -6,15 +6,57 @@ export class RGBA { } static fromArray(array: Float32Array) { + if (array.length === 4) { + // Legacy 4-element array — add meta = 0 (RGB) + const buf = new Float32Array(5) + buf.set(array) + return new RGBA(buf) + } return new RGBA(array) } - static fromValues(r: number, g: number, b: number, a: number = 1.0) { - return new RGBA(new Float32Array([r, g, b, a])) + static fromValues(r: number, g: number, b: number, a: number = 1.0, meta: number = 0) { + return new RGBA(new Float32Array([r, g, b, a, meta])) } static fromInts(r: number, g: number, b: number, a: number = 255) { - return new RGBA(new Float32Array([r / 255, g / 255, b / 255, a / 255])) + return new RGBA(new Float32Array([r / 255, g / 255, b / 255, a / 255, 0])) + } + + static fromIndex(index: number): RGBA { + // Approximate RGB from 256-color palette, with indexed meta + const meta = 256 + index // colorType=1 (indexed) * 256 + index + if (index < 16) { + // Standard + bright colors - use predefined values + const STANDARD: [number, number, number][] = [ + [0, 0, 0], [0.5, 0, 0], [0, 0.5, 0], [0.5, 0.5, 0], + [0, 0, 0.5], [0.5, 0, 0.5], [0, 0.5, 0.5], [0.75, 0.75, 0.75], + [0.5, 0.5, 0.5], [1, 0, 0], [0, 1, 0], [1, 1, 0], + [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], + ] + const [r, g, b] = STANDARD[index] + return new RGBA(new Float32Array([r, g, b, 1.0, meta])) + } else if (index < 232) { + // 6x6x6 color cube (indices 16-231) + const ci = index - 16 + const ri = Math.floor(ci / 36) + const gi = Math.floor((ci % 36) / 6) + const bi = ci % 6 + return new RGBA(new Float32Array([ + ri === 0 ? 0 : (55 + ri * 40) / 255, + gi === 0 ? 0 : (55 + gi * 40) / 255, + bi === 0 ? 0 : (55 + bi * 40) / 255, + 1.0, meta, + ])) + } else { + // Grayscale (indices 232-255) + const gray = (8 + (index - 232) * 10) / 255 + return new RGBA(new Float32Array([gray, gray, gray, 1.0, meta])) + } + } + + static defaultColor(): RGBA { + return new RGBA(new Float32Array([1.0, 1.0, 1.0, 1.0, 512])) // colorType=2 (default) * 256 } static fromHex(hex: string): RGBA { @@ -57,6 +99,34 @@ export class RGBA { this.buffer[3] = value } + get meta(): number { + return this.buffer[4] + } + + set meta(value: number) { + this.buffer[4] = value + } + + get colorType(): number { + return Math.floor(this.buffer[4] / 256) + } + + get colorIndex(): number { + return this.buffer[4] % 256 + } + + isRgb(): boolean { + return this.colorType === 0 + } + + isIndexed(): boolean { + return this.colorType === 1 + } + + isDefault(): boolean { + return this.colorType === 2 + } + map(fn: (value: number) => R) { return [fn(this.r), fn(this.g), fn(this.b), fn(this.a)] } @@ -67,7 +137,7 @@ export class RGBA { equals(other?: RGBA): boolean { if (!other) return false - return this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a + return this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a && this.meta === other.meta } } diff --git a/packages/core/src/post/filters.ts b/packages/core/src/post/filters.ts index 05d9d1e66..866159f70 100644 --- a/packages/core/src/post/filters.ts +++ b/packages/core/src/post/filters.ts @@ -10,7 +10,7 @@ export function applyScanlines(buffer: OptimizedBuffer, strength: number = 0.8, for (let y = 0; y < height; y += step) { for (let x = 0; x < width; x++) { - const colorIndex = (y * width + x) * 4 + const colorIndex = (y * width + x) * 5 bg[colorIndex] *= strength // R bg[colorIndex + 1] *= strength // G bg[colorIndex + 2] *= strength // B @@ -28,7 +28,7 @@ export function applyGrayscale(buffer: OptimizedBuffer): void { const bg = buffer.buffers.bg for (let i = 0; i < size; i++) { - const colorIndex = i * 4 + const colorIndex = i * 5 // Grayscale foreground const fgR = fg[colorIndex] @@ -59,7 +59,7 @@ export function applySepia(buffer: OptimizedBuffer): void { const bg = buffer.buffers.bg for (let i = 0; i < size; i++) { - const colorIndex = i * 4 + const colorIndex = i * 5 // Sepia foreground let fgR = fg[colorIndex] @@ -94,7 +94,7 @@ export function applyInvert(buffer: OptimizedBuffer): void { const bg = buffer.buffers.bg for (let i = 0; i < size; i++) { - const colorIndex = i * 4 + const colorIndex = i * 5 fg[colorIndex] = 1.0 - fg[colorIndex] fg[colorIndex + 1] = 1.0 - fg[colorIndex + 1] fg[colorIndex + 2] = 1.0 - fg[colorIndex + 2] @@ -114,7 +114,7 @@ export function applyNoise(buffer: OptimizedBuffer, strength: number = 0.1): voi const bg = buffer.buffers.bg for (let i = 0; i < size; i++) { - const colorIndex = i * 4 + const colorIndex = i * 5 const noise = (Math.random() - 0.5) * strength fg[colorIndex] = Math.max(0, Math.min(1, fg[colorIndex] + noise)) @@ -147,10 +147,10 @@ export function applyChromaticAberration(buffer: OptimizedBuffer, strength: numb const rX = Math.max(0, Math.min(width - 1, x - offset)) const bX = Math.max(0, Math.min(width - 1, x + offset)) - const rIndex = (y * width + rX) * 4 - const gIndex = (y * width + x) * 4 // Green from original position - const bIndex = (y * width + bX) * 4 - const destIndex = (y * width + x) * 4 + const rIndex = (y * width + rX) * 5 + const gIndex = (y * width + x) * 5 // Green from original position + const bIndex = (y * width + bX) * 5 + const destIndex = (y * width + x) * 5 destFg[destIndex] = srcFg[rIndex] // Red from left offset destFg[destIndex + 1] = srcFg[gIndex + 1] // Green from center @@ -173,7 +173,7 @@ export function applyAsciiArt(buffer: OptimizedBuffer, ramp: string = " .:-=+*#% for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const index = y * width + x - const colorIndex = index * 4 + const colorIndex = index * 5 const bgR = bg[colorIndex] const bgG = bg[colorIndex + 1] const bgB = bg[colorIndex + 2] @@ -279,16 +279,16 @@ export class DistortionEffect { // Lazily create temp buffers only when needed for shift/flip if (!tempChar) { tempChar = new Uint32Array(width) - tempFg = new Float32Array(width * 4) - tempBg = new Float32Array(width * 4) + tempFg = new Float32Array(width * 5) + tempBg = new Float32Array(width * 5) tempAttr = new Uint8Array(width) } // 1. Copy original row data to temp buffers try { tempChar.set(buf.char.subarray(baseIndex, baseIndex + width)) - tempFg!.set(buf.fg.subarray(baseIndex * 4, (baseIndex + width) * 4)) - tempBg!.set(buf.bg.subarray(baseIndex * 4, (baseIndex + width) * 4)) + tempFg!.set(buf.fg.subarray(baseIndex * 5, (baseIndex + width) * 5)) + tempBg!.set(buf.bg.subarray(baseIndex * 5, (baseIndex + width) * 5)) tempAttr!.set(buf.attributes.subarray(baseIndex, baseIndex + width)) } catch (e) { // Handle potential range errors if buffer size changes unexpectedly @@ -306,11 +306,11 @@ export class DistortionEffect { buf.char[destIndex] = tempChar[srcTempIndex] buf.attributes[destIndex] = tempAttr![srcTempIndex] - const destColorIndex = destIndex * 4 - const srcTempColorIndex = srcTempIndex * 4 + const destColorIndex = destIndex * 5 + const srcTempColorIndex = srcTempIndex * 5 - buf.fg.set(tempFg!.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex) - buf.bg.set(tempBg!.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex) + buf.fg.set(tempFg!.subarray(srcTempColorIndex, srcTempColorIndex + 5), destColorIndex) + buf.bg.set(tempBg!.subarray(srcTempColorIndex, srcTempColorIndex + 5), destColorIndex) } } else { // type === 'flip' @@ -322,11 +322,11 @@ export class DistortionEffect { buf.char[destIndex] = tempChar[srcTempIndex] buf.attributes[destIndex] = tempAttr![srcTempIndex] - const destColorIndex = destIndex * 4 - const srcTempColorIndex = srcTempIndex * 4 + const destColorIndex = destIndex * 5 + const srcTempColorIndex = srcTempIndex * 5 - buf.fg.set(tempFg!.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex) - buf.bg.set(tempBg!.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex) + buf.fg.set(tempFg!.subarray(srcTempColorIndex, srcTempColorIndex + 5), destColorIndex) + buf.bg.set(tempBg!.subarray(srcTempColorIndex, srcTempColorIndex + 5), destColorIndex) } } } else if (glitch.type === "color") { @@ -345,7 +345,7 @@ export class DistortionEffect { if (x >= width) break // Boundary check const destIndex = baseIndex + x - const destColorIndex = destIndex * 4 + const destColorIndex = destIndex * 5 let rFg, gFg, bFg, rBg, gBg, bBg @@ -495,7 +495,7 @@ export class VignetteEffect { for (let i = 0; i < size; i++) { // Calculate the final factor dynamically const factor = Math.max(0, 1 - this.precomputedBaseAttenuation![i] * this._strength) - const colorIndex = i * 4 + const colorIndex = i * 5 buf.fg[colorIndex] *= factor buf.fg[colorIndex + 1] *= factor @@ -541,7 +541,7 @@ export class BrightnessEffect { } for (let i = 0; i < size; i++) { - const colorIndex = i * 4 + const colorIndex = i * 5 // Adjust foreground fg[colorIndex] = Math.min(1.0, fg[colorIndex] * factor) @@ -592,7 +592,7 @@ export class BlurEffect { const destBg = buf.bg const chars = buf.char // Get reference to character buffer const size = width * height - const numChannels = 4 // RGBA + const numChannels = 5 // RGBA + meta // Temporary buffer for the horizontal pass result const tempBufferFg = new Float32Array(size * numChannels) @@ -833,7 +833,7 @@ export class BloomEffect { // 1. Find bright pixels based on original data for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - const index = (y * width + x) * 4 + const index = (y * width + x) * 5 // Consider max component brightness, or luminance? Using luminance. const fgLum = 0.299 * srcFg[index] + 0.587 * srcFg[index + 1] + 0.114 * srcFg[index + 2] const bgLum = 0.299 * srcBg[index] + 0.587 * srcBg[index + 1] + 0.114 * srcBg[index + 2] @@ -869,7 +869,7 @@ export class BloomEffect { // Simple linear falloff based on squared distance const falloff = 1 - distSq / radiusSq const bloomAmount = bright.intensity * strength * falloff - const destIndex = (sampleY * width + sampleX) * 4 + const destIndex = (sampleY * width + sampleX) * 5 // Add bloom to both fg and bg, clamping at 1.0 destFg[destIndex] = Math.min(1.0, destFg[destIndex] + bloomAmount) diff --git a/packages/core/src/renderables/Text.selection-buffer.test.ts b/packages/core/src/renderables/Text.selection-buffer.test.ts index 398418ddf..ba677f6b8 100644 --- a/packages/core/src/renderables/Text.selection-buffer.test.ts +++ b/packages/core/src/renderables/Text.selection-buffer.test.ts @@ -86,10 +86,10 @@ describe("TextRenderable Selection - Buffer Validation", () => { const getBgAt = (x: number, y: number) => { const index = y * width + x return RGBA.fromValues( - buffers.bg[index * 4], - buffers.bg[index * 4 + 1], - buffers.bg[index * 4 + 2], - buffers.bg[index * 4 + 3], + buffers.bg[index * 5], + buffers.bg[index * 5 + 1], + buffers.bg[index * 5 + 2], + buffers.bg[index * 5 + 3], ) } diff --git a/packages/core/src/renderables/Text.test.ts b/packages/core/src/renderables/Text.test.ts index 6f5d35726..23c01b4b1 100644 --- a/packages/core/src/renderables/Text.test.ts +++ b/packages/core/src/renderables/Text.test.ts @@ -1293,9 +1293,9 @@ describe("TextRenderable Selection", () => { const bufferWidth = buffer.width const ellipsisIdx = text.y * bufferWidth + text.x + 3 - const ellipsisBgR = bg[ellipsisIdx * 4 + 0] - const ellipsisBgG = bg[ellipsisIdx * 4 + 1] - const ellipsisBgB = bg[ellipsisIdx * 4 + 2] + const ellipsisBgR = bg[ellipsisIdx * 5 + 0] + const ellipsisBgG = bg[ellipsisIdx * 5 + 1] + const ellipsisBgB = bg[ellipsisIdx * 5 + 2] expect(Math.abs(ellipsisBgR - 1.0)).toBeLessThan(0.05) expect(Math.abs(ellipsisBgG - 0.0)).toBeLessThan(0.05) @@ -1323,9 +1323,9 @@ describe("TextRenderable Selection", () => { const bufferWidth = buffer.width const ellipsisIdx = (text.y + 1) * bufferWidth + text.x + 3 - const ellipsisBgR = bg[ellipsisIdx * 4 + 0] - const ellipsisBgG = bg[ellipsisIdx * 4 + 1] - const ellipsisBgB = bg[ellipsisIdx * 4 + 2] + const ellipsisBgR = bg[ellipsisIdx * 5 + 0] + const ellipsisBgG = bg[ellipsisIdx * 5 + 1] + const ellipsisBgB = bg[ellipsisIdx * 5 + 2] expect(Math.abs(ellipsisBgR - 1.0)).toBeGreaterThan(0.05) expect(Math.abs(ellipsisBgG - 0.0)).toBeLessThan(0.05) diff --git a/packages/core/src/renderables/TextTable.test.ts b/packages/core/src/renderables/TextTable.test.ts index ef32bce27..2bb1bf0b1 100644 --- a/packages/core/src/renderables/TextTable.test.ts +++ b/packages/core/src/renderables/TextTable.test.ts @@ -20,7 +20,7 @@ function getCharAt(buffer: TestRenderer["currentRenderBuffer"], x: number, y: nu } function getFgAt(buffer: TestRenderer["currentRenderBuffer"], x: number, y: number): RGBA { - const index = (y * buffer.width + x) * 4 + const index = (y * buffer.width + x) * 5 return RGBA.fromValues( buffer.buffers.fg[index] ?? 0, buffer.buffers.fg[index + 1] ?? 0, @@ -30,7 +30,7 @@ function getFgAt(buffer: TestRenderer["currentRenderBuffer"], x: number, y: numb } function getBgAt(buffer: TestRenderer["currentRenderBuffer"], x: number, y: number): RGBA { - const index = (y * buffer.width + x) * 4 + const index = (y * buffer.width + x) * 5 return RGBA.fromValues( buffer.buffers.bg[index] ?? 0, buffer.buffers.bg[index + 1] ?? 0, diff --git a/packages/core/src/renderables/__tests__/LineNumberRenderable.test.ts b/packages/core/src/renderables/__tests__/LineNumberRenderable.test.ts index 70b408294..f27232723 100644 --- a/packages/core/src/renderables/__tests__/LineNumberRenderable.test.ts +++ b/packages/core/src/renderables/__tests__/LineNumberRenderable.test.ts @@ -169,7 +169,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -242,7 +242,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -358,7 +358,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -441,7 +441,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -540,7 +540,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -656,7 +656,7 @@ describe("LineNumberRenderable", () => { // Helper to get RGBA values from buffer at position const getFgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: fgBuffer[offset], g: fgBuffer[offset + 1], @@ -1352,7 +1352,7 @@ describe("LineNumberRenderable", () => { const bgBuffer = buffer.buffers.bg const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -1409,7 +1409,7 @@ describe("LineNumberRenderable", () => { const bgBuffer = buffer.buffers.bg const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -1469,7 +1469,7 @@ describe("LineNumberRenderable", () => { const bgBuffer = buffer.buffers.bg const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], @@ -1525,7 +1525,7 @@ describe("LineNumberRenderable", () => { const bgBuffer = buffer.buffers.bg const getBgColor = (x: number, y: number) => { - const offset = (y * buffer.width + x) * 4 + const offset = (y * buffer.width + x) * 5 return { r: bgBuffer[offset], g: bgBuffer[offset + 1], diff --git a/packages/core/src/renderables/__tests__/Textarea.scroll.test.ts b/packages/core/src/renderables/__tests__/Textarea.scroll.test.ts index 22195b6ec..bfbf9311a 100644 --- a/packages/core/src/renderables/__tests__/Textarea.scroll.test.ts +++ b/packages/core/src/renderables/__tests__/Textarea.scroll.test.ts @@ -722,7 +722,7 @@ describe("Textarea - Scroll Tests", () => { for (let y = editor.y; y < editor.y + editor.height; y++) { for (let x = editor.x; x < editor.x + editor.width; x++) { const bufferIdx = y * bufferWidth + x - const bgG = frame.buffers.bg[bufferIdx * 4 + 1] + const bgG = frame.buffers.bg[bufferIdx * 5 + 1] if (Math.abs(bgG - 1.0) < 0.01) { selectedCells++ } diff --git a/packages/core/src/renderables/__tests__/Textarea.selection.test.ts b/packages/core/src/renderables/__tests__/Textarea.selection.test.ts index 1dc573171..7faf31a8c 100644 --- a/packages/core/src/renderables/__tests__/Textarea.selection.test.ts +++ b/packages/core/src/renderables/__tests__/Textarea.selection.test.ts @@ -341,9 +341,9 @@ describe("Textarea - Selection Tests", () => { for (let cellX = editor.x; cellX < editor.x + 5; cellX++) { const bufferIdx = editor.y * bufferWidth + cellX - const bgR = bg[bufferIdx * 4 + 0] - const bgG = bg[bufferIdx * 4 + 1] - const bgB = bg[bufferIdx * 4 + 2] + const bgR = bg[bufferIdx * 5 + 0] + const bgG = bg[bufferIdx * 5 + 1] + const bgB = bg[bufferIdx * 5 + 2] expect(Math.abs(bgR - 1.0)).toBeLessThan(0.01) expect(Math.abs(bgG - 0.0)).toBeLessThan(0.01) @@ -381,9 +381,9 @@ describe("Textarea - Selection Tests", () => { for (let cellX = 0; cellX < 4; cellX++) { const bufferIdx = 2 * bufferWidth + cellX - const bgR = bg[bufferIdx * 4 + 0] - const bgG = bg[bufferIdx * 4 + 1] - const bgB = bg[bufferIdx * 4 + 2] + const bgR = bg[bufferIdx * 5 + 0] + const bgG = bg[bufferIdx * 5 + 1] + const bgB = bg[bufferIdx * 5 + 2] expect(Math.abs(bgR - 1.0)).toBeLessThan(0.01) expect(Math.abs(bgG - 0.0)).toBeLessThan(0.01) @@ -1025,7 +1025,7 @@ describe("Textarea - Selection Tests", () => { for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgG = bgBefore[bufferIdx * 4 + 1] + const bgG = bgBefore[bufferIdx * 5 + 1] if (Math.abs(bgG - 1.0) < 0.01) { selectedCellsBefore.push({ x, y }) } @@ -1056,7 +1056,7 @@ describe("Textarea - Selection Tests", () => { for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgG = bgAfter[bufferIdx * 4 + 1] + const bgG = bgAfter[bufferIdx * 5 + 1] if (Math.abs(bgG - 1.0) < 0.01) { selectedCellsAfter.push({ x, y }) } @@ -1117,8 +1117,8 @@ describe("Textarea - Selection Tests", () => { for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgR = bgNarrow[bufferIdx * 4 + 0] - const bgB = bgNarrow[bufferIdx * 4 + 2] + const bgR = bgNarrow[bufferIdx * 5 + 0] + const bgB = bgNarrow[bufferIdx * 5 + 2] if (Math.abs(bgR - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) { selectedCellsNarrow++ } @@ -1149,8 +1149,8 @@ describe("Textarea - Selection Tests", () => { for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgR = bgWide[bufferIdx * 4 + 0] - const bgB = bgWide[bufferIdx * 4 + 2] + const bgR = bgWide[bufferIdx * 5 + 0] + const bgB = bgWide[bufferIdx * 5 + 2] if (Math.abs(bgR - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) { selectedCellsWide++ } @@ -1206,8 +1206,8 @@ describe("Textarea - Selection Tests", () => { for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgG = bgAfterResize[bufferIdx * 4 + 1] - const bgB = bgAfterResize[bufferIdx * 4 + 2] + const bgG = bgAfterResize[bufferIdx * 5 + 1] + const bgB = bgAfterResize[bufferIdx * 5 + 2] if (Math.abs(bgG - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) { selectedCellsAfterResize++ } @@ -1578,9 +1578,9 @@ function countSelectedCells( for (let y = 0; y < editor.height; y++) { for (let x = 0; x < editor.width; x++) { const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x) - const bgR = bg[bufferIdx * 4 + 0] - const bgG = bg[bufferIdx * 4 + 1] - const bgB = bg[bufferIdx * 4 + 2] + const bgR = bg[bufferIdx * 5 + 0] + const bgG = bg[bufferIdx * 5 + 1] + const bgB = bg[bufferIdx * 5 + 2] if (Math.abs(bgR - r) < 0.01 && Math.abs(bgG - g) < 0.01 && Math.abs(bgB - b) < 0.01) { count++ } diff --git a/packages/core/src/testing/test-recorder.test.ts b/packages/core/src/testing/test-recorder.test.ts index 183cd4c1a..c1507cb8c 100644 --- a/packages/core/src/testing/test-recorder.test.ts +++ b/packages/core/src/testing/test-recorder.test.ts @@ -406,8 +406,8 @@ describe("TestRecorder", () => { expect(frames.length).toBe(1) const expectedSize = renderer.width * renderer.height - expect(frames[0].buffers?.fg?.length).toBe(expectedSize * 4) - expect(frames[0].buffers?.bg?.length).toBe(expectedSize * 4) + expect(frames[0].buffers?.fg?.length).toBe(expectedSize * 5) + expect(frames[0].buffers?.bg?.length).toBe(expectedSize * 5) expect(frames[0].buffers?.attributes?.length).toBe(expectedSize) recorderWithAll.stop() diff --git a/packages/core/src/zig-structs.ts b/packages/core/src/zig-structs.ts index f6d4aeedc..61da79b2a 100644 --- a/packages/core/src/zig-structs.ts +++ b/packages/core/src/zig-structs.ts @@ -3,7 +3,7 @@ import { ptr, toArrayBuffer, type Pointer } from "bun:ffi" import { RGBA } from "./lib/RGBA" const rgbaPackTransform = (rgba?: RGBA) => (rgba ? ptr(rgba.buffer) : null) -const rgbaUnpackTransform = (ptr?: Pointer) => (ptr ? RGBA.fromArray(new Float32Array(toArrayBuffer(ptr))) : undefined) +const rgbaUnpackTransform = (ptr?: Pointer) => (ptr ? RGBA.fromArray(new Float32Array(toArrayBuffer(ptr, 0, 5 * 4))) : undefined) type StyledChunkInput = { text: string @@ -132,6 +132,7 @@ export const CursorStateStruct = defineStruct([ ["g", "f32"], ["b", "f32"], ["a", "f32"], + ["meta", "f32"], ]) export const CursorStyleOptionsStruct = defineStruct([ diff --git a/packages/core/src/zig.ts b/packages/core/src/zig.ts index 93e071c71..793a7ab47 100644 --- a/packages/core/src/zig.ts +++ b/packages/core/src/zig.ts @@ -2372,7 +2372,7 @@ class FFIRenderLib implements RenderLib { visible: struct.visible, style: CURSOR_ID_TO_STYLE[struct.style] ?? "block", blinking: struct.blinking, - color: RGBA.fromValues(struct.r, struct.g, struct.b, struct.a), + color: RGBA.fromValues(struct.r, struct.g, struct.b, struct.a, struct.meta), } } diff --git a/packages/core/src/zig/ansi.zig b/packages/core/src/zig/ansi.zig index 3382a0033..3cddefc03 100644 --- a/packages/core/src/zig/ansi.zig +++ b/packages/core/src/zig/ansi.zig @@ -1,7 +1,32 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -pub const RGBA = [4]f32; +pub const RGBA = [5]f32; + +pub const ColorType = enum(u8) { + rgb = 0, + indexed = 1, + default = 2, +}; + +/// Encode a ColorType and palette index into a single f32 metadata value. +/// Stored as RGBA[4] (the 5th float). +pub fn encodeColorMeta(color_type: ColorType, index: u8) f32 { + return @as(f32, @floatFromInt(@as(u32, @intFromEnum(color_type)) * 256 + @as(u32, index))); +} + +/// Decode ColorType from the metadata float (RGBA[4]). +pub fn decodeColorType(meta: f32) ColorType { + return @enumFromInt(@as(u2, @intFromFloat(@floor(meta / 256.0)))); +} + +/// Decode palette index from the metadata float (RGBA[4]). +pub fn decodeColorIndex(meta: f32) u8 { + return @intFromFloat(meta - @floor(meta / 256.0) * 256.0); +} + +/// Metadata constant for RGB colors (no palette index). +pub const RGB_META: f32 = 0.0; pub const AnsiError = error{ InvalidFormat, @@ -32,6 +57,50 @@ pub const ANSI = struct { writer.print("\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; } + pub fn fgIndexedColorOutput(writer: anytype, index: u8) AnsiError!void { + writer.print("\x1b[38;5;{d}m", .{index}) catch return AnsiError.WriteFailed; + } + + pub fn bgIndexedColorOutput(writer: anytype, index: u8) AnsiError!void { + writer.print("\x1b[48;5;{d}m", .{index}) catch return AnsiError.WriteFailed; + } + + pub fn fgAnsiPaletteOutput(writer: anytype, index: u8) AnsiError!void { + if (index < 8) { + writer.print("\x1b[{d}m", .{30 + index}) catch return AnsiError.WriteFailed; + return; + } + + if (index < 16) { + writer.print("\x1b[{d}m", .{90 + (index - 8)}) catch return AnsiError.WriteFailed; + return; + } + + return AnsiError.InvalidFormat; + } + + pub fn bgAnsiPaletteOutput(writer: anytype, index: u8) AnsiError!void { + if (index < 8) { + writer.print("\x1b[{d}m", .{40 + index}) catch return AnsiError.WriteFailed; + return; + } + + if (index < 16) { + writer.print("\x1b[{d}m", .{100 + (index - 8)}) catch return AnsiError.WriteFailed; + return; + } + + return AnsiError.InvalidFormat; + } + + pub fn fgDefaultOutput(writer: anytype) AnsiError!void { + writer.writeAll("\x1b[39m") catch return AnsiError.WriteFailed; + } + + pub fn bgDefaultOutput(writer: anytype) AnsiError!void { + writer.writeAll("\x1b[49m") catch return AnsiError.WriteFailed; + } + // Text attribute constants pub const bold = "\x1b[1m"; pub const dim = "\x1b[2m"; @@ -264,5 +333,58 @@ pub fn hsvToRgb(h: f32, s: f32, v: f32) RGBA { else => unreachable, }; - return .{ rgb[0], rgb[1], rgb[2], 1.0 }; + return .{ rgb[0], rgb[1], rgb[2], 1.0, RGB_META }; +} + +/// Convert a 256-color palette index to an approximate RGBA value. +/// The returned RGBA has ColorType.indexed encoded in [4]. +pub fn indexToApproximateRgba(index: u8) RGBA { + const meta = encodeColorMeta(.indexed, index); + + // Standard colors 0-7 + if (index < 8) { + const standard = [8]RGBA{ + .{ 0.0, 0.0, 0.0, 1.0, meta }, // 0: black + .{ 0.5, 0.0, 0.0, 1.0, meta }, // 1: red + .{ 0.0, 0.5, 0.0, 1.0, meta }, // 2: green + .{ 0.5, 0.5, 0.0, 1.0, meta }, // 3: yellow + .{ 0.0, 0.0, 0.5, 1.0, meta }, // 4: blue + .{ 0.5, 0.0, 0.5, 1.0, meta }, // 5: magenta + .{ 0.0, 0.5, 0.5, 1.0, meta }, // 6: cyan + .{ 0.75, 0.75, 0.75, 1.0, meta }, // 7: white + }; + return standard[index]; + } + + // High-intensity colors 8-15 + if (index < 16) { + const bright = [8]RGBA{ + .{ 0.5, 0.5, 0.5, 1.0, meta }, // 8: bright black + .{ 1.0, 0.0, 0.0, 1.0, meta }, // 9: bright red + .{ 0.0, 1.0, 0.0, 1.0, meta }, // 10: bright green + .{ 1.0, 1.0, 0.0, 1.0, meta }, // 11: bright yellow + .{ 0.0, 0.0, 1.0, 1.0, meta }, // 12: bright blue + .{ 1.0, 0.0, 1.0, 1.0, meta }, // 13: bright magenta + .{ 0.0, 1.0, 1.0, 1.0, meta }, // 14: bright cyan + .{ 1.0, 1.0, 1.0, 1.0, meta }, // 15: bright white + }; + return bright[index - 8]; + } + + // 216-color cube (indices 16-231): 6x6x6 RGB + if (index < 232) { + const ci = index - 16; + const ri = ci / 36; + const gi = (ci % 36) / 6; + const bi = ci % 6; + const r: f32 = if (ri == 0) 0.0 else (@as(f32, @floatFromInt(ri)) * 40.0 + 55.0) / 255.0; + const g: f32 = if (gi == 0) 0.0 else (@as(f32, @floatFromInt(gi)) * 40.0 + 55.0) / 255.0; + const b: f32 = if (bi == 0) 0.0 else (@as(f32, @floatFromInt(bi)) * 40.0 + 55.0) / 255.0; + return .{ r, g, b, 1.0, meta }; + } + + // Grayscale ramp (indices 232-255): 24 shades + // level = 8 + (index - 232) * 10, giving values 8, 18, 28, ..., 238 + const gray = (@as(f32, @floatFromInt(index - 232)) * 10.0 + 8.0) / 255.0; + return .{ gray, gray, gray, 1.0, meta }; } diff --git a/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig b/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig index 3399ce9d8..8956add21 100644 --- a/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig +++ b/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig @@ -105,7 +105,7 @@ fn benchRenderColdCache( const buf = try OptimizedBuffer.init(allocator, 120, 40, .{ .pool = pool }); defer buf.deinit(); - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -164,7 +164,7 @@ fn benchWrapAndRender( const buf = try OptimizedBuffer.init(allocator, 120, 40, .{ .pool = pool }); defer buf.deinit(); - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -229,7 +229,7 @@ fn benchRenderWarmCache( const buf = try OptimizedBuffer.init(allocator, 120, 40, .{ .pool = pool }); defer buf.deinit(); - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -268,7 +268,7 @@ fn benchRenderWarmCache( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -320,7 +320,7 @@ fn benchRenderSmallResolution( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -359,7 +359,7 @@ fn benchRenderSmallResolution( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -407,7 +407,7 @@ fn benchRenderMediumResolution( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -464,7 +464,7 @@ fn benchRenderMassiveResolution( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -521,7 +521,7 @@ fn benchRenderMassiveLines( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -584,7 +584,7 @@ fn benchRenderOneMassiveLine( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -641,7 +641,7 @@ fn benchRenderManySmallChunks( var final_buf_mem: usize = 0; for (0..iterations) |i| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -704,7 +704,7 @@ fn benchRenderWithViewport( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -733,7 +733,7 @@ fn benchRenderWithViewport( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -787,7 +787,7 @@ fn benchRenderWithSelection( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); @@ -816,7 +816,7 @@ fn benchRenderWithSelection( var stats = BenchStats{}; for (0..iterations) |_| { - try buf.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try buf.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, null); var timer = try std.time.Timer.start(); try buf.drawTextBuffer(view, 0, 0); diff --git a/packages/core/src/zig/bench/styled-text_bench.zig b/packages/core/src/zig/bench/styled-text_bench.zig index 561ee125f..89cb7b606 100644 --- a/packages/core/src/zig/bench/styled-text_bench.zig +++ b/packages/core/src/zig/bench/styled-text_bench.zig @@ -15,7 +15,7 @@ const SyntaxStyle = syntax_style_mod.SyntaxStyle; pub const benchName = "Styled Text Operations"; // Helper to convert RGBA to pointer for benchmark -fn rgbaToPtr(rgba: *const [4]f32) [*]const f32 { +fn rgbaToPtr(rgba: *const [5]f32) [*]const f32 { return @ptrCast(rgba); } @@ -42,7 +42,7 @@ fn benchSetStyledTextOperations( var stats = BenchStats{}; const text = "Hello, World! This is a test of styled text rendering."; - const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; + const fg_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; for (0..iterations) |_| { const tb = try TextBuffer.init(allocator, pool, link_pool, .wcwidth); @@ -83,12 +83,12 @@ fn benchSetStyledTextOperations( if (bench_utils.matchesBenchFilter(name, bench_filter)) { var stats = BenchStats{}; - const red = [4]f32{ 1.0, 0.0, 0.0, 1.0 }; - const green = [4]f32{ 0.0, 1.0, 0.0, 1.0 }; - const blue = [4]f32{ 0.0, 0.0, 1.0, 1.0 }; - const yellow = [4]f32{ 1.0, 1.0, 0.0, 1.0 }; - const cyan = [4]f32{ 0.0, 1.0, 1.0, 1.0 }; - const magenta = [4]f32{ 1.0, 0.0, 1.0, 1.0 }; + const red = [5]f32{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const green = [5]f32{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const blue = [5]f32{ 0.0, 0.0, 1.0, 1.0, 0.0 }; + const yellow = [5]f32{ 1.0, 1.0, 0.0, 1.0, 0.0 }; + const cyan = [5]f32{ 0.0, 1.0, 1.0, 1.0, 0.0 }; + const magenta = [5]f32{ 1.0, 0.0, 1.0, 1.0, 0.0 }; for (0..iterations) |_| { const tb = try TextBuffer.init(allocator, pool, link_pool, .wcwidth); @@ -137,10 +137,10 @@ fn benchSetStyledTextOperations( if (bench_utils.matchesBenchFilter(name, bench_filter)) { var stats = BenchStats{}; - const keyword_color = [4]f32{ 0.8, 0.4, 1.0, 1.0 }; - const identifier_color = [4]f32{ 0.7, 0.9, 1.0, 1.0 }; - const operator_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; - const number_color = [4]f32{ 0.7, 1.0, 0.7, 1.0 }; + const keyword_color = [5]f32{ 0.8, 0.4, 1.0, 1.0, 0.0 }; + const identifier_color = [5]f32{ 0.7, 0.9, 1.0, 1.0, 0.0 }; + const operator_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const number_color = [5]f32{ 0.7, 1.0, 0.7, 1.0, 0.0 }; for (0..iterations) |_| { const tb = try TextBuffer.init(allocator, pool, link_pool, .wcwidth); @@ -205,7 +205,7 @@ fn benchSetStyledTextOperations( tb.setSyntaxStyle(style); // Just repeat the same chunk 10 times - const color = [4]f32{ 1.0, 0.5, 0.5, 1.0 }; + const color = [5]f32{ 1.0, 0.5, 0.5, 1.0, 0.0 }; const chunks = [_]StyledChunk{ .{ .text_ptr = text.ptr, .text_len = text.len, .fg_ptr = rgbaToPtr(&color), .bg_ptr = null, .attributes = 0 }, .{ .text_ptr = text.ptr, .text_len = text.len, .fg_ptr = rgbaToPtr(&color), .bg_ptr = null, .attributes = 0 }, @@ -400,11 +400,11 @@ fn benchHighlightOperations( var chunk_list: std.ArrayListUnmanaged(StyledChunk) = .{}; defer chunk_list.deinit(allocator); - const keyword_color = [4]f32{ 0.8, 0.4, 1.0, 1.0 }; - const identifier_color = [4]f32{ 0.7, 0.9, 1.0, 1.0 }; - const operator_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; - const number_color = [4]f32{ 0.7, 1.0, 0.7, 1.0 }; - const string_color = [4]f32{ 0.9, 0.8, 0.5, 1.0 }; + const keyword_color = [5]f32{ 0.8, 0.4, 1.0, 1.0, 0.0 }; + const identifier_color = [5]f32{ 0.7, 0.9, 1.0, 1.0, 0.0 }; + const operator_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const number_color = [5]f32{ 0.7, 1.0, 0.7, 1.0, 0.0 }; + const string_color = [5]f32{ 0.9, 0.8, 0.5, 1.0, 0.0 }; // Repeat a pattern to create 100 chunks for (0..10) |_| { diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index bd5ccb8b0..6e2d5de43 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -82,7 +82,7 @@ pub fn rgbaEqual(a: RGBA, b: RGBA, epsilon: f32) bool { const vb = rgbaToVec4f(b); const diff = @abs(va - vb); const eps = @as(Vec4f, @splat(epsilon)); - return @reduce(.And, diff < eps); + return @reduce(.And, diff < eps) and a[4] == b[4]; } pub const Cell = struct { @@ -105,15 +105,20 @@ fn blendColors(overlay: RGBA, text: RGBA) RGBA { return overlay; } + // Fully transparent overlay — preserve destination entirely (including meta) + if (overlay[3] < 0.001) { + return text; + } + if (text[3] == 0.0) { const alpha = overlay[3]; const r = overlay[0] * alpha; const g = overlay[1] * alpha; const b = overlay[2] * alpha; if (r < 0.01 and g < 0.01 and b < 0.01) { - return .{ 0.0, 0.0, 0.0, 0.0 }; + return .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; } - return .{ r, g, b, alpha }; + return .{ r, g, b, alpha, ansi.RGB_META }; } const alpha = overlay[3]; @@ -136,7 +141,7 @@ fn blendColors(overlay: RGBA, text: RGBA) RGBA { const resultAlpha = alpha + text[3] * (1.0 - alpha); - return .{ blended[0], blended[1], blended[2], resultAlpha }; + return .{ blended[0], blended[1], blended[2], resultAlpha, ansi.RGB_META }; } /// Optimized buffer for terminal rendering @@ -224,8 +229,8 @@ pub const OptimizedBuffer = struct { }; @memset(self.buffer.char, 0); - @memset(self.buffer.fg, .{ 0.0, 0.0, 0.0, 0.0 }); - @memset(self.buffer.bg, .{ 0.0, 0.0, 0.0, 0.0 }); + @memset(self.buffer.fg, .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }); + @memset(self.buffer.bg, .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }); @memset(self.buffer.attributes, 0); return self; @@ -386,7 +391,7 @@ pub const OptimizedBuffer = struct { // Always clear after resize to initialize cells (realloc doesn't zero memory) // This handles both growing (new cells are garbage) and shrinking (grapheme cleanup) - try self.clear(.{ 0.0, 0.0, 0.0, 1.0 }, null); + try self.clear(.{ 0.0, 0.0, 0.0, 1.0, ansi.RGB_META }, null); } fn coordsToIndex(self: *const OptimizedBuffer, x: u32, y: u32) u32 { @@ -406,7 +411,7 @@ pub const OptimizedBuffer = struct { self.grapheme_tracker.clear(); @memset(self.buffer.char, @intCast(cellChar)); @memset(self.buffer.attributes, 0); - @memset(self.buffer.fg, .{ 1.0, 1.0, 1.0, 1.0 }); + @memset(self.buffer.fg, .{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }); @memset(self.buffer.bg, bg); } @@ -711,13 +716,14 @@ pub const OptimizedBuffer = struct { const overlayLinkId = ansi.TextAttributes.getLinkId(overlayCell.attributes); const finalAttributes = ansi.TextAttributes.setLinkId(@as(u32, baseAttrs), overlayLinkId); - // When overlay background is fully transparent, preserve destination background alpha - const finalBgAlpha = if (overlayCell.bg[3] == 0.0) destCell.bg[3] else overlayCell.bg[3]; + // When overlay background is fully transparent, preserve destination background alpha and meta + const finalBgAlpha = if (overlayCell.bg[3] < 0.001) destCell.bg[3] else overlayCell.bg[3]; + const finalBgMeta = blendedBgRgb[4]; return Cell{ .char = finalChar, .fg = finalFg, - .bg = .{ blendedBgRgb[0], blendedBgRgb[1], blendedBgRgb[2], finalBgAlpha }, + .bg = .{ blendedBgRgb[0], blendedBgRgb[1], blendedBgRgb[2], finalBgAlpha, finalBgMeta }, .attributes = finalAttributes, }; } @@ -743,8 +749,8 @@ pub const OptimizedBuffer = struct { return; } - const effectiveFg = RGBA{ fg[0], fg[1], fg[2], fg[3] * opacity }; - const effectiveBg = RGBA{ bg[0], bg[1], bg[2], bg[3] * opacity }; + const effectiveFg = RGBA{ fg[0], fg[1], fg[2], fg[3] * opacity, fg[4] }; + const effectiveBg = RGBA{ bg[0], bg[1], bg[2], bg[3] * opacity, bg[4] }; const overlayCell = Cell{ .char = char, .fg = effectiveFg, .bg = effectiveBg, .attributes = attributes }; @@ -777,8 +783,8 @@ pub const OptimizedBuffer = struct { return; } - const effectiveFg = RGBA{ fg[0], fg[1], fg[2], fg[3] * opacity }; - const effectiveBg = RGBA{ bg[0], bg[1], bg[2], bg[3] * opacity }; + const effectiveFg = RGBA{ fg[0], fg[1], fg[2], fg[3] * opacity, fg[4] }; + const effectiveBg = RGBA{ bg[0], bg[1], bg[2], bg[3] * opacity, bg[4] }; const overlayCell = Cell{ .char = char, .fg = effectiveFg, .bg = effectiveBg, .attributes = attributes }; @@ -856,7 +862,7 @@ pub const OptimizedBuffer = struct { while (fillY <= clippedEndY) : (fillY += 1) { var fillX = clippedStartX; while (fillX <= clippedEndX) : (fillX += 1) { - try self.setCellWithAlphaBlending(fillX, fillY, DEFAULT_SPACE_CHAR, .{ 1.0, 1.0, 1.0, 1.0 }, bg, 0); + try self.setCellWithAlphaBlending(fillX, fillY, DEFAULT_SPACE_CHAR, .{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }, bg, 0); } } } else { @@ -872,7 +878,7 @@ pub const OptimizedBuffer = struct { const rowSliceAttrs = self.buffer.attributes[rowStartIndex .. rowStartIndex + rowWidth]; @memset(rowSliceChar, @intCast(DEFAULT_SPACE_CHAR)); - @memset(rowSliceFg, .{ 1.0, 1.0, 1.0, 1.0 }); + @memset(rowSliceFg, .{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }); @memset(rowSliceBg, bg); @memset(rowSliceAttrs, 0); } @@ -939,7 +945,7 @@ pub const OptimizedBuffer = struct { } else if (self.get(charX, y)) |existingCell| { bgColor = existingCell.bg; } else { - bgColor = .{ 0.0, 0.0, 0.0, 1.0 }; + bgColor = .{ 0.0, 0.0, 0.0, 1.0, ansi.RGB_META }; } const cell_width = utf8.getWidthAt(text, if (at_special) specials[special_idx - 1].byte_offset else byte_offset - 1, tab_width, self.width_method); @@ -1177,8 +1183,8 @@ pub const OptimizedBuffer = struct { const col_offset = vline_span_info.col_offset; var span_idx: usize = 0; const defaults = text_buffer.defaults(); - var lineFg = defaults.fg orelse RGBA{ 1.0, 1.0, 1.0, 1.0 }; - var lineBg = defaults.bg orelse RGBA{ 0.0, 0.0, 0.0, 0.0 }; + var lineFg = defaults.fg orelse RGBA{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }; + var lineBg = defaults.bg orelse RGBA{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; var lineAttributes = defaults.attributes orelse 0; const defaultFg = lineFg; const defaultBg = lineBg; @@ -1398,7 +1404,7 @@ pub const OptimizedBuffer = struct { } } else { const temp = lineFg; - finalFg = if (lineBg[3] > 0) lineBg else RGBA{ 0.0, 0.0, 0.0, 1.0 }; + finalFg = if (lineBg[3] > 0) lineBg else RGBA{ 0.0, 0.0, 0.0, 1.0, ansi.RGB_META }; finalBg = temp; } break; @@ -1866,10 +1872,10 @@ pub const OptimizedBuffer = struct { if (!self.isPointInScissor(@intCast(cellX), @intCast(cellY))) continue; const bgPtr = @as([*]const f32, @ptrCast(@alignCast(data + cellDataOffset))); - const bg: RGBA = .{ bgPtr[0], bgPtr[1], bgPtr[2], bgPtr[3] }; + const bg: RGBA = .{ bgPtr[0], bgPtr[1], bgPtr[2], bgPtr[3], ansi.RGB_META }; const fgPtr = @as([*]const f32, @ptrCast(@alignCast(data + cellDataOffset + 16))); - const fg: RGBA = .{ fgPtr[0], fgPtr[1], fgPtr[2], fgPtr[3] }; + const fg: RGBA = .{ fgPtr[0], fgPtr[1], fgPtr[2], fgPtr[3], ansi.RGB_META }; const charPtr = @as([*]const u32, @ptrCast(@alignCast(data + cellDataOffset + 32))); var char = charPtr[0]; @@ -1903,7 +1909,7 @@ pub const OptimizedBuffer = struct { fgColor: ?RGBA, bgColor: ?RGBA, ) void { - const bg = bgColor orelse RGBA{ 0.0, 0.0, 0.0, 0.0 }; + const bg = bgColor orelse RGBA{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; if (srcWidth == 0 or srcHeight == 0) return; if (posX >= @as(i32, @intCast(self.width)) or posY >= @as(i32, @intCast(self.height))) return; @@ -1920,7 +1926,7 @@ pub const OptimizedBuffer = struct { if (visibleWidth == 0 or visibleHeight == 0) return; - const baseFg = fgColor orelse RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const baseFg = fgColor orelse RGBA{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }; const opacity = self.getCurrentOpacity(); const graphemeAware = self.grapheme_tracker.hasAny(); @@ -1948,7 +1954,7 @@ pub const OptimizedBuffer = struct { const char = getGrayscaleChar(intensity); const gray = @min(@max(intensity, 0.0), 1.0); - const fg: RGBA = .{ baseFg[0], baseFg[1], baseFg[2], gray * baseFg[3] * opacity }; + const fg: RGBA = .{ baseFg[0], baseFg[1], baseFg[2], gray * baseFg[3] * opacity, baseFg[4] }; if (graphemeAware or linkAware) { self.setCellWithAlphaBlending(destX, destY, char, fg, bg, 0) catch {}; @@ -1969,7 +1975,7 @@ pub const OptimizedBuffer = struct { fgColor: ?RGBA, bgColor: ?RGBA, ) void { - const bg = bgColor orelse RGBA{ 0.0, 0.0, 0.0, 0.0 }; + const bg = bgColor orelse RGBA{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; const termWidth = srcWidth / 2; const termHeight = srcHeight / 2; @@ -1989,7 +1995,7 @@ pub const OptimizedBuffer = struct { if (visibleWidth == 0 or visibleHeight == 0) return; - const baseFg = fgColor orelse RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const baseFg = fgColor orelse RGBA{ 1.0, 1.0, 1.0, 1.0, ansi.RGB_META }; const opacity = self.getCurrentOpacity(); const graphemeAware = self.grapheme_tracker.hasAny(); @@ -2030,7 +2036,7 @@ pub const OptimizedBuffer = struct { const char = getGrayscaleChar(avgIntensity); const gray = @min(@max(avgIntensity, 0.0), 1.0); - const fg: RGBA = .{ baseFg[0], baseFg[1], baseFg[2], gray * baseFg[3] * opacity }; + const fg: RGBA = .{ baseFg[0], baseFg[1], baseFg[2], gray * baseFg[3] * opacity, baseFg[4] }; if (graphemeAware or linkAware) { self.setCellWithAlphaBlending(destX, destY, char, fg, bg, 0) catch {}; @@ -2044,7 +2050,7 @@ pub const OptimizedBuffer = struct { fn getPixelColor(idx: usize, data: [*]const u8, dataLen: usize, bgra: bool) RGBA { if (idx + 3 >= dataLen) { - return .{ 1.0, 0.0, 1.0, 0.0 }; // Return Transparent Magenta for out-of-bounds + return .{ 1.0, 0.0, 1.0, 0.0, ansi.RGB_META }; // Return Transparent Magenta for out-of-bounds } var rByte: u8 = undefined; var gByte: u8 = undefined; @@ -2068,6 +2074,7 @@ fn getPixelColor(idx: usize, data: [*]const u8, dataLen: usize, bgra: bool) RGBA @as(f32, @floatFromInt(gByte)) * INV_255, @as(f32, @floatFromInt(bByte)) * INV_255, @as(f32, @floatFromInt(aByte)) * INV_255, + ansi.RGB_META, }; } @@ -2102,7 +2109,7 @@ fn closestColorIndex(pixel: RGBA, candidates: [2]RGBA) u1 { } fn averageColorRgba(pixels: []const RGBA) RGBA { - if (pixels.len == 0) return .{ 0.0, 0.0, 0.0, 0.0 }; + if (pixels.len == 0) return .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; var sumR: f32 = 0.0; var sumG: f32 = 0.0; @@ -2117,7 +2124,7 @@ fn averageColorRgba(pixels: []const RGBA) RGBA { } const len = @as(f32, @floatFromInt(pixels.len)); - return .{ sumR / len, sumG / len, sumB / len, sumA / len }; + return .{ sumR / len, sumG / len, sumB / len, sumA / len, ansi.RGB_META }; } fn luminance(color: RGBA) f32 { diff --git a/packages/core/src/zig/lib.zig b/packages/core/src/zig/lib.zig index fa4039c40..8a73cf1e7 100644 --- a/packages/core/src/zig/lib.zig +++ b/packages/core/src/zig/lib.zig @@ -405,6 +405,7 @@ pub const ExternalCursorState = extern struct { g: f32, b: f32, a: f32, + meta: f32, }; export fn getCursorState(rendererPtr: *renderer.CliRenderer, outPtr: *ExternalCursorState) void { @@ -428,6 +429,7 @@ export fn getCursorState(rendererPtr: *renderer.CliRenderer, outPtr: *ExternalCu .g = color[1], .b = color[2], .a = color[3], + .meta = color[4], }; } diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index e41674d84..a2bebf256 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -218,7 +218,7 @@ pub const CliRenderer = struct { .currentRenderBuffer = currentBuffer, .nextRenderBuffer = nextBuffer, .pool = pool, - .backgroundColor = .{ 0.0, 0.0, 0.0, 0.0 }, + .backgroundColor = .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }, .renderOffset = 0, .terminal = Terminal.init(.{ .remote = remote }), .testing = testing, @@ -260,8 +260,8 @@ pub const CliRenderer = struct { .hitScissorStack = hitScissorStack, }; - try currentBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, CLEAR_CHAR); - try nextBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null); + try currentBuffer.clear(self.backgroundColor, CLEAR_CHAR); + try nextBuffer.clear(self.backgroundColor, null); return self; } @@ -465,8 +465,8 @@ pub const CliRenderer = struct { try self.currentRenderBuffer.resize(width, height); try self.nextRenderBuffer.resize(width, height); - try self.currentRenderBuffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, CLEAR_CHAR); - try self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null); + try self.currentRenderBuffer.clear(.{ 0.0, 0.0, 0.0, 1.0, ansi.RGB_META }, CLEAR_CHAR); + try self.nextRenderBuffer.clear(self.backgroundColor, null); const newHitGridSize = width * height; const currentHitGridSize = self.hitGridWidth * self.hitGridHeight; @@ -689,22 +689,37 @@ pub const CliRenderer = struct { ansi.ANSI.moveToOutput(writer, x + 1, y + 1 + self.renderOffset) catch {}; - const fgR = rgbaComponentToU8(cell.fg[0]); - const fgG = rgbaComponentToU8(cell.fg[1]); - const fgB = rgbaComponentToU8(cell.fg[2]); - - const bgR = rgbaComponentToU8(cell.bg[0]); - const bgG = rgbaComponentToU8(cell.bg[1]); - const bgB = rgbaComponentToU8(cell.bg[2]); - const bgA = cell.bg[3]; - - ansi.ANSI.fgColorOutput(writer, fgR, fgG, fgB) catch {}; + // Output foreground color based on color type + switch (ansi.decodeColorType(cell.fg[4])) { + .indexed => { + const fgIndex = ansi.decodeColorIndex(cell.fg[4]); + if (fgIndex < 16) { + ansi.ANSI.fgAnsiPaletteOutput(writer, fgIndex) catch {}; + } else { + ansi.ANSI.fgIndexedColorOutput(writer, fgIndex) catch {}; + } + }, + .default => ansi.ANSI.fgDefaultOutput(writer) catch {}, + .rgb => ansi.ANSI.fgColorOutput(writer, rgbaComponentToU8(cell.fg[0]), rgbaComponentToU8(cell.fg[1]), rgbaComponentToU8(cell.fg[2])) catch {}, + } + // Output background color based on color type // If alpha is 0 (transparent), use terminal default background instead of black - if (bgA < 0.001) { - writer.writeAll("\x1b[49m") catch {}; + if (cell.bg[3] < 0.001) { + ansi.ANSI.bgDefaultOutput(writer) catch {}; } else { - ansi.ANSI.bgColorOutput(writer, bgR, bgG, bgB) catch {}; + switch (ansi.decodeColorType(cell.bg[4])) { + .indexed => { + const bgIndex = ansi.decodeColorIndex(cell.bg[4]); + if (bgIndex < 16) { + ansi.ANSI.bgAnsiPaletteOutput(writer, bgIndex) catch {}; + } else { + ansi.ANSI.bgIndexedColorOutput(writer, bgIndex) catch {}; + } + }, + .default => ansi.ANSI.bgDefaultOutput(writer) catch {}, + .rgb => ansi.ANSI.bgColorOutput(writer, rgbaComponentToU8(cell.bg[0]), rgbaComponentToU8(cell.bg[1]), rgbaComponentToU8(cell.bg[2])) catch {}, + } } ansi.TextAttributes.applyAttributesOutputWriter(writer, cell.attributes) catch {}; @@ -824,7 +839,7 @@ pub const CliRenderer = struct { self.renderStats.cellsUpdated = cellsUpdated; self.renderStats.renderTime = renderTime; - self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null) catch {}; + self.nextRenderBuffer.clear(self.backgroundColor, null) catch {}; // Compare hit grids before swap to detect changes. This allows TypeScript to // know if hover state needs rechecking without manually tracking dirty state. @@ -1287,12 +1302,12 @@ pub const CliRenderer = struct { }, } - self.nextRenderBuffer.fillRect(x, y, width, height, .{ 20.0 / 255.0, 20.0 / 255.0, 40.0 / 255.0, 1.0 }) catch {}; - self.nextRenderBuffer.drawText("Debug Information", x + 1, y + 1, .{ 1.0, 1.0, 100.0 / 255.0, 1.0 }, .{ 0.0, 0.0, 0.0, 0.0 }, ansi.TextAttributes.BOLD) catch {}; + self.nextRenderBuffer.fillRect(x, y, width, height, .{ 20.0 / 255.0, 20.0 / 255.0, 40.0 / 255.0, 1.0, ansi.RGB_META }) catch {}; + self.nextRenderBuffer.drawText("Debug Information", x + 1, y + 1, .{ 1.0, 1.0, 100.0 / 255.0, 1.0, ansi.RGB_META }, .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }, ansi.TextAttributes.BOLD) catch {}; var row: u32 = 2; - const bg: RGBA = .{ 0.0, 0.0, 0.0, 0.0 }; - const fg: RGBA = .{ 200.0 / 255.0, 200.0 / 255.0, 200.0 / 255.0, 1.0 }; + const bg: RGBA = .{ 0.0, 0.0, 0.0, 0.0, ansi.RGB_META }; + const fg: RGBA = .{ 200.0 / 255.0, 200.0 / 255.0, 200.0 / 255.0, 1.0, ansi.RGB_META }; // Calculate averages const lastFrameTimeAvg = getStatAverage(f64, &self.statSamples.lastFrameTime); diff --git a/packages/core/src/zig/terminal.zig b/packages/core/src/zig/terminal.zig index a495c1eff..fb182350f 100644 --- a/packages/core/src/zig/terminal.zig +++ b/packages/core/src/zig/terminal.zig @@ -125,7 +125,7 @@ state: struct { visible: bool = true, style: CursorStyle = .block, blinking: bool = false, - color: [4]f32 = .{ 1.0, 1.0, 1.0, 1.0 }, // RGBA + color: [5]f32 = .{ 1.0, 1.0, 1.0, 1.0, 0.0 }, // RGBA + color meta } = .{}, } = .{}, @@ -783,7 +783,7 @@ pub fn setCursorStyle(self: *Terminal, style: CursorStyle, blinking: bool) void self.state.cursor.blinking = blinking; } -pub fn setCursorColor(self: *Terminal, color: [4]f32) void { +pub fn setCursorColor(self: *Terminal, color: [5]f32) void { self.state.cursor.color = color; } @@ -802,7 +802,7 @@ pub fn getCursorStyle(self: *Terminal) struct { style: CursorStyle, blinking: bo }; } -pub fn getCursorColor(self: *Terminal) [4]f32 { +pub fn getCursorColor(self: *Terminal) [5]f32 { return self.state.cursor.color; } diff --git a/packages/core/src/zig/tests/buffer_test.zig b/packages/core/src/zig/tests/buffer_test.zig index 1dbed58ae..5bbc40c75 100644 --- a/packages/core/src/zig/tests/buffer_test.zig +++ b/packages/core/src/zig/tests/buffer_test.zig @@ -63,7 +63,7 @@ test "OptimizedBuffer - clear fills with default char" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var y: u32 = 0; @@ -90,10 +90,10 @@ test "OptimizedBuffer - drawText with ASCII" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.drawText("Hello", 0, 0, fg, bg, 0); const cell_h = buf.get(0, 0).?; @@ -117,8 +117,8 @@ test "OptimizedBuffer - repeated emoji rendering should not exhaust pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 1000) : (i += 1) { @@ -144,8 +144,8 @@ test "OptimizedBuffer - repeated CJK rendering should not exhaust pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 1000) : (i += 1) { @@ -179,7 +179,7 @@ test "OptimizedBuffer - drawTextBuffer repeatedly should not exhaust pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 1000) : (i += 1) { @@ -202,8 +202,8 @@ test "OptimizedBuffer - mixed ASCII and emoji repeated rendering" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 500) : (i += 1) { @@ -231,8 +231,8 @@ test "OptimizedBuffer - overwriting graphemes repeatedly" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 1000) : (i += 1) { @@ -259,8 +259,8 @@ test "OptimizedBuffer - rendering to different positions" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 100) : (i += 1) { @@ -314,7 +314,7 @@ test "OptimizedBuffer - large text buffer with wrapping repeated render" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 200) : (i += 1) { @@ -337,8 +337,8 @@ test "OptimizedBuffer - grapheme tracker counts" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.drawText("🌟ðŸŽĻ🚀", 0, 0, fg, bg, 0); @@ -370,8 +370,8 @@ test "OptimizedBuffer - alternating emojis should not leak" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 500) : (i += 1) { @@ -408,7 +408,7 @@ test "OptimizedBuffer - drawTextBuffer without clear should not exhaust pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var i: u32 = 0; @@ -442,7 +442,7 @@ test "OptimizedBuffer - many small graphemes without clear" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var i: u32 = 0; @@ -484,7 +484,7 @@ test "OptimizedBuffer - stress test with many graphemes" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var i: u32 = 0; @@ -522,7 +522,7 @@ test "OptimizedBuffer - pool slot exhaustion test" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; var i: u32 = 0; while (i < 10000) : (i += 1) { @@ -561,7 +561,7 @@ test "OptimizedBuffer - many unique graphemes with small pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; var render_count: u32 = 0; var failure_count: u32 = 0; @@ -696,7 +696,7 @@ test "OptimizedBuffer - continuous render without clear with small pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var i: u32 = 0; @@ -728,7 +728,7 @@ test "OptimizedBuffer - graphemes with scissor clipping and small pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); try buf.pushScissorRect(0, 0, 5, 5); @@ -754,9 +754,9 @@ test "OptimizedBuffer - drawText with alpha blending and scissor" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg_alpha = RGBA{ 0.0, 0.0, 0.0, 0.5 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg_alpha = RGBA{ 0.0, 0.0, 0.0, 0.5, 0.0 }; try buf.clear(bg, null); @@ -783,9 +783,9 @@ test "OptimizedBuffer - many unique graphemes with alpha and small pool" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg_alpha = RGBA{ 0.0, 0.0, 0.0, 0.5 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg_alpha = RGBA{ 0.0, 0.0, 0.0, 0.5, 0.0 }; try buf.clear(bg, null); @@ -821,8 +821,8 @@ test "OptimizedBuffer - fill buffer with many unique graphemes" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); @@ -860,8 +860,8 @@ test "OptimizedBuffer - verify pool growth works correctly" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); @@ -896,8 +896,8 @@ test "OptimizedBuffer - repeated overwriting of same grapheme" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.drawText("â€Ē", 0, 0, fg, bg, 0); @@ -932,8 +932,8 @@ test "OptimizedBuffer - two-buffer pattern should not leak" { ); defer currentBuffer.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var frame: u32 = 0; while (frame < 100) : (frame += 1) { @@ -961,8 +961,8 @@ test "OptimizedBuffer - set and clear cycle should not leak" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; var frame: u32 = 0; while (frame < 200) : (frame += 1) { @@ -994,7 +994,7 @@ test "OptimizedBuffer - repeated drawTextBuffer without clear should not leak" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var frame: u32 = 0; @@ -1034,7 +1034,7 @@ test "OptimizedBuffer - renderer two-buffer swap pattern should not leak" { ); defer next.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try current.clear(bg, null); var frame: u32 = 0; @@ -1064,8 +1064,8 @@ test "OptimizedBuffer - set should not clear newly written adjacent grapheme con ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); const old_gid = try local_pool.alloc("🌟"); @@ -1119,7 +1119,7 @@ test "OptimizedBuffer - sustained rendering should not leak" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var frame: u32 = 0; @@ -1149,7 +1149,7 @@ test "OptimizedBuffer - rendering with changing content should not leak" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var frame: u32 = 0; @@ -1209,7 +1209,7 @@ test "OptimizedBuffer - multiple TextBuffers rendering simultaneously should not ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); var frame: u32 = 0; @@ -1235,8 +1235,8 @@ test "OptimizedBuffer - grapheme refcount management" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.drawText("â€Ē", 0, 0, fg, bg, 0); const initial_cell = buf.get(0, 0).?; @@ -1282,7 +1282,7 @@ test "OptimizedBuffer - drawTextBuffer with graphemes then clear removes all poo ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.drawTextBuffer(view, 0, 0); @@ -1370,7 +1370,7 @@ test "OptimizedBuffer - drawTextBuffer with negative y coordinate should not pan ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); // Draw text buffer at negative y coordinate (-2) @@ -1439,8 +1439,8 @@ test "OptimizedBuffer - link encoding round-trip" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); // Allocate a link @@ -1475,8 +1475,8 @@ test "OptimizedBuffer - link tracker per-cell counting" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); // Allocate a link @@ -1524,8 +1524,8 @@ test "OptimizedBuffer - fillRect removes links" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg, null); // Allocate a link @@ -1565,8 +1565,8 @@ test "OptimizedBuffer - link reuse after free" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; // Allocate first link const link_id1 = try local_link_pool.alloc("https://first.com"); @@ -1601,9 +1601,9 @@ test "OptimizedBuffer - alpha blending preserves overlay link not dest link" { ); defer buf.deinit(); - const bg_opaque = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const bg_alpha = RGBA{ 0.5, 0.5, 0.5, 0.5 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg_opaque = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const bg_alpha = RGBA{ 0.5, 0.5, 0.5, 0.5, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg_opaque, null); // Draw underlying text with link A @@ -1642,9 +1642,9 @@ test "OptimizedBuffer - alpha blending with no link clears underlying link" { ); defer buf.deinit(); - const bg_opaque = RGBA{ 0.0, 0.0, 0.0, 1.0 }; - const bg_alpha = RGBA{ 0.5, 0.5, 0.5, 0.5 }; - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg_opaque = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; + const bg_alpha = RGBA{ 0.5, 0.5, 0.5, 0.5, 0.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; try buf.clear(bg_opaque, null); // Draw underlying text with link @@ -1682,7 +1682,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer basic rendering" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); // Create a 3x3 intensity buffer with varying values @@ -1720,7 +1720,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer negative position clipping" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); // Create a 4x4 intensity buffer @@ -1754,7 +1754,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer negative position fully clipped" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); const intensities = [_]f32{ @@ -1784,7 +1784,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer respects scissor rect" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); try buf.pushScissorRect(0, 0, 2, 2); @@ -1823,7 +1823,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer intensity to character mapping" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); const intensities = [_]f32{ @@ -1861,7 +1861,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer alpha blending preserves underlying ); defer buf.deinit(); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(red_bg, null); const initial_cell = buf.get(1, 1).?; @@ -1869,7 +1869,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer alpha blending preserves underlying try std.testing.expectEqual(@as(f32, 0.0), initial_cell.bg[1]); try std.testing.expectEqual(@as(f32, 0.0), initial_cell.bg[2]); - const semi_transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.5 }; + const semi_transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.5, 0.0 }; const intensities = [_]f32{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1901,10 +1901,10 @@ test "OptimizedBuffer - drawGrayscaleBuffer fully transparent bg preserves under ); defer buf.deinit(); - const green_bg = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const green_bg = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; try buf.clear(green_bg, null); - const transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.0 }; + const transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.0, 0.0 }; const intensities = [_]f32{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1935,10 +1935,10 @@ test "OptimizedBuffer - drawGrayscaleBuffer opaque bg overwrites underlying" { ); defer buf.deinit(); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(red_bg, null); - const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; const intensities = [_]f32{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1967,12 +1967,12 @@ test "OptimizedBuffer - drawGrayscaleBuffer with opacity stack" { ); defer buf.deinit(); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(red_bg, null); try buf.pushOpacity(0.5); - const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; const intensities = [_]f32{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -2002,7 +2002,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled alpha blending" { ); defer buf.deinit(); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(red_bg, null); const intensities = [_]f32{ @@ -2012,7 +2012,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled alpha blending" { 1.0, 1.0, 1.0, 1.0, }; - const semi_transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.5 }; + const semi_transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.5, 0.0 }; buf.drawGrayscaleBufferSupersampled(0, 0, &intensities, 4, 4, null, semi_transparent_bg); const cell = buf.get(0, 0).?; @@ -2034,7 +2034,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled fully transparent preser ); defer buf.deinit(); - const green_bg = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const green_bg = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; try buf.clear(green_bg, null); const intensities = [_]f32{ @@ -2044,7 +2044,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled fully transparent preser 1.0, 1.0, 1.0, 1.0, }; - const transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.0 }; + const transparent_bg = RGBA{ 0.0, 0.0, 1.0, 0.0, 0.0 }; buf.drawGrayscaleBufferSupersampled(0, 0, &intensities, 4, 4, null, transparent_bg); const cell = buf.get(0, 0).?; @@ -2067,7 +2067,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled respects scissor" { ); defer buf.deinit(); - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(bg, null); try buf.pushScissorRect(0, 0, 1, 1); @@ -2103,7 +2103,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled with opacity stack" { ); defer buf.deinit(); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(red_bg, null); try buf.pushOpacity(0.5); @@ -2115,7 +2115,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled with opacity stack" { 1.0, 1.0, 1.0, 1.0, }; - const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; buf.drawGrayscaleBufferSupersampled(0, 0, &intensities, 4, 4, null, blue_bg); buf.popOpacity(); @@ -2139,11 +2139,11 @@ test "OptimizedBuffer - blendColors with transparent destination" { ); defer buf.deinit(); - const transparent_bg = RGBA{ 0.0, 0.0, 0.0, 0.0 }; + const transparent_bg = RGBA{ 0.0, 0.0, 0.0, 0.0, 0.0 }; try buf.clear(transparent_bg, null); - const semi_white = RGBA{ 1.0, 1.0, 1.0, 0.5 }; - const transparent_fg = RGBA{ 0.0, 0.0, 0.0, 0.0 }; + const semi_white = RGBA{ 1.0, 1.0, 1.0, 0.5, 0.0 }; + const transparent_fg = RGBA{ 0.0, 0.0, 0.0, 0.0, 0.0 }; try buf.setCellWithAlphaBlending(0, 0, 'X', semi_white, transparent_fg, 0); const cell = buf.get(0, 0).?; @@ -2166,7 +2166,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer with custom fg color" { ); defer buf.deinit(); - const black_bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const black_bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(black_bg, null); const intensities = [_]f32{ @@ -2175,7 +2175,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer with custom fg color" { 1.0, 1.0, 1.0, }; - const red_fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; buf.drawGrayscaleBuffer(0, 0, &intensities, 3, 3, red_fg, black_bg); const cell = buf.get(1, 1).?; @@ -2198,7 +2198,7 @@ test "OptimizedBuffer - drawGrayscaleBuffer custom fg with partial intensity" { ); defer buf.deinit(); - const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const blue_bg = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; try buf.clear(blue_bg, null); const intensities = [_]f32{ @@ -2207,8 +2207,8 @@ test "OptimizedBuffer - drawGrayscaleBuffer custom fg with partial intensity" { 0.5, 0.5, 0.5, }; - const green_fg = RGBA{ 0.0, 1.0, 0.0, 1.0 }; - const transparent_bg = RGBA{ 0.0, 0.0, 0.0, 0.0 }; + const green_fg = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const transparent_bg = RGBA{ 0.0, 0.0, 0.0, 0.0, 0.0 }; buf.drawGrayscaleBuffer(0, 0, &intensities, 3, 3, green_fg, transparent_bg); const cell = buf.get(1, 1).?; @@ -2230,7 +2230,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled with custom fg color" { ); defer buf.deinit(); - const black_bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const black_bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try buf.clear(black_bg, null); const intensities = [_]f32{ @@ -2240,7 +2240,7 @@ test "OptimizedBuffer - drawGrayscaleBufferSupersampled with custom fg color" { 1.0, 1.0, 1.0, 1.0, }; - const cyan_fg = RGBA{ 0.0, 1.0, 1.0, 1.0 }; + const cyan_fg = RGBA{ 0.0, 1.0, 1.0, 1.0, 0.0 }; buf.drawGrayscaleBufferSupersampled(0, 0, &intensities, 4, 4, cyan_fg, black_bg); const cell = buf.get(0, 0).?; @@ -2267,8 +2267,8 @@ test "buffer - set same grapheme ID with different extents keeps slot alive" { }); defer buf.deinit(); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; const emoji = "👋"; @@ -2307,8 +2307,8 @@ test "renderer - grapheme WrongGeneration repro with pool slot reuse" { ); defer cli_renderer.destroy(); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; { const next = cli_renderer.getNextBuffer(); diff --git a/packages/core/src/zig/tests/editor-view_test.zig b/packages/core/src/zig/tests/editor-view_test.zig index 8f33d7c8f..217670db3 100644 --- a/packages/core/src/zig/tests/editor-view_test.zig +++ b/packages/core/src/zig/tests/editor-view_test.zig @@ -2010,8 +2010,8 @@ test "EditorView - placeholder with styled text renders with correct highlights" const text_part2 = "something"; const text_part3 = " here"; - const fg_gray = [4]f32{ 0.5, 0.5, 0.5, 1.0 }; - const fg_blue = [4]f32{ 0.3, 0.5, 0.9, 1.0 }; + const fg_gray = [5]f32{ 0.5, 0.5, 0.5, 1.0, 0.0 }; + const fg_blue = [5]f32{ 0.3, 0.5, 0.9, 1.0, 0.0 }; const chunks = [_]text_buffer.StyledChunk{ .{ @@ -2055,7 +2055,7 @@ test "EditorView - placeholder with styled text renders with correct highlights" ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(tbv_ptr, 0, 0); const epsilon: f32 = 0.01; @@ -2621,7 +2621,7 @@ test "EditorView - placeholder shows when empty" { defer ev.deinit(); const text = "Enter text here..."; - const gray_color = text_buffer.RGBA{ 0.4, 0.4, 0.4, 1.0 }; + const gray_color = text_buffer.RGBA{ 0.4, 0.4, 0.4, 1.0, 0.0 }; const chunks = [_]text_buffer.StyledChunk{.{ .text_ptr = text.ptr, .text_len = text.len, @@ -2653,7 +2653,7 @@ test "EditorView - placeholder cleared when set to empty" { defer ev.deinit(); const text = "Placeholder"; - const gray_color = text_buffer.RGBA{ 0.4, 0.4, 0.4, 1.0 }; + const gray_color = text_buffer.RGBA{ 0.4, 0.4, 0.4, 1.0, 0.0 }; const chunks = [_]text_buffer.StyledChunk{.{ .text_ptr = text.ptr, .text_len = text.len, @@ -2685,8 +2685,8 @@ test "EditorView - placeholder with styled text" { const text1 = "Hello "; const text2 = "World"; - const red_color = text_buffer.RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const blue_color = text_buffer.RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const red_color = text_buffer.RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const blue_color = text_buffer.RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; const chunks = [_]text_buffer.StyledChunk{ .{ @@ -2725,7 +2725,7 @@ test "EditorView - placeholder renders to buffer when empty" { defer ev.deinit(); const placeholder_text = "Type something..."; - const gray_color = text_buffer.RGBA{ 0.5, 0.5, 0.5, 1.0 }; + const gray_color = text_buffer.RGBA{ 0.5, 0.5, 0.5, 1.0, 0.0 }; const placeholder_chunks = [_]text_buffer.StyledChunk{.{ .text_ptr = placeholder_text.ptr, .text_len = placeholder_text.len, @@ -2746,7 +2746,7 @@ test "EditorView - placeholder renders to buffer when empty" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawEditorView(ev, 0, 0); var out_buffer: [1000]u8 = undefined; @@ -2757,7 +2757,7 @@ test "EditorView - placeholder renders to buffer when empty" { try eb.insertText("Hello"); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawEditorView(ev, 0, 0); try std.testing.expect(!ev.placeholder_active); @@ -2782,8 +2782,8 @@ test "EditorView - placeholder shrink clears tail and preserves background" { const long_text = "Ask anything... \"Fix a TODO in the codebase\""; const short_text = "Run a command... \"pwd\""; - const fg = text_buffer.RGBA{ 0.6, 0.6, 0.6, 1.0 }; - const panel_bg = text_buffer.RGBA{ 0.14, 0.14, 0.16, 1.0 }; + const fg = text_buffer.RGBA{ 0.6, 0.6, 0.6, 1.0, 0.0 }; + const panel_bg = text_buffer.RGBA{ 0.14, 0.14, 0.16, 1.0, 0.0 }; const long_chunks = [_]text_buffer.StyledChunk{.{ .text_ptr = long_text.ptr, @@ -2808,7 +2808,7 @@ test "EditorView - placeholder shrink clears tail and preserves background" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); var x: u32 = 0; while (x < 80) : (x += 1) { @@ -2858,7 +2858,7 @@ test "EditorView - tab indicator set and get" { try std.testing.expect(ev.getTabIndicatorColor() == null); ev.setTabIndicator('·'); - ev.setTabIndicatorColor(.{ 0.5, 0.5, 0.5, 1.0 }); + ev.setTabIndicatorColor(.{ 0.5, 0.5, 0.5, 1.0, 0.0 }); try std.testing.expectEqual(@as(u32, '·'), ev.getTabIndicator().?); try std.testing.expectEqual(@as(f32, 0.5), ev.getTabIndicatorColor().?[0]); @@ -2880,7 +2880,7 @@ test "EditorView - tab indicator renders in buffer" { try eb.insertText("A\tB"); ev.setTabIndicator('→'); - ev.setTabIndicatorColor(.{ 0.3, 0.3, 0.3, 1.0 }); + ev.setTabIndicatorColor(.{ 0.3, 0.3, 0.3, 1.0, 0.0 }); var opt_buffer = try opt_buffer_mod.OptimizedBuffer.init( std.testing.allocator, @@ -2890,7 +2890,7 @@ test "EditorView - tab indicator renders in buffer" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawEditorView(ev, 0, 0); const cell_0 = opt_buffer.get(0, 0); diff --git a/packages/core/src/zig/tests/renderer_test.zig b/packages/core/src/zig/tests/renderer_test.zig index cc0add6ef..1749e0d97 100644 --- a/packages/core/src/zig/tests/renderer_test.zig +++ b/packages/core/src/zig/tests/renderer_test.zig @@ -355,7 +355,7 @@ test "renderer - background color setting" { ); defer cli_renderer.destroy(); - const bg_color = RGBA{ 0.1, 0.2, 0.3, 1.0 }; + const bg_color = RGBA{ 0.1, 0.2, 0.3, 1.0, 0.0 }; cli_renderer.setBackgroundColor(bg_color); try std.testing.expectEqual(bg_color, cli_renderer.backgroundColor); @@ -471,8 +471,8 @@ test "renderer - 1000 frame render loop with setStyledText" { "Mixed 😀 äļ–", }; - const fg_color = [4]f32{ 1.0, 0.8, 0.6, 1.0 }; - const bg_color = [4]f32{ 0.1, 0.1, 0.2, 1.0 }; + const fg_color = [5]f32{ 1.0, 0.8, 0.6, 1.0, 0.0 }; + const bg_color = [5]f32{ 0.1, 0.1, 0.2, 1.0, 0.0 }; var frame: u32 = 0; while (frame < 1000) : (frame += 1) { @@ -488,11 +488,11 @@ test "renderer - 1000 frame render loop with setStyledText" { }}; try tb.setStyledText(&chunks); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const next_buffer = cli_renderer.getNextBuffer(); - try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); next_buffer.drawFrameBuffer(0, 0, opt_buffer, null, null, null, null); cli_renderer.render(false); @@ -548,8 +548,8 @@ test "renderer - grapheme pool refcounting with frame buffer fast path" { ); defer frame_buffer.deinit(); - const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; - const bg_color = [4]f32{ 0.0, 0.0, 0.0, 0.0 }; + const fg_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg_color = [5]f32{ 0.0, 0.0, 0.0, 0.0, 0.0 }; const text_with_emoji = "👋"; const chunks = [_]text_buffer.StyledChunk{.{ @@ -560,16 +560,16 @@ test "renderer - grapheme pool refcounting with frame buffer fast path" { .attributes = 0, }}; try tb.setStyledText(&chunks); - try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try frame_buffer.drawTextBuffer(view, 0, 0); const next_buffer = cli_renderer.getNextBuffer(); next_buffer.setRespectAlpha(false); - try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); next_buffer.drawFrameBuffer(0, 0, frame_buffer, null, null, null, null); - try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); var i: usize = 0; while (i < 10) : (i += 1) { @@ -583,7 +583,7 @@ test "renderer - grapheme pool refcounting with frame buffer fast path" { }}; try tb.setStyledText(&new_chunks); try frame_buffer.drawTextBuffer(view, 0, 0); - try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); } cli_renderer.render(false); @@ -608,8 +608,8 @@ test "renderer - unchanged grapheme should not churn IDs across frames" { ); defer cli_renderer.destroy(); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; const first_next_buffer = cli_renderer.getNextBuffer(); try first_next_buffer.drawText("👋", 0, 0, fg, bg, 0); @@ -666,8 +666,8 @@ test "renderer - hyperlinks enabled with OSC 8 output" { const next_buffer = cli_renderer.getNextBuffer(); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes); cli_renderer.render(false); @@ -712,8 +712,8 @@ test "renderer - hyperlinks disabled no OSC 8 output" { const next_buffer = cli_renderer.getNextBuffer(); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes); cli_renderer.render(false); @@ -751,8 +751,8 @@ test "renderer - link transition mid-line" { const attr1 = ansi.TextAttributes.setLinkId(0, link_id1); const attr2 = ansi.TextAttributes.setLinkId(0, link_id2); - const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; // Draw first link try next_buffer.drawText("First", 0, 0, fg, bg, attr1); diff --git a/packages/core/src/zig/tests/syntax-style_test.zig b/packages/core/src/zig/tests/syntax-style_test.zig index 3f2485112..5494bd124 100644 --- a/packages/core/src/zig/tests/syntax-style_test.zig +++ b/packages/core/src/zig/tests/syntax-style_test.zig @@ -19,7 +19,7 @@ test "SyntaxStyle - multiple independent instances" { const style2 = try SyntaxStyle.init(std.testing.allocator); defer style2.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style1.registerStyle("test", fg, null, 0); try std.testing.expectEqual(@as(usize, 1), style1.getStyleCount()); @@ -30,7 +30,7 @@ test "SyntaxStyle - register simple style" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id = try style.registerStyle("keyword", fg, null, 0); try std.testing.expect(id > 0); @@ -41,8 +41,8 @@ test "SyntaxStyle - register style with fg and bg" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; const id = try style.registerStyle("string", fg, bg, 0); try std.testing.expect(id > 0); @@ -53,7 +53,7 @@ test "SyntaxStyle - register style with attributes" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const attributes: u32 = 0b0001; // Bold const id = try style.registerStyle("bold-keyword", fg, null, attributes); @@ -67,7 +67,7 @@ test "SyntaxStyle - register style with all attributes" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const attributes: u32 = 0b1111; // Bold, italic, underline, dim const id = try style.registerStyle("all-attrs", fg, null, attributes); @@ -94,9 +94,9 @@ test "SyntaxStyle - register multiple styles" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; - const fg3 = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const fg3 = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; const id1 = try style.registerStyle("keyword", fg1, null, 0); const id2 = try style.registerStyle("string", fg2, null, 0); @@ -112,8 +112,8 @@ test "SyntaxStyle - register same name returns same ID" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("keyword", fg1, null, 0); const id2 = try style.registerStyle("keyword", fg2, null, 0); @@ -130,7 +130,7 @@ test "SyntaxStyle - register style with special characters" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style.registerStyle("keyword.control", fg, null, 0); _ = try style.registerStyle("variable.parameter", fg, null, 0); @@ -150,7 +150,7 @@ test "SyntaxStyle - register many styles" { var name_buffer: [32]u8 = undefined; const name = try std.fmt.bufPrint(&name_buffer, "style-{d}", .{i}); - const fg = RGBA{ @as(f32, @floatFromInt(i)) / 100.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ @as(f32, @floatFromInt(i)) / 100.0, 0.0, 0.0, 1.0, 0.0 }; ids[i] = try style.registerStyle(name, fg, null, 0); } @@ -167,8 +167,8 @@ test "SyntaxStyle - resolveById returns correct style" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; const attributes: u32 = 0b0011; // Bold + italic const id = try style.registerStyle("test", fg, bg, attributes); @@ -199,7 +199,7 @@ test "SyntaxStyle - resolveByName returns correct ID" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const registered_id = try style.registerStyle("keyword", fg, null, 0); const resolved_id = style.resolveByName("keyword").?; @@ -218,7 +218,7 @@ test "SyntaxStyle - resolveByName is case-sensitive" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style.registerStyle("keyword", fg, null, 0); try std.testing.expect(style.resolveByName("keyword") != null); @@ -230,8 +230,8 @@ test "SyntaxStyle - resolve multiple styles" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("keyword", fg1, null, 0); const id2 = try style.registerStyle("string", fg2, null, 0); @@ -244,7 +244,7 @@ test "SyntaxStyle - merge single style" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const attributes: u32 = 0b0001; const id = try style.registerStyle("keyword", fg, null, attributes); @@ -260,9 +260,9 @@ test "SyntaxStyle - merge two styles" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; - const bg2 = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const bg2 = RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("base", fg1, null, 0b0001); // Bold const id2 = try style.registerStyle("modifier", fg2, bg2, 0b0010); // Italic @@ -279,9 +279,9 @@ test "SyntaxStyle - merge three styles" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; - const fg3 = RGBA{ 0.0, 0.0, 1.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const fg3 = RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg1, null, 0b0001); // Bold const id2 = try style.registerStyle("s2", fg2, null, 0b0010); // Italic @@ -310,7 +310,7 @@ test "SyntaxStyle - merge with invalid ID skips it" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("valid", fg, null, 0b0001); const ids = [_]u32{ id1, 9999 }; // 9999 is invalid @@ -324,8 +324,8 @@ test "SyntaxStyle - merge caches results" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg1, null, 0); const id2 = try style.registerStyle("s2", fg2, null, 0); @@ -343,8 +343,8 @@ test "SyntaxStyle - merge different order produces different results" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg1, null, 0); const id2 = try style.registerStyle("s2", fg2, null, 0); @@ -362,8 +362,8 @@ test "SyntaxStyle - clearCache empties cache" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0 }; - const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0 }; + const fg1 = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const fg2 = RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg1, null, 0); const id2 = try style.registerStyle("s2", fg2, null, 0); @@ -382,7 +382,7 @@ test "SyntaxStyle - clearCache preserves styles" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style.registerStyle("keyword", fg, null, 0); _ = try style.registerStyle("string", fg, null, 0); @@ -399,7 +399,7 @@ test "SyntaxStyle - getCacheSize returns correct count" { try std.testing.expectEqual(@as(usize, 0), style.getCacheSize()); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg, null, 0); const id2 = try style.registerStyle("s2", fg, null, 0); @@ -424,7 +424,7 @@ test "SyntaxStyle - very long style name" { var long_name: [1000]u8 = undefined; @memset(&long_name, 'a'); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id = try style.registerStyle(&long_name, fg, null, 0); try std.testing.expect(id > 0); @@ -435,7 +435,7 @@ test "SyntaxStyle - empty string style name" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id = try style.registerStyle("", fg, null, 0); try std.testing.expect(id > 0); @@ -446,7 +446,7 @@ test "SyntaxStyle - unicode style names" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("å…ģé”Ū字", fg, null, 0); const id2 = try style.registerStyle("キマãƒŊマド", fg, null, 0); @@ -461,8 +461,8 @@ test "SyntaxStyle - all color channels" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 0.1, 0.2, 0.3, 0.4 }; - const bg = RGBA{ 0.5, 0.6, 0.7, 0.8 }; + const fg = RGBA{ 0.1, 0.2, 0.3, 0.4, 0.0 }; + const bg = RGBA{ 0.5, 0.6, 0.7, 0.8, 0.0 }; const id = try style.registerStyle("test", fg, bg, 0); const resolved = style.resolveById(id).?; @@ -487,7 +487,7 @@ test "SyntaxStyle - stress test many registrations" { var name_buffer: [32]u8 = undefined; const name = try std.fmt.bufPrint(&name_buffer, "style-{d}", .{i}); - const fg = RGBA{ @as(f32, @floatFromInt(i % 256)) / 255.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ @as(f32, @floatFromInt(i % 256)) / 255.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style.registerStyle(name, fg, null, 0); } @@ -498,7 +498,7 @@ test "SyntaxStyle - stress test many merges" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id1 = try style.registerStyle("s1", fg, null, 0); const id2 = try style.registerStyle("s2", fg, null, 0); const id3 = try style.registerStyle("s3", fg, null, 0); @@ -518,7 +518,7 @@ test "SyntaxStyle - merge many styles at once" { const count = 50; var ids: [count]u32 = undefined; - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; for (0..count) |i| { var name_buffer: [32]u8 = undefined; const name = try std.fmt.bufPrint(&name_buffer, "s{d}", .{i}); @@ -533,7 +533,7 @@ test "SyntaxStyle - merge many styles at once" { test "SyntaxStyle - multiple init/deinit cycles" { for (0..10) |_| { const style = try SyntaxStyle.init(std.testing.allocator); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = try style.registerStyle("test", fg, null, 0); style.deinit(); } @@ -543,7 +543,7 @@ test "SyntaxStyle - register and resolve after clear cache" { const style = try SyntaxStyle.init(std.testing.allocator); defer style.deinit(); - const fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; const id = try style.registerStyle("keyword", fg, null, 0); const ids = [_]u32{id}; diff --git a/packages/core/src/zig/tests/text-buffer-drawing_test.zig b/packages/core/src/zig/tests/text-buffer-drawing_test.zig index 61d48114d..8cd946e9c 100644 --- a/packages/core/src/zig/tests/text-buffer-drawing_test.zig +++ b/packages/core/src/zig/tests/text-buffer-drawing_test.zig @@ -35,7 +35,7 @@ test "drawTextBuffer - simple single line text" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -67,7 +67,7 @@ test "drawTextBuffer - empty text buffer" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); } @@ -93,7 +93,7 @@ test "drawTextBuffer - multiple lines without wrapping" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -125,7 +125,7 @@ test "drawTextBuffer - text wrapping at word boundaries" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -157,7 +157,7 @@ test "drawTextBuffer - text wrapping at character boundaries" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -189,7 +189,7 @@ test "drawTextBuffer - no wrapping with none mode" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -221,7 +221,7 @@ test "drawTextBuffer - wrapped text with multiple lines" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -253,7 +253,7 @@ test "drawTextBuffer - unicode characters with wrapping" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -285,7 +285,7 @@ test "drawTextBuffer - wrapping preserves wide characters" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -361,7 +361,7 @@ test "drawTextBuffer - wrapped text with offset position" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 5, 5); const cell = opt_buffer.get(5, 5); @@ -391,7 +391,7 @@ test "drawTextBuffer - clipping with scrolled view" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -423,7 +423,7 @@ test "drawTextBuffer - wrapping with very narrow width" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -455,7 +455,7 @@ test "drawTextBuffer - word wrap doesn't break mid-word" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -484,7 +484,7 @@ test "drawTextBuffer - empty lines render correctly" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -516,7 +516,7 @@ test "drawTextBuffer - wrapping with tabs" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); } @@ -548,7 +548,7 @@ test "drawTextBuffer - very long unwrapped line clipping" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -645,7 +645,7 @@ test "drawTextBuffer - wrapping with mixed ASCII and Unicode" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const virtual_lines = view.getVirtualLines(); @@ -666,7 +666,7 @@ test "setStyledText - basic rendering with single chunk" { tb.setSyntaxStyle(style); const text = "Hello World"; - const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; + const fg_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; const chunks = [_]StyledChunk{.{ .text_ptr = text.ptr, @@ -700,7 +700,7 @@ test "setStyledText - multiple chunks render correctly" { const text0 = "Hello "; const text1 = "World"; - const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; + const fg_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; const chunks = [_]StyledChunk{ .{ .text_ptr = text0.ptr, .text_len = text0.len, .fg_ptr = @ptrCast(&fg_color), .bg_ptr = null, .attributes = 0 }, @@ -1276,7 +1276,7 @@ test "loadFile - loads and renders file correctly" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var render_buffer: [200]u8 = undefined; @@ -1312,7 +1312,7 @@ test "drawTextBuffer - horizontal viewport offset renders correctly without wrap ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -1348,7 +1348,7 @@ test "drawTextBuffer - horizontal viewport offset with multiple lines" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -1386,7 +1386,7 @@ test "drawTextBuffer - combined horizontal and vertical viewport offsets" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -1423,7 +1423,7 @@ test "drawTextBuffer - horizontal viewport stops rendering at viewport width" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -1463,7 +1463,7 @@ test "drawTextBuffer - horizontal viewport with small buffer renders only viewpo ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0); @@ -1513,7 +1513,7 @@ test "drawTextBuffer - horizontal viewport width limits rendering (efficiency te ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var non_space_count: u32 = 0; @@ -1550,7 +1550,7 @@ test "drawTextBuffer - overwriting wide grapheme with ASCII leaves no ghost char defer opt_buffer.deinit(); try tb.setText("äļ–į•Œ"); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const first_cell = opt_buffer.get(0, 0) orelse unreachable; @@ -1561,7 +1561,7 @@ test "drawTextBuffer - overwriting wide grapheme with ASCII leaves no ghost char try std.testing.expect(gp.isContinuationChar(second_cell.char)); try tb.setText("ABC"); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_a = opt_buffer.get(0, 0) orelse unreachable; @@ -1600,7 +1600,7 @@ test "drawTextBuffer - syntax style destroy does not crash" { var style = try ss.SyntaxStyle.init(std.testing.allocator); tb.setSyntaxStyle(style); - const style_id = try style.registerStyle("test", .{ 1.0, 0.0, 0.0, 1.0 }, null, 0); + const style_id = try style.registerStyle("test", .{ 1.0, 0.0, 0.0, 1.0, 0.0 }, null, 0); try tb.setText("Hello World"); try tb.addHighlightByCharRange(0, 5, style_id, 1, 0); @@ -1612,7 +1612,7 @@ test "drawTextBuffer - syntax style destroy does not crash" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [100]u8 = undefined; @@ -1624,7 +1624,7 @@ test "drawTextBuffer - syntax style destroy does not crash" { try std.testing.expect(tb.getSyntaxStyle() == null); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const written2 = try opt_buffer.writeResolvedChars(&out_buffer, false); @@ -1656,7 +1656,7 @@ test "drawTextBuffer - tabs are rendered as spaces (empty cells)" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -1695,7 +1695,7 @@ test "drawTextBuffer - tab indicator renders with correct color" { try tb.setText("A\tB"); view.setTabIndicator(@as(u32, '→')); - view.setTabIndicatorColor(RGBA{ 0.25, 0.25, 0.25, 1.0 }); + view.setTabIndicatorColor(RGBA{ 0.25, 0.25, 0.25, 1.0, 0.0 }); var opt_buffer = try OptimizedBuffer.init( std.testing.allocator, @@ -1705,7 +1705,7 @@ test "drawTextBuffer - tab indicator renders with correct color" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -1754,7 +1754,7 @@ test "drawTextBuffer - tab without indicator renders as spaces" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -1799,7 +1799,7 @@ test "drawTextBuffer - mixed ASCII and Unicode with emoji renders completely" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -1933,7 +1933,7 @@ test "viewport width = 31 exactly - last character rendering" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // BUG CHECK: The last 's' at cell 30 should be present @@ -1995,7 +1995,7 @@ test "drawTextBuffer - complex multilingual text with diverse scripts and emojis ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Verify the text buffer can handle complex multilingual content @@ -2047,7 +2047,7 @@ test "drawTextBuffer - complex multilingual text with diverse scripts and emojis try std.testing.expect(viewport_lines.len <= 20); // Verify rendering doesn't crash with complex emoji sequences - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Test that line count is reasonable @@ -2080,8 +2080,8 @@ test "setStyledText - highlight positioning with Unicode text" { const text_part4 = "please"; const text_part5 = "."; - const fg_normal = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; - const bg_highlight = [4]f32{ 0.0, 1.0, 0.0, 1.0 }; // Green background + const fg_normal = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const bg_highlight = [5]f32{ 0.0, 1.0, 0.0, 1.0, 0.0 }; // Green background const chunks = [_]StyledChunk{ .{ .text_ptr = text_part1.ptr, .text_len = text_part1.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = null, .attributes = 0 }, @@ -2114,7 +2114,7 @@ test "setStyledText - highlight positioning with Unicode text" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Check that "please" (6 characters) all have the green background @@ -2162,10 +2162,10 @@ test "drawTextBuffer - multiple syntax highlights with various horizontal viewpo tb.setSyntaxStyle(style); // Register different color styles - const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0); - const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0); - const blue_style = try style.registerStyle("blue", RGBA{ 0.0, 0.0, 1.0, 1.0 }, null, 0); - const yellow_style = try style.registerStyle("yellow", RGBA{ 1.0, 1.0, 0.0, 1.0 }, null, 0); + const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }, null, 0); + const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }, null, 0); + const blue_style = try style.registerStyle("blue", RGBA{ 0.0, 0.0, 1.0, 1.0, 0.0 }, null, 0); + const yellow_style = try style.registerStyle("yellow", RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }, null, 0); // Text: "const x = function(y) { return y * 2; }" const test_text = "const x = function(y) { return y * 2; }"; @@ -2193,7 +2193,7 @@ test "drawTextBuffer - multiple syntax highlights with various horizontal viewpo var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 40, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Check "const" is red @@ -2221,7 +2221,7 @@ test "drawTextBuffer - multiple syntax highlights with various horizontal viewpo var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Buffer shows characters 3-22 from source: "st x = function(y) {" @@ -2268,7 +2268,7 @@ test "drawTextBuffer - multiple syntax highlights with various horizontal viewpo var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Actual rendering shows: " y * 2; }" @@ -2301,7 +2301,7 @@ test "drawTextBuffer - syntax highlighting with horizontal viewport offset" { tb.setSyntaxStyle(style); // Register a red style - const red_style_id = try style.registerStyle("keyword", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0); + const red_style_id = try style.registerStyle("keyword", RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }, null, 0); // Text: "const x = 1" // Highlight "const" (characters 0-5) in red @@ -2321,11 +2321,11 @@ test "drawTextBuffer - syntax highlighting with horizontal viewport offset" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const epsilon: f32 = 0.01; - const red_fg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_fg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; // Check that 's' at buffer position 0 is RED const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -2389,11 +2389,11 @@ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolli const chunk7_text = "2"; const chunk8_text = "; }"; - const red_color = [4]f32{ 1.0, 0.0, 0.0, 1.0 }; - const white_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 }; - const green_color = [4]f32{ 0.0, 1.0, 0.0, 1.0 }; - const blue_color = [4]f32{ 0.0, 0.0, 1.0, 1.0 }; - const yellow_color = [4]f32{ 1.0, 1.0, 0.0, 1.0 }; + const red_color = [5]f32{ 1.0, 0.0, 0.0, 1.0, 0.0 }; + const white_color = [5]f32{ 1.0, 1.0, 1.0, 1.0, 0.0 }; + const green_color = [5]f32{ 0.0, 1.0, 0.0, 1.0, 0.0 }; + const blue_color = [5]f32{ 0.0, 0.0, 1.0, 1.0, 0.0 }; + const yellow_color = [5]f32{ 1.0, 1.0, 0.0, 1.0, 0.0 }; const chunks = [_]StyledChunk{ .{ .text_ptr = chunk1_text.ptr, .text_len = chunk1_text.len, .fg_ptr = @ptrCast(&red_color), .bg_ptr = null, .attributes = 0 }, @@ -2450,7 +2450,7 @@ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolli var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 40, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; // 'c' from "const" @@ -2468,7 +2468,7 @@ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolli var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // At x=5, showing chars 5-24: " x = function(y) { " @@ -2489,7 +2489,7 @@ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolli var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // At x=15, showing chars 15-34: "ion(y) { return y * " @@ -2527,7 +2527,7 @@ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolli var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode }); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // At x=25, showing chars 25-44: "eturn y * 2; }" @@ -2576,7 +2576,7 @@ test "drawTextBuffer - selection with horizontal viewport offset" { view.setViewport(.{ .x = 5, .y = 0, .width = 10, .height = 1 }); // Select characters at positions 7-12 in the original text ("789AB") - view.setSelection(7, 12, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 }); + view.setSelection(7, 12, RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }); var opt_buffer = try OptimizedBuffer.init( std.testing.allocator, @@ -2586,7 +2586,7 @@ test "drawTextBuffer - selection with horizontal viewport offset" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // The viewport shows positions 5-14 of the text @@ -2604,7 +2604,7 @@ test "drawTextBuffer - selection with horizontal viewport offset" { // Position 9: 'E' - not highlighted const epsilon: f32 = 0.01; - const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 }; + const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }; // Check non-highlighted cells const cell_0 = opt_buffer.get(0, 0) orelse unreachable; @@ -2668,8 +2668,8 @@ test "drawTextBuffer - syntax highlight respects truncation" { defer style.deinit(); tb.setSyntaxStyle(style); - const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0); - const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0); + const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }, null, 0); + const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }, null, 0); try tb.setText("0123456789ABCDEFGHIJ"); try tb.addHighlightByCharRange(4, 7, red_style, 1, 0); // highlight "456" @@ -2688,7 +2688,7 @@ test "drawTextBuffer - syntax highlight respects truncation" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const epsilon: f32 = 0.01; @@ -2728,8 +2728,8 @@ test "drawTextBuffer - highlight spanning ellipsis continues on suffix" { defer style.deinit(); tb.setSyntaxStyle(style); - const magenta_style = try style.registerStyle("magenta", RGBA{ 1.0, 0.0, 1.0, 1.0 }, null, 0); - const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0); + const magenta_style = try style.registerStyle("magenta", RGBA{ 1.0, 0.0, 1.0, 1.0, 0.0 }, null, 0); + const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }, null, 0); try tb.setText("0123456789ABCDEFGHIJ"); try tb.addHighlightByCharRange(2, 18, magenta_style, 1, 0); // spans through ellipsis @@ -2748,7 +2748,7 @@ test "drawTextBuffer - highlight spanning ellipsis continues on suffix" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const epsilon: f32 = 0.01; @@ -2794,7 +2794,7 @@ test "drawTextBuffer - selection respects truncation" { view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 1 }); // Select across the ellipsis and suffix - view.setSelection(2, 19, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 }); + view.setSelection(2, 19, RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }); var opt_buffer = try OptimizedBuffer.init( std.testing.allocator, @@ -2804,11 +2804,11 @@ test "drawTextBuffer - selection respects truncation" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const epsilon: f32 = 0.01; - const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 }; + const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }; const cell_0 = opt_buffer.get(0, 0) orelse unreachable; try std.testing.expectEqual(@as(u32, '0'), cell_0.char); @@ -2869,7 +2869,7 @@ test "drawTextBuffer - truncation selection does not overshoot multiline" { view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 2 }); // Select from line 1 col 2 through line 2 col 5 (exclusive) - view.setSelection(2, 26, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 }); + view.setSelection(2, 26, RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0, 0.0 }); var opt_buffer = try OptimizedBuffer.init( std.testing.allocator, @@ -2879,11 +2879,11 @@ test "drawTextBuffer - truncation selection does not overshoot multiline" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const epsilon: f32 = 0.01; - const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 }; + const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0, 0.0 }; const line2_cell_0 = opt_buffer.get(0, 1) orelse unreachable; try std.testing.expectEqual(@as(u32, 'k'), line2_cell_0.char); @@ -2943,7 +2943,7 @@ test "drawTextBuffer - Chinese text with wrapping no stray bytes" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Write the rendered buffer to check for stray bytes @@ -3012,7 +3012,7 @@ test "drawTextBuffer - Chinese text WITHOUT wrapping no duplicate chunks" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Write the rendered buffer @@ -3066,7 +3066,7 @@ test "drawTextBuffer - Chinese text with CHAR wrapping no stray bytes" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); // Write the rendered buffer to check for stray bytes @@ -3111,7 +3111,7 @@ test "drawTextBuffer - word wrap CJK mixed text without break points" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [1000]u8 = undefined; @@ -3158,7 +3158,7 @@ test "drawTextBuffer - word wrap CJK text preserves UTF-8 boundaries" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); var out_buffer: [1000]u8 = undefined; @@ -3213,7 +3213,7 @@ test "drawTextBuffer - Thai āļ§āđˆ grapheme in quotes occupies one cell" { ); defer opt_buffer.deinit(); - try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32); + try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0, 0.0 }, 32); try opt_buffer.drawTextBuffer(view, 0, 0); const cell_0 = opt_buffer.get(0, 0) orelse unreachable; diff --git a/packages/core/src/zig/tests/text-buffer-highlights_test.zig b/packages/core/src/zig/tests/text-buffer-highlights_test.zig index 495f4acce..fa5575cbd 100644 --- a/packages/core/src/zig/tests/text-buffer-highlights_test.zig +++ b/packages/core/src/zig/tests/text-buffer-highlights_test.zig @@ -261,9 +261,9 @@ test "TextBuffer highlights - integration with SyntaxStyle" { var syntax_style = try ss.SyntaxStyle.init(std.testing.allocator); defer syntax_style.deinit(); - const keyword_id = try syntax_style.registerStyle("keyword", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0); - const string_id = try syntax_style.registerStyle("string", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0); - const comment_id = try syntax_style.registerStyle("comment", RGBA{ 0.5, 0.5, 0.5, 1.0 }, null, 0); + const keyword_id = try syntax_style.registerStyle("keyword", RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }, null, 0); + const string_id = try syntax_style.registerStyle("string", RGBA{ 0.0, 1.0, 0.0, 1.0, 0.0 }, null, 0); + const comment_id = try syntax_style.registerStyle("comment", RGBA{ 0.5, 0.5, 0.5, 1.0, 0.0 }, null, 0); try tb.setText("function hello() // comment"); tb.setSyntaxStyle(syntax_style); diff --git a/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig b/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig index 38fcd5089..6a775f99d 100644 --- a/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig +++ b/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig @@ -332,7 +332,7 @@ test "Selection - RENDER TEST: selection highlights correct cells with viewport view.setViewport(Viewport{ .x = 0, .y = 3, .width = 10, .height = 5 }); - const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0 }; + const red_bg = RGBA{ 1.0, 0.0, 0.0, 1.0, 0.0 }; _ = view.setLocalSelection(0, 0, 3, 0, red_bg, null); var render_buffer = try buffer_mod.OptimizedBuffer.init(std.testing.allocator, pool, 20, 10, .unicode); diff --git a/packages/core/src/zig/tests/text-buffer-view_test.zig b/packages/core/src/zig/tests/text-buffer-view_test.zig index ecf85408a..6552fe1ac 100644 --- a/packages/core/src/zig/tests/text-buffer-view_test.zig +++ b/packages/core/src/zig/tests/text-buffer-view_test.zig @@ -3499,7 +3499,7 @@ test "TextBufferView - tab indicator set and get" { try std.testing.expect(view.getTabIndicatorColor() == null); view.setTabIndicator(@as(u32, '·')); - view.setTabIndicatorColor(RGBA{ 0.4, 0.4, 0.4, 1.0 }); + view.setTabIndicatorColor(RGBA{ 0.4, 0.4, 0.4, 1.0, 0.0 }); try std.testing.expectEqual(@as(u32, '·'), view.getTabIndicator().?); try std.testing.expectEqual(@as(f32, 0.4), view.getTabIndicatorColor().?[0]); diff --git a/packages/core/src/zig/utils.zig b/packages/core/src/zig/utils.zig index d046ba735..3872442b8 100644 --- a/packages/core/src/zig/utils.zig +++ b/packages/core/src/zig/utils.zig @@ -1,9 +1,9 @@ -const std = @import("std"); +const ansi = @import("ansi.zig"); -/// RGBA color type (4 f32 values) -pub const RGBA = @Vector(4, f32); +/// RGBA color type (5 f32 values: r, g, b, a, colorMeta) +pub const RGBA = ansi.RGBA; -/// Convert a pointer to 4 f32 values into an RGBA color +/// Convert a pointer to 5 f32 values into an RGBA color pub fn f32PtrToRGBA(ptr: [*]const f32) RGBA { - return .{ ptr[0], ptr[1], ptr[2], ptr[3] }; + return .{ ptr[0], ptr[1], ptr[2], ptr[3], ptr[4] }; }