Copy Korean terminal text without extra spaces (#7254)

* fix(terminal): preserve Korean text when copying native terminal cells

* fix(terminal): omit continuation cells from copied text

Native terminal buffers already retain authoritative cell widths. Skip zero-width continuation cells when serializing a selection so Korean text copies with only its real spaces, while preserving the buffer representation.

Co-authored-by: helloimdevman <333048235+helloimdevman@users.noreply.github.com>

---------

Co-authored-by: helloimdevman <333048235+helloimdevman@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-17 22:57:04 +02:00
committed by GitHub
co-authored by helloimdevman
parent e2ad26d238
commit 9bd58f66b2
2 changed files with 49 additions and 1 deletions
@@ -41,6 +41,51 @@ function rowWithText(
}
describe("native terminal selection", () => {
it("copies Korean output without adding spaces for continuation cells", async () => {
const terminal = createNativeHeadlessTerminal({ rows: 3, cols: 30 });
const copied: string[] = [];
try {
await terminal.write("한글 문장");
const bounds = terminal.getBufferBounds();
await copyTerminalSelection({
terminal,
selection: {
start: { row: bounds.cursorRow, col: 0 },
end: { row: bounds.cursorRow, col: bounds.cursorCol - 1 },
coordinateEpoch: bounds.coordinateEpoch,
},
clipboard: {
writeText: async (text) => {
copied.push(text);
},
},
});
expect(copied).toEqual(["한글 문장"]);
} finally {
terminal.dispose();
}
});
it("copies Korean text and real spaces without wide-character continuation spaces", async () => {
const terminal = createNativeHeadlessTerminal({ rows: 3, cols: 30 });
try {
await terminal.write("한글 문장 | 성공");
const bounds = terminal.getBufferBounds();
expect(
extractTerminalSelectedText({
terminal,
selection: {
start: { row: bounds.cursorRow, col: 0 },
end: { row: bounds.cursorRow, col: bounds.cursorCol - 1 },
coordinateEpoch: bounds.coordinateEpoch,
},
}),
).toBe("한글 문장 | 성공");
} finally {
terminal.dispose();
}
});
it("expands a long-press coordinate to the whole terminal word", async () => {
const terminal = createNativeHeadlessTerminal({ rows: 4, cols: 32, scrollbackLines: 20 });
await terminal.write("before PASEO_TARGET after\r\n");
@@ -327,7 +327,10 @@ export function extractTerminalSelectedText(input: TerminalSelectedTextInput): s
const startCol = startsOnThisRow ? selection.start.col : 0;
const endCol = endsOnThisRow ? selection.end.col : cells.length - 1;
const selectedCells = cells.slice(Math.max(0, startCol), Math.max(0, endCol) + 1);
const line = selectedCells.map((cell) => cell.char || " ").join("");
const line = selectedCells
.filter((cell) => cell.width !== 0)
.map((cell) => cell.char)
.join("");
if (offset > 0 && window.wrappedRows[offset] !== true) {
text += "\n";
}