분석 대상: zed-industries/zed · ~200 crates · Cargo.toml 의존성 + 실제 소스 확인

Zed 내부 구조 도식화

Zed는 약 200개의 Rust crate로 구성된 대형 monorepo다. 핵심은 자체 제작 UI 프레임워크 GPUI, CRDT 기반 텍스트 엔진 text, 그리고 이를 조율하는 Project / Workspace / Editor 레이어다. 아래 다이어그램은 실제 Cargo.toml 의존성과 소스 코드를 확인해서 작성됐다.

레이어 구조

6단계 의존성 아키텍처 · Layer 0 (플랫폼) → Layer 5 (바이너리)

Zed의 crate들은 6개 레이어로 나뉜다. 의존 방향은 항상 위 → 아래다. 상위 레이어는 하위 레이어를 알지만, 하위 레이어는 상위 레이어를 모른다. 덕분에 하위 레이어는 독립적으로 테스트 가능하고, 플랫폼 백엔드만 교체해서 macOS/Linux/Windows를 지원할 수 있다.

Layer 5 — 실행 바이너리 zed (에디터 실행파일) collab (협업 서버 · axum + PostgreSQL) 의존 Layer 4 — Feature Crates (기능 단위 UI) workspace 탭 / Pane 분할 editor 텍스트 편집 UI terminal 터미널 에뮬레이터 agent_ui AI 어시스턴트 패널 + search, git, debugger 80여 개 추가 feature crate Layer 3 — Application Core (앱 로직 중추) project multi_buffer client settings · theme · language_model Layer 2 — Core Primitives (핵심 라이브러리) text CRDT 텍스트 버퍼 language tree-sitter · 문법 lsp JSON-RPC rpc protobuf gpui UI 프레임워크 fs · rope 파일 · 텍스트 Layer 1 — Foundation (의존 없는 순수 유틸리티) sum_tree clock collections gpui_util shared_string · util Layer 0 — Platform Backends (GPU · OS 별 구현체) gpui의 Platform / PlatformRenderer / PlatformTextSystem trait를 각 OS에서 구현 gpui_macos Metal · AppKit · CoreText gpui_linux wgpu · Wayland/X11 · cosmic-text gpui_windows DirectX · Win32 · DirectWrite gpui_wgpu Vulkan · WebGPU · WGSL
핵심 crate (sum_tree, text, gpui)
플랫폼 구현체 (교체 가능)
일반 crate
레이어별 역할 요약
  • Layer 5 Binary: 모든 crate를 조립해 실제 실행파일을 만드는 최상위. zedcollab(서버) 두 바이너리.
  • Layer 4 Features: 사용자가 보는 기능 단위 UI — 편집기, 터미널, AI 패널, 검색, Git 등. 각 crate가 독립적이어서 기능을 추가/제거하기 쉽다.
  • Layer 3 App Core: project는 파일시스템+LSP+Git을 총괄하는 핵심 컨트롤러. multi_buffer는 여러 파일을 하나의 편집 뷰로 합치고, client는 협업 서버 연결을 관리한다.
  • Layer 2 Core Primitives: text(CRDT 버퍼), language(파싱/하이라이팅), gpui(UI 렌더링). 이 세 개가 Zed의 핵심 엔진.
  • Layer 1 Foundation: 다른 어떤 Zed crate에도 의존하지 않는 순수 유틸리티. sum_tree(B+ 트리), clock(Lamport 시계), collections(커스텀 해시맵 등).
  • Layer 0 Platform: gpui의 trait들을 각 OS/GPU API로 구현. 이 레이어만 교체하면 macOS ↔ Linux ↔ Web 포팅이 가능한 구조다.
실제 코드 — 레이어 경계는 Cargo.toml 의존성으로 강제된다

아래는 실제 editor/Cargo.toml의 의존성 일부다. editor는 Layer 4이므로 Layer 2(gpui, text)를 알지만, Layer 5 바이너리(zed)를 알 수 없다. 역방향 import는 컴파일러가 막는다.

# crates/editor/Cargo.toml (실제 의존성 발췌)
[dependencies]
gpui      = { workspace = true }   # Layer 2: UI 프레임워크
text      = { workspace = true }   # Layer 2: CRDT 텍스트
language  = { workspace = true }   # Layer 2: 파싱/하이라이팅
project   = { workspace = true }   # Layer 3: 파일시스템/LSP
lsp       = { workspace = true }   # Layer 2: LSP 클라이언트
theme     = { workspace = true }   # Layer 3: 테마/색상
sum_tree  = { workspace = true }   # Layer 1: 기반 자료구조
# ❌ zed = ... 불가 — 상위 바이너리는 절대 참조할 수 없다

Layer 1 sum_tree는 반대로 Zed 자체 crate를 하나도 의존하지 않는다:

# crates/sum_tree/Cargo.toml
[dependencies]
heapless = "0.8"          # 스택 배열
rayon    = "1"            # 병렬 이터레이터
# 👆 Zed crate 의존 없음 — 완전 독립 라이브러리

GPUI 내부

렌더 파이프라인 · Entity 모델 · Arena 할당 · Subscription RAII

GPUI는 Zed 팀이 직접 만든 UI 프레임워크다. React처럼 컴포넌트 트리를 매 프레임 재구성하되, Rust의 소유권 모델 위에서 동작한다. 핵심 차이점은 동적 디스패치(dyn Trait) 대신 함수 포인터를 쓰고, 프레임당 아레나 할당으로 수천 번의 Box::new()를 없앴다는 것이다.

App 전역 싱글턴 (Rc<AppCell>) entities: EntityMap windows: SlotMap<WindowId, Window> platform: Rc<dyn Platform> 소유 Window 네이티브 창 하나 = Window 인스턴스 하나 platform_window: Box<dyn PlatformWindow> root: Option<AnyView> ← 루트 뷰 layout_engine: TaffyLayoutEngine ← CSS Flexbox element_arena: Arena ← 프레임당 할당 풀 렌더 «trait» Element — 3단계 페인트 모든 UI 컴포넌트(div, text, button...)가 구현 ① request_layout() → Taffy에 크기 요청 ② prepaint(state) → Hitbox 등록 / 스크롤 ③ paint(state) → Scene에 드로우 명령 추가 Scene CPU → GPU 중간 표현 (한 프레임의 명령 목록) Quad, Shadow, MonochromeSprite, Path, PolychromeSprite, Underline PlatformRenderer::draw(scene) Metal / wgpu(Vulkan) / DirectX → 화면 Entity<T> id: EntityId Weak → EntityMap PhantomData<fn(T)→T> AnyView entity: AnyEntity render: fn ptr vtable 없이 fn 포인터 Executors Background: tokio 스레드풀 Foreground: 메인스레드 큐 Arena 프레임당 Element clear()로 전체 리셋
App — 진입점
Window — 창 하나
Element trait — UI 컴포넌트 인터페이스
Platform 경계 — 플랫폼별 구현
GPUI가 React와 다른 점
  • 소유권 기반 상태: React에서 state는 컴포넌트 내부에 캡슐화되지만, GPUI에서 상태는 Entity<T>로 EntityMap에 저장된다. 컴포넌트는 핸들(Entity)만 갖고, 실제 데이터는 중앙에서 관리된다.
  • fn 포인터 디스패치: React의 가상 DOM은 런타임 타입 추상화를 위해 간접 호출을 많이 쓴다. GPUI는 AnyView.render에 fn 포인터를 저장해 vtable 없이 타입별 렌더 함수를 호출한다.
  • 아레나 할당: 매 프레임 Element 트리를 재구성할 때 수천 번의 힙 할당이 발생한다. GPUI는 프레임당 아레나를 써서 clear() 한 번으로 전부 해제한다.
  • 3단계 페인트: React는 render → commit 2단계. GPUI는 레이아웃(Taffy) → prepaint(hitbox) → paint(Scene) 3단계로 나뉜다. 덕분에 레이아웃 후 실제 위치를 알고 나서야 hitbox를 등록하므로 정확한 이벤트 처리가 가능하다.
실제 코드 — Entity<T> 정의

crates/gpui/src/app/entity_map.rs에서 발췌. PhantomData<fn(T)→T>불변(invariant) 타입 파라미터를 만드는 Rust 고급 패턴이 쓰인다.

// gpui/src/app/entity_map.rs
slotmap::new_key_type! {
    pub struct EntityId;  // SlotMap 키 — 재사용 없는 고유 ID
}

/// 타입-지워진 동적 엔티티 핸들 (기반)
pub struct AnyEntity {
    pub(crate) entity_id:   EntityId,
    pub(crate) entity_type: TypeId,
    entity_map: Weak<RwLock<EntityRefCounts>>, // 약한 참조 → 해제 감지
}

/// 타입-안전 핸들. T의 실제 값은 EntityMap에 저장됨
#[derive(Deref, DerefMut)]
pub struct Entity<T> {
    #[deref]
    pub(crate) any_entity:   AnyEntity,
    pub(crate) entity_type:  PhantomData<fn(T) -> T>,
    //                                   ^^^^^^^^^^^
    // fn(T)->T 트릭: T에 대해 공변도 반변도 아닌 "불변" 분산 확보
    // → Entity<Child>를 Entity<Parent>로 coerce 불가
}

impl Clone for AnyEntity {
    fn clone(&self) -> Self {
        // 해제된 엔티티 복제 시도 → 즉시 패닉
        if let Some(map) = self.entity_map.upgrade() {
            let count = map.read().counts.get(self.entity_id).unwrap();
            let prev = count.fetch_add(1, SeqCst);
            assert_ne!(prev, 0, "Detected over-release of a entity.");
        }
        Self { entity_id: self.entity_id, entity_type: self.entity_type,
               entity_map: self.entity_map.clone(), .. }
    }
}
실제 코드 — Element trait (3단계 페인트)

crates/gpui/src/element.rs에서 발췌. 모든 GPUI UI 컴포넌트는 이 trait을 구현한다.

// gpui/src/element.rs
pub trait Element: 'static + IntoElement {
    type RequestLayoutState: 'static;  // request_layout이 반환하는 상태
    type PrepaintState:      'static;  // prepaint가 반환하는 상태

    fn id(&self) -> Option<ElementId>;

    /// 1단계: Taffy에 크기를 요청하고 초기 상태를 초기화
    fn request_layout(
        &mut self,
        id: Option<&GlobalElementId>,
        inspector_id: Option<&InspectorElementId>,
        window: &mut Window,
        cx: &mut App,
    ) -> (LayoutId, Self::RequestLayoutState);

    /// 2단계: 레이아웃이 확정된 뒤 Hitbox·스크롤 등 등록
    fn prepaint(
        &mut self,
        id: Option<&GlobalElementId>,
        inspector_id: Option<&InspectorElementId>,
        bounds: Bounds<Pixels>,          // Taffy 계산 결과
        request_layout: &mut Self::RequestLayoutState,
        window: &mut Window,
        cx: &mut App,
    ) -> Self::PrepaintState;

    /// 3단계: Scene에 드로우 명령 추가 (GPU로 갈 명령)
    fn paint(
        &mut self,
        id: Option<&GlobalElementId>,
        inspector_id: Option<&InspectorElementId>,
        bounds: Bounds<Pixels>,
        request_layout: &mut Self::RequestLayoutState,
        prepaint: &mut Self::PrepaintState,
        window: &mut Window,
        cx: &mut App,
    );
}

// 사용 예: 한 프레임에서 element_arena를 clear()로 일괄 해제
// window.element_arena.clear();  ← 수천 번 Box::new() 비용 제거
실제 코드 — Subscription (RAII Drop 패턴)

crates/gpui/src/subscription.rs에서 발췌. Subscription이 스코프를 벗어나면 Drop이 자동으로 이벤트 리스너를 해제한다. #[must_use]로 의도치 않은 즉시 drop을 컴파일 경고로 잡는다.

// gpui/src/subscription.rs
#[must_use]  // 반환값을 무시하면 즉시 drop → 경고
pub struct Subscription {
    unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
}

impl Subscription {
    /// 영구 구독 — 직접 해제할 때까지 유지
    pub fn detach(mut self) {
        self.unsubscribe.take(); // drop 시 unsubscribe 호출 안 함
    }

    /// 두 구독을 하나로 묶기
    pub fn join(mut a: Self, mut b: Self) -> Self {
        let fa = a.unsubscribe.take();
        let fb = b.unsubscribe.take();
        Self {
            unsubscribe: Some(Box::new(move || {
                if let Some(f) = fa { f(); }
                if let Some(f) = fb { f(); }
            })),
        }
    }
}

impl Drop for Subscription {
    fn drop(&mut self) {
        // 스코프 종료 시 자동으로 리스너 해제
        if let Some(unsubscribe) = self.unsubscribe.take() {
            unsubscribe();
        }
    }
}

// 사용 예:
// let _sub = cx.observe(&model, |this, cx| { ... });
// _sub이 drop되면 → observer 자동 해제

텍스트 엔진

5레이어 스택 · SumTree → Rope → Buffer(CRDT) → MultiBuffer → Editor

Zed의 텍스트 엔진은 5개 레이어 스택으로 이루어진다. 맨 아래에 고성능 B+ 트리(SumTree)가 있고, 그 위에 Unicode 텍스트(Rope), CRDT 버퍼(Buffer), 다중 파일 뷰(MultiBuffer), 최상단에 편집 UI(Editor)가 쌓인다. 각 레이어는 명확한 책임을 갖는다.

Editor (editor crate) buffer: Entity<MultiBuffer> display_map: Entity<DisplayMap> · selections · scroll MultiBuffer (multi_buffer crate) buffers: HashMap<BufferId, Buffer> 여러 파일을 하나의 가상 문서로 합침 Buffer (text crate) — CRDT 핵심 snapshot: BufferSnapshot (불변 스냅샷) history: Vec<Transaction> (undo 스택) clock: Lamport · replica_id: ReplicaId Rope (rope crate) chunks: SumTree<Chunk> 바이트 / 줄번호 / 문자 다중 인덱싱 SumTree<T: Item> (sum_tree crate) Arc 기반 공유 · 편집 시 Copy-on-Write Summary trait로 임의 집계 지원 Editor가 하는 일 커서 · 선택 · 스크롤 · 자동완성 · 인레이힌트 DisplayMap으로 화면 좌표 변환 (소프트랩 · 폴딩) MultiBuffer가 하는 일 검색 결과: 여러 파일의 일부를 이어서 보여줌 Diff 뷰: 두 버전 나란히 편집 CRDT 기반이라는 의미 모든 편집 = Operation { position, content, lamport_ts } 삭제된 텍스트도 Fragment로 보관 → undo 가능 다른 레플리카의 Operation을 병합해도 결과 동일 → 실시간 협업 · 충돌 없는 병합 Rope가 필요한 이유 String은 중간 삽입 시 O(n) 이동 필요 Rope는 SumTree로 O(log n) 삽입/삭제 수십만 줄 파일도 빠르게 편집 SumTree의 강점 Item trait: 각 노드가 Summary를 계산 → 바이트 수 / 줄 수 / 문자 수를 동시에 추적 Arc 공유로 스냅샷 복사 O(1)
핵심 — Buffer (CRDT 중추)
Foundation — SumTree
일반 레이어
실제 코드 — SumTree: Item · Summary · Dimension 트리오

crates/sum_tree/src/sum_tree.rs에서 발췌. 이 세 trait의 조합이 Zed 전체 자료구조의 핵심이다.

// sum_tree/src/sum_tree.rs

/// 트리에 저장되는 아이템. 자신의 Summary를 계산할 수 있어야 함
pub trait Item: Clone {
    type Summary: Summary;
    fn summary(&self, cx: <Self::Summary as Summary>::Context<'_>) -> Self::Summary;
}

/// 서브트리 전체의 집계값. add_summary로 누적, zero로 초기화
pub trait Summary: Clone {
    type Context<'a>: Copy;  // GAT — 집계에 필요한 컨텍스트 (빌림 가능)
    fn zero<'a>(cx: Self::Context<'a>) -> Self;
    fn add_summary<'a>(&mut self, summary: &Self, cx: Self::Context<'a>);
}

/// Summary에서 특정 차원(예: 바이트 수, 줄 수)을 추출해 탐색에 쓰는 인터페이스
pub trait Dimension<'a, S: Summary>: Clone {
    fn zero(cx: S::Context<'_>) -> Self;
    fn add_summary(&mut self, summary: &'a S, cx: S::Context<'_>);
}

/// 실제 B+ 트리 구조체 — Arc로 공유, 편집 시 CoW
#[derive(Clone)]
pub struct SumTree<T: Item>(Arc<Node<T>>);

// 구체적인 예: Rope의 Chunk
// impl Item for Chunk {
//     type Summary = TextSummary;
//     fn summary(&self, _: ()) -> TextSummary {
//         TextSummary { bytes: self.0.len(), lines: self.0.matches('\n').count(), .. }
//     }
// }
// → SumTree<Chunk>.summary().lines 로 전체 줄 수를 O(1)에 읽을 수 있다
실제 코드 — Rope와 Buffer 구조체

crates/rope/src/rope.rs, crates/text/src/text.rs에서 발췌.

// rope/src/rope.rs
#[derive(Clone, Default)]
pub struct Rope {
    chunks: SumTree<Chunk>,  // Chunk = 최대 CHUNK_BASE 바이트의 UTF-8 슬라이스
}

// text/src/text.rs
pub struct Buffer {
    snapshot: BufferSnapshot,          // 현재 상태의 불변 스냅샷
    history:  History,                 // 편집 이력 + undo/redo 스택
    deferred_ops: OperationQueue<Operation>, // 아직 처리 안 된 원격 op
    pub lamport_clock: clock::Lamport, // 이 레플리카의 논리 시계
    subscriptions: Topic<usize>,       // 변경 알림 구독자 목록
}

#[derive(Clone)]
pub struct BufferSnapshot {
    visible_text:  Rope,               // 현재 보이는 텍스트
    deleted_text:  Rope,               // 삭제된 텍스트 (undo를 위해 보관)
    fragments:     SumTree<Fragment>,  // CRDT의 핵심 — 삽입/삭제 이력
    insertions:    SumTree<InsertionFragment>,
    undo_map:      UndoMap,
    pub version:   clock::Global,      // 이 스냅샷의 벡터 시계
    replica_id:    ReplicaId,
}

// Buffer 편집의 핵심:
// 1. Lamport 타임스탬프를 부여한 Operation 생성
// 2. Rope(SumTree) 수정
// 3. Fragment 목록 갱신 (삭제된 텍스트도 보관!)
// 4. cx.notify()로 dirty flag → 다음 tick에 재렌더링

키 입력 흐름

OS 이벤트 → Keymap 매칭 → Buffer CRDT 편집 → 재렌더링 → GPU 출력

키보드 하나를 눌렀을 때 화면이 바뀌기까지 어떤 경로를 거치는지 단계별로 따라가 본다. 크게 입력 처리 → 데이터 변경 → 재렌더링 → GPU 출력 네 구간으로 나뉜다.

🖥 CPU — 입력 처리 구간
OS 키보드 이벤트 NSEvent (macOS) · XKeyEvent (X11) · wayland_client (Wayland) · Win32 WM_KEYDOWN
PlatformWindow::handle_input() 플랫폼 추상화 레이어. OS 이벤트를 GPUI의 InputEvent로 변환.
Window::dispatch_key_event() dispatch_tree에서 현재 포커스 노드까지의 경로를 탐색해 이벤트 전파 대상 결정.
Keymap 매칭 keystroke(예: Ctrl+S)를 Action(예: editor::Save)으로 변환. 컨텍스트별 바인딩 오버라이드 지원.
ActionRegistry → 등록된 핸들러 호출 포커스 경로 순서대로 핸들러를 시도. 처리하면 전파 중단, 아니면 부모로 버블링.
Editor::handle_input(char) — editor crate 실제 편집 로직 진입점. 선택 영역 삭제 후 새 문자 삽입, 자동완성 트리거 등.
MultiBuffer::edit() — multi_buffer crate 적절한 하위 Buffer에 편집 요청 위임. 여러 파일에 걸친 경우 각 Buffer에 분산.
text::Buffer::edit() — text crate ① CRDT Operation 생성 (Lamport 타임스탬프 부여) ② Rope 수정 (SumTree 편집) ③ subscriptions에 변경 알림 발행 → cx.notify() 호출로 dirty views 등록
🔄 재렌더링 구간 (다음 event loop tick)
Window::draw() dirty views가 있는 경우 다음 tick에 실행. Element 트리 rebuild → Taffy CSS Flexbox 레이아웃 계산 → prepaint(hitbox 등록) → paint(Scene에 드로우 명령 누적)
PlatformRenderer::draw(scene) — GPU 구간 macOS: Metal CommandBuffer 생성 → shadow / quad / glyph / path 배치 draw call → CAMetalLayer present
Linux: wgpu CommandEncoder → Vulkan submit → surface present
왜 재렌더가 즉시 일어나지 않는가?
cx.notify()는 즉시 화면을 그리지 않고 dirty 플래그만 세운다. 현재 이벤트 처리가 끝난 뒤 다음 event loop tick에서 dirty views를 한 번에 그린다. 이 배치 처리가 한 프레임에 여러 상태 변경이 있어도 화면을 한 번만 그리게 보장한다 — React의 setState batching과 같은 원리.
실제 코드 — dispatch_keystroke → dispatch_action_on_node

crates/gpui/src/window.rs에서 발췌. ③④⑤ 단계의 실제 구현이다.

// gpui/src/window.rs

/// ③ 키스트로크를 GPUI 이벤트로 변환해 디스패치
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke, cx: &mut App) -> bool {
    let keystroke = keystroke.with_simulated_ime();
    let result = self.dispatch_event(
        PlatformInput::KeyDown(KeyDownEvent {
            keystroke: keystroke.clone(),
            is_held: false,
            prefer_character_input: false,
        }),
        cx,
    );
    if !result.propagate {
        return true;  // 누군가 처리 → 문자 입력 단계 건너뜀
    }
    // 키맵 매칭 실패 시 문자로 직접 IME에 전달
    if let Some(input) = keystroke.key_char
        && let Some(mut handler) = self.platform_window.take_input_handler()
    {
        handler.dispatch_input(&input, self, cx);  // ⑥ Editor::handle_input
        self.platform_window.set_input_handler(handler);
        return true;
    }
    false
}

/// ⑤ 포커스 경로를 따라 Action 핸들러를 capture → bubble 순으로 시도
fn dispatch_action_on_node_inner(&mut self, node_id: DispatchNodeId,
                                  action: &dyn Action, cx: &mut App) {
    let dispatch_path = self.rendered_frame.dispatch_tree.dispatch_path(node_id);

    // 캡처 단계: 루트 → 포커스 노드 방향
    cx.propagate_event = true;
    for node_id in &dispatch_path {
        let node = self.rendered_frame.dispatch_tree.node(*node_id);
        for DispatchActionListener { action_type, listener } in node.action_listeners.clone() {
            if action_type == action.as_any().type_id() {
                listener(action.as_any(), DispatchPhase::Capture, self, cx);
                if !cx.propagate_event { return; }  // 처리 완료 → 버블링 중단
            }
        }
    }

    // 버블 단계: 포커스 노드 → 루트 방향
    for node_id in dispatch_path.iter().rev() {
        let node = self.rendered_frame.dispatch_tree.node(*node_id);
        for DispatchActionListener { action_type, listener } in node.action_listeners.clone() {
            if action_type == action.as_any().type_id() {
                listener(action.as_any(), DispatchPhase::Bubble, self, cx);
                if !cx.propagate_event { return; }
            }
        }
    }
}

핵심 Struct 참조표

crate → 타입 → 역할 → 핵심 필드 순서로 읽는다

crate 이름 → struct 이름 → 한 줄 역할 순서로 읽으면 된다. 녹색 굵은 항목은 Zed 아키텍처에서 특히 핵심적인 타입이다.

crate타입역할핵심 필드 / 특이사항
sum_tree SumTree<T> Zed 전체의 데이터 기반. 함수형 B+ 트리. Arc로 공유, 편집 시 Copy-on-Write Item trait · Summary trait · GAT Context
rope Rope Unicode 텍스트 저장. 중간 삽입 O(log n). 바이트/줄/문자 다중 인덱싱 chunks: SumTree<Chunk>
clock Lamport, ReplicaId CRDT 연산에 전역 순서를 부여하는 논리 시계 value: u64 · ReplicaId는 u16 newtype
text Buffer CRDT 텍스트 버퍼. 모든 편집이 Operation으로 기록됨. 실시간 협업 + 무한 undo 지원 snapshot, history, replica_id, clock
text BufferSnapshot Buffer의 특정 시점 불변 스냅샷. 스레드 간 공유 가능 visible_text: Rope, fragments: SumTree<Fragment>
multi_buffer MultiBuffer 여러 Buffer를 하나의 가상 문서로 합침. 검색 결과, Diff 뷰, 다중 커서 편집에 사용 buffers: HashMap<BufferId, Buffer>
lsp LanguageServer LSP 서버 프로세스를 생성하고 JSON-RPC로 통신 server_id, capabilities, stdin/stdout 채널
gpui App GPUI 전역 싱글턴. 모든 Entity, Window, Action, Platform을 소유 entities, windows, platform, executors
gpui Entity<T> 타입 안전 핸들. 실제 데이터는 EntityMap에, 핸들만 코드에서 전달됨 AnyEntity + PhantomData<fn(T)→T> (불변 분산)
gpui AnyView 동적 뷰 핸들. render 함수를 fn 포인터로 저장 (vtable 없음) entity: AnyEntity, render: fn ptr
gpui Window 네이티브 창 + CSS Flexbox 레이아웃(Taffy) + 프레임 버퍼 + 글리프 Atlas platform_window, root, layout_engine, element_arena
gpui Scene CPU→GPU 중간 표현. 한 프레임의 모든 draw 명령 목록. #[repr(C)]로 GPU 버퍼에 직접 복사 quads, shadows, paths, sprites Vec 목록
project Project 파일시스템 + LSP + Git + 빌드 태스크를 총괄하는 앱 로직 중추 worktrees, lsp_store, buffer_store, git_store
project Worktree 하나의 루트 폴더. 파일 트리를 SumTree로 관리, 파일시스템 변경사항 감시 entries: SumTree<Entry>, fs_watcher
editor Editor 텍스트 편집 UI 전체. 커서/선택/스크롤/자동완성/인레이힌트/문법 하이라이팅 buffer, display_map, selections, project
editor DisplayMap Buffer 내용을 화면 좌표로 변환. 소프트랩·폴딩·인레이힌트 반영 Buffer 오프셋 ↔ 화면 행/열 변환
workspace Workspace 에디터 창 전체의 레이아웃. Pane 분할, 탭 관리, 도크 패널 panes: Vec<Entity<Pane>>, project
client Client Zed 협업 서버와의 WebSocket 연결. RPC peer, HTTP 클라이언트 관리 peer: Arc<Peer>, http_client
실제 코드 — 3가지 핵심 구조체 전체 정의

위 표에서 가장 중요한 세 타입의 실제 소스 발췌. 주석은 역할과 설계 의도를 설명한다.

// ① sum_tree/src/sum_tree.rs
//    Zed 전체에서 리스트형 자료구조로 쓰이는 함수형 B+ 트리

pub struct SumTree<T: Item>(Arc<Node<T>>);
//                           ^^^
//                           Arc 하나만 복사하면 스냅샷 완성 — O(1)

// Node는 Internal 또는 Leaf
// Leaf  { summary, items: ArrayVec<T>, item_summaries: ArrayVec<S> }
// Internal { summary, child_summaries, child_trees: ArrayVec<SumTree<T>> }

// 편집(push/splice)은 항상 새 Arc를 만들어 CoW; 공유된 서브트리는 그대로 재사용
// ② text/src/text.rs
//    Zed 실시간 협업의 핵심 — 모든 편집이 CRDT Operation으로 기록됨

pub struct Buffer {
    snapshot:      BufferSnapshot,             // 현재 보이는 상태
    history:       History,                    // undo/redo 스택 + base_text
    deferred_ops:  OperationQueue<Operation>, // 아직 정렬 안 된 원격 편집
    pub lamport_clock: clock::Lamport,         // 전역 순서 부여용 논리 시계
    subscriptions: Topic<usize>,              // 변경 알림 구독자
    edit_id_resolvers: HashMap<clock::Lamport, Vec<oneshot::Sender<()>>>,
    wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>,
}

#[derive(Clone)]
pub struct BufferSnapshot {
    visible_text: Rope,               // 삽입 텍스트만 (실제 보이는 내용)
    deleted_text: Rope,               // 삭제된 텍스트 (undo 복원용)
    fragments:    SumTree<Fragment>,  // 삽입/삭제 이력 — CRDT 핵심
    pub version:  clock::Global,      // 이 스냅샷의 벡터 시계 (협업 병합 기준)
    replica_id:   ReplicaId,
    line_ending:  LineEnding,
}
// ③ gpui/src/app/entity_map.rs
//    EntityId는 SlotMap 키라 재발급 없음, Weak 참조로 해제 감지

pub struct EntityMap {
    entities:          SecondaryMap<EntityId, Box<dyn Any>>, // T를 type-erase
    accessed_entities: RefCell<FxHashSet<EntityId>>,         // 렌더 추적용
    ref_counts:        Arc<RwLock<EntityRefCounts>>,
}

pub struct EntityRefCounts {
    counts:            SlotMap<EntityId, AtomicUsize>,  // 원자적 refcount
    dropped_entity_ids: Vec<EntityId>,                  // 지연 해제 큐
}

// EntityMap::lease() — 업데이트 중 일시적으로 소유권을 "대출"
pub fn lease<T>(&mut self, pointer: &Entity<T>) -> Lease<T> {
    // entities에서 꺼냄 → 업데이트 콜백에서 mutable 접근 가능
    // end_lease() 호출로 반납 필수 (Drop이 패닉으로 강제)
}

pub struct Lease<T> {
    entity: Option<Box<dyn Any>>,
    pub id: EntityId,
    entity_type: PhantomData<T>,
}

impl<T> Drop for Lease<T> {
    fn drop(&mut self) {
        // end_lease() 없이 drop되면 패닉 — 버그를 런타임에 즉시 포착
        if self.entity.is_some() && !panicking() {
            panic!("Leases must be ended with EntityMap::end_lease")
        }
    }
}