Run doctests in CI and fix up existing doctests (#37851)

Martin Pool created

Follows on from
https://github.com/zed-industries/zed/pull/37716#pullrequestreview-3195695110
by @SomeoneToIgnore

After this the doctests will be run in CI to check that the examples are
still accurate.

Note that doctests aren't run by Nextest: you can run them locally with
`cargo test --doc`.

Summary:
* Run tests from CI
* Loosen an exact float comparison to match approximately (otherwise it
fails)
* Fixed one actual bug in the tests for `dilate` where the test code
assumed that `dilate` mutates `self` rather than returning a new object
* Add some `must_use` on some functions that seemed at risk of similar
bugs, following the Rust stdlib style to add it where ignoring the
result is almost certainly a bug.
* Fix some cases where the doc examples seem to have gone out of date
with the code
* Add imports to doctests that need them
* Add some dev-dependencies to make the tests build
* Fix the `key_dispatch` module docstring, which was accidentally
attached to objects within that module
* Skip some doctest examples that seem like they need an async
environment or that just looked hard to get running

AI usage: I asked Claude to do some of the repetitive tests. I checked
the output and fixed up some things that seemed to not be in the right
spirit of the test, or too longwinded.

I think we could reasonably run the tests on only Linux to save CI
CPU-seconds and latency, but I haven't done that yet, partly because of
how it's implemented in the action.

Release Notes:

- N/A

Change summary

.github/actions/run_tests/action.yml                |   5 
Cargo.lock                                          |   3 
crates/gpui/src/action.rs                           |  15 +
crates/gpui/src/color.rs                            |   6 
crates/gpui/src/geometry.rs                         |  85 +++++++----
crates/gpui/src/gpui.rs                             |   3 
crates/gpui/src/key_dispatch.rs                     | 102 +++++++-------
crates/gpui/src/taffy.rs                            |   1 
crates/gpui/src/window.rs                           |   4 
crates/proto/src/error.rs                           |  10 
crates/refineable/src/refineable.rs                 |   7 
crates/repl/src/outputs/plain.rs                    |   6 
crates/repl/src/outputs/table.rs                    |   4 
crates/settings_ui_macros/Cargo.toml                |   3 
crates/settings_ui_macros/src/settings_ui_macros.rs |   2 
crates/telemetry/src/telemetry.rs                   |   1 
crates/terminal/src/terminal.rs                     |   3 
crates/ui/src/components/avatar.rs                  |   5 
crates/ui/src/components/banner.rs                  |  11 
crates/ui/src/components/button/button.rs           |  12 
crates/ui/src/components/callout.rs                 |  11 
crates/ui/src/components/chip.rs                    |   4 
crates/ui/src/components/facepile.rs                |   8 
crates/ui/src/components/keybinding_hint.rs         |  79 +++++++++--
crates/ui/src/components/label/label.rs             |   9 
crates/ui/src/components/toggle.rs                  |   5 
crates/ui_macros/Cargo.toml                         |   4 
crates/ui_macros/src/ui_macros.rs                   |   1 
28 files changed, 262 insertions(+), 147 deletions(-)

Detailed changes

.github/actions/run_tests/action.yml 🔗

@@ -21,3 +21,8 @@ runs:
     - name: Run tests
       shell: bash -euxo pipefail {0}
       run: cargo nextest run --workspace --no-fail-fast
+
+    - name: Run doctests
+      shell: bash -euxo pipefail {0}
+      # Nextest currently doesn't support doctests
+      run: cargo test --workspace --doc --no-fail-fast

Cargo.lock 🔗

@@ -14941,6 +14941,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
+ "settings",
  "syn 2.0.101",
  "workspace-hack",
 ]
@@ -17660,8 +17661,10 @@ dependencies = [
 name = "ui_macros"
 version = "0.1.0"
 dependencies = [
+ "component",
  "quote",
  "syn 2.0.101",
+ "ui",
  "workspace-hack",
 ]
 

crates/gpui/src/action.rs 🔗

@@ -13,6 +13,7 @@ use std::{
 /// For example:
 ///
 /// ```
+/// use gpui::actions;
 /// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]);
 /// ```
 ///
@@ -45,6 +46,7 @@ macro_rules! actions {
 /// struct action for each listed action name in the given namespace.
 ///
 /// ```
+/// use gpui::actions;
 /// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]);
 /// ```
 ///
@@ -55,6 +57,7 @@ macro_rules! actions {
 /// More complex data types can also be actions, by using the derive macro for `Action`:
 ///
 /// ```
+/// use gpui::Action;
 /// #[derive(Clone, PartialEq, serde::Deserialize, schemars::JsonSchema, Action)]
 /// #[action(namespace = editor)]
 /// pub struct SelectNext {
@@ -93,14 +96,22 @@ macro_rules! actions {
 /// `main`.
 ///
 /// ```
-/// #[derive(gpui::private::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone)]
+/// use gpui::{SharedString, register_action};
+/// #[derive(Clone, PartialEq, Eq, serde::Deserialize, schemars::JsonSchema)]
 /// pub struct Paste {
 ///     pub content: SharedString,
 /// }
 ///
 /// impl gpui::Action for Paste {
-///      ///...
+///     # fn boxed_clone(&self) -> Box<dyn gpui::Action> { unimplemented!()}
+///     # fn partial_eq(&self, other: &dyn gpui::Action) -> bool { unimplemented!() }
+///     # fn name(&self) -> &'static str { "Paste" }
+///     # fn name_for_type() -> &'static str { "Paste" }
+///     # fn build(value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>> {
+///     #     unimplemented!()
+///     # }
 /// }
+///
 /// register_action!(Paste);
 /// ```
 pub trait Action: Any + Send {

crates/gpui/src/color.rs 🔗

@@ -537,9 +537,10 @@ impl Hsla {
     ///
     /// Example:
     /// ```
-    /// let color = hlsa(0.7, 1.0, 0.5, 0.7); // A saturated blue
+    /// use gpui::hsla;
+    /// let color = hsla(0.7, 1.0, 0.5, 0.7); // A saturated blue
     /// let faded_color = color.opacity(0.16);
-    /// assert_eq!(faded_color.a, 0.112);
+    /// assert!((faded_color.a - 0.112).abs() < 1e-6);
     /// ```
     ///
     /// This will return a blue color with around ~10% opacity,
@@ -568,6 +569,7 @@ impl Hsla {
     ///
     /// Example:
     /// ```
+    /// use gpui::hsla;
     /// let color = hsla(0.7, 1.0, 0.5, 0.7); // A saturated blue
     /// let faded_color = color.alpha(0.25);
     /// assert_eq!(faded_color.a, 0.25);

crates/gpui/src/geometry.rs 🔗

@@ -102,7 +102,7 @@ pub struct Point<T: Clone + Debug + Default + PartialEq> {
 /// # Examples
 ///
 /// ```
-/// # use gpui::Point;
+/// use gpui::point;
 /// let p = point(10, 20);
 /// assert_eq!(p.x, 10);
 /// assert_eq!(p.y, 20);
@@ -122,6 +122,7 @@ impl<T: Clone + Debug + Default + PartialEq> Point<T> {
     /// # Examples
     ///
     /// ```
+    /// use gpui::Point;
     /// let p = Point::new(10, 20);
     /// assert_eq!(p.x, 10);
     /// assert_eq!(p.y, 20);
@@ -148,6 +149,7 @@ impl<T: Clone + Debug + Default + PartialEq> Point<T> {
     /// let p_float = p.map(|coord| coord as f32);
     /// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
     /// ```
+    #[must_use]
     pub fn map<U: Clone + Debug + Default + PartialEq>(&self, f: impl Fn(T) -> U) -> Point<U> {
         Point {
             x: f(self.x.clone()),
@@ -418,7 +420,7 @@ impl<T: Clone + Debug + Default + PartialEq> Size<T> {
 /// # Examples
 ///
 /// ```
-/// # use gpui::Size;
+/// use gpui::size;
 /// let my_size = size(10, 20);
 /// assert_eq!(my_size.width, 10);
 /// assert_eq!(my_size.height, 20);
@@ -1025,12 +1027,13 @@ where
     ///     origin: Point { x: 10, y: 10 },
     ///     size: Size { width: 10, height: 10 },
     /// };
-    /// bounds.dilate(5);
-    /// assert_eq!(bounds, Bounds {
+    /// let expanded_bounds = bounds.dilate(5);
+    /// assert_eq!(expanded_bounds, Bounds {
     ///     origin: Point { x: 5, y: 5 },
     ///     size: Size { width: 20, height: 20 },
     /// });
     /// ```
+    #[must_use]
     pub fn dilate(&self, amount: T) -> Bounds<T> {
         let double_amount = amount.clone() + amount.clone();
         Bounds {
@@ -1040,6 +1043,7 @@ where
     }
 
     /// Extends the bounds different amounts in each direction.
+    #[must_use]
     pub fn extend(&self, amount: Edges<T>) -> Bounds<T> {
         Bounds {
             origin: self.origin.clone() - point(amount.left.clone(), amount.top.clone()),
@@ -1359,7 +1363,7 @@ where
     /// # Examples
     ///
     /// ```
-    /// # use zed::{Bounds, Corner, Point, Size};
+    /// use gpui::{Bounds, Corner, Point, Size};
     /// let bounds = Bounds {
     ///     origin: Point { x: 0, y: 0 },
     ///     size: Size { width: 10, height: 20 },
@@ -1399,7 +1403,7 @@ where
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Point, Bounds};
+    /// # use gpui::{Point, Bounds, Size};
     /// let bounds = Bounds {
     ///     origin: Point { x: 0, y: 0 },
     ///     size: Size { width: 10, height: 10 },
@@ -1407,8 +1411,8 @@ where
     /// let inside_point = Point { x: 5, y: 5 };
     /// let outside_point = Point { x: 15, y: 15 };
     ///
-    /// assert!(bounds.contains_point(&inside_point));
-    /// assert!(!bounds.contains_point(&outside_point));
+    /// assert!(bounds.contains(&inside_point));
+    /// assert!(!bounds.contains(&outside_point));
     /// ```
     pub fn contains(&self, point: &Point<T>) -> bool {
         point.x >= self.origin.x
@@ -1565,6 +1569,7 @@ impl<T: PartialOrd + Clone + Debug + Default + PartialEq> Bounds<T> {
     /// # Returns
     ///
     /// Returns `true` if either the width or the height of the bounds is less than or equal to zero, indicating an empty area.
+    #[must_use]
     pub fn is_empty(&self) -> bool {
         self.size.width <= T::default() || self.size.height <= T::default()
     }
@@ -1621,7 +1626,7 @@ impl Bounds<Pixels> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Bounds, Point, Size, Pixels};
+    /// # use gpui::{Bounds, Point, Size, Pixels, ScaledPixels, DevicePixels};
     /// let bounds = Bounds {
     ///     origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
     ///     size: Size { width: Pixels(30.0), height: Pixels(40.0) },
@@ -1629,8 +1634,14 @@ impl Bounds<Pixels> {
     /// let display_scale_factor = 2.0;
     /// let scaled_bounds = bounds.scale(display_scale_factor);
     /// assert_eq!(scaled_bounds, Bounds {
-    ///     origin: Point { x: ScaledPixels(20.0), y: ScaledPixels(40.0) },
-    ///     size: Size { width: ScaledPixels(60.0), height: ScaledPixels(80.0) },
+    ///     origin: Point {
+    ///         x: ScaledPixels(20.0),
+    ///         y: ScaledPixels(40.0),
+    ///     },
+    ///     size: Size {
+    ///         width: ScaledPixels(60.0),
+    ///         height: ScaledPixels(80.0)
+    ///     },
     /// });
     /// ```
     pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
@@ -1847,7 +1858,7 @@ impl Edges<Length> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::Edges;
+    /// # use gpui::{Edges, Length};
     /// let auto_edges = Edges::auto();
     /// assert_eq!(auto_edges.top, Length::Auto);
     /// assert_eq!(auto_edges.right, Length::Auto);
@@ -1875,8 +1886,8 @@ impl Edges<Length> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::Edges;
-    /// let no_edges = Edges::zero();
+    /// # use gpui::{DefiniteLength, Edges, Length, Pixels};
+    /// let no_edges = Edges::<Length>::zero();
     /// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
     /// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
     /// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
@@ -1905,8 +1916,8 @@ impl Edges<DefiniteLength> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{px, Edges};
-    /// let no_edges = Edges::zero();
+    /// # use gpui::{px, DefiniteLength, Edges};
+    /// let no_edges = Edges::<DefiniteLength>::zero();
     /// assert_eq!(no_edges.top, DefiniteLength::from(px(0.)));
     /// assert_eq!(no_edges.right, DefiniteLength::from(px(0.)));
     /// assert_eq!(no_edges.bottom, DefiniteLength::from(px(0.)));
@@ -1938,7 +1949,7 @@ impl Edges<DefiniteLength> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Edges, DefiniteLength, px, AbsoluteLength, Size};
+    /// # use gpui::{Edges, DefiniteLength, px, AbsoluteLength, rems, Size};
     /// let edges = Edges {
     ///     top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
     ///     right: DefiniteLength::Fraction(0.5),
@@ -1980,8 +1991,8 @@ impl Edges<AbsoluteLength> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::Edges;
-    /// let no_edges = Edges::zero();
+    /// # use gpui::{AbsoluteLength, Edges, Pixels};
+    /// let no_edges = Edges::<AbsoluteLength>::zero();
     /// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
     /// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
     /// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
@@ -2012,7 +2023,7 @@ impl Edges<AbsoluteLength> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Edges, AbsoluteLength, Pixels, px};
+    /// # use gpui::{Edges, AbsoluteLength, Pixels, px, rems};
     /// let edges = Edges {
     ///     top: AbsoluteLength::Pixels(px(10.0)),
     ///     right: AbsoluteLength::Rems(rems(1.0)),
@@ -2053,7 +2064,7 @@ impl Edges<Pixels> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Edges, Pixels};
+    /// # use gpui::{Edges, Pixels, ScaledPixels};
     /// let edges = Edges {
     ///     top: Pixels(10.0),
     ///     right: Pixels(20.0),
@@ -2104,7 +2115,7 @@ impl From<Pixels> for Edges<Pixels> {
 }
 
 /// Identifies a corner of a 2d box.
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Corner {
     /// The top left corner
     TopLeft,
@@ -2122,9 +2133,10 @@ impl Corner {
     /// # Examples
     ///
     /// ```
-    /// # use zed::Corner;
+    /// # use gpui::Corner;
     /// assert_eq!(Corner::TopLeft.opposite_corner(), Corner::BottomRight);
     /// ```
+    #[must_use]
     pub fn opposite_corner(self) -> Self {
         match self {
             Corner::TopLeft => Corner::BottomRight,
@@ -2139,10 +2151,11 @@ impl Corner {
     /// # Examples
     ///
     /// ```
-    /// # use zed::Corner;
+    /// # use gpui::{Axis, Corner};
     /// let result = Corner::TopLeft.other_side_corner_along(Axis::Horizontal);
     /// assert_eq!(result, Corner::TopRight);
     /// ```
+    #[must_use]
     pub fn other_side_corner_along(self, axis: Axis) -> Self {
         match axis {
             Axis::Vertical => match self {
@@ -2224,7 +2237,7 @@ where
     /// # Examples
     ///
     /// ```
-    /// # use zed::{Corner, Corners};
+    /// # use gpui::{Corner, Corners};
     /// let corners = Corners {
     ///     top_left: 1,
     ///     top_right: 2,
@@ -2233,6 +2246,7 @@ where
     /// };
     /// assert_eq!(corners.corner(Corner::BottomLeft), 3);
     /// ```
+    #[must_use]
     pub fn corner(&self, corner: Corner) -> T {
         match corner {
             Corner::TopLeft => self.top_left.clone(),
@@ -2257,7 +2271,7 @@ impl Corners<AbsoluteLength> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Corners, AbsoluteLength, Pixels, Size};
+    /// # use gpui::{Corners, AbsoluteLength, Pixels, Rems, Size};
     /// let corners = Corners {
     ///     top_left: AbsoluteLength::Pixels(Pixels(15.0)),
     ///     top_right: AbsoluteLength::Rems(Rems(1.0)),
@@ -2265,7 +2279,7 @@ impl Corners<AbsoluteLength> {
     ///     bottom_left: AbsoluteLength::Rems(Rems(2.0)),
     /// };
     /// let rem_size = Pixels(16.0);
-    /// let corners_in_pixels = corners.to_pixels(size, rem_size);
+    /// let corners_in_pixels = corners.to_pixels(rem_size);
     ///
     /// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
     /// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
@@ -2298,7 +2312,7 @@ impl Corners<Pixels> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Corners, Pixels};
+    /// # use gpui::{Corners, Pixels, ScaledPixels};
     /// let corners = Corners {
     ///     top_left: Pixels(10.0),
     ///     top_right: Pixels(20.0),
@@ -2311,6 +2325,7 @@ impl Corners<Pixels> {
     /// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
     /// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
     /// ```
+    #[must_use]
     pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
         Corners {
             top_left: self.top_left.scale(factor),
@@ -2325,6 +2340,7 @@ impl Corners<Pixels> {
     /// # Returns
     ///
     /// The maximum `Pixels` value among all four corners.
+    #[must_use]
     pub fn max(&self) -> Pixels {
         self.top_left
             .max(self.top_right)
@@ -2343,6 +2359,7 @@ impl<T: Div<f32, Output = T> + Ord + Clone + Debug + Default + PartialEq> Corner
     /// # Returns
     ///
     /// Corner radii values clamped to fit.
+    #[must_use]
     pub fn clamp_radii_for_quad_size(self, size: Size<T>) -> Corners<T> {
         let max = cmp::min(size.width, size.height) / 2.;
         Corners {
@@ -2372,7 +2389,7 @@ impl<T: Clone + Debug + Default + PartialEq> Corners<T> {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Corners, Pixels};
+    /// # use gpui::{Corners, Pixels, Rems};
     /// let corners = Corners {
     ///     top_left: Pixels(10.0),
     ///     top_right: Pixels(20.0),
@@ -2387,6 +2404,7 @@ impl<T: Clone + Debug + Default + PartialEq> Corners<T> {
     ///     bottom_left: Rems(2.5),
     /// });
     /// ```
+    #[must_use]
     pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Corners<U>
     where
         U: Clone + Debug + Default + PartialEq,
@@ -2526,14 +2544,14 @@ impl From<Percentage> for Radians {
 /// # Examples
 ///
 /// ```
-/// use gpui::Pixels;
+/// use gpui::{Pixels, ScaledPixels};
 ///
 /// // Define a length of 10 pixels
 /// let length = Pixels(10.0);
 ///
 /// // Define a length and scale it by a factor of 2
 /// let scaled_length = length.scale(2.0);
-/// assert_eq!(scaled_length, Pixels(20.0));
+/// assert_eq!(scaled_length, ScaledPixels(20.0));
 /// ```
 #[derive(
     Clone,
@@ -2687,6 +2705,7 @@ impl Pixels {
     ///
     /// The resulting `ScaledPixels` represent the scaled value which can be used for rendering
     /// calculations where display scaling is considered.
+    #[must_use]
     pub fn scale(&self, factor: f32) -> ScaledPixels {
         ScaledPixels(self.0 * factor)
     }
@@ -2926,7 +2945,7 @@ impl From<usize> for DevicePixels {
 /// display resolutions.
 #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)]
 #[repr(transparent)]
-pub struct ScaledPixels(pub(crate) f32);
+pub struct ScaledPixels(pub f32);
 
 impl ScaledPixels {
     /// Floors the `ScaledPixels` value to the nearest whole number.
@@ -3160,7 +3179,7 @@ impl AbsoluteLength {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{AbsoluteLength, Pixels};
+    /// # use gpui::{AbsoluteLength, Pixels, Rems};
     /// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
     /// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
     /// let rem_size = Pixels(16.0);

crates/gpui/src/gpui.rs 🔗

@@ -8,7 +8,8 @@
 //! GPUI is still in active development as we work on the Zed code editor and isn't yet on crates.io.
 //! You'll also need to use the latest version of stable rust. Add the following to your Cargo.toml:
 //!
-//! ```
+//! ```toml
+//! [dependencies]
 //! gpui = { git = "https://github.com/zed-industries/zed" }
 //! ```
 //!

crates/gpui/src/key_dispatch.rs 🔗

@@ -1,54 +1,54 @@
-/// KeyDispatch is where GPUI deals with binding actions to key events.
-///
-/// The key pieces to making a key binding work are to define an action,
-/// implement a method that takes that action as a type parameter,
-/// and then to register the action during render on a focused node
-/// with a keymap context:
-///
-/// ```rust
-/// actions!(editor,[Undo, Redo]);
-///
-/// impl Editor {
-///   fn undo(&mut self, _: &Undo, _window: &mut Window, _cx: &mut Context<Self>) { ... }
-///   fn redo(&mut self, _: &Redo, _window: &mut Window, _cx: &mut Context<Self>) { ... }
-/// }
-///
-/// impl Render for Editor {
-///   fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-///     div()
-///       .track_focus(&self.focus_handle(cx))
-///       .key_context("Editor")
-///       .on_action(cx.listener(Editor::undo))
-///       .on_action(cx.listener(Editor::redo))
-///     ...
-///    }
-/// }
-///```
-///
-/// The keybindings themselves are managed independently by calling cx.bind_keys().
-/// (Though mostly when developing Zed itself, you just need to add a new line to
-///  assets/keymaps/default-{platform}.json).
-///
-/// ```rust
-/// cx.bind_keys([
-///   KeyBinding::new("cmd-z", Editor::undo, Some("Editor")),
-///   KeyBinding::new("cmd-shift-z", Editor::redo, Some("Editor")),
-/// ])
-/// ```
-///
-/// With all of this in place, GPUI will ensure that if you have an Editor that contains
-/// the focus, hitting cmd-z will Undo.
-///
-/// In real apps, it is a little more complicated than this, because typically you have
-/// several nested views that each register keyboard handlers. In this case action matching
-/// bubbles up from the bottom. For example in Zed, the Workspace is the top-level view, which contains Pane's, which contain Editors. If there are conflicting keybindings defined
-/// then the Editor's bindings take precedence over the Pane's bindings, which take precedence over the Workspace.
-///
-/// In GPUI, keybindings are not limited to just single keystrokes, you can define
-/// sequences by separating the keys with a space:
-///
-///  KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
-///
+//! KeyDispatch is where GPUI deals with binding actions to key events.
+//!
+//! The key pieces to making a key binding work are to define an action,
+//! implement a method that takes that action as a type parameter,
+//! and then to register the action during render on a focused node
+//! with a keymap context:
+//!
+//! ```ignore
+//! actions!(editor,[Undo, Redo]);
+//!
+//! impl Editor {
+//!   fn undo(&mut self, _: &Undo, _window: &mut Window, _cx: &mut Context<Self>) { ... }
+//!   fn redo(&mut self, _: &Redo, _window: &mut Window, _cx: &mut Context<Self>) { ... }
+//! }
+//!
+//! impl Render for Editor {
+//!   fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+//!     div()
+//!       .track_focus(&self.focus_handle(cx))
+//!       .key_context("Editor")
+//!       .on_action(cx.listener(Editor::undo))
+//!       .on_action(cx.listener(Editor::redo))
+//!     ...
+//!    }
+//! }
+//!```
+//!
+//! The keybindings themselves are managed independently by calling cx.bind_keys().
+//! (Though mostly when developing Zed itself, you just need to add a new line to
+//!  assets/keymaps/default-{platform}.json).
+//!
+//! ```ignore
+//! cx.bind_keys([
+//!   KeyBinding::new("cmd-z", Editor::undo, Some("Editor")),
+//!   KeyBinding::new("cmd-shift-z", Editor::redo, Some("Editor")),
+//! ])
+//! ```
+//!
+//! With all of this in place, GPUI will ensure that if you have an Editor that contains
+//! the focus, hitting cmd-z will Undo.
+//!
+//! In real apps, it is a little more complicated than this, because typically you have
+//! several nested views that each register keyboard handlers. In this case action matching
+//! bubbles up from the bottom. For example in Zed, the Workspace is the top-level view, which contains Pane's, which contain Editors. If there are conflicting keybindings defined
+//! then the Editor's bindings take precedence over the Pane's bindings, which take precedence over the Workspace.
+//!
+//! In GPUI, keybindings are not limited to just single keystrokes, you can define
+//! sequences by separating the keys with a space:
+//!
+//!  KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
+
 use crate::{
     Action, ActionRegistry, App, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
     Keystroke, ModifiersChangedEvent, Window,

crates/gpui/src/taffy.rs 🔗

@@ -490,6 +490,7 @@ impl AvailableSpace {
     /// # Examples
     ///
     /// ```
+    /// use gpui::AvailableSpace;
     /// let min_content_size = AvailableSpace::min_size();
     /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
     /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);

crates/gpui/src/window.rs 🔗

@@ -580,7 +580,7 @@ pub enum HitboxBehavior {
     /// For mouse handlers that check those hitboxes, this behaves the same as registering a
     /// bubble-phase handler for every mouse event type:
     ///
-    /// ```
+    /// ```ignore
     /// window.on_mouse_event(move |_: &EveryMouseEventTypeHere, phase, window, cx| {
     ///     if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
     ///         cx.stop_propagation();
@@ -604,7 +604,7 @@ pub enum HitboxBehavior {
     /// For mouse handlers that check those hitboxes, this behaves the same as registering a
     /// bubble-phase handler for every mouse event type **except** `ScrollWheelEvent`:
     ///
-    /// ```
+    /// ```ignore
     /// window.on_mouse_event(move |_: &EveryMouseEventTypeExceptScroll, phase, window, cx| {
     ///     if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
     ///         cx.stop_propagation();

crates/proto/src/error.rs 🔗

@@ -22,12 +22,14 @@
 /// When handling an error you can use .error_code() to match which error it was
 /// and .error_tag() to read any tags.
 ///
-/// ```
+/// ```ignore
+/// use proto::{ErrorCode, ErrorExt};
+///
 /// match err.error_code() {
-///   ErrorCode::Forbidden => alert("I'm sorry I can't do that.")
+///   ErrorCode::Forbidden => alert("I'm sorry I can't do that."),
 ///   ErrorCode::WrongReleaseChannel =>
-///     alert(format!("You need to be on the {} release channel.", err.error_tag("required").unwrap()))
-///   ErrorCode::Internal => alert("Sorry, something went wrong")
+///     alert(format!("You need to be on the {} release channel.", err.error_tag("required").unwrap())),
+///   ErrorCode::Internal => alert("Sorry, something went wrong"),
 /// }
 /// ```
 ///

crates/refineable/src/refineable.rs 🔗

@@ -19,7 +19,10 @@ pub use derive_refineable::Refineable;
 ///
 /// ## Example
 ///
-/// ```rust
+/// ```
+/// use derive_refineable::Refineable as _;
+/// use refineable::Refineable;
+///
 /// #[derive(Refineable, Clone, Default)]
 /// struct Example {
 ///     color: String,
@@ -36,7 +39,7 @@ pub use derive_refineable::Refineable;
 ///
 ///
 /// fn example() {
-///     let mut example = Example::default();
+///     let mut base_style = Example::default();
 ///     let refinement = ExampleRefinement {
 ///         color: Some("red".to_string()),
 ///         font_size: None,

crates/repl/src/outputs/plain.rs 🔗

@@ -171,9 +171,9 @@ impl TerminalOutput {
     ///
     /// Then append_text will be called twice, with the following arguments:
     ///
-    /// ```rust
-    /// terminal_output.append_text("Hello,")
-    /// terminal_output.append_text(" world!")
+    /// ```ignore
+    /// terminal_output.append_text("Hello,");
+    /// terminal_output.append_text(" world!");
     /// ```
     /// Resulting in a single output of "Hello, world!".
     ///

crates/repl/src/outputs/table.rs 🔗

@@ -66,7 +66,9 @@ use util::markdown::MarkdownEscaped;
 use crate::outputs::OutputContent;
 
 /// TableView renders a static table inline in a buffer.
-/// It uses the https://specs.frictionlessdata.io/tabular-data-resource/ specification for data interchange.
+///
+/// It uses the <https://specs.frictionlessdata.io/tabular-data-resource/>
+/// specification for data interchange.
 pub struct TableView {
     pub table: TabularDataResource,
     pub widths: Vec<Pixels>,

crates/settings_ui_macros/Cargo.toml 🔗

@@ -21,3 +21,6 @@ proc-macro2.workspace = true
 quote.workspace = true
 syn.workspace = true
 workspace-hack.workspace = true
+
+[dev-dependencies]
+settings.workspace = true

crates/settings_ui_macros/src/settings_ui_macros.rs 🔗

@@ -12,7 +12,7 @@ use syn::{Data, DeriveInput, LitStr, Token, parse_macro_input};
 /// # Example
 ///
 /// ```
-/// use settings::SettingsUi;
+/// use settings_ui_macros::SettingsUi;
 ///
 /// #[derive(SettingsUi)]
 /// #[settings_ui(group = "Standard")]

crates/telemetry/src/telemetry.rs 🔗

@@ -12,6 +12,7 @@ pub use telemetry_events::FlexibleEvent as Event;
 /// The properties can be any value that implements serde::Serialize.
 ///
 /// ```
+/// # let url = "https://example.com";
 /// telemetry::event!("Keymap Changed", version = "1.0.0");
 /// telemetry::event!("Documentation Viewed", url, source = "Extension Upsell");
 /// ```

crates/terminal/src/terminal.rs 🔗

@@ -2160,11 +2160,12 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla {
 }
 
 /// Generates the RGB channels in [0, 5] for a given index into the 6x6x6 ANSI color cube.
+///
 /// See: [8 bit ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
 ///
 /// Wikipedia gives a formula for calculating the index for a given color:
 ///
-/// ```
+/// ```text
 /// index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
 /// ```
 ///

crates/ui/src/components/avatar.rs 🔗

@@ -8,10 +8,9 @@ use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
 /// # Examples
 ///
 /// ```
-/// use ui::{Avatar, AvatarShape};
+/// use ui::Avatar;
 ///
 /// Avatar::new("path/to/image.png")
-///     .shape(AvatarShape::Circle)
 ///     .grayscale(true)
 ///     .border_color(gpui::red());
 /// ```
@@ -39,7 +38,7 @@ impl Avatar {
     /// # Examples
     ///
     /// ```
-    /// use ui::{Avatar, AvatarShape};
+    /// use ui::Avatar;
     ///
     /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
     /// ```

crates/ui/src/components/banner.rs 🔗

@@ -7,17 +7,18 @@ use gpui::{AnyElement, IntoElement, ParentElement, Styled};
 /// # Usage Example
 ///
 /// ```
-/// use ui::{Banner};
+/// use ui::prelude::*;
+/// use ui::{Banner, Button, IconName, IconPosition, IconSize, Label, Severity};
 ///
-///    Banner::new()
+/// Banner::new()
 ///     .severity(Severity::Success)
-///     .children(Label::new("This is a success message"))
+///     .children([Label::new("This is a success message")])
 ///     .action_slot(
 ///         Button::new("learn-more", "Learn More")
 ///             .icon(IconName::ArrowUpRight)
 ///             .icon_size(IconSize::Small)
-///             .icon_position(IconPosition::End),
-///     )
+///             .icon_position(IconPosition::End)
+///     );
 /// ```
 #[derive(IntoElement, RegisterComponent)]
 pub struct Banner {

crates/ui/src/components/button/button.rs 🔗

@@ -43,7 +43,7 @@ use super::button_icon::ButtonIcon;
 ///
 /// Button::new("button_id", "Click me!")
 ///     .icon(IconName::Check)
-///     .selected(true)
+///     .toggle_state(true)
 ///     .on_click(|event, window, cx| {
 ///         // Handle click event
 ///     });
@@ -56,7 +56,7 @@ use super::button_icon::ButtonIcon;
 /// use ui::TintColor;
 ///
 /// Button::new("button_id", "Click me!")
-///     .selected(true)
+///     .toggle_state(true)
 ///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
 ///     .on_click(|event, window, cx| {
 ///         // Handle click event
@@ -228,7 +228,7 @@ impl Toggleable for Button {
     /// use ui::prelude::*;
     ///
     /// Button::new("button_id", "Click me!")
-    ///     .selected(true)
+    ///     .toggle_state(true)
     ///     .on_click(|event, window, cx| {
     ///         // Handle click event
     ///     });
@@ -251,7 +251,7 @@ impl SelectableButton for Button {
     /// use ui::TintColor;
     ///
     /// Button::new("button_id", "Click me!")
-    ///     .selected(true)
+    ///     .toggle_state(true)
     ///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
     ///     .on_click(|event, window, cx| {
     ///         // Handle click event
@@ -317,7 +317,7 @@ impl FixedWidth for Button {
     /// use ui::prelude::*;
     ///
     /// Button::new("button_id", "Click me!")
-    ///     .width(px(100.).into())
+    ///     .width(px(100.))
     ///     .on_click(|event, window, cx| {
     ///         // Handle click event
     ///     });
@@ -381,7 +381,7 @@ impl ButtonCommon for Button {
     /// use ui::Tooltip;
     ///
     /// Button::new("button_id", "Click me!")
-    ///     .tooltip(Tooltip::text_f("This is a tooltip", cx))
+    ///     .tooltip(Tooltip::text("This is a tooltip"))
     ///     .on_click(|event, window, cx| {
     ///         // Handle click event
     ///     });

crates/ui/src/components/callout.rs 🔗

@@ -13,14 +13,15 @@ pub enum BorderPosition {
 /// # Usage Example
 ///
 /// ```
-/// use ui::{Callout};
+/// use ui::prelude::*;
+/// use ui::{Button, Callout, IconName, Label, Severity};
 ///
-/// Callout::new()
+/// let callout = Callout::new()
 ///     .severity(Severity::Warning)
 ///     .icon(IconName::Warning)
-///     .title(Label::new("Be aware of your subscription!"))
-///     .description(Label::new("Your subscription is about to expire. Renew now!"))
-///     .actions_slot(Button::new("renew", "Renew Now"))
+///     .title("Be aware of your subscription!")
+///     .description("Your subscription is about to expire. Renew now!")
+///     .actions_slot(Button::new("renew", "Renew Now"));
 /// ```
 ///
 #[derive(IntoElement, RegisterComponent)]

crates/ui/src/components/chip.rs 🔗

@@ -6,9 +6,9 @@ use gpui::{AnyElement, Hsla, IntoElement, ParentElement, Styled};
 /// # Usage Example
 ///
 /// ```
-/// use ui::{Chip};
+/// use ui::Chip;
 ///
-///    Chip::new("This Chip")
+/// let chip = Chip::new("This Chip");
 /// ```
 #[derive(IntoElement, RegisterComponent)]
 pub struct Chip {

crates/ui/src/components/facepile.rs 🔗

@@ -19,11 +19,13 @@ use super::Avatar;
 /// A default, horizontal facepile.
 ///
 /// ```
+/// use gpui::IntoElement;
 /// use ui::{Avatar, Facepile, EXAMPLE_FACES};
 ///
-/// Facepile::new(
-/// EXAMPLE_FACES.iter().take(3).iter().map(|&url|
-///    Avatar::new(url).into_any_element()).collect())
+/// let facepile = Facepile::new(
+///     EXAMPLE_FACES.iter().take(3).map(|&url|
+///         Avatar::new(url).into_any_element()).collect()
+/// );
 /// ```
 #[derive(IntoElement, Documented, RegisterComponent)]
 pub struct Facepile {

crates/ui/src/components/keybinding_hint.rs 🔗

@@ -10,12 +10,19 @@ use theme::Appearance;
 ///
 /// # Examples
 ///
-/// ```
+/// ```no_run
+/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
 /// use ui::prelude::*;
+/// use ui::{KeyBinding, KeybindingHint};
 ///
-/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+S"))
+/// # fn example(cx: &App) {
+/// let hint = KeybindingHint::new(
+///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-s").unwrap())], cx),
+///     Hsla::black()
+/// )
 ///     .prefix("Save:")
 ///     .size(Pixels::from(14.0));
+/// # }
 /// ```
 #[derive(Debug, IntoElement, RegisterComponent)]
 pub struct KeybindingHint {
@@ -34,10 +41,17 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::new(
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())], cx),
+    ///     Hsla::black()
+    /// );
+    /// # }
     /// ```
     pub fn new(keybinding: KeyBinding, background_color: Hsla) -> Self {
         Self {
@@ -56,10 +70,18 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::with_prefix(
+    ///     "Copy:",
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())], cx),
+    ///     Hsla::black()
+    /// );
+    /// # }
     /// ```
     pub fn with_prefix(
         prefix: impl Into<SharedString>,
@@ -82,10 +104,18 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste", Hsla::new(0.0, 0.0, 0.0, 1.0));
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::with_suffix(
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-v").unwrap())], cx),
+    ///     "Paste",
+    ///     Hsla::black()
+    /// );
+    /// # }
     /// ```
     pub fn with_suffix(
         keybinding: KeyBinding,
@@ -107,11 +137,18 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+X"))
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::new(
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-x").unwrap())], cx),
+    ///     Hsla::black()
+    /// )
     ///     .prefix("Cut:");
+    /// # }
     /// ```
     pub fn prefix(mut self, prefix: impl Into<SharedString>) -> Self {
         self.prefix = Some(prefix.into());
@@ -124,11 +161,18 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+F"))
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::new(
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-f").unwrap())], cx),
+    ///     Hsla::black()
+    /// )
     ///     .suffix("Find");
+    /// # }
     /// ```
     pub fn suffix(mut self, suffix: impl Into<SharedString>) -> Self {
         self.suffix = Some(suffix.into());
@@ -141,11 +185,18 @@ impl KeybindingHint {
     ///
     /// # Examples
     ///
-    /// ```
+    /// ```no_run
+    /// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
     /// use ui::prelude::*;
+    /// use ui::{KeyBinding, KeybindingHint};
     ///
-    /// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+Z"))
+    /// # fn example(cx: &App) {
+    /// let hint = KeybindingHint::new(
+    ///     KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-z").unwrap())], cx),
+    ///     Hsla::black()
+    /// )
     ///     .size(Pixels::from(16.0));
+    /// # }
     /// ```
     pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
         self.size = size.into();

crates/ui/src/components/label/label.rs 🔗

@@ -27,7 +27,7 @@ use gpui::StyleRefinement;
 /// ```
 /// use ui::prelude::*;
 ///
-/// let my_label = Label::new("Deleted").strikethrough(true);
+/// let my_label = Label::new("Deleted").strikethrough();
 /// ```
 #[derive(IntoElement, RegisterComponent)]
 pub struct Label {
@@ -89,9 +89,10 @@ impl LabelCommon for Label {
     /// # Examples
     ///
     /// ```
+    /// use gpui::FontWeight;
     /// use ui::prelude::*;
     ///
-    /// let my_label = Label::new("Hello, World!").weight(FontWeight::Bold);
+    /// let my_label = Label::new("Hello, World!").weight(FontWeight::BOLD);
     /// ```
     fn weight(mut self, weight: gpui::FontWeight) -> Self {
         self.base = self.base.weight(weight);
@@ -133,7 +134,7 @@ impl LabelCommon for Label {
     /// ```
     /// use ui::prelude::*;
     ///
-    /// let my_label = Label::new("Hello, World!").strikethrough(true);
+    /// let my_label = Label::new("Hello, World!").strikethrough();
     /// ```
     fn strikethrough(mut self) -> Self {
         self.base = self.base.strikethrough();
@@ -147,7 +148,7 @@ impl LabelCommon for Label {
     /// ```
     /// use ui::prelude::*;
     ///
-    /// let my_label = Label::new("Hello, World!").italic(true);
+    /// let my_label = Label::new("Hello, World!").italic();
     /// ```
     fn italic(mut self) -> Self {
         self.base = self.base.italic();

crates/ui/src/components/toggle.rs 🔗

@@ -581,11 +581,12 @@ impl RenderOnce for Switch {
 ///
 /// ```
 /// use ui::prelude::*;
+/// use ui::{SwitchField, ToggleState};
 ///
-/// SwitchField::new(
+/// let switch_field = SwitchField::new(
 ///     "feature-toggle",
 ///     "Enable feature",
-///     "This feature adds new functionality to the app.",
+///     Some("This feature adds new functionality to the app.".into()),
 ///     ToggleState::Unselected,
 ///     |state, window, cx| {
 ///         // Logic here

crates/ui_macros/Cargo.toml 🔗

@@ -16,3 +16,7 @@ proc-macro = true
 quote.workspace = true
 syn.workspace = true
 workspace-hack.workspace = true
+
+[dev-dependencies]
+component.workspace = true
+ui.workspace = true

crates/ui_macros/src/ui_macros.rs 🔗

@@ -19,6 +19,7 @@ pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
 /// # Example
 ///
 /// ```
+/// use ui::Component;
 /// use ui_macros::RegisterComponent;
 ///
 /// #[derive(RegisterComponent)]