본문 바로가기
C.W.K.
Stream
Lesson 01 of 05 · published

decoration 메커니즘

~12 min · decoration, mark, widget, cm6

Level 0식은 초고
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"decoration 은 소스를 안 바꾸고 그 위에 칠하기만 해. 그 성질 하나 덕에 메커니즘 하나로 전부 해낼 수 있어."

두 종류의 decoration

CodeMirror 6 이 소스 위에 시각 레이어를 얹는 방법은 딱 두 가지야. mark decoration 은 텍스트 범위에 스타일을 붙여. 4번부터 8번 문자까지 bold, 12번부터 16번까지 빨간 밑줄, 이런 식이지. widget decoration 은 특정 위치에 UI 조각을 끼워 넣어. 단어 뒤에 붙는 여백 노트나 줄 아래 통째로 들어가는 제안 블록 같은 것들. mark 는 이미 있는 걸 꾸미고, widget 은 없던 걸 집어넣어. 이 둘이면 기능이 원할 법한 '뭔가 더 보여주기' 는 거의 다 커버돼.

소스 위에 칠할 뿐, 절대 건드리지 않아

decoration 을 강력하게 만드는 건 그게 안 하는 일이야. 문서를 절대 안 바꾸거든. bold mark 가 텍스트에 서식 문자를 집어넣는 게 아니라, 이미 거기 있는 문자를 bold 로 렌더할 뿐이야. widget 도 파일에 뭘 쓰지 않고 화면의 한 지점에 UI 를 칠할 뿐이고. 소스 문자열은 그대로 남아. decoration 은 편집이 있을 때마다 문서에서 DecorationSet 을 다시 계산하는 StateField 안에 살아.

const decoField = StateField.define<DecorationSet>({
  create: () => Decoration.none,
  update: (deco, tr) => buildDecorations(tr.state), // 범위 -> mark + widget
  provide: (f) => EditorView.decorations.from(f),
});

원시 요소 하나로 '더 보여주기' 기능 전부

요령은 전부 여기 있어. decoration 이 모델을 안 건드리고 레이어만 얹으니까, '사용자 텍스트 위에 뭔가 더 보여주기' 로 표현되는 기능이라면 무엇이든 같은 원시 요소로 정리돼. 범위 집합을 계산하고, mark 나 widget 을 붙이고, 렌더는 CM6 한테 맡기면 끝이야. Live Preview 는 범위를 mark 로 바꾸고, voice underline 은 범위를 mark 와 widget 으로 바꾸고, CMD+K 는 제안을 담은 widget 을 만들어. 겉으로는 상관없어 보이는 세 기능이 밑에서는 입력만 다른 같은 계산인 거지. 이 트랙 전체가 여기서 자라 나와.

Code

mark 는 범위를 꾸미고 widget 은 UI 를 끼워 넣어. 둘 다 소스 위에서·typescript
import { Decoration, DecorationSet, EditorView, WidgetType } from "@codemirror/view";
import { StateField } from "@codemirror/state";

// MARK: 이미 있는 소스의 범위를 꾸며 (**bold** 를 bold 로 렌더).
const boldMark = Decoration.mark({ class: "cm-strong" });

// WIDGET: 텍스트를 안 바꾸고 특정 위치에 UI 를 끼워 넣어.
class MarginNote extends WidgetType {
  constructor(readonly text: string) { super(); }
  toDOM() {
    const s = document.createElement("span");
    s.className = "cm-margin-note";
    s.textContent = this.text;
    return s;
  }
}

// StateField 가 현재 DecorationSet 을 쥐고 편집마다 다시 계산해.
const decoField = StateField.define<DecorationSet>({
  create: () => Decoration.none,
  update: (deco, tr) => buildDecorations(tr.state), // 범위 -> mark + widget
  provide: (f) => EditorView.decorations.from(f),
});
// 문서는 절대 안 바뀌어. 칠해놓은 레이어만 바뀔 뿐이야.

External links

Exercise

Rekindle 의 soul 기능 셋이 각각 주로 mark 를 쓰는지 widget 을 쓰는지 정해봐. 하나, **bold** 를 bold 로 렌더하기. 둘, voice 이슈에 밑줄을 긋고 옆에 노트 띄우기. 셋, 수락 전에 선택한 줄 밑으로 AI 재작성 제안 보여주기. 그다음 왜 셋 다 문서를 건드릴 필요가 없는지 한 문장으로 말해봐.
Hint
렌더와 밑줄은 이미 있는 문자를 꾸미는 거라 mark 야. 밑줄 옆의 노트만 작은 widget 이고. 제안 블록은 특정 위치에 새로 끼워 넣는 UI 니까 widget 이지. 셋 다 문서를 안 건드리는 이유는 decoration 이 소스 위에 칠한 뷰 레이어일 뿐 소스에 대한 편집이 아니기 때문이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.