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

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
This commit is contained in:
Martin Pool
2025-09-12 23:24:04 +03:00
committed by GitHub
parent 174a0b1517
commit b9cf5886e4
28 changed files with 262 additions and 147 deletions
+5
View File
@@ -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
Generated
+3
View File
@@ -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",
]
+13 -2
View File
@@ -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 {
+4 -2
View File
@@ -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);
+52 -33
View File
@@ -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);
+2 -1
View File
@@ -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" }
//! ```
//!
+51 -51
View File
@@ -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,
+1
View File
@@ -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);
+2 -2
View File
@@ -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();
+6 -4
View File
@@ -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"),
/// }
/// ```
///
+5 -2
View File
@@ -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,
+3 -3
View File
@@ -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!".
///
+3 -1
View File
@@ -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>,
+3
View File
@@ -21,3 +21,6 @@ proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
settings.workspace = true
@@ -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")]
+1
View File
@@ -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");
/// ```
+2 -1
View File
@@ -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)
/// ```
///
+2 -3
View File
@@ -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);
/// ```
+6 -5
View File
@@ -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 {
+6 -6
View File
@@ -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
/// });
+6 -5
View File
@@ -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)]
+2 -2
View File
@@ -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 {
+5 -3
View File
@@ -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 {
+65 -14
View File
@@ -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();
+5 -4
View File
@@ -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();
+3 -2
View File
@@ -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
+4
View File
@@ -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
+1
View File
@@ -19,6 +19,7 @@ pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```
/// use ui::Component;
/// use ui_macros::RegisterComponent;
///
/// #[derive(RegisterComponent)]