"Live Preview 는 decoration 엔진의 첫 번째 역할이야. 소스 위에 모습을 렌더하되 커서가 놓인 곳에선 raw 마크업을 드러내지."
역할 하나 — 소스 위에 모습을 렌더
Live Preview 는 세 역할 중에 제일 눈에 띄면서 메커니즘은 제일 단순해. 마크다운 문법을 훑으면서 각 구성 요소에 그 모습을 렌더할 mark decoration 을 붙이는 게 전부야. **fire** 에는 bold 로 렌더하는 mark 가 붙고, # 헤딩에는 텍스트를 키우고 해시를 흐리게 하는 mark 가 붙어. 뭘 넣지도 지우지도 않아. 소스는 네가 친 마크다운 그대로고 그 위에 렌더된 겉옷만 걸친 셈이야.
커서 줄만 드러내기
이걸 살아 있게 느끼게 만드는 동작이 커서 줄만 드러내기야. 커서가 없는 줄에선 마크업이 렌더돼. bold 는 bold 로 보이고 # 는 흐려지지. 커서가 놓인 줄에선 raw 마크업이 돌아와서 정확하게 편집할 수 있고. 구현은 단순해. decoration 집합을 다시 계산할 때 커서가 있는 줄만 '렌더' 대상에서 빼면 돼. 그러면 깨끗한 문서를 읽으면서 정확한 소스를 편집하고, 그 경계가 네 시선을 따라다녀.
function buildLivePreview(state) {
const cursorLine = state.doc.lineAt(state.selection.main.head).number;
const marks = [];
for (const node of parseMarkdown(state.doc)) {
if (node.line === cursorLine) continue; // 커서 줄에선 raw 마크업 드러냄
marks.push(renderMark(node)); // 나머지 줄에선 모습 렌더
}
return Decoration.set(marks, true);
}
전부 mark, 문서 변경은 없음
Live Preview 를 이루는 조각은 전부 손대지 않은 소스 위의 mark decoration 이야. 그래서 다른 모든 것과 나란히 있을 수 있어. 꺼버리면 문서는 바이트까지 똑같은 마크다운이고, 켜면 모델이 아니라 레이어가 하나 얹힌 거지. 다음 두 역할이 같은 엔진을 쓰는 이유도 여기 있어. voice underline 도 CMD+K 도 '범위를 계산하고 decoration 을 붙인다' 는 같은 모양이거든. 범위가 다르고, CMD+K 만 mark 대신 widget 을 쓸 뿐이야. 첫 번째 역할은 soul 레이어 전체가 올라탈 메커니즘을 부담 없이 소개하는 자리인 셈이야.