chenbhao commited on
Commit
70a8e01
·
1 Parent(s): b752dc9

raw two image

Browse files
src/ink/log-update.ts CHANGED
@@ -302,22 +302,10 @@ export class LogUpdate {
302
  let currentStyleId = stylePool.none
303
  let currentHyperlink: Hyperlink = undefined
304
 
305
- // Emit per-row raw writes (APC, Kitty protocol) before cell diff loop.
306
- // Track which rows have been emitted to avoid duplicates across damage regions.
307
- const emittedRawRows = new Set<number>()
308
-
309
  // First pass: render changes to existing rows (rows < prev.screen.height)
310
  let needsFullReset = false
311
  let resetTriggerY = -1
312
  diffEach(prev.screen, next.screen, (x, y, removed, added) => {
313
- // Emit raw write (e.g. Kitty APC) for this row if not yet emitted
314
- if (!emittedRawRows.has(y)) {
315
- emittedRawRows.add(y)
316
- const rawWrite = next.screen.rawWritesAtRow.get(y)
317
- if (rawWrite) {
318
- screen.diff.push({ type: 'stdout', content: rawWrite })
319
- }
320
- }
321
  // Skip new rows - we'll render them directly after
322
  if (growing && y >= prev.screen.height) {
323
  return
@@ -412,14 +400,8 @@ export class LogUpdate {
412
  undefined,
413
  )
414
 
415
- // Handle growth: emit raw writes for new rows, then render cells
416
  if (growing) {
417
- for (let y = prev.screen.height; y < next.screen.height; y++) {
418
- const rawWrite = next.screen.rawWritesAtRow.get(y)
419
- if (rawWrite) {
420
- screen.diff.push({ type: 'stdout', content: rawWrite })
421
- }
422
- }
423
  renderFrameSlice(
424
  screen,
425
  next,
@@ -429,6 +411,20 @@ export class LogUpdate {
429
  )
430
  }
431
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  // Restore cursor. Skipped in alt-screen: the cursor is hidden, its
433
  // position only matters as the starting point for the NEXT frame's
434
  // relative moves, and in alt-screen the next frame always begins with
@@ -536,6 +532,12 @@ function renderFrame(
536
  stylePool: StylePool,
537
  ): void {
538
  renderFrameSlice(screen, frame, 0, frame.screen.height, stylePool)
 
 
 
 
 
 
539
  }
540
 
541
  /**
@@ -576,6 +578,7 @@ function renderFrameSlice(
576
  return [patches, { dx: -prev.x, dy: rowsToAdvance }]
577
  })
578
  }
 
579
  // Reset at start of each line — no cell rendered yet
580
  lastRenderedStyleId = -1
581
 
 
302
  let currentStyleId = stylePool.none
303
  let currentHyperlink: Hyperlink = undefined
304
 
 
 
 
 
305
  // First pass: render changes to existing rows (rows < prev.screen.height)
306
  let needsFullReset = false
307
  let resetTriggerY = -1
308
  diffEach(prev.screen, next.screen, (x, y, removed, added) => {
 
 
 
 
 
 
 
 
309
  // Skip new rows - we'll render them directly after
310
  if (growing && y >= prev.screen.height) {
311
  return
 
400
  undefined,
401
  )
402
 
403
+ // Handle growth: render new rows directly (they naturally scroll the terminal)
404
  if (growing) {
 
 
 
 
 
 
405
  renderFrameSlice(
406
  screen,
407
  next,
 
411
  )
412
  }
413
 
414
+ // Emit raw writes (APC/DCS) at viewport-adjusted positions so native
415
+ // images appear at the correct scroll position every frame, even when
416
+ // the diff loop skips unchanged rows. The CUP is rewritten to target
417
+ // a viewport-relative row instead of an absolute screen-buffer row.
418
+ for (const [row, rawWrite] of next.screen.rawWritesAtRow) {
419
+ if (row < viewportY) continue
420
+ const vpRow = row - viewportY
421
+ const adjusted = rawWrite.replace(
422
+ /^\x1b\[(\d+);(\d+)H/,
423
+ (_, __, col) => `\x1b[${vpRow + 1};${col}H`,
424
+ )
425
+ screen.diff.push({ type: 'stdout', content: adjusted })
426
+ }
427
+
428
  // Restore cursor. Skipped in alt-screen: the cursor is hidden, its
429
  // position only matters as the starting point for the NEXT frame's
430
  // relative moves, and in alt-screen the next frame always begins with
 
532
  stylePool: StylePool,
533
  ): void {
534
  renderFrameSlice(screen, frame, 0, frame.screen.height, stylePool)
535
+ // Emit raw writes (APC/DCS) after cells so native images are drawn at
536
+ // the correct viewport position. Full reset always starts from (0,0),
537
+ // so the original CUP row (screen-buffer row + 1) is already correct.
538
+ for (const [, rawWrite] of frame.screen.rawWritesAtRow) {
539
+ screen.diff.push({ type: 'stdout', content: rawWrite })
540
+ }
541
  }
542
 
543
  /**
 
578
  return [patches, { dx: -prev.x, dy: rowsToAdvance }]
579
  })
580
  }
581
+
582
  // Reset at start of each line — no cell rendered yet
583
  lastRenderedStyleId = -1
584
 
src/tools/ImageShowTool/ImageShowTool.tsx CHANGED
@@ -139,19 +139,27 @@ export const ImageShowTool = buildTool({
139
  }
140
 
141
  // Block-mode placeholder rendered in-band via RawAnsi so Ink knows the
142
- // image dimensions and its virtual cursor stays in sync. The Kitty protocol
143
- // APC escape sequence (when available) is prepended to the first block-mode
144
- // line; output.ts's writeLineToScreen passes APC through to the screen
145
- // buffer, so the terminal receives the native image at this position.
 
146
  if (content.timgOutput) {
147
  const cleaned = content.timgOutput.replace(/\x1b\[\?25[hl]/g, '')
148
- const lines = cleaned.split('\n').filter(l => l.length > 0)
149
  if (lines.length > 0) {
150
- const ansiStrip = (s: string) => s.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
151
- const width = Math.max(...lines.map(l => ansiStrip(l).length))
152
  if (content.kittyOutput) {
 
 
 
 
 
 
153
  lines[0] = content.kittyOutput + lines[0]
 
154
  }
 
 
155
  return <RawAnsi lines={lines} width={width} />
156
  }
157
  }
 
139
  }
140
 
141
  // Block-mode placeholder rendered in-band via RawAnsi so Ink knows the
142
+ // image dimensions and its virtual cursor stays in sync. When Kitty
143
+ // protocol is available, visible block characters are replaced with
144
+ // spaces so the native image shows through transparently, while the
145
+ // line structure (row count, per-row visual width) is preserved for
146
+ // Ink DOM cursor tracking.
147
  if (content.timgOutput) {
148
  const cleaned = content.timgOutput.replace(/\x1b\[\?25[hl]/g, '')
149
+ let lines = cleaned.split('\n').filter(l => l.length > 0)
150
  if (lines.length > 0) {
 
 
151
  if (content.kittyOutput) {
152
+ // Strip ANSI SGR codes and replace non-space visible chars with
153
+ // spaces. The native Kitty image will show through these spaces.
154
+ lines = lines.map(l =>
155
+ l.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '').replace(/[^\s]/g, ' '),
156
+ )
157
+ const width = Math.max(...lines.map(l => l.length))
158
  lines[0] = content.kittyOutput + lines[0]
159
+ return <RawAnsi lines={lines} width={width} />
160
  }
161
+ const ansiStrip = (s: string) => s.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
162
+ const width = Math.max(...lines.map(l => ansiStrip(l).length))
163
  return <RawAnsi lines={lines} width={width} />
164
  }
165
  }