Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -46,11 +46,11 @@ const GIT_PANEL_KEY: &str = "GitPanel";
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<GitPanel>(cx);
pub fn init(cx: &mut App) {
cx.observe_new(
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<GitPanel>(window, cx);
});
},
)
@ -83,15 +83,15 @@ pub struct GitPanel {
fs: Arc<dyn Fs>,
hide_scrollbar_task: Option<Task<()>>,
pending_serialization: Task<Option<()>>,
workspace: WeakView<Workspace>,
project: Model<Project>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
active_repository: Option<RepositoryHandle>,
scroll_handle: UniformListScrollHandle,
scrollbar_state: ScrollbarState,
selected_entry: Option<usize>,
show_scrollbar: bool,
update_visible_entries_task: Task<()>,
commit_editor: View<Editor>,
commit_editor: Entity<Editor>,
visible_entries: Vec<GitListEntry>,
all_staged: Option<bool>,
width: Option<Pixels>,
@ -100,11 +100,12 @@ pub struct GitPanel {
fn commit_message_editor(
active_repository: Option<&RepositoryHandle>,
cx: &mut ViewContext<'_, Editor>,
window: &mut Window,
cx: &mut Context<'_, Editor>,
) -> Editor {
let theme = ThemeSettings::get_global(cx);
let mut text_style = cx.text_style();
let mut text_style = window.text_style();
let refinement = TextStyleRefinement {
font_family: Some(theme.buffer_font.family.clone()),
font_features: Some(FontFeatures::disable_ligatures()),
@ -116,17 +117,17 @@ fn commit_message_editor(
text_style.refine(&refinement);
let mut commit_editor = if let Some(active_repository) = active_repository.as_ref() {
let buffer =
cx.new_model(|cx| MultiBuffer::singleton(active_repository.commit_message(), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(active_repository.commit_message(), cx));
Editor::new(
EditorMode::AutoHeight { max_lines: 10 },
buffer,
None,
false,
window,
cx,
)
} else {
Editor::auto_height(10, cx)
Editor::auto_height(10, window, cx)
};
commit_editor.set_use_autoclose(false);
commit_editor.set_show_gutter(false, cx);
@ -139,40 +140,48 @@ fn commit_message_editor(
impl GitPanel {
pub fn load(
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move { workspace.update(&mut cx, Self::new) })
) -> Task<Result<Entity<Self>>> {
cx.spawn(|mut cx| async move { workspace.update_in(&mut cx, Self::new) })
}
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
pub fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let fs = workspace.app_state().fs.clone();
let project = workspace.project().clone();
let git_state = project.read(cx).git_state().cloned();
let active_repository = project.read(cx).active_repository(cx);
let (err_sender, mut err_receiver) = mpsc::channel(1);
let workspace = cx.view().downgrade();
let workspace = cx.model().downgrade();
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
let git_panel = cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, |this, _, cx| {
this.hide_scrollbar(cx);
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
this.hide_scrollbar(window, cx);
})
.detach();
let commit_editor =
cx.new_view(|cx| commit_message_editor(active_repository.as_ref(), cx));
cx.new(|cx| commit_message_editor(active_repository.as_ref(), window, cx));
let scroll_handle = UniformListScrollHandle::new();
if let Some(git_state) = git_state {
cx.subscribe(&git_state, move |this, git_state, event, cx| match event {
project::git::Event::RepositoriesUpdated => {
this.active_repository = git_state.read(cx).active_repository();
this.schedule_update(cx);
}
})
cx.subscribe_in(
&git_state,
window,
move |this, git_state, event, window, cx| match event {
project::git::Event::RepositoriesUpdated => {
this.active_repository = git_state.read(cx).active_repository();
this.schedule_update(window, cx);
}
},
)
.detach();
}
@ -181,9 +190,10 @@ impl GitPanel {
pending_serialization: Task::ready(None),
visible_entries: Vec::new(),
all_staged: None,
current_modifiers: cx.modifiers(),
current_modifiers: window.modifiers(),
width: Some(px(360.)),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
scrollbar_state: ScrollbarState::new(scroll_handle.clone())
.parent_model(&cx.model()),
selected_entry: None,
show_scrollbar: false,
hide_scrollbar_task: None,
@ -196,7 +206,7 @@ impl GitPanel {
err_sender,
workspace,
};
git_panel.schedule_update(cx);
git_panel.schedule_update(window, cx);
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
git_panel
});
@ -219,13 +229,14 @@ impl GitPanel {
})
.detach();
cx.subscribe(
cx.subscribe_in(
&git_panel,
move |workspace, _, event: &Event, cx| match event.clone() {
window,
move |workspace, _, event: &Event, window, cx| match event.clone() {
Event::OpenedEntry { path } => {
workspace
.open_path_preview(path, None, false, false, cx)
.detach_and_prompt_err("Failed to open file", cx, |e, _| {
.open_path_preview(path, None, false, false, window, cx)
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
Some(format!("{e}"))
});
}
@ -237,7 +248,7 @@ impl GitPanel {
git_panel
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
fn serialize(&mut self, cx: &mut Context<Self>) {
// TODO: we can store stage status here
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
@ -254,45 +265,46 @@ impl GitPanel {
);
}
fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
fn dispatch_context(&self, window: &mut Window, cx: &Context<Self>) -> KeyContext {
let mut dispatch_context = KeyContext::new_with_defaults();
dispatch_context.add("GitPanel");
if self.is_focused(cx) {
if self.is_focused(window, cx) {
dispatch_context.add("menu");
dispatch_context.add("ChangesList");
}
if self.commit_editor.read(cx).is_focused(cx) {
if self.commit_editor.read(cx).is_focused(window) {
dispatch_context.add("CommitEditor");
}
dispatch_context
}
fn is_focused(&self, cx: &ViewContext<Self>) -> bool {
cx.focused()
fn is_focused(&self, window: &Window, cx: &Context<Self>) -> bool {
window
.focused(cx)
.map_or(false, |focused| self.focus_handle == focused)
}
fn close_panel(&mut self, _: &Close, cx: &mut ViewContext<Self>) {
fn close_panel(&mut self, _: &Close, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(PanelEvent::Close);
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
if !self.focus_handle.contains_focused(cx) {
fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if !self.focus_handle.contains_focused(window, cx) {
cx.emit(Event::Focus);
}
}
fn show_scrollbar(&self, cx: &mut ViewContext<Self>) -> ShowScrollbar {
fn show_scrollbar(&self, cx: &mut Context<Self>) -> ShowScrollbar {
GitPanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
fn should_show_scrollbar(&self, cx: &mut ViewContext<Self>) -> bool {
fn should_show_scrollbar(&self, cx: &mut Context<Self>) -> bool {
let show = self.show_scrollbar(cx);
match show {
ShowScrollbar::Auto => true,
@ -302,7 +314,7 @@ impl GitPanel {
}
}
fn should_autohide_scrollbar(&self, cx: &mut ViewContext<Self>) -> bool {
fn should_autohide_scrollbar(&self, cx: &mut Context<Self>) -> bool {
let show = self.show_scrollbar(cx);
match show {
ShowScrollbar::Auto => true,
@ -314,12 +326,12 @@ impl GitPanel {
}
}
fn hide_scrollbar(&mut self, cx: &mut ViewContext<Self>) {
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !self.should_autohide_scrollbar(cx) {
return;
}
self.hide_scrollbar_task = Some(cx.spawn(|panel, mut cx| async move {
self.hide_scrollbar_task = Some(cx.spawn_in(window, |panel, mut cx| async move {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
@ -335,7 +347,8 @@ impl GitPanel {
fn handle_modifiers_changed(
&mut self,
event: &ModifiersChangedEvent,
cx: &mut ViewContext<Self>,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.current_modifiers = event.modifiers;
cx.notify();
@ -366,7 +379,7 @@ impl GitPanel {
(0, 0)
}
fn scroll_to_selected_entry(&mut self, cx: &mut ViewContext<Self>) {
fn scroll_to_selected_entry(&mut self, cx: &mut Context<Self>) {
if let Some(selected_entry) = self.selected_entry {
self.scroll_handle
.scroll_to_item(selected_entry, ScrollStrategy::Center);
@ -375,14 +388,14 @@ impl GitPanel {
cx.notify();
}
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
if self.visible_entries.first().is_some() {
self.selected_entry = Some(0);
self.scroll_to_selected_entry(cx);
}
}
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
fn select_prev(&mut self, _: &SelectPrev, _window: &mut Window, cx: &mut Context<Self>) {
let item_count = self.visible_entries.len();
if item_count == 0 {
return;
@ -403,7 +416,7 @@ impl GitPanel {
cx.notify();
}
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
let item_count = self.visible_entries.len();
if item_count == 0 {
return;
@ -424,21 +437,21 @@ impl GitPanel {
cx.notify();
}
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
fn select_last(&mut self, _: &SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
if self.visible_entries.last().is_some() {
self.selected_entry = Some(self.visible_entries.len() - 1);
self.scroll_to_selected_entry(cx);
}
}
fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
self.commit_editor.update(cx, |editor, cx| {
editor.focus(cx);
window.focus(&editor.focus_handle(cx));
});
cx.notify();
}
fn select_first_entry_if_none(&mut self, cx: &mut ViewContext<Self>) {
fn select_first_entry_if_none(&mut self, cx: &mut Context<Self>) {
let have_entries = self
.active_repository
.as_ref()
@ -452,10 +465,15 @@ impl GitPanel {
}
}
fn focus_changes_list(&mut self, _: &FocusChanges, cx: &mut ViewContext<Self>) {
fn focus_changes_list(
&mut self,
_: &FocusChanges,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.select_first_entry_if_none(cx);
cx.focus_self();
cx.focus_self(window);
cx.notify();
}
@ -464,7 +482,7 @@ impl GitPanel {
.and_then(|i| self.visible_entries.get(i))
}
fn open_selected(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn open_selected(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
if let Some(entry) = self
.selected_entry
.and_then(|i| self.visible_entries.get(i))
@ -473,7 +491,12 @@ impl GitPanel {
}
}
fn toggle_staged_for_entry(&mut self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
fn toggle_staged_for_entry(
&mut self,
entry: &GitListEntry,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -489,13 +512,18 @@ impl GitPanel {
cx.notify();
}
fn toggle_staged_for_selected(&mut self, _: &git::ToggleStaged, cx: &mut ViewContext<Self>) {
fn toggle_staged_for_selected(
&mut self,
_: &git::ToggleStaged,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(selected_entry) = self.get_selected_entry().cloned() {
self.toggle_staged_for_entry(&selected_entry, cx);
self.toggle_staged_for_entry(&selected_entry, window, cx);
}
}
fn open_entry(&self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
fn open_entry(&self, entry: &GitListEntry, cx: &mut Context<Self>) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -512,7 +540,7 @@ impl GitPanel {
cx.emit(Event::OpenedEntry { path });
}
fn stage_all(&mut self, _: &git::StageAll, cx: &mut ViewContext<Self>) {
fn stage_all(&mut self, _: &git::StageAll, _window: &mut Window, cx: &mut Context<Self>) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -526,7 +554,7 @@ impl GitPanel {
};
}
fn unstage_all(&mut self, _: &git::UnstageAll, cx: &mut ViewContext<Self>) {
fn unstage_all(&mut self, _: &git::UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -539,13 +567,18 @@ impl GitPanel {
};
}
fn discard_all(&mut self, _: &git::RevertAll, _cx: &mut ViewContext<Self>) {
fn discard_all(&mut self, _: &git::RevertAll, _window: &mut Window, _cx: &mut Context<Self>) {
// TODO: Implement discard all
println!("Discard all triggered");
}
/// Commit all staged changes
fn commit_changes(&mut self, _: &git::CommitChanges, cx: &mut ViewContext<Self>) {
fn commit_changes(
&mut self,
_: &git::CommitChanges,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -556,7 +589,12 @@ impl GitPanel {
}
/// Commit all changes, regardless of whether they are staged or not
fn commit_all_changes(&mut self, _: &git::CommitAllChanges, cx: &mut ViewContext<Self>) {
fn commit_all_changes(
&mut self,
_: &git::CommitAllChanges,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
@ -566,7 +604,7 @@ impl GitPanel {
active_repository.commit_all(self.err_sender.clone(), cx);
}
fn fill_co_authors(&mut self, _: &FillCoAuthors, cx: &mut ViewContext<Self>) {
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
const CO_AUTHOR_PREFIX: &str = "Co-authored-by: ";
let Some(room) = self
@ -626,16 +664,16 @@ impl GitPanel {
}
editor.edit(Some((editor_end..editor_end, edit)), cx);
editor.move_to_end(&MoveToEnd, cx);
editor.focus(cx);
editor.move_to_end(&MoveToEnd, window, cx);
editor.focus_handle(cx).focus(window);
});
}
fn for_each_visible_entry(
&self,
range: Range<usize>,
cx: &mut ViewContext<Self>,
mut callback: impl FnMut(usize, GitListEntry, &mut ViewContext<Self>),
cx: &mut Context<Self>,
mut callback: impl FnMut(usize, GitListEntry, &mut Context<Self>),
) {
let visible_entries = &self.visible_entries;
@ -664,23 +702,23 @@ impl GitPanel {
}
}
fn schedule_update(&mut self, cx: &mut ViewContext<Self>) {
let handle = cx.view().downgrade();
self.update_visible_entries_task = cx.spawn(|_, mut cx| async move {
fn schedule_update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let handle = cx.model().downgrade();
self.update_visible_entries_task = cx.spawn_in(window, |_, mut cx| async move {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
if let Some(this) = handle.upgrade() {
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.update_visible_entries(cx);
let active_repository = this.active_repository.as_ref();
this.commit_editor =
cx.new_view(|cx| commit_message_editor(active_repository, cx));
cx.new(|cx| commit_message_editor(active_repository, window, cx));
})
.ok();
}
});
}
fn update_visible_entries(&mut self, cx: &mut ViewContext<Self>) {
fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
self.visible_entries.clear();
let Some(repo) = self.active_repository.as_ref() else {
@ -747,15 +785,15 @@ impl GitPanel {
cx.notify();
}
fn show_err_toast(&self, id: &'static str, e: anyhow::Error, cx: &mut ViewContext<Self>) {
fn show_err_toast(&self, id: &'static str, e: anyhow::Error, cx: &mut Context<Self>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let notif_id = NotificationId::Named(id.into());
let message = e.to_string();
workspace.update(cx, |workspace, cx| {
let toast = Toast::new(notif_id, message).on_click("Open Zed Log", |cx| {
cx.dispatch_action(workspace::OpenLog.boxed_clone());
let toast = Toast::new(notif_id, message).on_click("Open Zed Log", |window, cx| {
window.dispatch_action(workspace::OpenLog.boxed_clone(), cx);
});
workspace.show_toast(toast, cx);
});
@ -779,14 +817,18 @@ impl GitPanel {
.style(ButtonStyle::Filled)
}
pub fn render_divider(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
pub fn render_divider(&self, _cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.items_center()
.h(px(8.))
.child(Divider::horizontal_dashed().color(DividerColor::Border))
}
pub fn render_panel_header(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
pub fn render_panel_header(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let focus_handle = self.focus_handle(cx).clone();
let entry_count = self
.active_repository
@ -822,18 +864,18 @@ impl GitPanel {
)
.fill()
.elevation(ElevationIndex::Surface)
.tooltip(move |cx| {
if all_staged {
Tooltip::text("Unstage all changes", cx)
} else {
Tooltip::text("Stage all changes", cx)
}
.tooltip(if all_staged {
Tooltip::text("Unstage all changes")
} else {
Tooltip::text("Stage all changes")
})
.disabled(entry_count == 0)
.on_click(cx.listener(move |git_panel, _, cx| match all_staged {
true => git_panel.unstage_all(&UnstageAll, cx),
false => git_panel.stage_all(&StageAll, cx),
})),
.on_click(cx.listener(
move |git_panel, _, window, cx| match all_staged {
true => git_panel.unstage_all(&UnstageAll, window, cx),
false => git_panel.stage_all(&StageAll, window, cx),
},
)),
)
.child(
div()
@ -841,10 +883,12 @@ impl GitPanel {
.text_buffer(cx)
.text_ui_sm(cx)
.child(changes_string)
.on_click(cx.listener(move |git_panel, _, cx| match all_staged {
true => git_panel.unstage_all(&UnstageAll, cx),
false => git_panel.stage_all(&StageAll, cx),
})),
.on_click(cx.listener(
move |git_panel, _, window, cx| match all_staged {
true => git_panel.unstage_all(&UnstageAll, window, cx),
false => git_panel.stage_all(&StageAll, window, cx),
},
)),
),
)
.child(div().flex_grow())
@ -872,11 +916,12 @@ impl GitPanel {
self.panel_button("unstage-all", "Unstage All")
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Unstage all changes",
&UnstageAll,
&focus_handle,
window,
cx,
)
}
@ -884,20 +929,21 @@ impl GitPanel {
.key_binding(ui::KeyBinding::for_action_in(
&UnstageAll,
&focus_handle,
cx,
window,
))
.on_click(
cx.listener(move |this, _, cx| this.unstage_all(&UnstageAll, cx)),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.unstage_all(&UnstageAll, window, cx)
}))
} else {
self.panel_button("stage-all", "Stage All")
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Stage all changes",
&StageAll,
&focus_handle,
window,
cx,
)
}
@ -905,14 +951,16 @@ impl GitPanel {
.key_binding(ui::KeyBinding::for_action_in(
&StageAll,
&focus_handle,
cx,
window,
))
.on_click(cx.listener(move |this, _, cx| this.stage_all(&StageAll, cx)))
.on_click(cx.listener(move |this, _, window, cx| {
this.stage_all(&StageAll, window, cx)
}))
}),
)
}
pub fn render_commit_editor(&self, cx: &ViewContext<Self>) -> impl IntoElement {
pub fn render_commit_editor(&self, cx: &Context<Self>) -> impl IntoElement {
let editor = self.commit_editor.clone();
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let (can_commit, can_commit_all) =
@ -930,34 +978,36 @@ impl GitPanel {
let commit_staged_button = self
.panel_button("commit-staged-changes", "Commit")
.tooltip(move |cx| {
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(
"Commit all staged changes",
&CommitChanges,
&focus_handle,
window,
cx,
)
})
.disabled(!can_commit)
.on_click(
cx.listener(|this, _: &ClickEvent, cx| this.commit_changes(&CommitChanges, cx)),
);
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.commit_changes(&CommitChanges, window, cx)
}));
let commit_all_button = self
.panel_button("commit-all-changes", "Commit All")
.tooltip(move |cx| {
.tooltip(move |window, cx| {
let focus_handle = focus_handle_2.clone();
Tooltip::for_action_in(
"Commit all changes, including unstaged changes",
&CommitAllChanges,
&focus_handle,
window,
cx,
)
})
.disabled(!can_commit_all)
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
this.commit_all_changes(&CommitAllChanges, cx)
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.commit_all_changes(&CommitAllChanges, window, cx)
}));
div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
@ -968,7 +1018,9 @@ impl GitPanel {
.py_2p5()
.px_3()
.bg(cx.theme().colors().editor_background)
.on_click(cx.listener(move |_, _: &ClickEvent, cx| cx.focus(&editor_focus_handle)))
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
window.focus(&editor_focus_handle);
}))
.child(self.commit_editor.clone())
.child(
h_flex()
@ -985,7 +1037,7 @@ impl GitPanel {
)
}
fn render_empty_state(&self, cx: &ViewContext<Self>) -> impl IntoElement {
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.h_full()
.flex_1()
@ -1001,7 +1053,7 @@ impl GitPanel {
)
}
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<Stateful<Div>> {
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
let scroll_bar_style = self.show_scrollbar(cx);
let show_container = matches!(scroll_bar_style, ShowScrollbar::Always);
@ -1022,30 +1074,30 @@ impl GitPanel {
.when(!show_container, |this| {
this.absolute().right_1().top_1().bottom_1().w(px(12.))
})
.on_mouse_move(cx.listener(|_, _, cx| {
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, cx| {
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, cx| {
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, cx| {
cx.listener(|this, _, window, cx| {
if !this.scrollbar_state.is_dragging()
&& !this.focus_handle.contains_focused(cx)
&& !this.focus_handle.contains_focused(window, cx)
{
this.hide_scrollbar(cx);
this.hide_scrollbar(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, cx| {
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(
@ -1055,15 +1107,15 @@ impl GitPanel {
)
}
fn render_entries(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_entries(&self, cx: &mut Context<Self>) -> impl IntoElement {
let entry_count = self.visible_entries.len();
h_flex()
.size_full()
.overflow_hidden()
.child(
uniform_list(cx.view().clone(), "entries", entry_count, {
move |git_panel, range, cx| {
uniform_list(cx.model().clone(), "entries", entry_count, {
move |git_panel, range, _window, cx| {
let mut items = Vec::with_capacity(range.end - range.start);
git_panel.for_each_visible_entry(range, cx, |ix, details, cx| {
items.push(git_panel.render_entry(ix, details, cx));
@ -1084,7 +1136,7 @@ impl GitPanel {
&self,
ix: usize,
entry_details: GitListEntry,
cx: &ViewContext<Self>,
cx: &Context<Self>,
) -> impl IntoElement {
let repo_path = entry_details.repo_path.clone();
let selected = self.selected_entry == Some(ix);
@ -1115,7 +1167,7 @@ impl GitPanel {
let checkbox_id =
ElementId::Name(format!("checkbox_{}", entry_details.display_name).into());
let is_tree_view = false;
let handle = cx.view().downgrade();
let handle = cx.model().downgrade();
let end_slot = h_flex()
.invisible()
@ -1168,7 +1220,7 @@ impl GitPanel {
.on_click({
let handle = handle.clone();
let repo_path = repo_path.clone();
move |toggle, cx| {
move |toggle, _window, cx| {
let Some(this) = handle.upgrade() else {
return;
};
@ -1220,9 +1272,9 @@ impl GitPanel {
)
.child(div().flex_1())
.child(end_slot)
.on_click(move |_, cx| {
.on_click(move |_, window, cx| {
// TODO: add `select_entry` method then do after that
cx.dispatch_action(Box::new(OpenSelected));
window.dispatch_action(Box::new(OpenSelected), cx);
handle
.update(cx, |git_panel, _| {
@ -1236,7 +1288,7 @@ impl GitPanel {
}
impl Render for GitPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let project = self.project.read(cx);
let has_entries = self
.active_repository
@ -1260,24 +1312,32 @@ impl Render for GitPanel {
v_flex()
.id("git_panel")
.key_context(self.dispatch_context(cx))
.key_context(self.dispatch_context(window, cx))
.track_focus(&self.focus_handle)
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
.when(!project.is_read_only(cx), |this| {
this.on_action(cx.listener(|this, &ToggleStaged, cx| {
this.toggle_staged_for_selected(&ToggleStaged, cx)
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
this.toggle_staged_for_selected(&ToggleStaged, window, cx)
}))
.on_action(cx.listener(|this, &StageAll, cx| this.stage_all(&StageAll, cx)))
.on_action(cx.listener(|this, &UnstageAll, cx| this.unstage_all(&UnstageAll, cx)))
.on_action(cx.listener(|this, &RevertAll, cx| this.discard_all(&RevertAll, cx)))
.on_action(
cx.listener(|this, &CommitChanges, cx| this.commit_changes(&CommitChanges, cx)),
cx.listener(|this, &StageAll, window, cx| {
this.stage_all(&StageAll, window, cx)
}),
)
.on_action(cx.listener(|this, &CommitAllChanges, cx| {
this.commit_all_changes(&CommitAllChanges, cx)
.on_action(cx.listener(|this, &UnstageAll, window, cx| {
this.unstage_all(&UnstageAll, window, cx)
}))
.on_action(cx.listener(|this, &RevertAll, window, cx| {
this.discard_all(&RevertAll, window, cx)
}))
.on_action(cx.listener(|this, &CommitChanges, window, cx| {
this.commit_changes(&CommitChanges, window, cx)
}))
.on_action(cx.listener(|this, &CommitAllChanges, window, cx| {
this.commit_all_changes(&CommitAllChanges, window, cx)
}))
})
.when(self.is_focused(cx), |this| {
.when(self.is_focused(window, cx), |this| {
this.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
@ -1292,13 +1352,13 @@ impl Render for GitPanel {
git_panel.on_action(cx.listener(Self::fill_co_authors))
})
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
.on_hover(cx.listener(|this, hovered, cx| {
.on_hover(cx.listener(|this, hovered, window, cx| {
if *hovered {
this.show_scrollbar = true;
this.hide_scrollbar_task.take();
cx.notify();
} else if !this.focus_handle.contains_focused(cx) {
this.hide_scrollbar(cx);
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbar(window, cx);
}
}))
.size_full()
@ -1306,7 +1366,7 @@ impl Render for GitPanel {
.font_buffer(cx)
.py_1()
.bg(ElevationIndex::Surface.bg(cx))
.child(self.render_panel_header(cx))
.child(self.render_panel_header(window, cx))
.child(self.render_divider(cx))
.child(if has_entries {
self.render_entries(cx).into_any_element()
@ -1318,8 +1378,8 @@ impl Render for GitPanel {
}
}
impl FocusableView for GitPanel {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
impl Focusable for GitPanel {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
@ -1333,7 +1393,7 @@ impl Panel for GitPanel {
"GitPanel"
}
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, _: &Window, cx: &App) -> DockPosition {
GitPanelSettings::get_global(cx).dock
}
@ -1341,7 +1401,7 @@ impl Panel for GitPanel {
matches!(position, DockPosition::Left | DockPosition::Right)
}
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
settings::update_settings_file::<GitPanelSettings>(
self.fs.clone(),
cx,
@ -1349,22 +1409,22 @@ impl Panel for GitPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, _: &Window, cx: &App) -> Pixels {
self.width
.unwrap_or_else(|| GitPanelSettings::get_global(cx).default_width)
}
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
fn icon(&self, _: &Window, cx: &App) -> Option<ui::IconName> {
Some(ui::IconName::GitBranch).filter(|_| GitPanelSettings::get_global(cx).button)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
Some("Git Panel")
}

View file

@ -76,7 +76,7 @@ impl Settings for GitPanelSettings {
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
_: &mut gpui::App,
) -> anyhow::Result<Self> {
sources.json_merge()
}

View file

@ -1,19 +1,19 @@
use ::settings::Settings;
use git::status::FileStatus;
use git_panel_settings::GitPanelSettings;
use gpui::AppContext;
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement, WindowContext};
use gpui::App;
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
pub mod git_panel;
mod git_panel_settings;
pub mod repository_selector;
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
GitPanelSettings::register(cx);
}
// TODO: Add updated status colors to theme
pub fn git_status_icon(status: FileStatus, cx: &WindowContext) -> impl IntoElement {
pub fn git_status_icon(status: FileStatus, cx: &App) -> impl IntoElement {
let (icon_name, color) = if status.is_conflicted() {
(
IconName::Warning,

View file

@ -1,6 +1,6 @@
use gpui::{
AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
Subscription, Task, View, WeakModel, WeakView,
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
Task, WeakEntity,
};
use picker::{Picker, PickerDelegate};
use project::{
@ -11,7 +11,7 @@ use std::sync::Arc;
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
pub struct RepositorySelector {
picker: View<Picker<RepositorySelectorDelegate>>,
picker: Entity<Picker<RepositorySelectorDelegate>>,
/// The task used to update the picker's matches when there is a change to
/// the repository list.
update_matches_task: Option<Task<()>>,
@ -19,7 +19,7 @@ pub struct RepositorySelector {
}
impl RepositorySelector {
pub fn new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let git_state = project.read(cx).git_state().cloned();
let all_repositories = git_state
.as_ref()
@ -27,17 +27,18 @@ impl RepositorySelector {
let filtered_repositories = all_repositories.clone();
let delegate = RepositorySelectorDelegate {
project: project.downgrade(),
repository_selector: cx.view().downgrade(),
repository_selector: cx.model().downgrade(),
repository_entries: all_repositories,
filtered_repositories,
selected_index: 0,
};
let picker =
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()))
});
let _subscriptions = if let Some(git_state) = git_state {
vec![cx.subscribe(&git_state, Self::handle_project_git_event)]
vec![cx.subscribe_in(&git_state, window, Self::handle_project_git_event)]
} else {
Vec::new()
};
@ -51,15 +52,16 @@ impl RepositorySelector {
fn handle_project_git_event(
&mut self,
git_state: Model<GitState>,
git_state: &Entity<GitState>,
_event: &project::git::Event,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
// TODO handle events individually
let task = self.picker.update(cx, |this, cx| {
let query = this.query(cx);
this.delegate.repository_entries = git_state.read(cx).all_repositories();
this.delegate.update_matches(query, cx)
this.delegate.update_matches(query, window, cx)
});
self.update_matches_task = Some(task);
}
@ -67,14 +69,14 @@ impl RepositorySelector {
impl EventEmitter<DismissEvent> for RepositorySelector {}
impl FocusableView for RepositorySelector {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
impl Focusable for RepositorySelector {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for RepositorySelector {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@ -84,13 +86,13 @@ pub struct RepositorySelectorPopoverMenu<T>
where
T: PopoverTrigger,
{
repository_selector: View<RepositorySelector>,
repository_selector: Entity<RepositorySelector>,
trigger: T,
handle: Option<PopoverMenuHandle<RepositorySelector>>,
}
impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
pub fn new(repository_selector: View<RepositorySelector>, trigger: T) -> Self {
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T) -> Self {
Self {
repository_selector,
trigger,
@ -105,11 +107,11 @@ impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
}
impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let repository_selector = self.repository_selector.clone();
PopoverMenu::new("repository-switcher")
.menu(move |_cx| Some(repository_selector.clone()))
.menu(move |_window, _cx| Some(repository_selector.clone()))
.trigger(self.trigger)
.attach(gpui::Corner::BottomLeft)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
@ -117,8 +119,8 @@ impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> {
}
pub struct RepositorySelectorDelegate {
project: WeakModel<Project>,
repository_selector: WeakView<RepositorySelector>,
project: WeakEntity<Project>,
repository_selector: WeakEntity<RepositorySelector>,
repository_entries: Vec<RepositoryHandle>,
filtered_repositories: Vec<RepositoryHandle>,
selected_index: usize,
@ -143,19 +145,29 @@ impl PickerDelegate for RepositorySelectorDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix.min(self.filtered_repositories.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select a repository...".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let all_repositories = self.repository_entries.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let filtered_repositories = cx
.background_executor()
.spawn(async move {
@ -173,30 +185,34 @@ impl PickerDelegate for RepositorySelectorDelegate {
})
.await;
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.delegate.filtered_repositories = filtered_repositories;
this.delegate.set_selected_index(0, cx);
this.delegate.set_selected_index(0, window, cx);
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(selected_repo) = self.filtered_repositories.get(self.selected_index) else {
return;
};
selected_repo.activate(cx);
self.dismissed(cx);
self.dismissed(window, cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.repository_selector
.update(cx, |_this, cx| cx.emit(DismissEvent))
.ok();
}
fn render_header(&self, _cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
fn render_header(
&self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
// TODO: Implement header rendering if needed
None
}
@ -205,7 +221,8 @@ impl PickerDelegate for RepositorySelectorDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let project = self.project.upgrade()?;
let repo_info = self.filtered_repositories.get(ix)?;
@ -220,7 +237,11 @@ impl PickerDelegate for RepositorySelectorDelegate {
)
}
fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<gpui::AnyElement> {
fn render_footer(
&self,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<gpui::AnyElement> {
// TODO: Implement footer rendering if needed
Some(
div()