Skip to content

Commit ecb0fe4

Browse files
committed
fixed double line issue
1 parent 478dc11 commit ecb0fe4

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

lib/input-handler.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ interface MockClipboardEvent {
3333
interface MockHTMLElement {
3434
addEventListener: (event: string, handler: (e: any) => void) => void;
3535
removeEventListener: (event: string, handler: (e: any) => void) => void;
36+
childNodes: Node[];
37+
removeChild: (node: Node) => Node;
38+
appendChild: (node: Node) => Node;
3639
}
3740

3841
// Helper to create mock keyboard event
@@ -125,6 +128,19 @@ function createMockContainer(): MockHTMLElement & {
125128
handler(event);
126129
}
127130
},
131+
// Mock childNodes and removeChild for text node cleanup test
132+
childNodes: [] as Node[],
133+
removeChild(node: Node) {
134+
const index = this.childNodes.indexOf(node);
135+
if (index >= 0) {
136+
this.childNodes.splice(index, 1);
137+
}
138+
return node;
139+
},
140+
appendChild(node: Node) {
141+
this.childNodes.push(node);
142+
return node;
143+
}
128144
};
129145
}
130146

@@ -353,6 +369,36 @@ describe('InputHandler', () => {
353369
container.dispatchEvent(createCompositionEvent('compositionend', 'a'));
354370
expect(dataReceived).toEqual(['a']);
355371
});
372+
373+
test('cleans up text nodes in container after composition', () => {
374+
const handler = new InputHandler(
375+
ghostty,
376+
container as any,
377+
(data) => dataReceived.push(data),
378+
() => {
379+
bellCalled = true;
380+
}
381+
);
382+
383+
// Simulate browser inserting text node during composition
384+
const textNode = { nodeType: 3, textContent: '你好' } as Node;
385+
container.appendChild(textNode);
386+
387+
// Also add a non-text node (e.g. canvas) to ensure it's not removed
388+
const elementNode = { nodeType: 1, nodeName: 'CANVAS' } as Node;
389+
container.appendChild(elementNode);
390+
391+
expect(container.childNodes.length).toBe(2);
392+
393+
// End composition
394+
const endEvent = createCompositionEvent('compositionend', '你好');
395+
container.dispatchEvent(endEvent);
396+
397+
// Should have removed the text node but kept the element node
398+
expect(container.childNodes.length).toBe(1);
399+
expect(container.childNodes[0]).toBe(elementNode);
400+
expect(dataReceived).toEqual(['你好']);
401+
});
356402
});
357403

358404
describe('Control Characters', () => {

lib/input-handler.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,19 @@ export class InputHandler {
550550
if (data && data.length > 0) {
551551
this.onDataCallback(data);
552552
}
553+
554+
// Cleanup text nodes in container (fix for duplicate text display)
555+
// When the container is contenteditable, the browser might insert text nodes
556+
// upon composition end. We need to remove them to prevent duplicate display.
557+
if (this.container && this.container.childNodes) {
558+
for (let i = this.container.childNodes.length - 1; i >= 0; i--) {
559+
const node = this.container.childNodes[i];
560+
// Node.TEXT_NODE === 3
561+
if (node.nodeType === 3) {
562+
this.container.removeChild(node);
563+
}
564+
}
565+
}
553566
}
554567

555568
/**

0 commit comments

Comments
 (0)