Detailed changes
@@ -29,7 +29,7 @@ use futures::future::Shared;
use futures::{FutureExt, StreamExt};
use gpui::{
div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
- ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
+ ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
ViewContext, VisualContext, WeakView, WindowContext,
@@ -296,7 +296,7 @@ impl AssistantPanel {
}
}
- fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+ fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
self.toolbar
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
cx.notify();
@@ -277,19 +277,19 @@ impl InlineAssistant {
) {
let assist_id = inline_assist_editor.read(cx).id;
match event {
- InlineAssistEditorEvent::Started => {
+ InlineAssistEditorEvent::StartRequested => {
self.start_inline_assist(assist_id, cx);
}
- InlineAssistEditorEvent::Stopped => {
+ InlineAssistEditorEvent::StopRequested => {
self.stop_inline_assist(assist_id, cx);
}
- InlineAssistEditorEvent::Confirmed => {
+ InlineAssistEditorEvent::ConfirmRequested => {
self.finish_inline_assist(assist_id, false, cx);
}
- InlineAssistEditorEvent::Canceled => {
+ InlineAssistEditorEvent::CancelRequested => {
self.finish_inline_assist(assist_id, true, cx);
}
- InlineAssistEditorEvent::Dismissed => {
+ InlineAssistEditorEvent::DismissRequested => {
self.dismiss_inline_assist(assist_id, cx);
}
InlineAssistEditorEvent::Resized { height_in_lines } => {
@@ -345,14 +345,8 @@ impl InlineAssistant {
match event {
EditorEvent::SelectionsChanged { local } if *local => {
- if let Some(decorations) = assist.editor_decorations.as_ref() {
- if decorations
- .prompt_editor
- .focus_handle(cx)
- .contains_focused(cx)
- {
- cx.focus_view(&editor);
- }
+ if let CodegenStatus::Idle = &assist.codegen.read(cx).status {
+ self.finish_inline_assist(assist_id, true, cx);
}
}
EditorEvent::Saved => {
@@ -813,11 +807,11 @@ impl InlineAssistId {
}
enum InlineAssistEditorEvent {
- Started,
- Stopped,
- Confirmed,
- Canceled,
- Dismissed,
+ StartRequested,
+ StopRequested,
+ ConfirmRequested,
+ CancelRequested,
+ DismissRequested,
Resized { height_in_lines: u8 },
}
@@ -850,15 +844,17 @@ impl Render for InlineAssistEditor {
.icon_size(IconSize::XSmall)
.tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
.on_click(
- cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Started)),
+ cx.listener(|_, _, cx| {
+ cx.emit(InlineAssistEditorEvent::StartRequested)
+ }),
),
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
- .on_click(
- cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
- ),
+ .on_click(cx.listener(|_, _, cx| {
+ cx.emit(InlineAssistEditorEvent::CancelRequested)
+ })),
]
}
CodegenStatus::Pending => {
@@ -876,15 +872,15 @@ impl Render for InlineAssistEditor {
)
})
.on_click(
- cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Stopped)),
+ cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::StopRequested)),
),
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
- .on_click(
- cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
- ),
+ .on_click(cx.listener(|_, _, cx| {
+ cx.emit(InlineAssistEditorEvent::CancelRequested)
+ })),
]
}
CodegenStatus::Error(_) | CodegenStatus::Done => {
@@ -903,7 +899,7 @@ impl Render for InlineAssistEditor {
)
})
.on_click(cx.listener(|_, _, cx| {
- cx.emit(InlineAssistEditorEvent::Started);
+ cx.emit(InlineAssistEditorEvent::StartRequested);
}))
} else {
IconButton::new("confirm", IconName::Check)
@@ -911,16 +907,16 @@ impl Render for InlineAssistEditor {
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| {
- cx.emit(InlineAssistEditorEvent::Confirmed);
+ cx.emit(InlineAssistEditorEvent::ConfirmRequested);
}))
},
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
- .on_click(
- cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
- ),
+ .on_click(cx.listener(|_, _, cx| {
+ cx.emit(InlineAssistEditorEvent::CancelRequested)
+ })),
]
}
};
@@ -1100,23 +1096,6 @@ impl InlineAssistEditor {
self.edited_since_done = true;
cx.notify();
}
- EditorEvent::Blurred => {
- if let CodegenStatus::Idle = &self.codegen.read(cx).status {
- let assistant_panel_is_focused = self
- .workspace
- .as_ref()
- .and_then(|workspace| {
- let panel =
- workspace.upgrade()?.read(cx).panel::<AssistantPanel>(cx)?;
- Some(panel.focus_handle(cx).contains_focused(cx))
- })
- .unwrap_or(false);
-
- if !assistant_panel_is_focused {
- cx.emit(InlineAssistEditorEvent::Canceled);
- }
- }
- }
_ => {}
}
}
@@ -1142,10 +1121,10 @@ impl InlineAssistEditor {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
- cx.emit(InlineAssistEditorEvent::Canceled);
+ cx.emit(InlineAssistEditorEvent::CancelRequested);
}
CodegenStatus::Pending => {
- cx.emit(InlineAssistEditorEvent::Stopped);
+ cx.emit(InlineAssistEditorEvent::StopRequested);
}
}
}
@@ -1153,16 +1132,16 @@ impl InlineAssistEditor {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
- cx.emit(InlineAssistEditorEvent::Started);
+ cx.emit(InlineAssistEditorEvent::StartRequested);
}
CodegenStatus::Pending => {
- cx.emit(InlineAssistEditorEvent::Dismissed);
+ cx.emit(InlineAssistEditorEvent::DismissRequested);
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
if self.edited_since_done {
- cx.emit(InlineAssistEditorEvent::Started);
+ cx.emit(InlineAssistEditorEvent::StartRequested);
} else {
- cx.emit(InlineAssistEditorEvent::Confirmed);
+ cx.emit(InlineAssistEditorEvent::ConfirmRequested);
}
}
}
@@ -150,7 +150,7 @@ impl ProjectDiagnosticsEditor {
let focus_handle = cx.focus_handle();
cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
.detach();
- cx.on_focus_out(&focus_handle, |this, cx| this.focus_out(cx))
+ cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
.detach();
let excerpts = cx.new_model(|cx| {
@@ -66,11 +66,12 @@ use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
- Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
- FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, ListSizingBehavior, Model,
- MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
- Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
- View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
+ Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
+ FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
+ ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
+ Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
+ UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
+ WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -448,6 +449,7 @@ struct BufferOffset(usize);
/// See the [module level documentation](self) for more information.
pub struct Editor {
focus_handle: FocusHandle,
+ last_focused: Option<WeakFocusHandle>,
/// The text buffer being edited
buffer: Model<MultiBuffer>,
/// Map of how text in the buffer should be displayed.
@@ -1735,6 +1737,8 @@ impl Editor {
);
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, Self::handle_focus).detach();
+ cx.on_focus_out(&focus_handle, Self::handle_focus_out)
+ .detach();
cx.on_blur(&focus_handle, Self::handle_blur).detach();
let show_indent_guides = if mode == EditorMode::SingleLine {
@@ -1745,6 +1749,7 @@ impl Editor {
let mut this = Self {
focus_handle,
+ last_focused: None,
buffer: buffer.clone(),
display_map: display_map.clone(),
selections,
@@ -11315,9 +11320,13 @@ impl Editor {
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(EditorEvent::Focused);
- if let Some(rename) = self.pending_rename.as_ref() {
- let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
- cx.focus(&rename_editor_focus_handle);
+
+ if let Some(last_focused) = self
+ .last_focused
+ .take()
+ .and_then(|last_focused| last_focused.upgrade())
+ {
+ cx.focus(&last_focused);
} else {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::focus)
@@ -11339,6 +11348,10 @@ impl Editor {
}
}
+ fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
+ self.last_focused = Some(event.blurred);
+ }
+
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
self.blink_manager.update(cx, BlinkManager::disable);
self.buffer
@@ -85,13 +85,20 @@ impl DispatchPhase {
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
-type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
+type AnyWindowFocusListener =
+ Box<dyn FnMut(&WindowFocusEvent, &mut WindowContext) -> bool + 'static>;
-struct FocusEvent {
+struct WindowFocusEvent {
previous_focus_path: SmallVec<[FocusId; 8]>,
current_focus_path: SmallVec<[FocusId; 8]>,
}
+/// This is provided when subscribing for `ViewContext::on_focus_out` events.
+pub struct FocusOutEvent {
+ /// A weak focus handle representing what was blurred.
+ pub blurred: WeakFocusHandle,
+}
+
slotmap::new_key_type! {
/// A globally unique identifier for a focusable element.
pub struct FocusId;
@@ -1397,7 +1404,7 @@ impl<'a> WindowContext<'a> {
.retain(&(), |listener| listener(self));
}
- let event = FocusEvent {
+ let event = WindowFocusEvent {
previous_focus_path: if previous_window_active {
previous_focus_path
} else {
@@ -4055,6 +4062,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
/// Register a listener to be called when the given focus handle or one of its descendants receives focus.
+ /// This does not fire if the given focus handle - or one of its descendants - was previously focused.
/// Returns a subscription and persists until the subscription is dropped.
pub fn on_focus_in(
&mut self,
@@ -4124,17 +4132,25 @@ impl<'a, V: 'static> ViewContext<'a, V> {
pub fn on_focus_out(
&mut self,
handle: &FocusHandle,
- mut listener: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
+ mut listener: impl FnMut(&mut V, FocusOutEvent, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
let focus_id = handle.id;
let (subscription, activate) =
self.window.new_focus_listener(Box::new(move |event, cx| {
view.update(cx, |view, cx| {
- if event.previous_focus_path.contains(&focus_id)
- && !event.current_focus_path.contains(&focus_id)
- {
- listener(view, cx)
+ if let Some(blurred_id) = event.previous_focus_path.last().copied() {
+ if event.previous_focus_path.contains(&focus_id)
+ && !event.current_focus_path.contains(&focus_id)
+ {
+ let event = FocusOutEvent {
+ blurred: WeakFocusHandle {
+ id: blurred_id,
+ handles: Arc::downgrade(&cx.window.focus_handles),
+ },
+ };
+ listener(view, event, cx)
+ }
}
})
.is_ok()
@@ -4193,7 +4209,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
});
}
- /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe].
+ /// Emit an event to be handled by any other views that have subscribed via [ViewContext::subscribe].
pub fn emit<Evt>(&mut self, event: Evt)
where
Evt: 'static,
@@ -152,7 +152,7 @@ impl TerminalView {
let focus_in = cx.on_focus_in(&focus_handle, |terminal_view, cx| {
terminal_view.focus_in(cx);
});
- let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, cx| {
+ let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, _event, cx| {
terminal_view.focus_out(cx);
});
@@ -87,7 +87,7 @@ impl ModalLayer {
cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| {
this.hide_modal(cx);
}),
- cx.on_focus_out(&focus_handle, |this, cx| {
+ cx.on_focus_out(&focus_handle, |this, _event, cx| {
if this.dismiss_on_focus_lost {
this.hide_modal(cx);
}
@@ -14,9 +14,10 @@ use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
AppContext, AsyncWindowContext, ClickEvent, DismissEvent, Div, DragMoveEvent, EntityId,
- EventEmitter, ExternalPaths, FocusHandle, FocusableView, KeyContext, Model, MouseButton,
- MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle,
- Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView, WindowContext,
+ EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext, Model,
+ MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
+ ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView,
+ WindowContext,
};
use itertools::Itertools;
use parking_lot::Mutex;
@@ -517,7 +518,7 @@ impl Pane {
.map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
}
- fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+ fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
self.was_focused = false;
self.toolbar.update(cx, |toolbar, cx| {
toolbar.focus_changed(false, cx);
@@ -2214,7 +2214,7 @@ mod tests {
cx.background_executor.run_until_parked();
window
- .read_with(cx, |workspace, cx| {
+ .update(cx, |workspace, cx| {
assert_eq!(workspace.panes().len(), 1);
assert!(workspace.active_item(cx).is_none());
})