Kaynağa Gözat

fix(app): terminal flakiness

Adam 1 ay önce
ebeveyn
işleme
cdbb009ab0

+ 47 - 0
packages/app/src/addons/serialize.test.ts

@@ -242,6 +242,53 @@ describe("SerializeAddon", () => {
       expect(term2.buffer.active.getLine(2)?.translateToString(true)).toBe("total 42")
     })
 
+    test("serialized output should restore after Terminal.reset()", async () => {
+      const { term, addon } = createTerminal()
+
+      const content = [
+        "\x1b[1;32m❯\x1b[0m \x1b[34mcd\x1b[0m /some/path",
+        "\x1b[1;32m❯\x1b[0m \x1b[34mls\x1b[0m -la",
+        "total 42",
+      ].join("\r\n")
+
+      await writeAndWait(term, content)
+
+      const serialized = addon.serialize()
+
+      const { term: term2 } = createTerminal()
+      terminals.push(term2)
+      term2.reset()
+      await writeAndWait(term2, serialized)
+
+      expect(term2.buffer.active.getLine(0)?.translateToString(true)).toContain("cd /some/path")
+      expect(term2.buffer.active.getLine(1)?.translateToString(true)).toContain("ls -la")
+      expect(term2.buffer.active.getLine(2)?.translateToString(true)).toBe("total 42")
+    })
+
+    test("alternate buffer should round-trip without garbage", async () => {
+      const { term, addon } = createTerminal(20, 5)
+
+      await writeAndWait(term, "normal\r\n")
+      await writeAndWait(term, "\x1b[?1049h\x1b[HALT")
+
+      expect(term.buffer.active.type).toBe("alternate")
+
+      const serialized = addon.serialize()
+
+      const { term: term2 } = createTerminal(20, 5)
+      terminals.push(term2)
+      await writeAndWait(term2, serialized)
+
+      expect(term2.buffer.active.type).toBe("alternate")
+
+      const line = term2.buffer.active.getLine(0)
+      expect(line?.translateToString(true)).toBe("ALT")
+
+      // Ensure a cell beyond content isn't garbage
+      const cellCode = line?.getCell(10)?.getCode()
+      expect(cellCode === 0 || cellCode === 32).toBe(true)
+    })
+
     test("serialized output written to new terminal should match original colors", async () => {
       const { term, addon } = createTerminal(40, 5)
 

+ 19 - 23
packages/app/src/addons/serialize.ts

@@ -157,23 +157,6 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
 abstract class BaseSerializeHandler {
   constructor(protected readonly _buffer: IBuffer) {}
 
-  private _isRealContent(codepoint: number): boolean {
-    if (codepoint === 0) return false
-    if (codepoint >= 0xf000) return false
-    return true
-  }
-
-  private _findLastContentColumn(line: IBufferLine): number {
-    let lastContent = -1
-    for (let col = 0; col < line.length; col++) {
-      const cell = line.getCell(col)
-      if (cell && this._isRealContent(cell.getCode())) {
-        lastContent = col
-      }
-    }
-    return lastContent + 1
-  }
-
   public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
     let oldCell = this._buffer.getNullCell()
 
@@ -182,14 +165,14 @@ abstract class BaseSerializeHandler {
     const startColumn = range.start.x
     const endColumn = range.end.x
 
-    this._beforeSerialize(endRow - startRow, startRow, endRow)
+    this._beforeSerialize(endRow - startRow + 1, startRow, endRow)
 
     for (let row = startRow; row <= endRow; row++) {
       const line = this._buffer.getLine(row)
       if (line) {
         const startLineColumn = row === range.start.y ? startColumn : 0
-        const maxColumn = row === range.end.y ? endColumn : this._findLastContentColumn(line)
-        const endLineColumn = Math.min(maxColumn, line.length)
+        const endLineColumn = Math.min(endColumn, line.length)
+
         for (let col = startLineColumn; col < endLineColumn; col++) {
           const c = line.getCell(col)
           if (!c) {
@@ -243,6 +226,13 @@ class StringSerializeHandler extends BaseSerializeHandler {
 
   protected _beforeSerialize(rows: number, start: number, _end: number): void {
     this._allRows = new Array<string>(rows)
+    this._allRowSeparators = new Array<string>(rows)
+    this._rowIndex = 0
+
+    this._currentRow = ""
+    this._nullCellCount = 0
+    this._cursorStyle = this._buffer.getNullCell()
+
     this._lastContentCursorRow = start
     this._lastCursorRow = start
     this._firstRow = start
@@ -251,6 +241,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
   protected _rowEnd(row: number, isLastRow: boolean): void {
     let rowSeparator = ""
 
+    if (this._nullCellCount > 0) {
+      this._currentRow += " ".repeat(this._nullCellCount)
+      this._nullCellCount = 0
+    }
+
     if (!isLastRow) {
       const nextLine = this._buffer.getLine(row + 1)
 
@@ -388,7 +383,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
     }
 
     const codepoint = cell.getCode()
-    const isGarbage = codepoint >= 0xf000
+    const isInvalidCodepoint = codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)
+    const isGarbage = isInvalidCodepoint || (codepoint >= 0xf000 && cell.getWidth() === 1)
     const isEmptyCell = codepoint === 0 || cell.getChars() === "" || isGarbage
 
     const sgrSeq = this._diffStyle(cell, this._cursorStyle)
@@ -397,7 +393,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
 
     if (styleChanged) {
       if (this._nullCellCount > 0) {
-        this._currentRow += `\u001b[${this._nullCellCount}C`
+        this._currentRow += " ".repeat(this._nullCellCount)
         this._nullCellCount = 0
       }
 
@@ -417,7 +413,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
       this._nullCellCount += cell.getWidth()
     } else {
       if (this._nullCellCount > 0) {
-        this._currentRow += `\u001b[${this._nullCellCount}C`
+        this._currentRow += " ".repeat(this._nullCellCount)
         this._nullCellCount = 0
       }
 

+ 6 - 5
packages/app/src/components/terminal.tsx

@@ -146,11 +146,12 @@ export const Terminal = (props: TerminalProps) => {
         term.resize(local.pty.cols, local.pty.rows)
       }
       term.reset()
-      term.write(local.pty.buffer)
-      if (local.pty.scrollY) {
-        term.scrollToLine(local.pty.scrollY)
-      }
-      fitAddon.fit()
+      term.write(local.pty.buffer, () => {
+        if (local.pty.scrollY) {
+          term.scrollToLine(local.pty.scrollY)
+        }
+        fitAddon.fit()
+      })
     }
 
     fitAddon.observeResize()