Lexical 에디터 오픈소스 기여 과정에서 발견한 텍스트 대치 시 포맷 적용 문제와 이를 해결한 과정을 공유합니다.
개요
Lexical은 Meta에서 만든 텍스트 에디터 프레임워크입니다.
텍스트를 전체 선택한 후 서식이 있는 새로운 텍스트를 입력할 때,
첫 글자에만 서식이 적용되고 나머지 글자에는 서식이 적용되지 않는 버그를 해결했습니다.
문제 상황
이슈에서 보고된 재현 과정은 다음과 같습니다.
- "hello world" 입력
- "hello" 문자열에 포맷(Bold 등) 적용
- 모든 문자 선택
- "hello" 다시 입력
결과: 입력된 결과에서 첫 글자인 'h'에는 포맷이 유지되지만, 이어지는 'ello'에는 포맷이 적용되지 않고 새로운 노드로 생성되는 현상이 발생했습니다.
원인 분석: InsertText 함수
Text를 삽입하는 insertText 함수에서 다중 노드(Multi-node)를 처리하는 로직에 문제가 있었습니다.
사용자가 텍스트를 선택하고 입력을 시작하면, Lexical은 선택된 영역을 처리하고 새로운 텍스트를 삽입합니다.
분석 결과, 다중 노드가 선택된 상태에서 텍스트를 입력할 때 첫 번째 노드에 텍스트를 삽입하는 과정에서 문제가 발생했습니다.
기존 로직은 다중 노드의 텍스트를 병합한 후, 새 텍스트를 삽입할 때 첫 문자를 입력 후 첫 번째 노드의 포맷으로 업데이트하지 않았습니다.
이로 인해 첫 글자('h')가 입력된 직후, 포맷 정보가 초기화되어 이어지는 문자열('ello')이 입력될 때는 포맷이 없는 새로운 노드로 분리되어 생성된 것입니다.
해결 방법
다중 노드 선택 상태에서 텍스트를 삽입할 때, 첫 번째 노드에 첫 글자를 삽입 후 포맷과 스타일을 첫 번째 노드로 동기화하는 로직을 추가했습니다.
// ... (생략) ...
if (!$isTokenOrTab(firstNode)) {
firstNode = firstNode.spliceText(
startOffset,
firstNodeTextLength - startOffset,
text,
true,
);
if (firstNode.getTextContent() === "") {
firstNode.remove();
} else if (this.anchor.type === "text") {
if (firstNode.isComposing()) {
// When composing, we need to adjust the anchor offset so that
// we correctly replace that right range.
this.anchor.offset -= text.length;
} else {
// (수정) 포맷과 스타일을 firstNode와 동기화
this.format = firstNode.getFormat();
this.style = firstNode.getStyle();
}
}
}
// ... (생략) ...
이렇게 수정하면 'h'가 입력된 후에도 에디터가 같은 포맷이라는 것을 인지하게 되어, 뒤이어 입력되는 'ello'도 동일한 노드 내에 같은 포맷으로 자연스럽게 입력됩니다.
느낀점
insertText 함수가 꽤 길고 복잡해서 전체 흐름을 이해하는 데 노력이 필요했습니다.
현재 발생하는 이슈를 디버깅하며 각 노드를 처리하는 부분을 탐색한 끝에 원인을 찾을 수 있었습니다.
Lexical 에디터의 기본적인 동작과 관련된 버그를 고칠 수 있었습니다.