Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 17 additions & 27 deletions crates/base/src/dock/detached.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,10 @@ impl DetachedDock {
/// name is the stable identity the summon means.
pub fn hosts_panel_named(&self, name: &str, cx: &App) -> bool {
let area = self.area.read(cx);
area.placed_panels()
.iter()
.any(|(id, _)| area.panel(*id).is_some_and(|view| view.panel_name(cx) == name))
area.placed_panels().iter().any(|(id, _)| {
area.panel(*id)
.is_some_and(|view| view.panel_name(cx) == name)
})
}

/// The title the header row leads with: the host's window title when one
Expand Down Expand Up @@ -225,9 +226,10 @@ impl DetachedDock {
all_home = false;
continue;
}
let Some(detached) = self.area.update(cx, |area, cx| {
area.detach_panel(panel, window, cx)
}) else {
let Some(detached) = self
.area
.update(cx, |area, cx| area.detach_panel(panel, window, cx))
else {
continue;
};
// The recorded origin beats `from` here — inside this window the
Expand Down Expand Up @@ -344,11 +346,7 @@ impl DetachedWindows {
.filter(|root| {
!root
.update_in(cx, |detached, pop_window, cx| {
let home = detached.redock_home_inner(
pop_window,
Some(&mut *window),
cx,
);
let home = detached.redock_home_inner(pop_window, Some(&mut *window), cx);
if home {
pop_window.remove_window();
}
Expand Down Expand Up @@ -450,11 +448,7 @@ impl Render for DetachedDock {
// lifts to the accent wash like a tab affordance.
.text_xs()
.text_color(colors.muted_foreground)
.hover(|style| {
style
.bg(colors.accent)
.text_color(colors.foreground)
})
.hover(|style| style.bg(colors.accent).text_color(colors.foreground))
.child(
svg()
.path("icons/panel-right.svg")
Expand Down Expand Up @@ -578,9 +572,7 @@ mod tests {
}

#[gpui::test]
fn an_adopted_panel_reenters_its_own_area_without_a_removed_edge(
cx: &mut TestAppContext,
) {
fn an_adopted_panel_reenters_its_own_area_without_a_removed_edge(cx: &mut TestAppContext) {
let log = log_of();
let (area, alpha, _beta, cx) = two_groups(&log, cx);
cx.run_until_parked();
Expand Down Expand Up @@ -732,9 +724,7 @@ mod tests {
}

#[gpui::test]
fn closing_the_pop_out_re_docks_to_the_region_the_panel_left(
cx: &mut TestAppContext,
) {
fn closing_the_pop_out_re_docks_to_the_region_the_panel_left(cx: &mut TestAppContext) {
let log = log_of();
let (area, _alpha, _beta, cx) = two_groups(&log, cx);
let gamma = cx.update(|window, cx| {
Expand Down Expand Up @@ -829,13 +819,12 @@ mod tests {
cx.run_until_parked();

assert!(
cx.update(|_window, cx| DetachedWindows::live(cx)).is_empty(),
cx.update(|_window, cx| DetachedWindows::live(cx))
.is_empty(),
"a closed window drops out of the inventory without an unregister"
);
assert!(
pop_window
.update(&mut cx.cx, |_, _, _| ())
.is_err(),
pop_window.update(&mut cx.cx, |_, _, _| ()).is_err(),
"and the window is really gone"
);
}
Expand Down Expand Up @@ -885,7 +874,8 @@ mod tests {
assert!(panel_is_in(&area, alpha_id, Placement::Center, cx));
assert!(panel_is_in(&area, beta_id, Placement::Center, cx));
assert!(
cx.update(|_window, cx| DetachedWindows::live(cx)).is_empty(),
cx.update(|_window, cx| DetachedWindows::live(cx))
.is_empty(),
"the inventory is empty once the windows are gone"
);
assert!(
Expand Down
12 changes: 6 additions & 6 deletions crates/component/src/dock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ pub use gpui_base::dock::PanelView as BasePanelView;
/// crate and handing it back with a different meaning is worse than dropping
/// it. A skin reads a dock through [`DockContext`].
pub use gpui_base::dock::{
AnyDrag, DockArea, DockAreaRenderer, DockAreaState, DockContext, DockEvent, DockLayout,
DockPlacement, DockSizing, DockState, DragPanel, DropIndicator, DropPlaceholderBounds,
DropTarget, EditResult, InsertTarget, NodeId, PaneNode, PaneRef, PaneTree, PanelBuildContext,
PanelBuilder, PanelEvent, PanelId, PanelInfo, PanelRegistry, PanelSource, PanelState, RootKind,
TabGroup, TabGroupConstraints, TabGroupContext, TabGroupEvent, TabGroupRenderer,
register_panel,
AnyDrag, CloseDetached, DetachedDock, DetachedPanel, DetachedWindows, DockArea,
DockAreaRenderer, DockAreaState, DockContext, DockEvent, DockLayout, DockPlacement, DockSizing,
DockState, DragPanel, DropIndicator, DropPlaceholderBounds, DropTarget, EditResult,
InsertTarget, NodeId, PaneNode, PaneRef, PaneTree, PanelBuildContext, PanelBuilder, PanelEvent,
PanelId, PanelInfo, PanelRegistry, PanelSource, PanelState, RootKind, TabGroup,
TabGroupConstraints, TabGroupContext, TabGroupEvent, TabGroupRenderer, register_panel,
};
pub use panel::*;
pub use tab_panel::DragPanelPreview;
Expand Down
41 changes: 29 additions & 12 deletions crates/component/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,9 +1154,8 @@ mod tests {
let mut textarea = None;
let window = cx.update(|cx| {
cx.open_window(Default::default(), |window, cx| {
let state = cx.new(|cx| {
crate::input::TextareaState::new(window, cx).submit_on_enter(true)
});
let state =
cx.new(|cx| crate::input::TextareaState::new(window, cx).submit_on_enter(true));
textarea = Some(state.clone());
let probe = cx.new(|_| Probe { textarea: state });
cx.new(|cx| Root::new(probe, window, cx))
Expand All @@ -1170,11 +1169,17 @@ mod tests {
let submitted = Arc::new(Mutex::new(Vec::new()));
cx.update(|_, cx| {
let submitted = submitted.clone();
cx.subscribe(&textarea, move |state, event: &gpui_base::input::InputEvent, cx| {
if let gpui_base::input::InputEvent::PressEnter { shift: false, .. } = event {
submitted.lock().unwrap().push(state.read(cx).value().to_string());
}
})
cx.subscribe(
&textarea,
move |state, event: &gpui_base::input::InputEvent, cx| {
if let gpui_base::input::InputEvent::PressEnter { shift: false, .. } = event {
submitted
.lock()
.unwrap()
.push(state.read(cx).value().to_string());
}
},
)
.detach();
});

Expand All @@ -1191,7 +1196,9 @@ mod tests {
// A value written through SetValue, then Return, submits it.
let base: TextInputState = textarea.clone().into();
let value = gpui::accesskit::ActionData::Value("hello".into());
cx.update(|window, cx| Input::handle_accessibility_set_value(&base, Some(&value), window, cx));
cx.update(|window, cx| {
Input::handle_accessibility_set_value(&base, Some(&value), window, cx)
});
cx.simulate_keystrokes("enter");
cx.run_until_parked();
// The test platform also delivers Return's "\n" as text input, so
Expand All @@ -1202,7 +1209,11 @@ mod tests {
.iter()
.map(|value| value.trim().to_string())
.collect();
assert_eq!(submitted, vec!["hello".to_string()], "Return submits the draft");
assert_eq!(
submitted,
vec!["hello".to_string()],
"Return submits the draft"
);
}

// A disabled Input keeps its editor unfocused when its frame is focused.
Expand Down Expand Up @@ -1290,14 +1301,20 @@ mod tests {
draw(&mut cx);
cx.update(|window, cx| window.focus_prev(cx));
draw(&mut cx);
assert!(cx.update(|window, _| before.is_focused(window)), "Shift+Tab leaves the input");
assert!(
cx.update(|window, _| before.is_focused(window)),
"Shift+Tab leaves the input"
);

cx.update(|window, cx| input.focus_handle(cx).focus(window, cx));
draw(&mut cx);
draw(&mut cx);
cx.update(|window, cx| window.focus_next(cx));
draw(&mut cx);
assert!(cx.update(|window, _| after.is_focused(window)), "Tab leaves the input");
assert!(
cx.update(|window, _| after.is_focused(window)),
"Tab leaves the input"
);
}

#[test]
Expand Down
93 changes: 91 additions & 2 deletions crates/component/src/input/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,99 @@ pub(super) fn sync_focused_input_registry(
let focused = state.focus_handle(cx).is_focused(window);
Root::try_update(window, cx, |root, _, cx| {
if focused {
root.focused_input = Some(state.clone());
if root.focused_input.as_ref() != Some(&state) {
root.focused_input = Some(state.clone());
cx.notify();
}
} else if root.focused_input.as_ref() == Some(&state) {
root.focused_input = None;
cx.notify();
}
cx.notify();
});
}

#[cfg(test)]
mod registry_tests {
use super::{AnyInputState, InputState, sync_focused_input_registry};
use crate::Root;
use gpui::{
AppContext as _, Context, Focusable as _, IntoElement, Render, TestAppContext,
VisualTestContext, Window, div,
};
use std::{cell::Cell, rc::Rc};

struct EmptyView;
impl Render for EmptyView {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
}
}

#[gpui::test]
fn focused_input_registry_only_notifies_on_registration_changes(cx: &mut TestAppContext) {
cx.update(crate::init);
let mut inputs = None;
let root = cx.update(|cx| {
cx.open_window(Default::default(), |window, cx| {
inputs = Some((
cx.new(|cx| InputState::new(window, cx)),
cx.new(|cx| InputState::new(window, cx)),
));
let view = cx.new(|_| EmptyView);
cx.new(|cx| Root::new(view, window, cx))
})
.unwrap()
});
let (first, second) = inputs.unwrap();
let notifications = Rc::new(Cell::new(0));
let observation = cx.update(|cx| {
let count = notifications.clone();
let entity = root.update(cx, |_, _, cx| cx.entity()).unwrap();
cx.observe(&entity, move |_, _| count.set(count.get() + 1))
});
let cx = &mut VisualTestContext::from_window(root.into(), cx);
cx.update(|window, cx| {
window.activate_window();
first.focus_handle(cx).focus(window, cx);
sync_focused_input_registry(first.clone(), window, cx);
});
assert_eq!(notifications.get(), 1);
cx.update(|window, cx| {
for _ in 0..4 {
sync_focused_input_registry(first.clone(), window, cx);
sync_focused_input_registry(second.clone(), window, cx);
}
});
assert_eq!(
notifications.get(),
1,
"unchanged render registration is silent"
);
cx.update(|window, cx| {
second.focus_handle(cx).focus(window, cx);
sync_focused_input_registry(second.clone(), window, cx);
sync_focused_input_registry(first.clone(), window, cx);
assert_eq!(
Root::read(window, cx).focused_input,
Some(AnyInputState::from(second.clone()))
);
});
assert_eq!(
notifications.get(),
2,
"an old input cannot clear the new focused input"
);
cx.update(|window, cx| {
window.blur(cx);
sync_focused_input_registry(second.clone(), window, cx);
sync_focused_input_registry(second.clone(), window, cx);
assert_eq!(Root::read(window, cx).focused_input, None);
});
assert_eq!(
notifications.get(),
3,
"unregister exactly once when focus leaves"
);
drop(observation);
}
}
10 changes: 5 additions & 5 deletions examples/ai_recipes/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading