feat: add direct PTY grid with expand/select mode for text copying
Replace tmux-based grid with direct PTY rendering. Add [MAX] button to expand a pane to fullscreen and [SEL] to enable native text selection within the expanded pane. Esc exits select/expanded mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
74
src/components/direct-pane.ts
Normal file
74
src/components/direct-pane.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
// Lightweight terminal pane: writes raw ANSI from PTY capture directly to stdout.
|
||||
// No parsing, no FrameBuffer, no OpenTUI. Just cursor-addressed raw lines.
|
||||
|
||||
import { onFrame, hasChanged, resetHash, type CaptureResult } from "../pty/capture"
|
||||
|
||||
export class DirectPane {
|
||||
screenX: number // 1-based screen column of content area
|
||||
screenY: number // 1-based screen row of content area
|
||||
width: number // content columns
|
||||
height: number // content rows
|
||||
sessionName = ""
|
||||
|
||||
private unsub: (() => void) | null = null
|
||||
|
||||
// Set by DirectGridRenderer to receive frame updates
|
||||
onFrame: ((lines: string[], pane: DirectPane) => void) | null = null
|
||||
|
||||
constructor(x: number, y: number, w: number, h: number) {
|
||||
this.screenX = x
|
||||
this.screenY = y
|
||||
this.width = w
|
||||
this.height = h
|
||||
}
|
||||
|
||||
attach(sessionName: string) {
|
||||
this.detach()
|
||||
this.sessionName = sessionName
|
||||
resetHash(`dp_${sessionName}`)
|
||||
this.unsub = onFrame(sessionName, (result) => {
|
||||
if (!hasChanged(result.lines, `dp_${sessionName}`)) return
|
||||
if (this.onFrame) this.onFrame(result.lines, this)
|
||||
})
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (this.unsub) { this.unsub(); this.unsub = null }
|
||||
if (this.sessionName) resetHash(`dp_${this.sessionName}`)
|
||||
this.sessionName = ""
|
||||
this.onFrame = null
|
||||
}
|
||||
|
||||
reposition(x: number, y: number, w: number, h: number) {
|
||||
this.screenX = x
|
||||
this.screenY = y
|
||||
this.width = w
|
||||
this.height = h
|
||||
}
|
||||
|
||||
// Build cursor-addressed ANSI output for this pane's content.
|
||||
// Returns a string ready for stdout.write(). No allocations beyond the string.
|
||||
buildFrame(lines: string[]): string {
|
||||
let out = ""
|
||||
const x = this.screenX
|
||||
const y = this.screenY
|
||||
const w = this.width
|
||||
const h = this.height
|
||||
|
||||
for (let row = 0; row < h; row++) {
|
||||
// Position cursor at start of this row
|
||||
out += `\x1b[${y + row};${x}H`
|
||||
// Erase w characters (clears old content)
|
||||
out += `\x1b[${w}X`
|
||||
|
||||
if (row < lines.length) {
|
||||
// Write raw ANSI line from PTY (already correct width)
|
||||
out += lines[row]
|
||||
// Reset SGR to prevent color bleed into border
|
||||
out += "\x1b[0m"
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user