8.7 KiB
8.7 KiB
Coding guidelines
- Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
- Do not write organizational or comments that summarize the code. Comments should only be written to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
- Keep doc comments and comments simple, one line, straightforward, and unbloated.
- Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
- Prefer
implblocks over standalone functions: place functionality as methods or associated functions on the relevant type, extending the type's existingimplblock instead of creating a duplicate one in another file. - Prefer generic parameters with a
whereclause over argument-positionimpl Traitin function signatures (fn extend<I>(&mut self, elements: I) where I: IntoIterator<Item = AnyElement>); return-positionimpl Traitstays. - Do not wrap dependency APIs in project structs or pass-through helper functions; call their methods directly where needed. Extract a helper only when it holds real logic that is used in more than one place.
- Bind a global store handle to a local before using it (
let theme = Theme::global(cx);thentheme.…) instead of chaining::global(cx)inline. - Avoid using functions that panic like
unwrap(); instead use mechanisms like?to propagate errors. - Be careful with operations like indexing which may panic if the indexes are out of bounds.
- Never silently discard errors with
let _ =on fallible operations. Always handle errors appropriately:- Propagate errors with
?when the calling function should handle them. - Use
.log_err()or similar when you need to ignore errors but want visibility. - Use explicit error handling with
matchorif let Err(...)when you need custom logic.
- Propagate errors with
- When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
- Avoid creative additions unless explicitly requested.
- Use full words for variable names (no abbreviations like "q" for "queue").
- Use variable shadowing to scope clones in async contexts for clarity, minimizing the lifetime of borrowed references.
Tests
- Keep only meaningful and critical tests: each test must lock state semantics (activation, state transitions, style priority, a11y mapping) or a non-obvious lifecycle invariant.
- Drop tests that assert mechanical passthroughs, getter/setter round-trips, compile-time API availability, or duplicate the same behavior at a second layer.
Timers in tests
- In GPUI tests, prefer GPUI executor timers over
smol::Timer::after(...)for timeouts, delays, or drivingrun_until_parked():- Use
cx.background_executor().timer(duration).await(orcx.background_executor.timer(duration).awaitinTestAppContext) so work is scheduled on GPUI's dispatcher. - Avoid
smol::Timer::after(...)for test timeouts when you rely onrun_until_parked(); it may not be tracked by GPUI's scheduler.
- Use
GPUI
GPUI is a UI framework which also provides primitives for state and concurrency management.
Context
- Context types allow interaction with global state, windows, entities, and system services. They are typically passed as the argument named
cx. When a function takes callbacks they come after thecxparameter. Appis the root context type, providing access to global state and read and update of entities.Context<T>is provided when updating anEntity<T>. It dereferences intoApp, so functions which take&Appcan also take&Context<T>.AsyncAppandAsyncWindowContextare provided bycx.spawnandcx.spawn_inand can be held across await points.
Window
Windowprovides access to the state of an application window. It is passed aswindowand comes beforecxwhen present.
Entities
- With
thing: Entity<T>:thing.entity_id(),thing.downgrade(),thing.read(cx),thing.read_with(cx, |thing, cx| ...),thing.update(cx, |thing, cx| ...),thing.update_in(cx, |thing, window, cx| ...). - Within update closures, use the inner
cxprovided to the closure, not the outer one. - Never update an entity while it is already being updated; this panics.
WeakEntity<T>methods returnanyhow::Resultand fail when the entity no longer exists; treat that as a normal lifecycle outcome. Prefer weak back-references to avoid ownership cycles.
Concurrency
- All use of entities and UI rendering occurs on a single foreground thread.
cx.spawn(async move |cx| ...)runs on the foreground thread; from aContext<T>it looks likecx.spawn(async move |this, cx| ...).cx.background_spawn(async move { ... })runs work on other threads.- Both return a
Task<R>; dropping it cancels the work. Prevent cancellation by awaiting, detaching deliberately, or storing the task in a field. - Prefer keeping a task in a field over
.detach()when the work belongs to a view or store; a detached task outlives its owner. A finishedTaskheld in a container is not reaped — drop finished handles or hold a singleOption<Task<_>>.
Rendering and state
Renderbuilds an element tree from entity state;RenderOnce(with#[derive(IntoElement)]) is for value-like components whoserendertakesselfand&mut App.- Keep
renderdeclarative; use.when(...)/.when_some(...)for small conditional refinements. - Controlled components: the owner passes the current value, callbacks report intent, the owner updates state and calls
cx.notify(). No second source of truth. - Call
cx.notify()after a mutation that changes rendering; notify the narrowest owning entity. Never notify fromrender. - Use
cx.emit(...)for semantic events (declareimpl EventEmitter<EventType>),cx.subscribe(...)/cx.observe(...)for reactions, and store returnedSubscriptions in the owner (commonly_subscriptions: Vec<Subscription>). - Use stable domain-derived
ElementIds (("delete-project", project.id)); never list indexes or freshly generated random IDs for reorderable content. cx.listener(...)adapts event handlers to update the current entity.- Actions:
actions!(namespace, [Action])for data-free actions, theActionderive otherwise; registeron_actionon the focused region with a matchingkey_context. - Retain a
FocusHandlein the entity that owns keyboard interaction; a tracked handle is a Tab stop only when built withtab_stop(true).
Project rules
- Dependency direction is one-way:
gpui-native-kit(facade) →gpui-native-core/gpui-native-editor/ … →gpui-native-theme+gpui-native-icons→ gpui. No cycles. Nothing depends ongpui-component,gpui-kit,gpuikit,bezel,native-theme, or any gpui fork. crates/themeowns the native theme engine and every color, radius, spacing, and metric value — no rawrgb/rgba/hslaor layoutpx(...)literals anywhere else.crates/iconsowns icon resolution; components reference icons by semantic role — no glyph names, unicode symbols, or SVG paths anywhere else.crates/coreand its sibling crates own components: each one is a small self-contained implementation gated behind its own cargo feature. Features are additive, gate whole modules only, and may depend on other features. Behavior and presentation live together. No separate behavior layer — promote a shared helper only when a third component needs it.- Components are written in-tree. Other UI toolkits are read for API ideas only; never port their code or depend on them.
- gpui/gpui_platform come from zed's repository at the rev pinned in
docs/PLAN.md; bump the pin deliberately and re-verify. SharedStringfor UI text retained across frames;Stringonly for mutable buffers.- Behavioral seams use private fields + builders/readers. Boolean readers are
is_/has_; general builders usewith_; in-place mutation usesset_; callbacks useon_; retained models end inState; pluggable owners inDelegate/Provider. - Never invent an API: search the gpui source for the real signature before writing code.
- Prefer
.SystemUIFontand rem-based helpers (p_2(),text_sm(),gap_3()) over pixel literals.
Pull request hygiene
-
Use a clear, correctly capitalized, imperative PR title (for example,
fix crash in project panel). -
Avoid conventional commit prefixes in PR titles (
fix:,feat:,docs:, etc.). -
Avoid trailing punctuation in PR titles.
-
Optionally prefix the title with a crate name when one crate is the clear scope (for example,
kit: add button port unit). -
Include a
Release Notes:section as the final section in the PR body, with one bullet:- Added ...,- Fixed ..., or- Improved ...for user-facing changes, or- N/Aotherwise.
Release Notes:
- N/A