Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions lib/box-drawing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ type Op =
| { kind: 'beginPath' }
| { kind: 'moveTo'; x: number; y: number }
| { kind: 'lineTo'; x: number; y: number }
| { kind: 'bezierCurveTo'; cp1x: number; cp1y: number; cp2x: number; cp2y: number; x: number; y: number }
| {
kind: 'bezierCurveTo';
cp1x: number;
cp1y: number;
cp2x: number;
cp2y: number;
x: number;
y: number;
}
| { kind: 'stroke' }
| { kind: 'translate'; x: number; y: number };

Expand All @@ -45,14 +53,7 @@ interface RecordingCtx {
beginPath(): void;
moveTo(x: number, y: number): void;
lineTo(x: number, y: number): void;
bezierCurveTo(
cp1x: number,
cp1y: number,
cp2x: number,
cp2y: number,
x: number,
y: number
): void;
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
stroke(): void;
translate(x: number, y: number): void;
}
Expand Down Expand Up @@ -188,9 +189,7 @@ describe('box-drawing', () => {
// fillRect would slip through the looser `o.kind === ...`
// check.
const drewSomething = ctx.ops.some(
(o) =>
(o.kind === 'fillRect' && o.w > 0 && o.h > 0) ||
o.kind === 'stroke'
(o) => (o.kind === 'fillRect' && o.w > 0 && o.h > 0) || o.kind === 'stroke'
);
if (!handled || !drewSomething) missing.push(cp);
}
Expand Down
5 changes: 1 addition & 4 deletions lib/ghostty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,7 @@ export class KeyEncoder {
// Always set unshifted_codepoint (0 when absent) so a stale value from a
// previous encode on the cached eventPtr doesn't leak through. The
// encoder treats 0 as "not set" and falls back accordingly.
this.exports.ghostty_key_event_set_unshifted_codepoint(
eventPtr,
event.unshiftedCodepoint ?? 0
);
this.exports.ghostty_key_event_set_unshifted_codepoint(eventPtr, event.unshiftedCodepoint ?? 0);

// Encode utf8 directly into the WASM scratch buffer with encodeInto,
// skipping the intermediate Uint8Array that TEXT_ENCODER.encode would
Expand Down
7 changes: 2 additions & 5 deletions lib/providers/url-regex-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ export class UrlRegexProvider implements ILinkProvider {
// Walk forward to the end of the chain by extending while the *next*
// row is a continuation.
let endRow = startRow;
while (
endRow - startRow < UrlRegexProvider.MAX_WRAP_CHAIN_ROWS &&
endRow < buffer.length - 1
) {
while (endRow - startRow < UrlRegexProvider.MAX_WRAP_CHAIN_ROWS && endRow < buffer.length - 1) {
const next = buffer.getLine(endRow + 1);
if (!next || !next.isWrapped) break;
endRow++;
Expand Down Expand Up @@ -185,7 +182,7 @@ export class UrlRegexProvider implements ILinkProvider {
private joinedIdxToRowCol(
joinedIdx: number,
rowStartIdx: number[],
baseRow: number,
baseRow: number
): { x: number; y: number } {
for (let i = rowStartIdx.length - 1; i >= 0; i--) {
if (joinedIdx >= rowStartIdx[i]) {
Expand Down
7 changes: 3 additions & 4 deletions lib/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ export class CanvasRenderer {
// we cannot infer it from the RGB triple because (0,0,0) is a valid
// explicit color (programs emit it for "true black" backgrounds, e.g.
// letterboxed image renderings).
const useThemeBg = (cell.flags & CellFlags.INVERSE) ? cell.fgIsDefault : cell.bgIsDefault;
const useThemeBg = cell.flags & CellFlags.INVERSE ? cell.fgIsDefault : cell.bgIsDefault;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for determining useThemeBg when CellFlags.INVERSE is set appears to be incorrect for cells using default colors. If INVERSE is set and cell.fgIsDefault is true, useThemeBg becomes true, which causes the cell to skip painting its background (leaving it as the default theme.background). However, an inverted cell with a default foreground should have its background painted with theme.foreground to reflect the inversion.

if (!useThemeBg) {
this.ctx.fillStyle = this.rgbToCSS(bg_r, bg_g, bg_b);
this.ctx.fillRect(cellX, cellY, cellWidth, this.metrics.height);
Expand Down Expand Up @@ -873,7 +873,7 @@ export class CanvasRenderer {
// Same reasoning as the bg path: only fall back to theme.foreground
// when the cell has the default fg (tag NONE), not when its explicit
// RGB happens to be (0,0,0).
const useThemeFg = (cell.flags & CellFlags.INVERSE) ? cell.bgIsDefault : cell.fgIsDefault;
const useThemeFg = cell.flags & CellFlags.INVERSE ? cell.bgIsDefault : cell.fgIsDefault;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, the logic for useThemeFg when CellFlags.INVERSE is set seems to result in incorrect text coloring for default colors. If INVERSE is set and cell.bgIsDefault is true, useThemeFg becomes true, causing the text to be painted with theme.foreground. This matches the non-inverted case, meaning the text color does not change when it should likely use theme.background to properly represent the inverted state.

this.ctx.fillStyle = useThemeFg ? this.theme.foreground : this.rgbToCSS(fg_r, fg_g, fg_b);
}

Expand Down Expand Up @@ -1213,8 +1213,7 @@ export class CanvasRenderer {
if (quads & QUAD_UL) this.ctx.fillRect(cellX, cellY, halfW, halfH);
if (quads & QUAD_UR) this.ctx.fillRect(cellX + halfW, cellY, w - halfW, halfH);
if (quads & QUAD_LL) this.ctx.fillRect(cellX, cellY + halfH, halfW, h - halfH);
if (quads & QUAD_LR)
this.ctx.fillRect(cellX + halfW, cellY + halfH, w - halfW, h - halfH);
if (quads & QUAD_LR) this.ctx.fillRect(cellX + halfW, cellY + halfH, w - halfW, h - halfH);
return true;
}

Expand Down
5 changes: 1 addition & 4 deletions lib/selection-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ export class SelectionManager {
renderer: CanvasRenderer,
wasmTerm: GhosttyTerminal,
textarea: HTMLTextAreaElement,
onLongPressActivation?: (
col: number,
absoluteRow: number
) => Promise<boolean> | boolean
onLongPressActivation?: (col: number, absoluteRow: number) => Promise<boolean> | boolean
) {
this.terminal = terminal;
this.renderer = renderer;
Expand Down
6 changes: 3 additions & 3 deletions lib/terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3029,9 +3029,9 @@ describe('Synchronous open()', () => {
const wasmTerm1 = term1.wasmTerm!;

// Write multi-codepoint grapheme clusters (flag emoji, skin tone, ZWJ sequence)
wasmTerm1.write('\u{1F1FA}\u{1F1F8}'); // 🇺🇸 regional indicator pair
wasmTerm1.write('\u{1F44B}\u{1F3FD}'); // 👋🏽 wave + skin tone modifier
wasmTerm1.write('\u{1F468}\u200D\u{1F469}\u200D\u{1F467}'); // 👨‍👩‍👧 ZWJ family
wasmTerm1.write('\u{1F1FA}\u{1F1F8}'); // 🇺🇸 regional indicator pair
wasmTerm1.write('\u{1F44B}\u{1F3FD}'); // 👋🏽 wave + skin tone modifier
wasmTerm1.write('\u{1F468}\u200D\u{1F469}\u200D\u{1F467}'); // 👨‍👩‍👧 ZWJ family

// Free the terminal that processed grapheme clusters
wasmTerm1.free();
Expand Down
1 change: 0 additions & 1 deletion lib/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,6 @@ export class Terminal implements ITerminalCore {
// row iterator immediately after open() and rely on the second
// update() / clearDirty pair to settle WASM state.
this.renderTick();

} catch (error) {
// Clean up on error
this.isOpen = false;
Expand Down
9 changes: 2 additions & 7 deletions lib/url-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ interface MockLine {
* indices map 1:1 onto the cell array (matching the real BufferLine).
*/
function createMockTerminal(lines: MockLine[] | string, cols = 80) {
const rows: MockLine[] =
typeof lines === 'string' ? [{ text: lines, isWrapped: false }] : lines;
const rows: MockLine[] = typeof lines === 'string' ? [{ text: lines, isWrapped: false }] : lines;

function makeBufferLine(row: MockLine) {
const chars = Array.from(row.text);
Expand Down Expand Up @@ -69,11 +68,7 @@ function getLinks(lineText: string): Promise<ILink[] | undefined> {
/**
* Helper to get links from a multi-row terminal at a specific row.
*/
function getLinksAt(
rows: MockLine[],
y: number,
cols: number,
): Promise<ILink[] | undefined> {
function getLinksAt(rows: MockLine[], y: number, cols: number): Promise<ILink[] | undefined> {
// biome-ignore lint/suspicious/noExplicitAny: matches existing test pattern
const terminal = createMockTerminal(rows, cols) as any;
const provider = new UrlRegexProvider(terminal);
Expand Down