Compare commits

...

108 Commits

Author SHA1 Message Date
Michael Sloan
6b4d9ffd96 Copy some build fixes from post merge branch to the pre merge state 2025-01-19 15:04:08 -07:00
Mikayla
9626de34f7 Fix one of the vim tests 2025-01-14 22:18:39 -08:00
Mikayla
764af2fd87 remove observe_new_window_models 2025-01-14 21:28:02 -08:00
Mikayla
a5fa81586e Fix all warnings 2025-01-14 21:13:47 -08:00
Mikayla
4d4ea61b12 Replace Window::notify with AppContext::notify
Fix several tests

Co-authored-by: Nathan <nathan@zed.dev>
2025-01-13 14:46:06 -08:00
Mikayla
9f56b9208a Rename BlockContext.context to BlockContext.app
Rename `App.windows_accessed` to `AppContext.window_update_stack`

co-authored-by: Nathan <nathan@zed.dev>
2025-01-13 14:12:47 -08:00
Mikayla
1cedbc04db Call Window::refresh() instead of AppContext::refresh_windows()
Rename entities_read to accessed_entities

co-authored-by: Nathan <nathan@zed.dev>
2025-01-13 13:56:11 -08:00
Mikayla
643f2e1e45 WIP 2025-01-13 11:12:40 -08:00
Mikayla
6f6e995603 Remove last TODOs 2025-01-13 01:49:25 -08:00
Mikayla
b43fcb7b09 Fix a bug where cached views would not rebind their window notifies 2025-01-13 00:17:05 -08:00
Mikayla
de6abee965 Implement model creation effect, remove new_view_observers 2025-01-13 00:16:32 -08:00
Mikayla
93ad5c38f0 Remove window_id from focus handle 2025-01-10 14:23:15 -08:00
Mikayla
16e9304af6 Fix some of the warnings and pull out spurious window arguments 2025-01-09 17:07:22 -08:00
Mikayla
71a6e7e677 Begin stripping out the window arguments
co-authored-by: max <max@zed.dev>
2025-01-09 14:03:09 -08:00
Mikayla
f92da24fae Resolve more todos, fix a test
co-authored-by: Joseph <joseph@zed.dev>
2025-01-09 08:29:29 -08:00
Mikayla
4b3ed1152f Delete several commented out methods
Remove the .view() method
Fix a failing test
2025-01-08 15:46:40 -08:00
Mikayla
fc43ec805d Restore minimal notifies 2025-01-08 15:03:40 -08:00
Mikayla
3738d9c34a Fix new view observations
Remove some comments
2025-01-08 14:08:37 -08:00
Mikayla
2776d382c3 Fix a bad naming issue 2025-01-07 14:02:54 -08:00
Mikayla
8e002b1e5a Implement window refreshing when models are read during a frame 2025-01-07 13:47:49 -08:00
Cole Miller
898bc8cb8c examples/image_loading runs 2025-01-07 16:12:05 -05:00
Cole Miller
991345ed4e examples/gradient runs 2025-01-07 16:10:29 -05:00
Cole Miller
55d2e26fa5 examples/gif_viewer runs 2025-01-07 16:07:41 -05:00
Cole Miller
2813cc077f examples/animation runs 2025-01-07 16:00:22 -05:00
Nathan Sobo
696530b1e5 It compiles. Holy shit. 2025-01-03 14:51:16 -07:00
Nathan Sobo
1c6b2b9dd0 everything compiles but zed crate 2025-01-03 14:27:57 -07:00
Nathan Sobo
f4d76abf39 vim compiles 2025-01-03 12:55:35 -07:00
Nathan Sobo
4034691669 theme_selector compiles 2025-01-03 12:34:32 -07:00
Nathan Sobo
904e1ef8f7 welcome compiles 2025-01-03 12:30:52 -07:00
Nathan Sobo
9f73fb6ddc project_symbols compiles 2025-01-03 12:21:46 -07:00
Nathan Sobo
339ed1bad2 tasks_ui compiles 2025-01-03 12:17:05 -07:00
Antonio Scandurra
59fce2924d WIP: midway through new_path_prompt.rs 2025-01-03 19:26:58 +01:00
Antonio Scandurra
8d42bc011d WIP: midway through file_finder_tests.rs 2025-01-03 17:24:22 +01:00
Antonio Scandurra
47f46fe786 extensions_ui compiles 2025-01-03 17:17:47 +01:00
Antonio Scandurra
56d9e60ad7 assistant2 compiles 2025-01-03 17:13:21 +01:00
Antonio Scandurra
b94279da00 assistant compiles 2025-01-03 16:44:33 +01:00
Antonio Scandurra
f971c9eb5f outline_panel compiles 2025-01-03 13:18:35 +01:00
Antonio Scandurra
be4ecd9e21 outline compiles 2025-01-03 13:07:42 +01:00
Antonio Scandurra
6a3834fc91 language_model_selector compiles 2025-01-03 12:56:47 +01:00
Antonio Scandurra
952d10239d command_palette compiles 2025-01-03 12:56:00 +01:00
Antonio Scandurra
8fdc5fa6ad collab_ui compiles 2025-01-03 12:53:52 +01:00
Antonio Scandurra
79ec03cd14 picker compiles, new raft of errors 2025-01-03 12:12:16 +01:00
Antonio Scandurra
df29eaf897 inline_completion_button compiles 2025-01-03 12:09:41 +01:00
Antonio Scandurra
e3bccd7f0d zeta compiles 2025-01-03 12:06:42 +01:00
Antonio Scandurra
4737c81c44 diagnostics compiles 2025-01-03 12:06:05 +01:00
Antonio Scandurra
603b398d32 git_ui compiles 2025-01-03 12:01:46 +01:00
Nathan Sobo
db387de6b3 terminal_view compiles 2025-01-02 14:40:02 -07:00
Nathan Sobo
a5fbde69ac WIP: language_tools compiles 2025-01-02 14:08:53 -07:00
Nathan Sobo
cc5c668744 settings_ui compiles 2025-01-02 14:03:22 -07:00
Nathan Sobo
953586f532 language_models compiles 2025-01-02 14:00:41 -07:00
Nathan Sobo
b1485631cd journal crate compiling 2025-01-02 11:32:31 -07:00
Nathan Sobo
55c46bbddf WIP: title_bar compiles 2025-01-02 10:57:46 -07:00
Nathan Sobo
9f409de5fe WIP 2025-01-02 10:56:04 -07:00
Nathan Sobo
426ae3339a WIP: go_to_line compiles 2025-01-02 10:54:27 -07:00
Nathan Sobo
fb05ab846b WIP: markdown_preview 2025-01-02 10:51:17 -07:00
Nathan Sobo
591352cd9e WIP: image_viewer compiles 2025-01-02 10:47:07 -07:00
Nathan Sobo
54945b922a WIP 2025-01-02 10:44:01 -07:00
Nathan Sobo
16fa413d2d WIP: project_panel compiling 2025-01-02 10:36:49 -07:00
Nathan Sobo
0de4094cc1 WIP: feedback compiles 2025-01-02 10:21:02 -07:00
Nathan Sobo
60ecf8345e WIP 2025-01-02 10:17:44 -07:00
Nathan Sobo
2a3a9d06ad WIP: get ui crate compiling again 2025-01-02 10:11:57 -07:00
Nathan Sobo
2aa3bd7099 WIP: semantic_index compiles 2025-01-02 10:09:18 -07:00
Nathan Sobo
bdc85d780e WIP: copilot compiles 2025-01-02 10:05:56 -07:00
Nathan Sobo
df4c51e9ea WIP: search crate compiles 2025-01-02 10:02:31 -07:00
Nathan Sobo
cb3bd8d442 WIP: editor crate is compiling 2025-01-02 09:38:39 -07:00
Nathan Sobo
d219bfc10f WIP 2025-01-02 09:34:22 -07:00
Nathan Sobo
d768bfdce1 WIP 2025-01-02 09:22:02 -07:00
Nathan Sobo
6c9d544341 WIPY 2025-01-02 08:37:19 -07:00
Nathan Sobo
ab1359b315 WIP 2025-01-02 08:27:28 -07:00
Nathan Sobo
c641bbf0b1 WIP 2025-01-02 07:59:59 -07:00
Nathan Sobo
6864b3e86d WIP 2025-01-02 07:57:09 -07:00
Nathan Sobo
25416ff303 workspace compiling 2025-01-02 07:47:40 -07:00
Nathan Sobo
5e3ec9d81c WIP 2025-01-02 07:45:13 -07:00
Nathan Sobo
5a3b9d8820 WIP 2025-01-02 07:14:07 -07:00
Nathan Sobo
ec72020cb1 WIP 2025-01-02 07:13:40 -07:00
Nathan Sobo
f2d41ca066 WIP 2025-01-02 07:12:09 -07:00
Nathan Sobo
7510afccb8 WIP 2025-01-02 07:11:43 -07:00
Nathan Sobo
ab8f8dde43 WIP 2025-01-02 07:11:15 -07:00
Nathan Sobo
41de86f87f WIP 2025-01-02 07:10:13 -07:00
Nathan Sobo
0ebd08871b WIP 2025-01-02 07:07:14 -07:00
Nathan Sobo
dbb74ca28d WIP 2025-01-02 07:06:30 -07:00
Nathan Sobo
8e987c273f WIP 2025-01-02 07:03:27 -07:00
Nathan Sobo
859da6b13a WIP 2025-01-02 06:27:50 -07:00
Nathan Sobo
54fe194f7b Fix markdown 2025-01-02 06:09:19 -07:00
Nathan Sobo
d07e1d2141 WIP 2025-01-02 05:51:20 -07:00
Nathan Sobo
c9a477350f WIP: 152 errors (but I dont trust it) 2025-01-02 05:34:26 -07:00
Nathan Sobo
c49c33bf61 WIP: Grinding through many remaining errors manually after running the script 2025-01-02 04:36:46 -07:00
Nathan Sobo
772489499c Format 2025-01-02 03:52:47 -07:00
Nathan Sobo
1d73e6646c Run the refactor script as a starting point 2025-01-02 03:45:20 -07:00
Nathan Sobo
efba1cb141 Get gpui examples compiling with new API 2025-01-02 00:48:14 -07:00
Nathan Sobo
19ac46a252 Revert example 2025-01-02 00:38:32 -07:00
Nathan Sobo
cc95b79913 Move impl block for ModelContext 2025-01-01 23:51:15 -07:00
Nathan Sobo
267df69b44 Fix privacy 2025-01-01 23:39:45 -07:00
Nathan Sobo
1de66771d6 Add a temporary view method to ModelContext 2025-01-01 23:12:02 -07:00
Nathan Sobo
d7b266ce62 Merge View into Model 2025-01-01 22:40:52 -07:00
Nathan Sobo
61aa1d8f08 Add Window::notify 2025-01-01 14:12:30 -07:00
Nathan Sobo
d4428c7c4b Restore examples so we can automate them 2025-01-01 13:10:48 -07:00
Nathan Sobo
021e8a17f6 Merge remote-tracking branch 'origin/main' into remove-window-context 2024-12-31 14:55:22 -07:00
Nathan Sobo
296fe144cb Remove WindowContext in GPUI (examples still broken) 2024-12-31 14:27:15 -07:00
Nathan Sobo
a7e5d5a298 Move more ViewContext methods to equivalent versions on ModelContext 2024-12-29 16:17:30 -07:00
Nathan Sobo
838ebe90cd Add some formerly ViewContext methods to ModelContext (many now take a window) 2024-12-29 10:31:43 -07:00
Nathan Sobo
dcd2d85127 Add ModelContext::subscribe_in 2024-12-29 10:21:43 -07:00
Nathan Sobo
b05f723e95 Move remaining WindowContext methods to Window 2024-12-29 10:10:41 -07:00
Nathan Sobo
30ffadfe32 Move more Window methods 2024-12-29 09:43:58 -07:00
Nathan Sobo
04ffcab87d Move some more WindowContext methods to Window 2024-12-29 09:25:23 -07:00
Nathan Sobo
5a3233ebe4 Add some Window equivalent methods to methods on WindowContext 2024-12-29 09:03:38 -07:00
Nathan Sobo
a037f71432 Compiling: Remove ViewContext completely 2024-12-29 00:36:17 -07:00
Nathan Sobo
d625a3e6ba Progress toward removing ViewContext
3 compiler errors and some todo!s
2024-12-28 23:28:36 -07:00
430 changed files with 29269 additions and 21323 deletions

View File

@@ -4,8 +4,8 @@ use extension_host::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
InteractiveElement as _, Model, ModelContext, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, Window,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName;
@@ -46,22 +46,25 @@ struct PendingWork<'a> {
struct Content {
icon: Option<gpui::AnyElement>,
message: String,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
on_click: Option<
Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut ModelContext<ActivityIndicator>)>,
>,
}
impl ActivityIndicator {
pub fn new(
workspace: &mut Workspace,
languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Workspace>,
) -> View<ActivityIndicator> {
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<ActivityIndicator> {
let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx);
let this = cx.new_view(|cx: &mut ViewContext<Self>| {
let this = cx.new_model(|cx| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(|this, mut cx| async move {
while let Some((name, status)) = status_events.next().await {
this.update(&mut cx, |this, cx| {
this.update(&mut cx, |this: &mut ActivityIndicator, cx| {
this.statuses.retain(|s| s.name != name);
this.statuses.push(LspStatus { name, status });
cx.notify();
@@ -70,6 +73,7 @@ impl ActivityIndicator {
anyhow::Ok(())
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
if let Some(auto_updater) = auto_updater.as_ref() {
@@ -84,13 +88,13 @@ impl ActivityIndicator {
}
});
cx.subscribe(&this, move |_, _, event, cx| match event {
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
Event::ShowError { lsp_name, error } => {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone();
let error = error.clone();
let lsp_name = lsp_name.clone();
cx.spawn(|workspace, mut cx| async move {
cx.spawn_in(window, |workspace, mut cx| async move {
let buffer = create_buffer.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.edit(
@@ -103,13 +107,14 @@ impl ActivityIndicator {
);
buffer.set_capability(language::Capability::ReadOnly, cx);
})?;
workspace.update(&mut cx, |workspace, cx| {
workspace.update_in(&mut cx, |workspace, window, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx)
Box::new(cx.new_model(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
})),
None,
true,
window,
cx,
);
})?;
@@ -123,7 +128,12 @@ impl ActivityIndicator {
this
}
fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
fn show_error_message(
&mut self,
_: &ShowErrorMessage,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.statuses.retain(|status| {
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
cx.emit(Event::ShowError {
@@ -139,7 +149,12 @@ impl ActivityIndicator {
cx.notify();
}
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
fn dismiss_error_message(
&mut self,
_: &DismissErrorMessage,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| {
updater.dismiss_error(cx);
@@ -183,7 +198,7 @@ impl ActivityIndicator {
self.project.read(cx).shell_environment_errors(cx)
}
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> {
fn content_to_render(&mut self, cx: &mut ModelContext<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
return Some(Content {
@@ -193,11 +208,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: error.0.clone(),
on_click: Some(Arc::new(move |this, cx| {
on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| {
project.remove_environment_error(cx, worktree_id);
});
cx.dispatch_action(Box::new(workspace::OpenLog));
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
});
}
@@ -280,10 +295,10 @@ impl ActivityIndicator {
}
)
),
on_click: Some(Arc::new(move |this, cx| {
on_click: Some(Arc::new(move |this, window, cx| {
this.statuses
.retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, cx)
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
});
}
@@ -308,10 +323,10 @@ impl ActivityIndicator {
}
),
),
on_click: Some(Arc::new(move |this, cx| {
on_click: Some(Arc::new(move |this, window, cx| {
this.statuses
.retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, cx)
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
});
}
@@ -336,8 +351,8 @@ impl ActivityIndicator {
acc
}),
),
on_click: Some(Arc::new(|this, cx| {
this.show_error_message(&Default::default(), cx)
on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), window, cx)
})),
});
}
@@ -351,11 +366,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|indicator, cx| {
on_click: Some(Arc::new(|indicator, window, cx| {
indicator.project.update(cx, |project, cx| {
project.reset_last_formatting_failure(cx);
});
cx.dispatch_action(Box::new(workspace::OpenLog));
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
});
}
@@ -370,8 +385,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Checking for Zed updates…".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
}),
AutoUpdateStatus::Downloading => Some(Content {
@@ -381,8 +396,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Downloading Zed update…".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
}),
AutoUpdateStatus::Installing => Some(Content {
@@ -392,8 +407,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Installing Zed update…".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
}),
AutoUpdateStatus::Updated { binary_path } => Some(Content {
@@ -403,7 +418,7 @@ impl ActivityIndicator {
let reload = workspace::Reload {
binary_path: Some(binary_path.clone()),
};
move |_, cx| workspace::reload(&reload, cx)
move |_, _, cx| workspace::reload(&reload, cx)
})),
}),
AutoUpdateStatus::Errored => Some(Content {
@@ -413,8 +428,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
}),
AutoUpdateStatus::Idle => None,
@@ -432,8 +447,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Updating {extension_id} extension…"),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
});
}
@@ -442,8 +457,12 @@ impl ActivityIndicator {
None
}
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
self.context_menu_handle.toggle(cx);
fn toggle_language_server_work_context_menu(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_menu_handle.toggle(window, cx);
}
}
@@ -452,7 +471,7 @@ impl EventEmitter<Event> for ActivityIndicator {}
const MAX_MESSAGE_LEN: usize = 50;
impl Render for ActivityIndicator {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let result = h_flex()
.id("activity-indicator")
.on_action(cx.listener(Self::show_error_message))
@@ -460,7 +479,7 @@ impl Render for ActivityIndicator {
let Some(content) = self.content_to_render(cx) else {
return result;
};
let this = cx.view().downgrade();
let this = cx.model().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
@@ -480,24 +499,24 @@ impl Render for ActivityIndicator {
))
.size(LabelSize::Small),
)
.tooltip(move |cx| Tooltip::text(&content.message, cx))
.tooltip(Tooltip::text(content.message))
} else {
button.child(Label::new(content.message).size(LabelSize::Small))
}
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, cx| {
handler(this, cx);
this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, window, cx);
}))
.cursor(CursorStyle::PointingHand)
}),
),
)
.anchor(gpui::Corner::BottomLeft)
.menu(move |cx| {
.menu(move |window, cx| {
let strong_this = this.upgrade()?;
let mut has_work = false;
let menu = ContextMenu::build(cx, |mut menu, cx| {
let menu = ContextMenu::build(window, cx, |mut menu, _, cx| {
for work in strong_this.read(cx).pending_language_server_work(cx) {
has_work = true;
let this = this.clone();
@@ -513,7 +532,7 @@ impl Render for ActivityIndicator {
let token = work.progress_token.to_string();
let title = SharedString::from(title);
menu = menu.custom_entry(
move |_| {
move |_, _| {
h_flex()
.w_full()
.justify_between()
@@ -521,7 +540,7 @@ impl Render for ActivityIndicator {
.child(Icon::new(IconName::XCircle))
.into_any_element()
},
move |cx| {
move |_, cx| {
this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.cancel_language_server_work(
@@ -554,5 +573,11 @@ impl Render for ActivityIndicator {
}
impl StatusItemView for ActivityIndicator {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
fn set_active_pane_item(
&mut self,
_: Option<&dyn ItemHandle>,
_window: &mut Window,
_: &mut ModelContext<Self>,
) {
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ use futures::{
channel::mpsc,
stream::{self, StreamExt},
};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakModel};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
@@ -35,7 +35,7 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
use ui::{IconName, WindowContext};
use ui::{IconName, Window};
use unindent::Unindent;
use util::{
test::{generate_marked_text, marked_text_ranges},
@@ -1642,8 +1642,9 @@ impl SlashCommand for FakeSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![]))
}
@@ -1657,9 +1658,10 @@ impl SlashCommand for FakeSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),

View File

@@ -320,7 +320,7 @@ impl ContextStore {
.client
.subscribe_to_entity(remote_id)
.log_err()
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
.map(|subscription| subscription.set_model(&cx.model(), &mut cx.to_async()));
self.advertise_contexts(cx);
} else {
self.client_subscription = None;

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ use futures::{
use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TextStyle, TitlebarOptions,
UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
EventEmitter, Focusable, Global, Model, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
TitlebarOptions, UpdateGlobal, WindowBounds, WindowHandle, WindowOptions,
};
use heed::{
types::{SerdeBincode, SerdeJson, Str},
@@ -38,8 +38,8 @@ use std::{
use text::LineEnding;
use theme::ThemeSettings;
use ui::{
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, Tooltip, ViewContext, VisualContext,
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ModelContext,
ParentElement, Render, SharedString, Styled, Tooltip, Window,
};
use util::{ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -88,7 +88,7 @@ pub fn open_prompt_library(
.find_map(|window| window.downcast::<PromptLibrary>());
if let Some(existing_window) = existing_window {
existing_window
.update(cx, |_, cx| cx.activate_window())
.update(cx, |_, window, _| window.activate_window())
.ok();
Task::ready(Ok(existing_window))
} else {
@@ -109,7 +109,9 @@ pub fn open_prompt_library(
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|window, cx| {
cx.new_model(|cx| PromptLibrary::new(store, language_registry, window, cx))
},
)
})?
})
@@ -121,14 +123,14 @@ pub struct PromptLibrary {
language_registry: Arc<LanguageRegistry>,
prompt_editors: HashMap<PromptId, PromptEditor>,
active_prompt_id: Option<PromptId>,
picker: View<Picker<PromptPickerDelegate>>,
picker: Model<Picker<PromptPickerDelegate>>,
pending_load: Task<()>,
_subscriptions: Vec<Subscription>,
}
struct PromptEditor {
title_editor: View<Editor>,
body_editor: View<Editor>,
title_editor: Model<Editor>,
body_editor: Model<Editor>,
token_count: Option<usize>,
pending_token_count: Task<Option<()>>,
next_title_and_body_to_save: Option<(String, Rope)>,
@@ -158,7 +160,7 @@ impl PickerDelegate for PromptPickerDelegate {
self.matches.len()
}
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
fn no_matches_text(&self, _window: &mut Window, _cx: &mut AppContext) -> SharedString {
if self.store.prompt_count() == 0 {
"No prompts.".into()
} else {
@@ -170,7 +172,12 @@ impl PickerDelegate for PromptPickerDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Selected {
@@ -179,14 +186,19 @@ impl PickerDelegate for PromptPickerDelegate {
}
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search...".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let search = self.store.search(query);
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let (matches, selected_index) = cx
.background_executor()
.spawn(async move {
@@ -201,16 +213,16 @@ impl PickerDelegate for PromptPickerDelegate {
})
.await;
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.delegate.matches = matches;
this.delegate.set_selected_index(selected_index, cx);
this.delegate.set_selected_index(selected_index, window, cx);
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Confirmed {
prompt_id: prompt.id,
@@ -218,13 +230,14 @@ impl PickerDelegate for PromptPickerDelegate {
}
}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut ModelContext<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let prompt = self.matches.get(ix)?;
let default = prompt.default;
@@ -241,8 +254,8 @@ impl PickerDelegate for PromptPickerDelegate {
.toggle_state(true)
.icon_color(Color::Accent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
.tooltip(Tooltip::text("Remove from Default Prompt"))
.on_click(cx.listener(move |_, _, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
}))
}))
@@ -253,11 +266,12 @@ impl PickerDelegate for PromptPickerDelegate {
div()
.id("built-in-prompt")
.child(Icon::new(IconName::FileLock).color(Color::Muted))
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
window,
cx,
)
})
@@ -266,8 +280,8 @@ impl PickerDelegate for PromptPickerDelegate {
IconButton::new("delete-prompt", IconName::Trash)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
.tooltip(Tooltip::text("Delete Prompt"))
.on_click(cx.listener(move |_, _, _, cx| {
cx.emit(PromptPickerEvent::Deleted { prompt_id })
}))
.into_any_element()
@@ -278,17 +292,12 @@ impl PickerDelegate for PromptPickerDelegate {
.selected_icon(IconName::SparkleFilled)
.icon_color(if default { Color::Accent } else { Color::Muted })
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::text(
if default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(cx.listener(move |_, _, cx| {
.tooltip(Tooltip::text(if default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
}))
.on_click(cx.listener(move |_, _, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
})),
),
@@ -296,7 +305,12 @@ impl PickerDelegate for PromptPickerDelegate {
Some(element)
}
fn render_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Picker<Self>>) -> Div {
fn render_editor(
&self,
editor: &Model<Editor>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Div {
h_flex()
.bg(cx.theme().colors().editor_background)
.rounded_md()
@@ -313,7 +327,8 @@ impl PromptLibrary {
fn new(
store: Arc<PromptStore>,
language_registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = PromptPickerDelegate {
store: store.clone(),
@@ -321,11 +336,11 @@ impl PromptLibrary {
matches: Vec::new(),
};
let picker = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx)
let picker = cx.new_model(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.modal(false)
.max_height(None);
picker.focus(cx);
picker.focus(window, cx);
picker
});
Self {
@@ -334,54 +349,63 @@ impl PromptLibrary {
prompt_editors: HashMap::default(),
active_prompt_id: None,
pending_load: Task::ready(()),
_subscriptions: vec![cx.subscribe(&picker, Self::handle_picker_event)],
_subscriptions: vec![cx.subscribe_in(&picker, window, Self::handle_picker_event)],
picker,
}
}
fn handle_picker_event(
&mut self,
_: View<Picker<PromptPickerDelegate>>,
_: &Model<Picker<PromptPickerDelegate>>,
event: &PromptPickerEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
PromptPickerEvent::Selected { prompt_id } => {
self.load_prompt(*prompt_id, false, cx);
self.load_prompt(*prompt_id, false, window, cx);
}
PromptPickerEvent::Confirmed { prompt_id } => {
self.load_prompt(*prompt_id, true, cx);
self.load_prompt(*prompt_id, true, window, cx);
}
PromptPickerEvent::ToggledDefault { prompt_id } => {
self.toggle_default_for_prompt(*prompt_id, cx);
self.toggle_default_for_prompt(*prompt_id, window, cx);
}
PromptPickerEvent::Deleted { prompt_id } => {
self.delete_prompt(*prompt_id, cx);
self.delete_prompt(*prompt_id, window, cx);
}
}
}
pub fn new_prompt(&mut self, cx: &mut ViewContext<Self>) {
pub fn new_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
// If we already have an untitled prompt, use that instead
// of creating a new one.
if let Some(metadata) = self.store.first() {
if metadata.title.is_none() {
self.load_prompt(metadata.id, true, cx);
self.load_prompt(metadata.id, true, window, cx);
return;
}
}
let prompt_id = PromptId::new();
let save = self.store.save(prompt_id, None, false, "".into());
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, |this, mut cx| async move {
save.await?;
this.update(&mut cx, |this, cx| this.load_prompt(prompt_id, true, cx))
this.update_in(&mut cx, |this, window, cx| {
this.load_prompt(prompt_id, true, window, cx)
})
})
.detach_and_log_err(cx);
}
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
pub fn save_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
if prompt_id.is_built_in() {
@@ -407,7 +431,7 @@ impl PromptLibrary {
prompt_editor.next_title_and_body_to_save = Some((title, body));
if prompt_editor.pending_save.is_none() {
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
prompt_editor.pending_save = Some(cx.spawn_in(window, |this, mut cx| {
async move {
loop {
let title_and_body = this.update(&mut cx, |this, _| {
@@ -427,8 +451,9 @@ impl PromptLibrary {
.save(prompt_id, title, prompt_metadata.default, body)
.await
.log_err();
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| picker.refresh(cx));
this.update_in(&mut cx, |this, window, cx| {
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
})?;
@@ -449,61 +474,77 @@ impl PromptLibrary {
}
}
pub fn delete_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
pub fn delete_active_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.delete_prompt(active_prompt_id, cx);
self.delete_prompt(active_prompt_id, window, cx);
}
}
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
pub fn duplicate_active_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.duplicate_prompt(active_prompt_id, cx);
self.duplicate_prompt(active_prompt_id, window, cx);
}
}
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
pub fn toggle_default_for_active_prompt(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.toggle_default_for_prompt(active_prompt_id, cx);
self.toggle_default_for_prompt(active_prompt_id, window, cx);
}
}
pub fn toggle_default_for_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
pub fn toggle_default_for_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
self.store
.save_metadata(prompt_id, prompt_metadata.title, !prompt_metadata.default)
.detach_and_log_err(cx);
self.picker.update(cx, |picker, cx| picker.refresh(cx));
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
}
}
pub fn load_prompt(&mut self, prompt_id: PromptId, focus: bool, cx: &mut ViewContext<Self>) {
pub fn load_prompt(
&mut self,
prompt_id: PromptId,
focus: bool,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
if focus {
prompt_editor
.body_editor
.update(cx, |editor, cx| editor.focus(cx));
.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)));
}
self.set_active_prompt(Some(prompt_id), cx);
self.set_active_prompt(Some(prompt_id), window, cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
let language_registry = self.language_registry.clone();
let prompt = self.store.load(prompt_id);
self.pending_load = cx.spawn(|this, mut cx| async move {
self.pending_load = cx.spawn_in(window, |this, mut cx| async move {
let prompt = prompt.await;
let markdown = language_registry.language_for_name("Markdown").await;
this.update(&mut cx, |this, cx| match prompt {
this.update_in(&mut cx, |this, window, cx| match prompt {
Ok(prompt) => {
let title_editor = cx.new_view(|cx| {
let mut editor = Editor::auto_width(cx);
let title_editor = cx.new_model(|cx| {
let mut editor = Editor::auto_width(window, cx);
editor.set_placeholder_text("Untitled", cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), window, cx);
if prompt_id.is_built_in() {
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.set_show_inline_completions(Some(false), window, cx);
}
editor
});
let body_editor = cx.new_view(|cx| {
let body_editor = cx.new_model(|cx| {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
@@ -511,10 +552,10 @@ impl PromptLibrary {
buffer
});
let mut editor = Editor::for_buffer(buffer, None, cx);
let mut editor = Editor::for_buffer(buffer, None, window, cx);
if prompt_id.is_built_in() {
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.set_show_inline_completions(Some(false), window, cx);
}
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
@@ -530,17 +571,29 @@ impl PromptLibrary {
),
)));
if focus {
editor.focus(cx);
window.focus(&editor.focus_handle(cx));
}
editor
});
let _subscriptions = vec![
cx.subscribe(&title_editor, move |this, editor, event, cx| {
this.handle_prompt_title_editor_event(prompt_id, editor, event, cx)
}),
cx.subscribe(&body_editor, move |this, editor, event, cx| {
this.handle_prompt_body_editor_event(prompt_id, editor, event, cx)
}),
cx.subscribe_in(
&title_editor,
window,
move |this, editor, event, window, cx| {
this.handle_prompt_title_editor_event(
prompt_id, editor, event, window, cx,
)
},
),
cx.subscribe_in(
&body_editor,
window,
move |this, editor, event, window, cx| {
this.handle_prompt_body_editor_event(
prompt_id, editor, event, window, cx,
)
},
),
];
this.prompt_editors.insert(
prompt_id,
@@ -554,8 +607,8 @@ impl PromptLibrary {
_subscriptions,
},
);
this.set_active_prompt(Some(prompt_id), cx);
this.count_tokens(prompt_id, cx);
this.set_active_prompt(Some(prompt_id), window, cx);
this.count_tokens(prompt_id, window, cx);
}
Err(error) => {
// TODO: we should show the error in the UI.
@@ -567,7 +620,12 @@ impl PromptLibrary {
}
}
fn set_active_prompt(&mut self, prompt_id: Option<PromptId>, cx: &mut ViewContext<Self>) {
fn set_active_prompt(
&mut self,
prompt_id: Option<PromptId>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.active_prompt_id = prompt_id;
self.picker.update(cx, |picker, cx| {
if let Some(prompt_id) = prompt_id {
@@ -585,19 +643,24 @@ impl PromptLibrary {
.iter()
.position(|mat| mat.id == prompt_id)
{
picker.set_selected_index(ix, true, cx);
picker.set_selected_index(ix, true, window, cx);
}
}
} else {
picker.focus(cx);
picker.focus(window, cx);
}
});
cx.notify();
}
pub fn delete_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
pub fn delete_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(metadata) = self.store.metadata(prompt_id) {
let confirmation = cx.prompt(
let confirmation = window.prompt(
PromptLevel::Warning,
&format!(
"Are you sure you want to delete {}",
@@ -605,17 +668,19 @@ impl PromptLibrary {
),
None,
&["Delete", "Cancel"],
cx,
);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
if confirmation.await.ok() == Some(0) {
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
if this.active_prompt_id == Some(prompt_id) {
this.set_active_prompt(None, cx);
this.set_active_prompt(None, window, cx);
}
this.prompt_editors.remove(&prompt_id);
this.store.delete(prompt_id).detach_and_log_err(cx);
this.picker.update(cx, |picker, cx| picker.refresh(cx));
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.notify();
})?;
}
@@ -625,7 +690,12 @@ impl PromptLibrary {
}
}
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
pub fn duplicate_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
const DUPLICATE_SUFFIX: &str = " copy";
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
@@ -655,31 +725,38 @@ impl PromptLibrary {
let save = self
.store
.save(new_id, Some(title.into()), false, body.into());
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, |this, mut cx| async move {
save.await?;
this.update(&mut cx, |prompt_library, cx| {
prompt_library.load_prompt(new_id, true, cx)
this.update_in(&mut cx, |prompt_library, window, cx| {
prompt_library.load_prompt(new_id, true, window, cx)
})
})
.detach_and_log_err(cx);
}
}
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
fn focus_active_prompt(&mut self, _: &Tab, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
.body_editor
.update(cx, |editor, cx| editor.focus(cx));
.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)));
cx.stop_propagation();
}
}
fn focus_picker(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| picker.focus(cx));
fn focus_picker(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut ModelContext<Self>) {
self.picker
.update(cx, |picker, cx| picker.focus(window, cx));
}
pub fn inline_assist(&mut self, action: &InlineAssist, cx: &mut ViewContext<Self>) {
pub fn inline_assist(
&mut self,
action: &InlineAssist,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let Some(active_prompt_id) = self.active_prompt_id else {
cx.propagate();
return;
@@ -693,15 +770,15 @@ impl PromptLibrary {
let initial_prompt = action.prompt.clone();
if provider.is_authenticated(cx) {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
})
} else {
for window in cx.windows() {
if let Some(workspace) = window.downcast::<Workspace>() {
let panel = workspace
.update(cx, |workspace, cx| {
cx.activate_window();
workspace.focus_panel::<AssistantPanel>(cx)
.update(cx, |workspace, window, cx| {
window.activate_window();
workspace.focus_panel::<AssistantPanel>(window, cx)
})
.ok()
.flatten();
@@ -713,18 +790,28 @@ impl PromptLibrary {
}
}
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
fn move_down_from_title(
&mut self,
_: &editor::actions::MoveDown,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.body_editor);
window.focus(&prompt_editor.body_editor.focus_handle(cx));
}
}
}
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
fn move_up_from_body(
&mut self,
_: &editor::actions::MoveUp,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.title_editor);
window.focus(&prompt_editor.title_editor.focus_handle(cx));
}
}
}
@@ -732,18 +819,19 @@ impl PromptLibrary {
fn handle_prompt_title_editor_event(
&mut self,
prompt_id: PromptId,
title_editor: View<Editor>,
title_editor: &Model<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
self.save_prompt(prompt_id, window, cx);
self.count_tokens(prompt_id, window, cx);
}
EditorEvent::Blurred => {
title_editor.update(cx, |title_editor, cx| {
title_editor.change_selections(None, cx, |selections| {
title_editor.change_selections(None, window, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
@@ -756,18 +844,19 @@ impl PromptLibrary {
fn handle_prompt_body_editor_event(
&mut self,
prompt_id: PromptId,
body_editor: View<Editor>,
body_editor: &Model<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
self.save_prompt(prompt_id, window, cx);
self.count_tokens(prompt_id, window, cx);
}
EditorEvent::Blurred => {
body_editor.update(cx, |body_editor, cx| {
body_editor.change_selections(None, cx, |selections| {
body_editor.change_selections(None, window, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
@@ -777,7 +866,12 @@ impl PromptLibrary {
}
}
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
fn count_tokens(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -785,13 +879,13 @@ impl PromptLibrary {
let editor = &prompt.body_editor.read(cx);
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
let body = buffer.as_rope().clone();
prompt.pending_token_count = cx.spawn(|this, mut cx| {
prompt.pending_token_count = cx.spawn_in(window, |this, mut cx| {
async move {
const DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
let token_count = cx
.update(|cx| {
.update(|_, cx| {
model.count_tokens(
LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
@@ -819,7 +913,7 @@ impl PromptLibrary {
}
}
fn render_prompt_list(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_prompt_list(&mut self, cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex()
.id("prompt-list")
.capture_action(cx.listener(Self::focus_active_prompt))
@@ -839,16 +933,21 @@ impl PromptLibrary {
IconButton::new("new-prompt", IconName::Plus)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::for_action("New Prompt", &NewPrompt, cx))
.on_click(|_, cx| {
cx.dispatch_action(Box::new(NewPrompt));
.tooltip(move |window, cx| {
Tooltip::for_action("New Prompt", &NewPrompt, window, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(NewPrompt), cx);
}),
),
)
.child(div().flex_grow().child(self.picker.clone()))
}
fn render_active_prompt(&mut self, cx: &mut ViewContext<PromptLibrary>) -> gpui::Stateful<Div> {
fn render_active_prompt(
&mut self,
cx: &mut ModelContext<PromptLibrary>,
) -> gpui::Stateful<Div> {
div()
.w_2_3()
.h_full()
@@ -873,8 +972,8 @@ impl PromptLibrary {
.overflow_hidden()
.pl(DynamicSpacing::Base16.rems(cx))
.pt(DynamicSpacing::Base08.rems(cx))
.on_click(cx.listener(move |_, _, cx| {
cx.focus(&focus_handle);
.on_click(cx.listener(move |_, _, window, _| {
window.focus(&focus_handle);
}))
.child(
h_flex()
@@ -957,7 +1056,7 @@ impl PromptLibrary {
h_flex()
.id("token_count")
.tooltip(move |cx| {
.tooltip(move |window, cx| {
let token_count =
token_count.clone();
@@ -976,6 +1075,7 @@ impl PromptLibrary {
.0)
.unwrap_or_default()
),
window,
cx,
)
})
@@ -995,11 +1095,12 @@ impl PromptLibrary {
Icon::new(IconName::FileLock)
.color(Color::Muted),
)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
window,
cx,
)
})
@@ -1013,15 +1114,19 @@ impl PromptLibrary {
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
window,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(DeletePrompt),
cx,
);
})
.into_any_element()
})
@@ -1034,17 +1139,19 @@ impl PromptLibrary {
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::for_action(
"Duplicate Prompt",
&DuplicatePrompt,
window,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
DuplicatePrompt,
));
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(DuplicatePrompt),
cx,
);
}),
)
.child(
@@ -1062,20 +1169,18 @@ impl PromptLibrary {
})
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
.tooltip(Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
))
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(ToggleDefaultPrompt),
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
ToggleDefaultPrompt,
));
);
}),
),
),
@@ -1096,18 +1201,24 @@ impl PromptLibrary {
}
impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
let theme = cx.theme().clone();
h_flex()
.id("prompt-manager")
.key_context("PromptLibrary")
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
this.toggle_default_for_active_prompt(cx)
.on_action(cx.listener(|this, &NewPrompt, window, cx| this.new_prompt(window, cx)))
.on_action(
cx.listener(|this, &DeletePrompt, window, cx| {
this.delete_active_prompt(window, cx)
}),
)
.on_action(cx.listener(|this, &DuplicatePrompt, window, cx| {
this.duplicate_active_prompt(window, cx)
}))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, window, cx| {
this.toggle_default_for_active_prompt(window, cx)
}))
.size_full()
.overflow_hidden()
@@ -1149,10 +1260,13 @@ impl Render for PromptLibrary {
Button::new("create-prompt", "New Prompt")
.full_width()
.key_binding(KeyBinding::for_action(
&NewPrompt, cx,
&NewPrompt, window,
))
.on_click(|_, cx| {
cx.dispatch_action(NewPrompt.boxed_clone())
.on_click(|_, window, cx| {
window.dispatch_action(
NewPrompt.boxed_clone(),
cx,
)
}),
),
)

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::AfterCompletion;
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
use gpui::{AppContext, Model, ModelContext, Task, WeakModel, Window};
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
use parking_lot::Mutex;
use project::CompletionIntent;
@@ -43,8 +43,8 @@ pub mod terminal_command;
pub(crate) struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
editor: Option<WeakModel<ContextEditor>>,
workspace: Option<WeakModel<Workspace>>,
}
pub(crate) struct SlashCommandLine {
@@ -57,8 +57,8 @@ pub(crate) struct SlashCommandLine {
impl SlashCommandCompletionProvider {
pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
editor: Option<WeakModel<ContextEditor>>,
workspace: Option<WeakModel<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@@ -73,7 +73,8 @@ impl SlashCommandCompletionProvider {
command_name: &str,
command_range: Range<Anchor>,
name_range: Range<Anchor>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<project::Completion>>> {
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
@@ -85,7 +86,7 @@ impl SlashCommandCompletionProvider {
let command_name = command_name.to_string();
let editor = self.editor.clone();
let workspace = self.workspace.clone();
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let matches = match_strings(
&candidates,
&command_name,
@@ -96,7 +97,7 @@ impl SlashCommandCompletionProvider {
)
.await;
cx.update(|cx| {
cx.update(|_, cx| {
matches
.into_iter()
.filter_map(|mat| {
@@ -118,28 +119,31 @@ impl SlashCommandCompletionProvider {
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
move |intent: CompletionIntent, cx: &mut WindowContext| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut AppContext| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
window,
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
});
Some(project::Completion {
old_range: name_range.clone(),
@@ -164,7 +168,8 @@ impl SlashCommandCompletionProvider {
command_range: Range<Anchor>,
argument_range: Range<Anchor>,
last_argument_range: Range<Anchor>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<project::Completion>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock();
@@ -175,6 +180,7 @@ impl SlashCommandCompletionProvider {
arguments,
new_cancel_flag.clone(),
self.workspace.clone(),
window,
cx,
);
let command_name: Arc<str> = command_name.into();
@@ -202,27 +208,30 @@ impl SlashCommandCompletionProvider {
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent, cx: &mut WindowContext| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&completed_arguments,
true,
workspace.clone(),
cx,
);
})
.ok();
false
} else {
!new_argument.after_completion.run()
}
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut AppContext| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&completed_arguments,
true,
workspace.clone(),
window,
cx,
);
})
.ok();
false
} else {
!new_argument.after_completion.run()
}
}
}) as Arc<_>
});
@@ -260,7 +269,8 @@ impl CompletionProvider for SlashCommandCompletionProvider {
buffer: &Model<Buffer>,
buffer_position: Anchor,
_: editor::CompletionContext,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Task<Result<Vec<project::Completion>>> {
let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| {
@@ -315,10 +325,11 @@ impl CompletionProvider for SlashCommandCompletionProvider {
command_range,
argument_range,
last_argument_range,
window,
cx,
)
} else {
self.complete_command_name(&name, command_range, last_argument_range, cx)
self.complete_command_name(&name, command_range, last_argument_range, window, cx)
}
}
@@ -327,7 +338,8 @@ impl CompletionProvider for SlashCommandCompletionProvider {
_: Model<Buffer>,
_: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>,
_: &mut ViewContext<Editor>,
_window: &mut Window,
_: &mut ModelContext<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
@@ -338,7 +350,8 @@ impl CompletionProvider for SlashCommandCompletionProvider {
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
cx: &mut ViewContext<Editor>,
_: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
let buffer = buffer.read(cx);
let position = position.to_point(buffer);

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
use gpui::{AppContext, AsyncAppContext, Task, WeakModel, Window};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@@ -53,8 +53,9 @@ impl SlashCommand for AutoCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
// There's no autocomplete for a prompt, since it's arbitrary text.
// However, we can use this opportunity to kick off a drain of the backlog.
@@ -96,9 +97,10 @@ impl SlashCommand for AutoCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -115,7 +117,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = cx.spawn(|cx: AsyncWindowContext| async move {
let task = window.spawn(cx, |cx| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
use gpui::{AppContext, Model, Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use std::{
@@ -107,8 +107,9 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -122,9 +123,10 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let project = workspace.project().clone();

View File

@@ -8,7 +8,7 @@ use context_server::{
manager::{ContextServer, ContextServerManager},
types::Prompt,
};
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
use gpui::{AppContext, Model, Task, WeakModel, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -77,8 +77,9 @@ impl SlashCommand for ContextServerSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
return Task::ready(Err(anyhow!("Failed to complete argument")));
@@ -128,9 +129,10 @@ impl SlashCommand for ContextServerSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
fmt::Write,
@@ -36,8 +36,9 @@ impl SlashCommand for DefaultSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -47,9 +48,10 @@ impl SlashCommand for DefaultSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let store = PromptStore::global(cx);
cx.background_executor().spawn(async move {

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
};
use collections::HashSet;
use futures::future;
use gpui::{Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakModel, Window};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt;
@@ -40,8 +40,9 @@ impl SlashCommand for DeltaSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -51,9 +52,10 @@ impl SlashCommand for DeltaSlashCommand {
_arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
@@ -77,6 +79,7 @@ impl SlashCommand for DeltaSlashCommand {
context_buffer.clone(),
workspace.clone(),
delegate.clone(),
window,
cx,
));
}

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView};
use gpui::{AppContext, Model, Task, WeakModel};
use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset,
@@ -30,7 +30,7 @@ impl DiagnosticsSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
workspace: &Model<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
@@ -118,8 +118,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -172,9 +173,10 @@ impl SlashCommand for DiagnosticsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -184,7 +186,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
cx.spawn(move |_| async move {
window.spawn(cx, move |_| async move {
task.await?
.map(|output| output.to_event_stream())
.ok_or_else(|| anyhow!("No diagnostics found"))

View File

@@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakModel};
use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
@@ -43,7 +43,7 @@ impl DocsSlashCommand {
/// access the workspace so we can read the project.
fn ensure_rust_doc_providers_are_registered(
&self,
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
@@ -164,8 +164,9 @@ impl SlashCommand for DocsSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rust_doc_providers_are_registered(workspace, cx);
@@ -272,9 +273,10 @@ impl SlashCommand for DocsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing an argument")));

View File

@@ -9,7 +9,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use futures::AsyncReadExt;
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -124,8 +124,9 @@ impl SlashCommand for FetchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -135,9 +136,10 @@ impl SlashCommand for FetchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(argument) = arguments.first() else {
return Task::ready(Err(anyhow!("missing URL")));

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
use futures::channel::mpsc;
use futures::Stream;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use gpui::{AppContext, Model, Task, WeakModel};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
@@ -28,7 +28,7 @@ impl FileSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
workspace: &Model<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
@@ -134,8 +134,9 @@ impl SlashCommand for FileSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -187,9 +188,10 @@ impl SlashCommand for FileSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));

View File

@@ -7,7 +7,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use chrono::Local;
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -35,8 +35,9 @@ impl SlashCommand for NowSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -46,9 +47,10 @@ impl SlashCommand for NowSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822());

View File

@@ -6,7 +6,7 @@ use crate::PromptBuilder;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakModel, Window};
use language::{Anchor, CodeLabel, LspAdapterDelegate};
use language_model::{LanguageModelRegistry, LanguageModelTool};
use schemars::JsonSchema;
@@ -67,8 +67,9 @@ impl SlashCommand for ProjectSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -78,9 +79,10 @@ impl SlashCommand for ProjectSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>],
context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let model_registry = LanguageModelRegistry::read_global(cx);
let current_model = model_registry.active_model();
@@ -97,7 +99,7 @@ impl SlashCommand for ProjectSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let current_model = current_model.ok_or_else(|| anyhow!("no model selected"))?;
let prompt =

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*;
@@ -37,8 +37,9 @@ impl SlashCommand for PromptSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let store = PromptStore::global(cx);
let query = arguments.to_owned().join(" ");
@@ -64,9 +65,10 @@ impl SlashCommand for PromptSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let title = arguments.to_owned().join(" ");
if title.trim().is_empty() {

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use gpui::{AppContext, Task, WeakModel};
use language::{CodeLabel, LspAdapterDelegate};
use semantic_index::{LoadedSearchResult, SemanticDb};
use std::{
@@ -58,8 +58,9 @@ impl SlashCommand for SearchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -69,9 +70,10 @@ impl SlashCommand for SearchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -107,7 +109,7 @@ impl SlashCommand for SearchSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
cx.spawn(|cx| async move {
window.spawn(cx, |cx| async move {
let results = project_index
.read_with(&cx, |project_index, cx| {
project_index.search(vec![query.clone()], limit.unwrap_or(5), cx)

View File

@@ -5,11 +5,11 @@ use assistant_slash_command::{
SlashCommandOutputSection, SlashCommandResult,
};
use futures::StreamExt;
use gpui::{AppContext, Task, WeakView};
use gpui::{AppContext, Task, WeakModel};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ui::{IconName, SharedString, WindowContext};
use ui::{IconName, SharedString, Window};
use workspace::Workspace;
pub(crate) struct SelectionCommand;
@@ -47,8 +47,9 @@ impl SlashCommand for SelectionCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -58,9 +59,10 @@ impl SlashCommand for SelectionCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let mut events = vec![];

View File

@@ -9,7 +9,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::channel::mpsc;
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use smol::stream::StreamExt;
use smol::Timer;
@@ -45,8 +45,9 @@ impl SlashCommand for StreamingExampleSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -56,9 +57,10 @@ impl SlashCommand for StreamingExampleSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let (events_tx, events_rx) = mpsc::unbounded();
cx.background_executor()

View File

@@ -4,11 +4,11 @@ use assistant_slash_command::{
SlashCommandResult,
};
use editor::Editor;
use gpui::{Task, WeakView};
use gpui::{Task, WeakModel};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext};
use ui::{AppContext, IconName, Window};
use workspace::Workspace;
pub(crate) struct OutlineSlashCommand;
@@ -34,8 +34,9 @@ impl SlashCommand for OutlineSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -49,9 +50,10 @@ impl SlashCommand for OutlineSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {

View File

@@ -6,13 +6,13 @@ use assistant_slash_command::{
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
use gpui::{Entity, Task, WeakView};
use gpui::{Entity, Task, WeakModel};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::{
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ActiveTheme, WindowContext};
use ui::{prelude::*, ActiveTheme, AppContext, Window};
use util::ResultExt;
use workspace::Workspace;
@@ -51,8 +51,9 @@ impl SlashCommand for TabSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let mut has_all_tabs_completion_item = false;
let argument_set = arguments
@@ -82,10 +83,10 @@ impl SlashCommand for TabSlashCommand {
});
let current_query = arguments.last().cloned().unwrap_or_default();
let tab_items_search =
tab_items_for_queries(workspace, &[current_query], cancel, false, cx);
tab_items_for_queries(workspace, &[current_query], cancel, false, window, cx);
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
cx.spawn(|_| async move {
window.spawn(cx, |_| async move {
let tab_items = tab_items_search.await?;
let run_command = tab_items.len() == 1;
let tab_completion_items = tab_items.into_iter().filter_map(|(path, ..)| {
@@ -137,15 +138,17 @@ impl SlashCommand for TabSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let tab_items_search = tab_items_for_queries(
Some(workspace),
arguments,
Arc::new(AtomicBool::new(false)),
true,
window,
cx,
);
@@ -160,15 +163,16 @@ impl SlashCommand for TabSlashCommand {
}
fn tab_items_for_queries(
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
queries: &[String],
cancel: Arc<AtomicBool>,
strict_match: bool,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> {
let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty());
let queries = queries.to_owned();
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let mut open_buffers =
workspace
.context("no workspace")?
@@ -281,7 +285,7 @@ fn tab_items_for_queries(
fn active_item_buffer(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
cx: &mut ModelContext<Workspace>,
) -> anyhow::Result<BufferSnapshot> {
let active_editor = workspace
.active_item(cx)

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, Task, View, WeakView};
use gpui::{AppContext, Model, Task, WeakModel};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*;
@@ -53,8 +53,9 @@ impl SlashCommand for TerminalSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -64,9 +65,10 @@ impl SlashCommand for TerminalSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -107,9 +109,9 @@ impl SlashCommand for TerminalSlashCommand {
}
fn resolve_active_terminal(
workspace: &View<Workspace>,
cx: &WindowContext,
) -> Option<View<TerminalView>> {
workspace: &Model<Workspace>,
cx: &mut AppContext,
) -> Option<Model<TerminalView>> {
if let Some(terminal_view) = workspace
.read(cx)
.active_item(cx)

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakModel};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
@@ -10,7 +10,7 @@ use crate::SlashCommandWorkingSet;
#[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>,
active_context_editor: WeakModel<ContextEditor>,
trigger: T,
}
@@ -27,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo),
Advert {
name: SharedString,
renderer: fn(&mut WindowContext) -> AnyElement,
on_confirm: fn(&mut WindowContext),
renderer: fn(&mut Window, &mut AppContext) -> AnyElement,
on_confirm: fn(&mut Window, &mut AppContext),
},
}
@@ -44,14 +44,14 @@ impl AsRef<str> for SlashCommandEntry {
pub(crate) struct SlashCommandDelegate {
all_commands: Vec<SlashCommandEntry>,
filtered_commands: Vec<SlashCommandEntry>,
active_context_editor: WeakView<ContextEditor>,
active_context_editor: WeakModel<ContextEditor>,
selected_index: usize,
}
impl<T: PopoverTrigger> SlashCommandSelector<T> {
pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>,
active_context_editor: WeakModel<ContextEditor>,
trigger: T,
) -> Self {
SlashCommandSelector {
@@ -73,18 +73,28 @@ impl PickerDelegate for SlashCommandDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Select a command...".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let all_commands = self.all_commands.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let filtered_commands = cx
.background_executor()
.spawn(async move {
@@ -104,9 +114,9 @@ impl PickerDelegate for SlashCommandDelegate {
})
.await;
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.delegate.filtered_commands = filtered_commands;
this.delegate.set_selected_index(0, cx);
this.delegate.set_selected_index(0, window, cx);
cx.notify();
})
.ok();
@@ -139,25 +149,30 @@ impl PickerDelegate for SlashCommandDelegate {
ret
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
if let Some(command) = self.filtered_commands.get(self.selected_index) {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, cx)
context_editor.insert_command(&info.name, window, cx)
})
.ok();
}
SlashCommandEntry::Advert { on_confirm, .. } => {
on_confirm(cx);
on_confirm(window, cx);
}
}
cx.emit(DismissEvent);
}
}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut ModelContext<Picker<Self>>) {}
fn editor_position(&self) -> PickerEditorPosition {
PickerEditorPosition::End
@@ -167,7 +182,8 @@ impl PickerDelegate for SlashCommandDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let command_info = self.filtered_commands.get(ix)?;
@@ -179,7 +195,7 @@ impl PickerDelegate for SlashCommandDelegate {
.toggle_state(selected)
.tooltip({
let description = info.description.clone();
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
move |_, cx| cx.new_model(|_| Tooltip::new(description.clone())).into()
})
.child(
v_flex()
@@ -229,14 +245,14 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true)
.spacing(ListItemSpacing::Dense)
.toggle_state(selected)
.child(renderer(cx)),
.child(renderer(window, cx)),
),
}
}
}
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let all_models = self
.working_set
.featured_command_names(cx)
@@ -259,7 +275,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
})
.chain([SlashCommandEntry::Advert {
name: "create-your-command".into(),
renderer: |cx| {
renderer: |_, cx| {
v_flex()
.w_full()
.child(
@@ -293,7 +309,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
)
.into_any_element()
},
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
on_confirm: |_, cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
}])
.collect::<Vec<_>>();
@@ -304,8 +320,9 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
selected_index: 0,
};
let picker_view = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
let picker_view = cx.new_model(|cx| {
let picker =
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()));
picker
});
@@ -314,7 +331,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
.update(cx, |this, _| this.slash_menu_handle.clone())
.ok();
PopoverMenu::new("model-switcher")
.menu(move |_cx| Some(picker_view.clone()))
.menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)

View File

@@ -13,8 +13,8 @@ use editor::{
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
AppContext, Context, EventEmitter, FocusHandle, Focusable, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, WeakModel,
};
use language::Buffer;
use language_model::{
@@ -87,11 +87,12 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &View<TerminalView>,
workspace: Option<WeakView<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>,
terminal_view: &Model<TerminalView>,
workspace: Option<WeakModel<Workspace>>,
assistant_panel: Option<&Model<AssistantPanel>>,
initial_prompt: Option<String>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
@@ -100,7 +101,7 @@ impl TerminalInlineAssistant {
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| {
let prompt_editor = cx.new_model(|cx| {
PromptEditor::new(
assist_id,
self.prompt_history.clone(),
@@ -109,6 +110,7 @@ impl TerminalInlineAssistant {
assistant_panel,
workspace.clone(),
self.fs.clone(),
window,
cx,
)
});
@@ -118,7 +120,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
};
terminal_view.update(cx, |terminal_view, cx| {
terminal_view.set_block_below_cursor(block, cx);
terminal_view.set_block_below_cursor(block, window, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -127,21 +129,27 @@ impl TerminalInlineAssistant {
assistant_panel.is_some(),
prompt_editor,
workspace.clone(),
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, cx);
self.focus_assist(assist_id, window, cx);
}
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
) {
let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| {
editor.focus(cx);
editor.select_all(&SelectAll, cx);
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
});
});
}
@@ -149,9 +157,10 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: View<PromptEditor>,
prompt_editor: Model<PromptEditor>,
event: &PromptEditorEvent,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
let assist_id = prompt_editor.read(cx).id;
match event {
@@ -162,21 +171,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, cx);
self.finish_assist(assist_id, false, *execute, window, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, cx);
self.finish_assist(assist_id, true, false, window, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, cx);
self.dismiss_assist(assist_id, window, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -214,7 +223,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -227,7 +236,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut WindowContext,
cx: &mut AppContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -297,16 +306,17 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
self.dismiss_assist(assist_id, cx);
self.dismiss_assist(assist_id, window, cx);
if let Some(assist) = self.assists.remove(&assist_id) {
assist
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx);
this.focus_handle(cx).focus(window);
})
.log_err();
@@ -349,7 +359,8 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -362,7 +373,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx);
this.focus_handle(cx).focus(window);
})
.is_ok()
}
@@ -371,7 +382,8 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -383,7 +395,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, cx);
terminal.set_block_below_cursor(block, window, cx);
})
.log_err();
}
@@ -392,10 +404,10 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor>>,
terminal: WeakModel<TerminalView>,
prompt_editor: Option<Model<PromptEditor>>,
codegen: Model<Codegen>,
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
include_context: bool,
_subscriptions: Vec<Subscription>,
}
@@ -403,11 +415,12 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>,
terminal: &Model<TerminalView>,
include_context: bool,
prompt_editor: View<PromptEditor>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
prompt_editor: Model<PromptEditor>,
workspace: Option<WeakModel<Workspace>>,
window: &mut Window,
cx: &mut AppContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone();
Self {
@@ -417,12 +430,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
include_context,
_subscriptions: vec![
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, cx)
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
})
}),
cx.subscribe(&codegen, move |codegen, event, cx| {
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -455,7 +468,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, cx);
this.finish_assist(assist_id, false, false, window, cx);
}
}
})
@@ -477,8 +490,8 @@ enum PromptEditorEvent {
struct PromptEditor {
id: TerminalInlineAssistId,
height_in_lines: u8,
editor: View<Editor>,
language_model_selector: View<LanguageModelSelector>,
editor: Model<Editor>,
language_model_selector: Model<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
@@ -489,13 +502,13 @@ struct PromptEditor {
pending_token_count: Task<Result<()>>,
token_count: Option<usize>,
_token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let buttons = match status {
CodegenStatus::Idle => {
@@ -503,16 +516,20 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
),
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.tooltip(|window, cx| {
Tooltip::for_action("Generate", &menu::Confirm, window, cx)
})
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
),
]
}
@@ -521,23 +538,24 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.tooltip(Tooltip::text("Cancel Assist"))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Interrupt Generation",
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
),
]
}
@@ -545,8 +563,12 @@ impl Render for PromptEditor {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
);
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
@@ -555,15 +577,16 @@ impl Render for PromptEditor {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Restart Generation",
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
})),
]
@@ -573,23 +596,29 @@ impl Render for PromptEditor {
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
.tooltip(|window, cx| {
Tooltip::for_action(
"Accept Generated Command",
&menu::Confirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
})),
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
})),
]
@@ -620,7 +649,7 @@ impl Render for PromptEditor {
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
@@ -631,6 +660,7 @@ impl Render for PromptEditor {
),
None,
"Change Model",
window,
cx,
)
}),
@@ -641,7 +671,7 @@ impl Render for PromptEditor {
Some(
div()
.id("error")
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.tooltip(Tooltip::text(error_message))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -664,7 +694,7 @@ impl Render for PromptEditor {
}
}
impl FocusableView for PromptEditor {
impl Focusable for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
@@ -679,12 +709,13 @@ impl PromptEditor {
prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
assistant_panel: Option<&View<AssistantPanel>>,
workspace: Option<WeakView<Workspace>>,
assistant_panel: Option<&Model<AssistantPanel>>,
workspace: Option<WeakModel<Workspace>>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let prompt_editor = cx.new_view(|cx| {
let prompt_editor = cx.new_model(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -692,24 +723,28 @@ impl PromptEditor {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor.set_placeholder_text(Self::placeholder_text(window), cx);
editor
});
let mut token_count_subscriptions = Vec::new();
if let Some(assistant_panel) = assistant_panel {
token_count_subscriptions
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
token_count_subscriptions.push(cx.subscribe_in(
assistant_panel,
window,
Self::handle_assistant_panel_event,
));
}
let mut this = Self {
id,
height_in_lines: 1,
editor: prompt_editor,
language_model_selector: cx.new_view(|cx| {
language_model_selector: cx.new_model(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -719,6 +754,7 @@ impl PromptEditor {
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -726,7 +762,7 @@ impl PromptEditor {
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
_codegen_subscription: cx.observe_in(&codegen, window, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
pending_token_count: Task::ready(Ok(())),
@@ -740,15 +776,15 @@ impl PromptEditor {
this
}
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
fn placeholder_text(window: &mut Window) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, window)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
fn subscribe_to_editor(&mut self, cx: &mut ModelContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
@@ -760,7 +796,7 @@ impl PromptEditor {
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
fn count_lines(&mut self, cx: &mut ModelContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -778,15 +814,16 @@ impl PromptEditor {
fn handle_assistant_panel_event(
&mut self,
_: View<AssistantPanel>,
_: &Model<AssistantPanel>,
event: &AssistantPanelEvent,
cx: &mut ViewContext<Self>,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
let AssistantPanelEvent::ContextEdited { .. } = event;
self.count_tokens(cx);
}
fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
fn count_tokens(&mut self, cx: &mut ModelContext<Self>) {
let assist_id = self.id;
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
@@ -806,15 +843,15 @@ impl PromptEditor {
})
}
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
fn handle_prompt_editor_changed(&mut self, _: Model<Editor>, cx: &mut ModelContext<Self>) {
self.count_lines(cx);
}
fn handle_prompt_editor_events(
&mut self,
_: View<Editor>,
_: Model<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
@@ -837,7 +874,12 @@ impl PromptEditor {
}
}
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
fn handle_codegen_changed(
&mut self,
_: Model<Codegen>,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.editor
@@ -855,7 +897,7 @@ impl PromptEditor {
}
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -866,7 +908,7 @@ impl PromptEditor {
}
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut ModelContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() {
@@ -889,53 +931,58 @@ impl PromptEditor {
}
}
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
fn secondary_confirm(
&mut self,
_: &menu::SecondaryConfirm,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
});
}
}
}
fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
fn render_token_count(&self, cx: &mut ModelContext<Self>) -> Option<impl IntoElement> {
let model = LanguageModelRegistry::read_global(cx).active_model()?;
let token_count = self.token_count?;
let max_token_count = model.max_token_count();
@@ -965,34 +1012,35 @@ impl PromptEditor {
);
if let Some(workspace) = self.workspace.clone() {
token_count = token_count
.tooltip(|cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Tokens Used by Inline Assistant",
None,
"Click to Open Assistant Panel",
window,
cx,
)
})
.cursor_pointer()
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(move |_, window, cx| {
cx.stop_propagation();
workspace
.update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx)
workspace.focus_panel::<AssistantPanel>(window, cx)
})
.ok();
});
} else {
token_count = token_count
.cursor_default()
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx));
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
}
Some(token_count)
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_prompt_editor(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {

View File

@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, View, WeakView,
TextStyleRefinement, WeakModel,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -19,13 +19,13 @@ use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
use crate::ui::ContextPill;
pub struct ActiveThread {
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
thread: Model<Thread>,
messages: Vec<MessageId>,
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
rendered_messages_by_id: HashMap<MessageId, Model<Markdown>>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
@@ -33,14 +33,15 @@ pub struct ActiveThread {
impl ActiveThread {
pub fn new(
thread: Model<Thread>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe(&thread, Self::handle_thread_event),
cx.subscribe_in(&thread, window, Self::handle_thread_event),
];
let mut this = Self {
@@ -51,8 +52,8 @@ impl ActiveThread {
messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.view().downgrade();
move |ix, cx: &mut WindowContext| {
let this = cx.model().downgrade();
move |ix, _: &mut Window, cx: &mut AppContext| {
this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
}
@@ -62,7 +63,7 @@ impl ActiveThread {
};
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
this.push_message(&message.id, message.text.clone(), cx);
this.push_message(&message.id, message.text.clone(), window, cx);
}
this
@@ -84,7 +85,13 @@ impl ActiveThread {
self.last_error.take();
}
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
fn push_message(
&mut self,
id: &MessageId,
text: String,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let old_len = self.messages.len();
self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1);
@@ -93,7 +100,7 @@ impl ActiveThread {
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.rems(cx);
let mut text_style = cx.text_style();
let mut text_style = window.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
@@ -143,12 +150,13 @@ impl ActiveThread {
..Default::default()
};
let markdown = cx.new_view(|cx| {
let markdown = cx.new_model(|cx| {
Markdown::new(
text,
markdown_style,
Some(self.language_registry.clone()),
None,
window,
cx,
)
});
@@ -161,9 +169,10 @@ impl ActiveThread {
fn handle_thread_event(
&mut self,
_: Model<Thread>,
_: &Model<Thread>,
event: &ThreadEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
ThreadEvent::ShowError(error) => {
@@ -174,7 +183,7 @@ impl ActiveThread {
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| {
markdown.append(text, cx);
markdown.append(text, window, cx);
});
}
}
@@ -185,7 +194,7 @@ impl ActiveThread {
.message(*message_id)
.map(|message| message.text.clone())
{
self.push_message(message_id, message_text, cx);
self.push_message(message_id, message_text, window, cx);
}
cx.notify();
@@ -202,7 +211,7 @@ impl ActiveThread {
for tool_use in pending_tool_uses {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(
@@ -219,7 +228,7 @@ impl ActiveThread {
}
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
fn render_message(&self, ix: usize, cx: &mut ModelContext<Self>) -> AnyElement {
let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
@@ -291,7 +300,7 @@ impl ActiveThread {
}
impl Render for ActiveThread {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
list(self.list_state.clone()).flex_1().py_1()
}
}

View File

@@ -1,5 +1,5 @@
use fs::Fs;
use gpui::View;
use gpui::{Focusable, Model};
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file;
@@ -9,7 +9,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
pub struct AssistantModelSelector {
selector: View<LanguageModelSelector>,
selector: Model<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
@@ -17,10 +17,11 @@ impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Self {
Self {
selector: cx.new_view(|cx| {
selector: cx.new_model(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -30,6 +31,7 @@ impl AssistantModelSelector {
move |settings, _cx| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -39,7 +41,7 @@ impl AssistantModelSelector {
}
impl Render for AssistantModelSelector {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.selector.focus_handle(cx).clone();
@@ -76,8 +78,14 @@ impl Render for AssistantModelSelector {
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
}),
)
.with_handle(self.menu_handle.clone())

View File

@@ -6,8 +6,7 @@ use client::zed_urls;
use fs::Fs;
use gpui::{
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
WindowContext,
FocusHandle, Focusable, FontWeight, Model, ModelContext, Pixels, Task, WeakModel, Window,
};
use language::LanguageRegistry;
use settings::Settings;
@@ -25,22 +24,22 @@ use crate::thread_store::ThreadStore;
use crate::{NewThread, OpenHistory, ToggleFocus};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
cx.observe_new_models(
|workspace: &mut Workspace, _window, _cx: &mut ModelContext<Workspace>| {
workspace
.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
})
.register_action(|workspace, _: &NewThread, cx| {
.register_action(|workspace, _: &NewThread, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.new_thread(window, cx));
workspace.focus_panel::<AssistantPanel>(window, cx);
}
})
.register_action(|workspace, _: &OpenHistory, cx| {
.register_action(|workspace, _: &OpenHistory, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_history(cx));
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_history(window, cx));
}
});
},
@@ -54,25 +53,25 @@ enum ActiveView {
}
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
thread_store: Model<ThreadStore>,
thread: View<ActiveThread>,
message_editor: View<MessageEditor>,
thread: Model<ActiveThread>,
message_editor: Model<MessageEditor>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset,
active_view: ActiveView,
history: View<ThreadHistory>,
history: Model<ThreadHistory>,
width: Option<Pixels>,
height: Option<Pixels>,
}
impl AssistantPanel {
pub fn load(
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
let thread_store = workspace
@@ -82,8 +81,8 @@ impl AssistantPanel {
})?
.await?;
workspace.update(&mut cx, |workspace, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
workspace.update_in(&mut cx, |workspace, window, cx| {
cx.new_model(|cx| Self::new(workspace, thread_store, tools, window, cx))
})
})
}
@@ -92,13 +91,14 @@ impl AssistantPanel {
workspace: &Workspace,
thread_store: Model<ThreadStore>,
tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone();
let language_registry = workspace.project().read(cx).languages().clone();
let workspace = workspace.weak_handle();
let weak_self = cx.view().downgrade();
let weak_self = cx.model().downgrade();
Self {
active_view: ActiveView::Thread,
@@ -106,21 +106,23 @@ impl AssistantPanel {
fs: fs.clone(),
language_registry: language_registry.clone(),
thread_store: thread_store.clone(),
thread: cx.new_view(|cx| {
thread: cx.new_model(|cx| {
ActiveThread::new(
thread.clone(),
workspace.clone(),
language_registry,
tools.clone(),
window,
cx,
)
}),
message_editor: cx.new_view(|cx| {
message_editor: cx.new_model(|cx| {
MessageEditor::new(
fs.clone(),
workspace,
thread_store.downgrade(),
thread.clone(),
window,
cx,
)
}),
@@ -129,7 +131,7 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(),
)
.unwrap(),
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
history: cx.new_model(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
width: None,
height: None,
}
@@ -143,40 +145,47 @@ impl AssistantPanel {
&self.thread_store
}
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
fn new_thread(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
let thread = self
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
self.active_view = ActiveView::Thread;
self.thread = cx.new_view(|cx| {
self.thread = cx.new_model(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
self.language_registry.clone(),
self.tools.clone(),
window,
cx,
)
});
self.message_editor = cx.new_view(|cx| {
self.message_editor = cx.new_model(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.thread_store.downgrade(),
thread,
window,
cx,
)
});
self.message_editor.focus_handle(cx).focus(cx);
self.message_editor.focus_handle(cx).focus(window);
}
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
fn open_history(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(cx);
self.history.focus_handle(cx).focus(window);
cx.notify();
}
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
pub(crate) fn open_thread(
&mut self,
thread_id: &ThreadId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let Some(thread) = self
.thread_store
.update(cx, |this, cx| this.open_thread(thread_id, cx))
@@ -185,34 +194,36 @@ impl AssistantPanel {
};
self.active_view = ActiveView::Thread;
self.thread = cx.new_view(|cx| {
self.thread = cx.new_model(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
self.language_registry.clone(),
self.tools.clone(),
window,
cx,
)
});
self.message_editor = cx.new_view(|cx| {
self.message_editor = cx.new_model(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.thread_store.downgrade(),
thread,
window,
cx,
)
});
self.message_editor.focus_handle(cx).focus(cx);
self.message_editor.focus_handle(cx).focus(window);
}
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ModelContext<Self>) {
self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx));
}
}
impl FocusableView for AssistantPanel {
impl Focusable for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx),
@@ -228,7 +239,7 @@ impl Panel for AssistantPanel {
"AssistantPanel2"
}
fn position(&self, _cx: &WindowContext) -> DockPosition {
fn position(&self, _window: &Window, _cx: &AppContext) -> DockPosition {
DockPosition::Right
}
@@ -236,7 +247,12 @@ impl Panel for AssistantPanel {
true
}
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
fn set_position(
&mut self,
position: DockPosition,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
settings::update_settings_file::<AssistantSettings>(
self.fs.clone(),
cx,
@@ -251,9 +267,9 @@ impl Panel for AssistantPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, window: &Window, cx: &AppContext) -> Pixels {
let settings = AssistantSettings::get_global(cx);
match self.position(cx) {
match self.position(window, cx) {
DockPosition::Left | DockPosition::Right => {
self.width.unwrap_or(settings.default_width)
}
@@ -261,25 +277,25 @@ impl Panel for AssistantPanel {
}
}
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
match self.position(cx) {
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut ModelContext<Self>) {
match self.position(window, cx) {
DockPosition::Left | DockPosition::Right => self.width = size,
DockPosition::Bottom => self.height = size,
}
cx.notify();
}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut ModelContext<Self>) {}
fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel)
}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
fn icon(&self, _window: &Window, _cx: &AppContext) -> Option<IconName> {
Some(IconName::ZedAssistant2)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
fn icon_tooltip(&self, _window: &Window, _cx: &AppContext) -> Option<&'static str> {
Some("Assistant Panel")
}
@@ -293,7 +309,7 @@ impl Panel for AssistantPanel {
}
impl AssistantPanel {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_toolbar(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
h_flex()
@@ -319,17 +335,18 @@ impl AssistantPanel {
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"New Thread",
&NewThread,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, cx| {
cx.dispatch_action(NewThread.boxed_clone());
.on_click(move |_event, window, cx| {
window.dispatch_action(NewThread.boxed_clone(), cx);
}),
)
.child(
@@ -338,40 +355,51 @@ impl AssistantPanel {
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Open History",
&OpenHistory,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
)
.child(
IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
.on_click(move |_event, _cx| {
.tooltip(Tooltip::text("Configure Assistant"))
.on_click(move |_event, _window, _cx| {
println!("Configure Assistant");
}),
),
)
}
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
fn render_active_thread_or_empty_state(
&self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> AnyElement {
if self.thread.read(cx).is_empty() {
return self.render_thread_empty_state(cx).into_any_element();
return self
.render_thread_empty_state(window, cx)
.into_any_element();
}
self.thread.clone().into_any()
self.thread.clone().into_any_element()
}
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_thread_empty_state(
&self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
let recent_threads = self
.thread_store
.update(cx, |this, cx| this.recent_threads(3, cx));
@@ -402,7 +430,7 @@ impl AssistantPanel {
v_flex().mx_auto().w_4_5().gap_2().children(
recent_threads
.into_iter()
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
.map(|thread| PastThread::new(thread, cx.model().downgrade())),
),
)
.child(
@@ -413,17 +441,17 @@ impl AssistantPanel {
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
cx,
window,
))
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
),
)
})
}
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
fn render_last_error(&self, cx: &mut ModelContext<Self>) -> Option<AnyElement> {
let last_error = self.thread.read(cx).last_error()?;
Some(
@@ -449,7 +477,7 @@ impl AssistantPanel {
)
}
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
fn render_payment_required_error(&self, cx: &mut ModelContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
v_flex()
@@ -473,7 +501,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, cx| {
|this, _, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -483,7 +511,7 @@ impl AssistantPanel {
},
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
|this, _, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -495,7 +523,7 @@ impl AssistantPanel {
.into_any()
}
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
fn render_max_monthly_spend_reached_error(&self, cx: &mut ModelContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex()
@@ -520,7 +548,7 @@ impl AssistantPanel {
.mt_1()
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, cx| {
cx.listener(|this, _, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -531,7 +559,7 @@ impl AssistantPanel {
),
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
|this, _, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -546,7 +574,7 @@ impl AssistantPanel {
fn render_error_message(
&self,
error_message: &SharedString,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> AnyElement {
v_flex()
.gap_0p5()
@@ -572,7 +600,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
|this, _, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -586,21 +614,21 @@ impl AssistantPanel {
}
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex()
.key_context("AssistantPanel2")
.justify_between()
.size_full()
.on_action(cx.listener(|this, _: &NewThread, cx| {
this.new_thread(cx);
.on_action(cx.listener(|this, _: &NewThread, window, cx| {
this.new_thread(window, cx);
}))
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.open_history(cx);
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
this.open_history(window, cx);
}))
.child(self.render_toolbar(cx))
.map(|parent| match self.active_view {
ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(cx))
.child(self.render_active_thread_or_empty_state(window, cx))
.child(
h_flex()
.border_t_1()

View File

@@ -6,8 +6,8 @@ mod thread_context_picker;
use std::sync::Arc;
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
WeakModel, WeakView,
AppContext, DismissEvent, EventEmitter, FocusHandle, Focusable, Model, SharedString, Task,
WeakModel,
};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
@@ -32,24 +32,25 @@ pub enum ConfirmBehavior {
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default,
File(View<FileContextPicker>),
Directory(View<DirectoryContextPicker>),
Fetch(View<FetchContextPicker>),
Thread(View<ThreadContextPicker>),
File(Model<FileContextPicker>),
Directory(Model<DirectoryContextPicker>),
Fetch(Model<FetchContextPicker>),
Thread(Model<ThreadContextPicker>),
}
pub(super) struct ContextPicker {
mode: ContextPickerMode,
picker: View<Picker<ContextPickerDelegate>>,
picker: Model<Picker<ContextPickerDelegate>>,
}
impl ContextPicker {
pub fn new(
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let mut entries = Vec::new();
entries.push(ContextPickerEntry {
@@ -82,7 +83,7 @@ impl ContextPicker {
}
let delegate = ContextPickerDelegate {
context_picker: cx.view().downgrade(),
context_picker: cx.model().downgrade(),
workspace,
thread_store,
context_store,
@@ -91,8 +92,9 @@ impl ContextPicker {
selected_ix: 0,
};
let picker = cx.new_view(|cx| {
Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
let picker = cx.new_model(|cx| {
Picker::nonsearchable_uniform_list(delegate, window, cx)
.max_height(Some(rems(20.).into()))
});
ContextPicker {
@@ -108,7 +110,7 @@ impl ContextPicker {
impl EventEmitter<DismissEvent> for ContextPicker {}
impl FocusableView for ContextPicker {
impl Focusable for ContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match &self.mode {
ContextPickerMode::Default => self.picker.focus_handle(cx),
@@ -121,7 +123,7 @@ impl FocusableView for ContextPicker {
}
impl Render for ContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex()
.w(px(400.))
.min_w(px(400.))
@@ -145,8 +147,8 @@ struct ContextPickerEntry {
}
pub(crate) struct ContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
@@ -165,65 +167,84 @@ impl PickerDelegate for ContextPickerDelegate {
self.selected_ix
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Select a context source…".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 ModelContext<Picker<Self>>,
) -> Task<()> {
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
if let Some(entry) = self.entries.get(self.selected_ix) {
self.context_picker
.update(cx, |this, cx| {
match entry.kind {
ContextKind::File => {
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
this.mode = ContextPickerMode::File(cx.new_model(|cx| {
FileContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Directory => {
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
this.mode = ContextPickerMode::Directory(cx.new_model(|cx| {
DirectoryContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::FetchedUrl => {
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
this.mode = ContextPickerMode::Fetch(cx.new_model(|cx| {
FetchContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() {
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
this.mode = ContextPickerMode::Thread(cx.new_model(|cx| {
ThreadContextPicker::new(
thread_store.clone(),
self.context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
@@ -231,13 +252,13 @@ impl PickerDelegate for ContextPickerDelegate {
}
}
cx.focus_self();
cx.focus_self(window);
})
.log_err();
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| match this.mode {
ContextPickerMode::Default => cx.emit(DismissEvent),
@@ -253,7 +274,8 @@ impl PickerDelegate for ContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let entry = &self.entries[ix];

View File

@@ -6,7 +6,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use ui::{prelude::*, ListItem};
@@ -18,16 +18,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct DirectoryContextPicker {
picker: View<Picker<DirectoryContextPickerDelegate>>,
picker: Model<Picker<DirectoryContextPickerDelegate>>,
}
impl DirectoryContextPicker {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = DirectoryContextPickerDelegate::new(
context_picker,
@@ -35,27 +36,27 @@ impl DirectoryContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl FocusableView for DirectoryContextPicker {
impl Focusable for DirectoryContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for DirectoryContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
@@ -64,8 +65,8 @@ pub struct DirectoryContextPickerDelegate {
impl DirectoryContextPickerDelegate {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -83,8 +84,9 @@ impl DirectoryContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
workspace: &Model<Workspace>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -150,22 +152,32 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
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 ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search folders…".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, window, cx);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let mut paths = search_task.await;
let empty_path = Path::new("");
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
@@ -177,7 +189,12 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -192,8 +209,8 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |this, cx| {
cx.spawn_in(window, |this, mut cx| async move {
this.update_in(&mut cx, |this, window, cx| {
let mut text = String::new();
// TODO: Add the files from the selected directory.
@@ -210,7 +227,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}
anyhow::Ok(())
@@ -221,7 +238,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
.detach_and_log_err(cx)
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -234,7 +251,8 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string();

View File

@@ -4,11 +4,11 @@ use std::sync::Arc;
use anyhow::{bail, Context as _, Result};
use futures::AsyncReadExt as _;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClientWithUrl};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ViewContext};
use ui::{prelude::*, ListItem, ModelContext, Window};
use workspace::Workspace;
use crate::context::ContextKind;
@@ -16,16 +16,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FetchContextPicker {
picker: View<Picker<FetchContextPickerDelegate>>,
picker: Model<Picker<FetchContextPickerDelegate>>,
}
impl FetchContextPicker {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(
context_picker,
@@ -33,20 +34,20 @@ impl FetchContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl FocusableView for FetchContextPicker {
impl Focusable for FetchContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FetchContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -59,8 +60,8 @@ enum ContentType {
}
pub struct FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
url: String,
@@ -68,8 +69,8 @@ pub struct FetchContextPickerDelegate {
impl FetchContextPickerDelegate {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -166,7 +167,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
}
}
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
fn no_matches_text(&self, _window: &mut Window, _cx: &mut AppContext) -> SharedString {
"Enter the URL that you would like to fetch".into()
}
@@ -174,19 +175,35 @@ impl PickerDelegate for FetchContextPickerDelegate {
0
}
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 ModelContext<Picker<Self>>,
) {
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Enter a URL…".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 ModelContext<Picker<Self>>,
) -> Task<()> {
self.url = query;
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
@@ -194,10 +211,10 @@ impl PickerDelegate for FetchContextPickerDelegate {
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let text = Self::build_message(http_client, &url).await?;
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.delegate
.context_store
.update(cx, |context_store, _cx| {
@@ -206,7 +223,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}
anyhow::Ok(())
@@ -217,7 +234,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
.detach_and_log_err(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -230,7 +247,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
Some(
ListItem::new(ix)

View File

@@ -5,7 +5,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use ui::{prelude::*, ListItem};
@@ -17,16 +17,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FileContextPicker {
picker: View<Picker<FileContextPickerDelegate>>,
picker: Model<Picker<FileContextPickerDelegate>>,
}
impl FileContextPicker {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(
context_picker,
@@ -34,27 +35,27 @@ impl FileContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl FocusableView for FileContextPicker {
impl Focusable for FileContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FileContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
@@ -63,8 +64,8 @@ pub struct FileContextPickerDelegate {
impl FileContextPickerDelegate {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -82,8 +83,9 @@ impl FileContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
workspace: &Model<Workspace>,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -165,22 +167,32 @@ impl PickerDelegate for FileContextPickerDelegate {
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 ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search files…".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
// TODO: This should be probably be run in the background.
let paths = search_task.await;
@@ -191,7 +203,12 @@ impl PickerDelegate for FileContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -206,7 +223,7 @@ impl PickerDelegate for FileContextPickerDelegate {
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let Some(open_buffer_task) = project
.update(&mut cx, |project, cx| {
project.open_buffer((worktree_id, path.clone()), cx)
@@ -218,7 +235,7 @@ impl PickerDelegate for FileContextPickerDelegate {
let buffer = open_buffer_task.await?;
this.update(&mut cx, |this, cx| {
this.update_in(&mut cx, |this, window, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
@@ -240,7 +257,7 @@ impl PickerDelegate for FileContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}
anyhow::Ok(())
@@ -251,7 +268,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.detach_and_log_err(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -264,7 +281,8 @@ impl PickerDelegate for FileContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
@@ -12,16 +12,17 @@ use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker {
picker: View<Picker<ThreadContextPickerDelegate>>,
picker: Model<Picker<ThreadContextPickerDelegate>>,
}
impl ThreadContextPicker {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_picker: WeakModel<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = ThreadContextPickerDelegate::new(
thread_store,
@@ -29,20 +30,20 @@ impl ThreadContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
ThreadContextPicker { picker }
}
}
impl FocusableView for ThreadContextPicker {
impl Focusable for ThreadContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ThreadContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -55,7 +56,7 @@ struct ThreadContextEntry {
pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_picker: WeakModel<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
@@ -65,7 +66,7 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_picker: WeakModel<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -91,15 +92,25 @@ impl PickerDelegate for ThreadContextPickerDelegate {
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 ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search threads…".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let Ok(threads) = self.thread_store.update(cx, |this, cx| {
this.threads(cx)
.into_iter()
@@ -142,7 +153,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
});
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let matches = search_task.await;
this.update(&mut cx, |this, cx| {
this.delegate.matches = matches;
@@ -153,7 +164,12 @@ impl PickerDelegate for ThreadContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
@@ -193,11 +209,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
ConfirmBehavior::Close => self.dismissed(window, cx),
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -210,7 +226,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let thread = &self.matches[ix];

View File

@@ -1,6 +1,6 @@
use std::rc::Rc;
use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
use gpui::{FocusHandle, Model, WeakModel};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use workspace::Workspace;
@@ -13,7 +13,7 @@ use settings::Settings;
pub struct ContextStrip {
context_store: Model<ContextStore>,
context_picker: View<ContextPicker>,
context_picker: Model<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle,
}
@@ -21,20 +21,22 @@ pub struct ContextStrip {
impl ContextStrip {
pub fn new(
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
focus_handle: FocusHandle,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
Self {
context_store: context_store.clone(),
context_picker: cx.new_view(|cx| {
context_picker: cx.new_model(|cx| {
ContextPicker::new(
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
ConfirmBehavior::KeepOpen,
window,
cx,
)
}),
@@ -45,7 +47,7 @@ impl ContextStrip {
}
impl Render for ContextStrip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context().clone();
let context_picker = self.context_picker.clone();
let focus_handle = self.focus_handle.clone();
@@ -55,16 +57,17 @@ impl Render for ContextStrip {
.gap_1()
.child(
PopoverMenu::new("context-picker")
.menu(move |_cx| Some(context_picker.clone()))
.menu(move |_window, _cx| Some(context_picker.clone()))
.trigger(
IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}),
@@ -92,7 +95,7 @@ impl Render for ContextStrip {
ui::KeyBinding::for_action_in(
&ToggleContextPicker,
&self.focus_handle,
cx,
window,
)
.map(|binding| binding.into_any_element()),
)
@@ -104,7 +107,7 @@ impl Render for ContextStrip {
ContextPill::new(context.clone()).on_remove({
let context = context.clone();
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, cx| {
Rc::new(cx.listener(move |_this, _event, _, cx| {
context_store.update(cx, |this, _cx| {
this.remove_context(&context.id);
});
@@ -117,10 +120,10 @@ impl Render for ContextStrip {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
.tooltip(Tooltip::text("Remove All Context"))
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
cx.listener(move |_this, _event, _, cx| {
context_store.update(cx, |this, _cx| this.clear());
cx.notify();
})

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@ use feature_flags::{FeatureFlagAppExt as _, ZedPro};
use fs::Fs;
use gpui::{
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext,
WeakModel, WeakView, WindowContext,
FocusHandle, Focusable, FontWeight, Model, ModelContext, Subscription, TextStyle, WeakModel,
Window,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
@@ -34,11 +34,11 @@ use util::ResultExt;
use workspace::Workspace;
pub struct PromptEditor<T> {
pub editor: View<Editor>,
pub editor: Model<Editor>,
mode: PromptEditorMode,
context_strip: View<ContextStrip>,
context_strip: Model<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector: Model<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
@@ -53,7 +53,7 @@ pub struct PromptEditor<T> {
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let mut buttons = Vec::new();
let left_gutter_spacing = match &self.mode {
@@ -83,7 +83,7 @@ impl<T: 'static> Render for PromptEditor<T> {
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
};
buttons.extend(self.render_buttons(cx));
buttons.extend(self.render_buttons(window, cx));
v_flex()
.key_context("PromptEditor")
@@ -154,9 +154,7 @@ impl<T: 'static> Render for PromptEditor<T> {
el.child(
div()
.id("error")
.tooltip(move |cx| {
Tooltip::text(error_message.clone(), cx)
})
.tooltip(Tooltip::text(error_message))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -170,7 +168,7 @@ impl<T: 'static> Render for PromptEditor<T> {
h_flex()
.w_full()
.justify_between()
.child(div().flex_1().child(self.render_editor(cx)))
.child(div().flex_1().child(self.render_editor(window, cx)))
.child(h_flex().gap_1().children(buttons)),
),
)
@@ -188,7 +186,7 @@ impl<T: 'static> Render for PromptEditor<T> {
}
}
impl<T: 'static> FocusableView for PromptEditor<T> {
impl<T: 'static> Focusable for PromptEditor<T> {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
@@ -204,40 +202,47 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
fn subscribe_to_editor(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
self.editor_subscriptions.push(cx.subscribe_in(
&self.editor,
window,
Self::handle_prompt_editor_events,
));
}
pub fn set_show_cursor_when_unfocused(
&mut self,
show_cursor_when_unfocused: bool,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
});
}
pub fn unlink(&mut self, cx: &mut ViewContext<Self>) {
pub fn unlink(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
let prompt = self.prompt(cx);
let focus = self.editor.focus_handle(cx).contains_focused(cx);
self.editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
self.editor = cx.new_model(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, window, cx), cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, cx);
editor.set_text(prompt, window, cx);
if focus {
editor.focus(cx);
window.focus(&editor.focus_handle(cx));
}
editor
});
self.subscribe_to_editor(cx);
self.subscribe_to_editor(window, cx);
}
pub fn placeholder_text(mode: &PromptEditorMode, cx: &WindowContext) -> String {
pub fn placeholder_text(
mode: &PromptEditorMode,
window: &mut Window,
cx: &mut AppContext,
) -> String {
let action = match mode {
PromptEditorMode::Buffer { codegen, .. } => {
if codegen.read(cx).is_insertion {
@@ -249,7 +254,7 @@ impl<T: 'static> PromptEditor<T> {
PromptEditorMode::Terminal { .. } => "Generate",
};
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, cx)
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, window)
.map(|keybinding| format!("{keybinding} to chat ― "))
.unwrap_or_default();
@@ -260,25 +265,31 @@ impl<T: 'static> PromptEditor<T> {
self.editor.read(cx).text(cx)
}
fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
fn toggle_rate_limit_notice(
&mut self,
_: &ClickEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.show_rate_limit_notice = !self.show_rate_limit_notice;
if self.show_rate_limit_notice {
cx.focus_view(&self.editor);
window.focus(&self.editor.focus_handle(cx));
}
cx.notify();
}
fn handle_prompt_editor_events(
&mut self,
_: View<Editor>,
_: &Model<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, cx| {
.update(cx, |workspace, _, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
@@ -312,15 +323,30 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx);
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -331,7 +357,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut ModelContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
cx.emit(PromptEditorEvent::StartRequested);
@@ -352,47 +378,47 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
});
}
}
}
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
fn render_buttons(&self, _window: &mut Window, cx: &mut ModelContext<Self>) -> Vec<AnyElement> {
let mode = match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
let codegen = codegen.read(cx);
@@ -414,21 +440,22 @@ impl<T: 'static> PromptEditor<T> {
.icon(IconName::Return)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element()]
}
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element()],
CodegenStatus::Done | CodegenStatus::Error(_) => {
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
@@ -436,15 +463,16 @@ impl<T: 'static> PromptEditor<T> {
vec![IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element()]
@@ -452,10 +480,10 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element();
@@ -466,14 +494,15 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
@@ -485,7 +514,12 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
fn cycle_prev(
&mut self,
_: &CyclePreviousInlineAssist,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx));
@@ -496,7 +530,12 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
fn cycle_next(
&mut self,
_: &CycleNextInlineAssist,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx));
@@ -507,16 +546,20 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
fn render_close_button(&self, cx: &mut ModelContext<Self>) -> AnyElement {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.tooltip(Tooltip::text("Close Assistant"))
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element()
}
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
fn render_cycle_controls(
&self,
codegen: &BufferCodegen,
cx: &ModelContext<Self>,
) -> AnyElement {
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
let model_registry = LanguageModelRegistry::read_global(cx);
@@ -556,13 +599,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |cx| {
cx.new_view(|cx| {
move |window, cx| {
cx.new_model(|_| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
cx,
window,
),
);
if !disabled && current_index != 0 {
@@ -573,8 +616,8 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(cx.listener(|this, _, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, cx);
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
})),
)
.child(
@@ -597,13 +640,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |cx| {
cx.new_view(|cx| {
move |window, cx| {
cx.new_model(|_| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
cx,
window,
),
);
if !disabled && current_index != total_models - 1 {
@@ -614,14 +657,14 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)),
),
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_next(&CycleNextInlineAssist, window, cx)
})),
)
.into_any_element()
}
fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_rate_limit_notice(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
Popover::new().child(
v_flex()
.occlude()
@@ -645,7 +688,7 @@ impl<T: 'static> PromptEditor<T> {
} else {
ui::ToggleState::Unselected
},
|selection, cx| {
|selection, _, cx| {
let is_dismissed = match selection {
ui::ToggleState::Unselected => false,
ui::ToggleState::Indeterminate => return,
@@ -664,10 +707,11 @@ impl<T: 'static> PromptEditor<T> {
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
)
.child(Button::new("more-info", "More Info").on_click(
|_event, cx| {
cx.dispatch_action(Box::new(
zed_actions::OpenAccountSettings,
))
|_event, window, cx| {
window.dispatch_action(
Box::new(zed_actions::OpenAccountSettings),
cx,
)
},
)),
),
@@ -675,9 +719,9 @@ impl<T: 'static> PromptEditor<T> {
)
}
fn render_editor(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
fn render_editor(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> AnyElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
div()
.key_context("MessageEditor")
@@ -752,9 +796,10 @@ impl PromptEditor<BufferCodegen> {
codegen: Model<BufferCodegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
window: &mut Window,
cx: &mut ModelContext<PromptEditor<BufferCodegen>>,
) -> PromptEditor<BufferCodegen> {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Buffer {
@@ -763,7 +808,7 @@ impl PromptEditor<BufferCodegen> {
gutter_dimensions,
};
let prompt_editor = cx.new_view(|cx| {
let prompt_editor = cx.new_model(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -771,6 +816,7 @@ impl PromptEditor<BufferCodegen> {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
@@ -778,7 +824,7 @@ impl PromptEditor<BufferCodegen> {
// always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them.
editor.set_show_cursor_when_unfocused(true, cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
@@ -786,19 +832,20 @@ impl PromptEditor<BufferCodegen> {
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| {
context_strip: cx.new_model(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
cx,
)
}),
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
}),
model_selector_menu_handle,
edited_since_done: false,
@@ -812,14 +859,14 @@ impl PromptEditor<BufferCodegen> {
_phantom: Default::default(),
};
this.subscribe_to_editor(cx);
this.subscribe_to_editor(window, cx);
this
}
fn handle_codegen_changed(
&mut self,
_: Model<BufferCodegen>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
cx: &mut ModelContext<PromptEditor<BufferCodegen>>,
) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
@@ -895,9 +942,10 @@ impl PromptEditor<TerminalCodegen> {
codegen: Model<TerminalCodegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Terminal {
@@ -906,7 +954,7 @@ impl PromptEditor<TerminalCodegen> {
height_in_lines: 1,
};
let prompt_editor = cx.new_view(|cx| {
let prompt_editor = cx.new_model(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -914,10 +962,11 @@ impl PromptEditor<TerminalCodegen> {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
@@ -925,19 +974,20 @@ impl PromptEditor<TerminalCodegen> {
let mut this = Self {
editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| {
context_strip: cx.new_model(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
cx,
)
}),
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
}),
model_selector_menu_handle,
edited_since_done: false,
@@ -951,11 +1001,11 @@ impl PromptEditor<TerminalCodegen> {
_phantom: Default::default(),
};
this.count_lines(cx);
this.subscribe_to_editor(cx);
this.subscribe_to_editor(window, cx);
this
}
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
fn count_lines(&mut self, cx: &mut ModelContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -979,7 +1029,7 @@ impl PromptEditor<TerminalCodegen> {
}
}
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ViewContext<Self>) {
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ModelContext<Self>) {
match &self.codegen().read(cx).status {
CodegenStatus::Idle => {
self.editor

View File

@@ -2,10 +2,7 @@ use std::sync::Arc;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
WeakView,
};
use gpui::{AppContext, DismissEvent, Focusable, Model, Subscription, TextStyle, WeakModel};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use rope::Point;
@@ -27,13 +24,13 @@ use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor {
thread: Model<Thread>,
editor: View<Editor>,
editor: Model<Editor>,
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_strip: Model<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker: Model<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector: Model<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
@@ -42,36 +39,39 @@ pub struct MessageEditor {
impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
thread_store: WeakModel<ThreadStore>,
thread: Model<Thread>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new());
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
let editor = cx.new_model(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
editor.set_placeholder_text("Ask anything…", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new_view(|cx| {
let inline_context_picker = cx.new_model(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
ConfirmBehavior::Close,
window,
cx,
)
});
let subscriptions = vec![
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(
&inline_context_picker,
window,
Self::handle_inline_context_picker_event,
),
];
@@ -80,21 +80,22 @@ impl MessageEditor {
thread,
editor: editor.clone(),
context_store: context_store.clone(),
context_strip: cx.new_view(|cx| {
context_strip: cx.new_model(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
cx,
)
}),
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
}),
model_selector_menu_handle,
use_tools: false,
@@ -102,22 +103,33 @@ impl MessageEditor {
}
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx)
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx)
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
}
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut ModelContext<Self>) {
self.send_to_model(RequestKind::Chat, window, cx);
}
fn send_to_model(
&mut self,
request_kind: RequestKind,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<()> {
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
@@ -133,7 +145,7 @@ impl MessageEditor {
let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx);
editor.clear(cx);
editor.clear(window, cx);
text
});
let context = self.context_store.update(cx, |this, _cx| this.drain());
@@ -163,9 +175,10 @@ impl MessageEditor {
fn handle_editor_event(
&mut self,
editor: View<Editor>,
editor: &Model<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
@@ -176,7 +189,7 @@ impl MessageEditor {
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') {
self.inline_context_picker_menu_handle.show(cx);
self.inline_context_picker_menu_handle.show(window, cx);
}
}
});
@@ -187,25 +200,26 @@ impl MessageEditor {
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: View<ContextPicker>,
_inline_context_picker: &Model<ContextPicker>,
_event: &DismissEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
window.focus(&editor_focus_handle);
}
}
impl FocusableView for MessageEditor {
impl Focusable for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for MessageEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
let focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
@@ -247,7 +261,7 @@ impl Render for MessageEditor {
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_cx| Some(inline_context_picker.clone()))
.menu(move |_window, _cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
@@ -263,7 +277,7 @@ impl Render for MessageEditor {
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
cx.listener(|this, selection, _window, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => {
@@ -279,11 +293,11 @@ impl Render for MessageEditor {
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
KeyBinding::for_action_in(&Chat, &focus_handle, window)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
}),
),
),

View File

@@ -11,10 +11,7 @@ use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs;
use gpui::{
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use gpui::{AppContext, Context, Focusable, Global, Model, Subscription, UpdateGlobal, WeakModel};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
@@ -68,10 +65,11 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &View<TerminalView>,
workspace: WeakView<Workspace>,
terminal_view: &Model<TerminalView>,
workspace: WeakModel<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
@@ -81,7 +79,7 @@ impl TerminalInlineAssistant {
let context_store = cx.new_model(|_cx| ContextStore::new());
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| {
let prompt_editor = cx.new_model(|cx| {
PromptEditor::new_terminal(
assist_id,
self.prompt_history.clone(),
@@ -91,6 +89,7 @@ impl TerminalInlineAssistant {
context_store.clone(),
workspace.clone(),
thread_store.clone(),
window,
cx,
)
});
@@ -100,7 +99,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
};
terminal_view.update(cx, |terminal_view, cx| {
terminal_view.set_block_below_cursor(block, cx);
terminal_view.set_block_below_cursor(block, window, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -109,21 +108,27 @@ impl TerminalInlineAssistant {
prompt_editor,
workspace.clone(),
context_store,
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, cx);
self.focus_assist(assist_id, window, cx);
}
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
) {
let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| {
editor.focus(cx);
editor.select_all(&SelectAll, cx);
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
});
});
}
@@ -131,9 +136,10 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
prompt_editor: Model<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
let assist_id = prompt_editor.read(cx).id();
match event {
@@ -144,21 +150,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, cx);
self.finish_assist(assist_id, false, *execute, window, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, cx);
self.finish_assist(assist_id, true, false, window, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, cx);
self.dismiss_assist(assist_id, window, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -196,7 +202,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -209,7 +215,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut WindowContext,
cx: &mut AppContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -265,16 +271,17 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
self.dismiss_assist(assist_id, cx);
self.dismiss_assist(assist_id, window, cx);
if let Some(assist) = self.assists.remove(&assist_id) {
assist
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx);
this.focus_handle(cx).focus(window);
})
.log_err();
@@ -317,7 +324,8 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -330,7 +338,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx);
this.focus_handle(cx).focus(window);
})
.is_ok()
}
@@ -339,7 +347,8 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -351,7 +360,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, cx);
terminal.set_block_below_cursor(block, window, cx);
})
.log_err();
}
@@ -360,10 +369,10 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
terminal: WeakModel<TerminalView>,
prompt_editor: Option<Model<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
context_store: Model<ContextStore>,
_subscriptions: Vec<Subscription>,
}
@@ -371,11 +380,12 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>,
terminal: &Model<TerminalView>,
prompt_editor: Model<PromptEditor<TerminalCodegen>>,
workspace: WeakModel<Workspace>,
context_store: Model<ContextStore>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen().clone();
Self {
@@ -385,12 +395,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
context_store,
_subscriptions: vec![
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, cx)
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
})
}),
cx.subscribe(&codegen, move |codegen, event, cx| {
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -419,7 +429,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, cx);
this.finish_assist(assist_id, false, false, window, cx);
}
}
})

View File

@@ -1,5 +1,5 @@
use gpui::{
uniform_list, AppContext, FocusHandle, FocusableView, Model, UniformListScrollHandle, WeakView,
uniform_list, AppContext, FocusHandle, Focusable, Model, UniformListScrollHandle, WeakModel,
};
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
@@ -10,16 +10,17 @@ use crate::AssistantPanel;
pub struct ThreadHistory {
focus_handle: FocusHandle,
assistant_panel: WeakView<AssistantPanel>,
assistant_panel: WeakModel<AssistantPanel>,
thread_store: Model<ThreadStore>,
scroll_handle: UniformListScrollHandle,
}
impl ThreadHistory {
pub(crate) fn new(
assistant_panel: WeakView<AssistantPanel>,
assistant_panel: WeakModel<AssistantPanel>,
thread_store: Model<ThreadStore>,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> Self {
Self {
focus_handle: cx.focus_handle(),
@@ -30,14 +31,14 @@ impl ThreadHistory {
}
}
impl FocusableView for ThreadHistory {
impl Focusable for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ThreadHistory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
v_flex()
@@ -59,10 +60,10 @@ impl Render for ThreadHistory {
} else {
history.child(
uniform_list(
cx.view().clone(),
cx.model().clone(),
"thread-history",
threads.len(),
move |history, range, _cx| {
move |history, range, _window, _cx| {
threads[range]
.iter()
.map(|thread| {
@@ -85,11 +86,11 @@ impl Render for ThreadHistory {
#[derive(IntoElement)]
pub struct PastThread {
thread: Model<Thread>,
assistant_panel: WeakView<AssistantPanel>,
assistant_panel: WeakModel<AssistantPanel>,
}
impl PastThread {
pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
pub fn new(thread: Model<Thread>, assistant_panel: WeakModel<AssistantPanel>) -> Self {
Self {
thread,
assistant_panel,
@@ -98,7 +99,7 @@ impl PastThread {
}
impl RenderOnce for PastThread {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let (id, summary) = {
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
let thread = self.thread.read(cx);
@@ -139,11 +140,11 @@ impl RenderOnce for PastThread {
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
.tooltip(Tooltip::text("Delete Thread"))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, cx| {
move |_event, _, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&id, cx);
@@ -156,10 +157,10 @@ impl RenderOnce for PastThread {
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, cx| {
move |_event, window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.open_thread(&id, cx);
this.open_thread(&id, window, cx);
})
.ok();
}

View File

@@ -8,7 +8,7 @@ use crate::context::{Context, ContextKind};
#[derive(IntoElement)]
pub struct ContextPill {
context: Context,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut AppContext)>>,
}
impl ContextPill {
@@ -19,14 +19,17 @@ impl ContextPill {
}
}
pub fn on_remove(mut self, on_remove: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
pub fn on_remove(
mut self,
on_remove: Rc<dyn Fn(&ClickEvent, &mut Window, &mut AppContext)>,
) -> Self {
self.on_remove = Some(on_remove);
self
}
}
impl RenderOnce for ContextPill {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let padding_right = if self.on_remove.is_some() {
px(2.)
} else {
@@ -57,7 +60,7 @@ impl RenderOnce for ContextPill {
.icon_size(IconSize::XSmall)
.on_click({
let on_remove = on_remove.clone();
move |event, cx| on_remove(event, cx)
move |event, window, cx| on_remove(event, window, cx)
}),
)
})

View File

@@ -6,7 +6,7 @@ pub use crate::slash_command_registry::*;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakModel, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
use serde::{Deserialize, Serialize};
@@ -78,8 +78,9 @@ pub trait SlashCommand: 'static + Send + Sync {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakModel<Workspace>>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>>;
fn requires_argument(&self) -> bool;
fn accepts_arguments(&self) -> bool {
@@ -90,21 +91,27 @@ pub trait SlashCommand: 'static + Send + Sync {
arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting.
//
// It may be that `LspAdapterDelegate` needs a more general name, or
// perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult>;
}
pub type RenderFoldPlaceholder = Arc<
dyn Send
+ Sync
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
+ Fn(
ElementId,
Arc<dyn Fn(&mut Window, &mut AppContext)>,
&mut Window,
&mut AppContext,
) -> AnyElement,
>;
#[derive(Debug, PartialEq)]

View File

@@ -4,7 +4,7 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result;
use async_trait::async_trait;
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakModel, Window};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -97,8 +97,9 @@ impl SlashCommand for ExtensionSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let command = self.command.clone();
let arguments = arguments.to_owned();
@@ -127,9 +128,10 @@ impl SlashCommand for ExtensionSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakModel<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_window: &mut Window,
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
let command = self.command.clone();
let arguments = arguments.to_owned();

View File

@@ -4,7 +4,7 @@ mod tool_working_set;
use std::sync::Arc;
use anyhow::Result;
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakModel, Window};
use workspace::Workspace;
pub use crate::tool_registry::*;
@@ -31,7 +31,8 @@ pub trait Tool: 'static + Send + Sync {
fn run(
self: Arc<Self>,
input: serde_json::Value,
workspace: WeakView<Workspace>,
cx: &mut WindowContext,
workspace: WeakModel<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<String>>;
}

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result};
use assistant_tool::Tool;
use chrono::{Local, Utc};
use gpui::{Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakModel, Window};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -41,8 +41,9 @@ impl Tool for NowTool {
fn run(
self: Arc<Self>,
input: serde_json::Value,
_workspace: WeakView<workspace::Workspace>,
_cx: &mut WindowContext,
_workspace: WeakModel<workspace::Workspace>,
_window: &mut Window,
_cx: &mut AppContext,
) -> Task<Result<String>> {
let input: FileToolInput = match serde_json::from_value(input) {
Ok(input) => input,

View File

@@ -4,7 +4,7 @@ use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, Task, WindowContext,
SemanticVersion, Task, Window,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use paths::remote_servers_dir;
@@ -130,10 +130,10 @@ impl Global for GlobalAutoUpdate {}
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx));
cx.observe_new_models(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|_, action: &Check, window, cx| check(action, window, cx));
workspace.register_action(|_, action, cx| {
workspace.register_action(|_, action, _, cx| {
view_release_notes(action, cx);
});
})
@@ -172,23 +172,25 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
pub fn check(_: &Check, cx: &mut WindowContext) {
pub fn check(_: &Check, window: &mut Window, cx: &mut AppContext) {
if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") {
drop(cx.prompt(
drop(window.prompt(
gpui::PromptLevel::Info,
"Zed was installed via a package manager.",
Some(message),
&["Ok"],
cx,
));
return;
}
if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") {
drop(cx.prompt(
drop(window.prompt(
gpui::PromptLevel::Info,
"Zed was installed via a package manager.",
Some(&message),
&["Ok"],
cx,
));
return;
}
@@ -203,11 +205,12 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx));
} else {
drop(cx.prompt(
drop(window.prompt(
gpui::PromptLevel::Info,
"Could not check for updates",
Some("Auto-updates disabled for non-bundled app."),
&["Ok"],
cx,
));
}
}

View File

@@ -2,7 +2,7 @@ mod update_notification;
use auto_update::AutoUpdater;
use editor::{Editor, MultiBuffer};
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
use gpui::{actions, prelude::*, AppContext, Model, ModelContext, SharedString, Window};
use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel};
@@ -17,9 +17,9 @@ use crate::update_notification::UpdateNotification;
actions!(auto_update, [ViewReleaseNotesLocally]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
cx.observe_new_models(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| {
view_release_notes_locally(workspace, window, cx);
});
})
.detach();
@@ -31,7 +31,11 @@ struct ReleaseNotesBody {
release_notes: String,
}
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
fn view_release_notes_locally(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
@@ -60,8 +64,8 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
.language_for_name("Markdown");
workspace
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
.with_local_workspace(window, cx, move |_, window, cx| {
cx.spawn_in(window, |workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
@@ -76,7 +80,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
if let Ok(body) = body {
workspace
.update(&mut cx, |workspace, cx| {
.update_in(&mut cx, |workspace, window, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
@@ -89,22 +93,24 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
let editor = cx.new_model(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, window, cx)
});
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
let view: Model<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
window,
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
window,
cx,
);
cx.notify();
@@ -117,12 +123,15 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
.detach();
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
pub fn notify_of_any_new_update(
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version();
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move {
cx.spawn_in(window, |workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
@@ -130,7 +139,7 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
|cx| cx.new_model(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater

View File

@@ -1,6 +1,6 @@
use gpui::{
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView,
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ModelContext, ParentElement,
Render, SemanticVersion, StatefulInteractiveElement, Styled, WeakModel, Window,
};
use menu::Cancel;
use release_channel::ReleaseChannel;
@@ -12,13 +12,13 @@ use workspace::{
pub struct UpdateNotification {
version: SemanticVersion,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
}
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name();
v_flex()
@@ -37,7 +37,9 @@ impl Render for UpdateNotification {
.id("cancel")
.child(Icon::new(IconName::Close))
.cursor_pointer()
.on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
.on_click(cx.listener(|this, _, window, cx| {
this.dismiss(&menu::Cancel, window, cx)
})),
),
)
.child(
@@ -45,24 +47,24 @@ impl Render for UpdateNotification {
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
.on_click(cx.listener(|this, _, cx| {
.on_click(cx.listener(|this, _, window, cx| {
this.workspace
.update(cx, |workspace, cx| {
crate::view_release_notes_locally(workspace, cx);
crate::view_release_notes_locally(workspace, window, cx);
})
.log_err();
this.dismiss(&menu::Cancel, cx)
this.dismiss(&menu::Cancel, window, cx)
})),
)
}
}
impl UpdateNotification {
pub fn new(version: SemanticVersion, workspace: WeakView<Workspace>) -> Self {
pub fn new(version: SemanticVersion, workspace: WeakModel<Workspace>) -> Self {
Self { version, workspace }
}
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
pub fn dismiss(&mut self, _: &Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
cx.emit(DismissEvent);
}
}

View File

@@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
Subscription, ViewContext,
Element, EventEmitter, Focusable, IntoElement, ModelContext, ParentElement, Render, StyledText,
Subscription, Window,
};
use itertools::Itertools;
use std::cmp;
@@ -37,7 +37,7 @@ impl Breadcrumbs {
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex()
@@ -72,7 +72,7 @@ impl Render for Breadcrumbs {
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style();
let mut text_style = window.text_style();
if let Some(font) = segment.font {
text_style.font_family = font.family;
text_style.font_features = font.features;
@@ -101,28 +101,30 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Transparent)
.on_click({
let editor = editor.clone();
move |_, cx| {
move |_, window, cx| {
if let Some((editor, callback)) = editor
.upgrade()
.zip(zed_actions::outline::TOGGLE_OUTLINE.get())
{
callback(editor.to_any(), cx);
callback(editor.to_any(), window, cx);
}
}
})
.tooltip(move |cx| {
.tooltip(move |window, cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
window,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
}
@@ -140,7 +142,8 @@ impl ToolbarItemView for Breadcrumbs {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> ToolbarItemLocation {
cx.notify();
self.active_item = None;
@@ -149,10 +152,11 @@ impl ToolbarItemView for Breadcrumbs {
return ToolbarItemLocation::Hidden;
};
let this = cx.view().downgrade();
let this = cx.model().downgrade();
self.subscription = Some(item.subscribe_to_item_events(
window,
cx,
Box::new(move |event, cx| {
Box::new(move |event, _, cx| {
if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |this, cx| {
cx.notify();
@@ -170,7 +174,12 @@ impl ToolbarItemView for Breadcrumbs {
item.breadcrumb_location(cx)
}
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
fn pane_focus_update(
&mut self,
pane_focused: bool,
_window: &mut Window,
_: &mut ModelContext<Self>,
) {
self.pane_focused = pane_focused;
}
}

View File

@@ -81,7 +81,7 @@ impl ChannelBuffer {
collaborators: Default::default(),
acknowledge_task: None,
channel_id: channel.id,
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
subscription: Some(subscription.set_model(&cx.model(), &mut cx.to_async())),
user_store,
channel_store,
};

View File

@@ -134,7 +134,7 @@ impl ChannelChat {
last_acknowledged_id: None,
rng: StdRng::from_entropy(),
first_loaded_message_id: None,
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
_subscription: subscription.set_model(&cx.model(), &mut cx.to_async()),
}
})?;
Self::handle_loaded_messages(

View File

@@ -311,7 +311,7 @@ impl ChannelStore {
) -> Task<Result<Model<ChannelBuffer>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let channel_store = cx.handle();
let channel_store = cx.model();
self.open_channel_resource(
channel_id,
|this| &mut this.opened_buffers,
@@ -441,7 +441,7 @@ impl ChannelStore {
) -> Task<Result<Model<ChannelChat>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let this = cx.handle();
let this = cx.model();
self.open_channel_resource(
channel_id,
|this| &mut this.opened_chats,

View File

@@ -2,7 +2,11 @@ use collab::env::get_dotenv_vars;
fn main() -> anyhow::Result<()> {
for (key, value) in get_dotenv_vars(".")? {
println!("export {}=\"{}\"", key, value);
if option_env!("POWERSHELL").is_some() {
println!("$Env:{} = \"{}\"", key, value);
} else {
println!("export {}=\"{}\"", key, value);
}
}
Ok(())
}

View File

@@ -6,6 +6,7 @@ use axum::{
routing::get,
Extension, Router,
};
use collab::api::billing::sync_llm_usage_with_stripe_periodically;
use collab::api::CloudflareIpCountryHeader;
use collab::llm::{db::LlmDatabase, log_usage_periodically};

View File

@@ -9,7 +9,7 @@ use collab_ui::channel_view::ChannelView;
use collections::HashMap;
use editor::{Anchor, Editor, ToOffset};
use futures::future;
use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
use gpui::{BackgroundExecutor, Model, ModelContext, TestAppContext, Window};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
use serde_json::json;
use std::ops::Range;
@@ -161,43 +161,43 @@ async fn test_channel_notes_participant_indices(
// Clients A, B, and C open the channel notes
let channel_view_a = cx_a
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
let channel_view_b = cx_b
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_b.clone(), window, cx))
.await
.unwrap();
let channel_view_c = cx_c
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_c.clone(), window, cx))
.await
.unwrap();
// Clients A, B, and C all insert and select some text
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.insert("a", cx);
editor.change_selections(None, cx, |selections| {
editor.insert("a", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
});
executor.run_until_parked();
channel_view_b.update(cx_b, |notes, cx| {
channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx);
editor.insert("b", cx);
editor.change_selections(None, cx, |selections| {
editor.move_down(&Default::default(), window, cx);
editor.insert("b", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![1..2]);
});
});
});
executor.run_until_parked();
channel_view_c.update(cx_c, |notes, cx| {
channel_view_c.update_in(cx_c, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx);
editor.insert("c", cx);
editor.change_selections(None, cx, |selections| {
editor.move_down(&Default::default(), window, cx);
editor.insert("c", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
@@ -206,9 +206,9 @@ async fn test_channel_notes_participant_indices(
// Client A sees clients B and C without assigned colors, because they aren't
// in a call together.
executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], window, cx);
});
});
@@ -222,20 +222,22 @@ async fn test_channel_notes_participant_indices(
// Clients A and B see each other with two different assigned colors. Client C
// still doesn't have a color.
executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
window,
cx,
);
});
});
channel_view_b.update(cx_b, |notes, cx| {
channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
window,
cx,
);
});
@@ -252,8 +254,8 @@ async fn test_channel_notes_participant_indices(
// Clients A and B open the same file.
executor.start_waiting();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -261,32 +263,32 @@ async fn test_channel_notes_participant_indices(
.unwrap();
executor.start_waiting();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |selections| {
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |selections| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
executor.run_until_parked();
// Clients A and B see each other with the same colors as in the channel notes.
editor_a.update(cx_a, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
editor_a.update_in(cx_a, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], window, cx);
});
editor_b.update(cx_b, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
editor_b.update_in(cx_b, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], window, cx);
});
}
@@ -294,9 +296,10 @@ async fn test_channel_notes_participant_indices(
fn assert_remote_selections(
editor: &mut Editor,
expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let snapshot = editor.snapshot(cx);
let snapshot = editor.snapshot(window, cx);
let range = Anchor::min()..Anchor::max();
let remote_selections = snapshot
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
@@ -641,9 +644,9 @@ async fn test_channel_buffer_changes(
});
// Closing the buffer should re-enable change tracking
cx_b.update(|cx| {
cx_b.update(|window, cx| {
workspace_b.update(cx, |workspace, cx| {
workspace.close_all_items_and_panes(&Default::default(), cx)
workspace.close_all_items_and_panes(&Default::default(), window, cx)
});
});
deterministic.run_until_parked();

View File

@@ -107,7 +107,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
});
assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
cx_b.update(|cx_b| {
cx_b.update(|_, cx_b| {
assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx)));
});
assert!(room_b
@@ -135,7 +135,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
// B sees themselves as muted, and can unmute.
cx_b.update(|cx_b| {
cx_b.update(|_, cx_b| {
assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx)));
});
room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));

View File

@@ -356,10 +356,10 @@ async fn test_channel_message_changes(
let project_b = client_b.build_empty_local_project(cx_b);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let chat_panel_b = workspace_b.update(cx_b, ChatPanel::new);
let chat_panel_b = workspace_b.update_in(cx_b, ChatPanel::new);
chat_panel_b
.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(true, cx);
.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(true, window, cx);
chat_panel.select_channel(channel_id, None, cx)
})
.await
@@ -367,7 +367,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@@ -384,7 +384,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@@ -394,8 +394,8 @@ async fn test_channel_message_changes(
assert!(!b_has_messages);
// Sending a message while the chat is closed should change the flag.
chat_panel_b.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(false, cx);
chat_panel_b.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(false, window, cx);
});
// Sending a message while the chat is open should not change the flag.
@@ -406,7 +406,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@@ -416,7 +416,7 @@ async fn test_channel_message_changes(
assert!(b_has_messages);
// Closing the chat should re-enable change tracking
cx_b.update(|_| drop(chat_panel_b));
cx_b.update(|_, _| drop(chat_panel_b));
channel_chat_a
.update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
@@ -425,7 +425,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)

View File

@@ -82,14 +82,21 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
let workspace_b = cx_b
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
let workspace_b = cx_b.add_window(|window, cx| {
Workspace::new(
None,
project_b.clone(),
client_b.app_state.clone(),
window,
cx,
)
});
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
let workspace_b_view = workspace_b.root_view(cx_b).unwrap();
let workspace_b_view = workspace_b.root_model(cx_b).unwrap();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
.update(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
})
.unwrap()
.await
@@ -98,10 +105,10 @@ async fn test_host_disconnect(
.unwrap();
//TODO: focus
assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.update_window_model(&editor_b, |editor, window, _| editor.is_focused(window)));
editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
cx_b.update(|cx| {
cx_b.update(|_, cx| {
assert!(workspace_b_view.read(cx).is_edited());
});
@@ -121,7 +128,7 @@ async fn test_host_disconnect(
// Ensure client B's edited state is reset and that the whole window is blurred.
workspace_b
.update(cx_b, |workspace, cx| {
.update(cx_b, |workspace, _, cx| {
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
assert!(!workspace.is_edited());
})
@@ -129,8 +136,8 @@ async fn test_host_disconnect(
// Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b
.update(cx_b, |workspace, cx| {
workspace.prepare_to_close(CloseIntent::Quit, cx)
.update(cx_b, |workspace, window, cx| {
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
})
.unwrap()
.await
@@ -201,11 +208,12 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let editor_a = cx_a
.new_window_model(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
let mut editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: editor_a,
assertion_cx: AssertionContextManager::new(),
};
@@ -216,11 +224,12 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let editor_b = cx_b
.new_window_model(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
let mut editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: editor_b,
assertion_cx: AssertionContextManager::new(),
};
@@ -232,8 +241,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
editor_cx_b.set_selections_state(indoc! {"
Some textˇ
"});
editor_cx_a
.update_editor(|editor, cx| editor.newline_above(&editor::actions::NewlineAbove, cx));
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_above(&editor::actions::NewlineAbove, window, cx)
});
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
ˇ
@@ -253,8 +263,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
Some textˇ
"});
editor_cx_a
.update_editor(|editor, cx| editor.newline_below(&editor::actions::NewlineBelow, cx));
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_below(&editor::actions::NewlineBelow, window, cx)
});
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
@@ -317,8 +328,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b =
cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.background_executor.run_until_parked();
@@ -328,11 +340,11 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Type a completion trigger character as the guest.
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", window, cx);
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
// Receive a completion request as the host's language server.
// Return some completions from the host's language server.
@@ -394,9 +406,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Confirm a completion on the guest.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
});
@@ -441,10 +453,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
// Now we do a second completion, this time to ensure that documentation/snippets are
// resolved
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", cx);
editor.handle_input(".", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", window, cx);
editor.handle_input(".", window, cx);
});
buffer_b.read_with(cx_b, |buffer, _| {
@@ -508,18 +520,18 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
completion_response.next().await.unwrap();
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.context_menu_first(&ContextMenuFirst {}, cx);
editor.context_menu_first(&ContextMenuFirst {}, window, cx);
});
resolve_completion_response.next().await.unwrap();
cx_b.executor().run_until_parked();
// When accepting the completion, the snippet is insert.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
assert_eq!(
editor.text(cx),
"use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
@@ -569,8 +581,8 @@ async fn test_collaborating_with_code_actions(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -593,12 +605,12 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Move cursor to a location that contains code actions.
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
});
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
let mut requests = fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
@@ -658,11 +670,12 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Toggle code actions and wait for them to display.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
},
window,
cx,
);
});
@@ -674,8 +687,8 @@ async fn test_collaborating_with_code_actions(
// Confirming the code action will trigger a resolve request.
let confirm_action = editor_b
.update(cx_b, |editor, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx)
.update_in(cx_b, |editor, window, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
})
.unwrap();
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
@@ -726,14 +739,14 @@ async fn test_collaborating_with_code_actions(
.downcast::<Editor>()
.unwrap()
});
code_action_editor.update(cx_b, |editor, cx| {
code_action_editor.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(
editor.text(cx),
"mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
);
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
});
}
@@ -785,8 +798,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -795,9 +808,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let fake_language_server = fake_language_servers.next().await.unwrap();
// Move cursor to a location that can be renamed.
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, cx).unwrap()
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, window, cx).unwrap()
});
fake_language_server
@@ -835,12 +848,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Cancel the rename, and repeat the same, but use selections instead of cursor movement
editor_b.update(cx_b, |editor, cx| {
editor.cancel(&editor::actions::Cancel, cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.cancel(&editor::actions::Cancel, window, cx);
});
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, cx).unwrap()
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, window, cx).unwrap()
});
fake_language_server
@@ -876,8 +889,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
});
let confirm_rename = editor_b.update(cx_b, |editor, cx| {
Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
});
fake_language_server
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
@@ -935,17 +948,17 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
workspace.active_item_as::<Editor>(cx).unwrap()
});
rename_editor.update(cx_b, |editor, cx| {
rename_editor.update_in(cx_b, |editor, window, cx| {
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
);
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(
editor.text(cx),
"const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
);
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
@@ -953,12 +966,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Ensure temporary rename edits cannot be undone/redone.
editor_b.update(cx_b, |editor, cx| {
editor.undo(&Undo, cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(editor.text(cx), "const THREE: usize = 1;");
})
}
@@ -1193,7 +1206,8 @@ async fn test_share_project(
.await
.unwrap();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
let editor_b =
cx_b.new_window_model(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
// Client A sees client B's selection
executor.run_until_parked();
@@ -1207,7 +1221,9 @@ async fn test_share_project(
});
// Edit the buffer as client B and see that edit as client A.
editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
editor_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("ok, ", window, cx)
});
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@@ -1234,7 +1250,7 @@ async fn test_share_project(
let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
// Client B closes the editor, and client A sees client B's selections removed.
cx_b.update(move |_| drop(editor_b));
cx_b.update(move |_, _| drop(editor_b));
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@@ -1298,7 +1314,9 @@ async fn test_on_input_format_from_host_to_guest(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
let editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
@@ -1330,10 +1348,10 @@ async fn test_on_input_format_from_host_to_guest(
.unwrap();
// Type a on type formatting trigger character as the guest.
cx_a.focus_view(&editor_a);
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", cx);
cx_a.focus(&editor_a);
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", window, cx);
});
executor.run_until_parked();
@@ -1343,9 +1361,9 @@ async fn test_on_input_format_from_host_to_guest(
});
// Undo should remove LSP edits first
editor_a.update(cx_a, |editor, cx| {
editor_a.update_in(cx_a, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a>~< }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a> }");
});
executor.run_until_parked();
@@ -1354,9 +1372,9 @@ async fn test_on_input_format_from_host_to_guest(
assert_eq!(buffer.text(), "fn main() { a> }")
});
editor_a.update(cx_a, |editor, cx| {
editor_a.update_in(cx_a, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a> }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@@ -1418,16 +1436,18 @@ async fn test_on_input_format_from_guest_to_host(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
// Type a on type formatting trigger character as the guest.
cx_b.focus_view(&editor_b);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", cx);
cx_b.focus(&editor_b);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", window, cx);
});
// Receive an OnTypeFormatting request as the host's language server.
@@ -1466,9 +1486,9 @@ async fn test_on_input_format_from_guest_to_host(
});
// Undo should remove LSP edits first
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a:~: }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a: }");
});
executor.run_until_parked();
@@ -1477,9 +1497,9 @@ async fn test_on_input_format_from_guest_to_host(
assert_eq!(buffer.text(), "fn main() { a: }")
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a: }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@@ -1590,8 +1610,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.unwrap();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -1646,8 +1666,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -1670,11 +1690,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", window, cx);
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@@ -1695,11 +1715,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", cx);
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", window, cx);
});
cx_a.focus_view(&editor_a);
cx_a.focus(&editor_a);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@@ -1848,8 +1868,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
cx_a.background_executor.start_waiting();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -1857,8 +1877,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.unwrap();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@@ -2042,8 +2062,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// Create editor_a
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -2054,8 +2074,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -2063,9 +2083,9 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap();
// client_b now requests git blame for the open buffer
editor_b.update(cx_b, |editor_b, cx| {
editor_b.update_in(cx_b, |editor_b, window, cx| {
assert!(editor_b.blame().is_none());
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, cx);
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, window, cx);
});
cx_a.executor().run_until_parked();
@@ -2103,7 +2123,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// editor_b updates the file, which gets sent to client_a, which updates git blame,
// which gets back to client_b.
editor_b.update(cx_b, |editor_b, cx| {
editor_b.update_in(cx_b, |editor_b, _, cx| {
editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
});
@@ -2130,7 +2150,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
});
// Now editor_a also updates the file
editor_a.update(cx_a, |editor_a, cx| {
editor_a.update_in(cx_a, |editor_a, _, cx| {
editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
});
@@ -2208,19 +2228,21 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let main_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
});
let other_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
});
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
@@ -2240,19 +2262,21 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let main_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
});
let other_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
});
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
@@ -2416,12 +2440,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
cx_a.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
cx_b.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
});
}
@@ -2432,12 +2456,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
cx_a.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
cx_b.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
});
}
cx_a.run_until_parked();

View File

@@ -8,8 +8,8 @@ use collab_ui::{
};
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
View, VisualContext, VisualTestContext,
point, BackgroundExecutor, BorrowAppContext, Context, Entity, Model, SharedString,
TestAppContext, VisualTestContext,
};
use language::Capability;
use project::WorktreeSettings;
@@ -77,23 +77,23 @@ async fn test_basic_following(
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
cx_b.update(|cx| {
assert!(cx.is_window_active());
cx_b.update(|window, _| {
assert!(window.is_window_active());
});
// Client A opens some editors.
let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
let editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_a2 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -102,8 +102,8 @@ async fn test_basic_following(
// Client B opens an editor.
let editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -116,22 +116,24 @@ async fn test_basic_following(
let peer_id_d = client_d.peer_id().unwrap();
// Client A updates their selections in those editors
editor_a1.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_a2.update(cx_a, |editor, cx| {
editor.handle_input("d", cx);
editor.handle_input("e", cx);
editor.select_left(&Default::default(), cx);
editor_a2.update_in(cx_a, |editor, window, cx| {
editor.handle_input("d", window, cx);
editor.handle_input("e", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
});
// When client B starts following client A, only the active view state is replicated to client B.
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
cx_c.executor().run_until_parked();
let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
@@ -165,7 +167,9 @@ async fn test_basic_following(
drop(project_c);
// Client C also follows client A.
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
cx_d.executor().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
@@ -188,8 +192,8 @@ async fn test_basic_following(
}
// Client C unfollows client A.
workspace_c.update(cx_c, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap();
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap();
});
// All clients see that clients B is following client A.
@@ -203,7 +207,9 @@ async fn test_basic_following(
}
// Client C re-follows client A.
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
// All clients see that clients B and C are following client A.
cx_c.executor().run_until_parked();
@@ -216,9 +222,13 @@ async fn test_basic_following(
}
// Client D follows client B, then switches to following client C.
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_b, cx));
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
cx_a.executor().run_until_parked();
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_c, cx));
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_c, window, cx)
});
// All clients see that D is following C
cx_a.executor().run_until_parked();
@@ -235,8 +245,8 @@ async fn test_basic_following(
// Client C closes the project.
let weak_workspace_c = workspace_c.downgrade();
workspace_c.update(cx_c, |workspace, cx| {
workspace.close_window(&Default::default(), cx);
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.close_window(&Default::default(), window, cx);
});
executor.run_until_parked();
// are you sure you want to leave the call?
@@ -260,8 +270,8 @@ async fn test_basic_following(
}
// When client A activates a different editor, client B does so as well.
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a1, true, true, cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a1, true, true, window, cx)
});
executor.run_until_parked();
workspace_b.update(cx_b, |workspace, cx| {
@@ -302,11 +312,11 @@ async fn test_basic_following(
);
result
});
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
let multibuffer_editor_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
let editor = cx.new_model(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, window, cx)
});
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
editor
});
executor.run_until_parked();
@@ -324,8 +334,8 @@ async fn test_basic_following(
// When client A navigates back and forth, client B does so as well.
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@@ -338,8 +348,8 @@ async fn test_basic_following(
});
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@@ -352,8 +362,8 @@ async fn test_basic_following(
});
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@@ -366,8 +376,8 @@ async fn test_basic_following(
});
// Changes to client A's editor are reflected on client B.
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -377,13 +387,15 @@ async fn test_basic_following(
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
});
editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.set_text("TWO", window, cx)
});
executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), cx);
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), window, cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -392,11 +404,11 @@ async fn test_basic_following(
});
// After unfollowing, client B stops receiving updates from client A.
workspace_b.update(cx_b, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap()
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap()
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a2, true, true, cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a2, true, true, window, cx)
});
executor.run_until_parked();
assert_eq!(
@@ -408,14 +420,16 @@ async fn test_basic_following(
);
// Client A starts following client B.
workspace_a.update(cx_a, |workspace, cx| workspace.follow(peer_id_b, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
Some(peer_id_b)
);
assert_eq!(
workspace_a.update(cx_a, |workspace, cx| workspace
workspace_a.update_in(cx_a, |workspace, _, cx| workspace
.active_item(cx)
.unwrap()
.item_id()),
@@ -593,19 +607,19 @@ async fn test_following_tab_order(
//Open 1, 3 in that order on client A
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap();
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
})
.await
.unwrap();
let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
let pane_paths = |pane: &Model<workspace::Pane>, cx: &mut VisualTestContext| {
pane.update(cx, |pane, cx| {
pane.items()
.map(|item| {
@@ -624,13 +638,15 @@ async fn test_following_tab_order(
assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
//Follow client B as client A
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
executor.run_until_parked();
//Open just 2 on client B
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap();
@@ -641,8 +657,8 @@ async fn test_following_tab_order(
//Open just 1 on client B
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap();
@@ -701,8 +717,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client A opens a file.
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -712,8 +728,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B opens a different file.
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -721,24 +737,38 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
.unwrap();
// Clients A and B follow each other in split panes
workspace_a.update(cx_a, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
});
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
// Clients A and B return focus to the original files they had open
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Both clients see the other client's focused file in their right pane.
@@ -775,15 +805,15 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Clients A and B each open a new file.
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
})
.await
.unwrap();
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, window, cx)
})
.await
.unwrap();
@@ -831,7 +861,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client A focuses their right pane, in which they're following client B.
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Client B sees that client A is now looking at the same file as them.
@@ -877,7 +909,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses their right pane, in which they're following client A,
// who is following them.
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Client A sees that client B is now looking at the same file as them.
@@ -923,9 +957,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses a file that they previously followed A to, breaking
// the follow.
workspace_b.update(cx_b, |workspace, cx| {
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@@ -974,9 +1008,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B closes tabs, some of which were originally opened by client A,
// and some of which were originally opened by client B.
workspace_b.update(cx_b, |workspace, cx| {
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.close_inactive_items(&Default::default(), cx)
pane.close_inactive_items(&Default::default(), window, cx)
.unwrap()
.detach();
});
@@ -1022,14 +1056,14 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client B follows client A again.
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
// Client A cycles through some tabs.
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@@ -1071,9 +1105,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@@ -1118,9 +1152,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@@ -1215,8 +1249,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -1228,7 +1262,9 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let leader_id = project_b.update(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1243,15 +1279,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
});
// When client B moves, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| {
editor.move_right(&editor::actions::MoveRight, cx)
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.move_right(&editor::actions::MoveRight, window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1259,13 +1297,15 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B edits, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
editor_b2.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
workspace_b.update_in(cx_b, |workspace, _, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1273,15 +1313,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B scrolls, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| {
editor.set_scroll_position(point(0., 3.), cx)
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.set_scroll_position(point(0., 3.), window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1289,15 +1331,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B activates a different pane, it continues following client A in the original pane.
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
@@ -1305,8 +1349,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
// When client B activates a different item in the original pane, it automatically stops following client A.
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap();
@@ -1352,8 +1396,12 @@ async fn test_peers_simultaneously_following_each_other(
project.collaborators().values().next().unwrap().peer_id
});
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
workspace_b.update(cx_b, |workspace, cx| workspace.follow(client_a_id, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a_id, window, cx)
});
executor.run_until_parked();
workspace_a.update(cx_a, |workspace, _| {
@@ -1434,8 +1482,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
.unwrap();
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, window, cx)
})
.await
.unwrap();
@@ -1443,8 +1491,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_b).len(), 1);
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
@@ -1490,8 +1538,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to x.rs in a's project, and a follows
workspace_b_project_a
.update(&mut cx_b2, |workspace, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
.update_in(&mut cx_b2, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, window, cx)
})
.await
.unwrap();
@@ -1505,8 +1553,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
);
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
@@ -1522,8 +1570,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to y.rs in b's project, a is still following but can't yet see
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, window, cx)
})
.await
.unwrap();
@@ -1544,7 +1592,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_a).len(), 1);
cx_a.update(|cx| {
cx_a.update(|_, cx| {
workspace::join_in_room_project(
project_b_id,
client_b.user_id().unwrap(),
@@ -1607,8 +1655,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// b should follow a to position 1
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@@ -1618,7 +1666,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// a unshares the project
cx_a.update(|cx| {
cx_a.update(|_, cx| {
let project = workspace_a.read(cx).project().clone();
ActiveCall::global(cx).update(cx, |call, cx| {
call.unshare_project(project, cx).unwrap();
@@ -1627,8 +1675,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
cx_a.run_until_parked();
// b should not follow a to position 2
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@@ -1636,7 +1684,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
});
cx_b.update(|cx| {
cx_b.update(|_, cx| {
let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx);
let participant = room.remote_participants().get(&client_a.id()).unwrap();
assert_eq!(participant.location, ParticipantLocation::UnsharedProject)
@@ -1703,16 +1751,16 @@ async fn test_following_into_excluded_file(
// Client A opens editors for a regular file and an excluded file.
let editor_for_regular = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_for_excluded_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, window, cx)
})
.await
.unwrap()
@@ -1720,22 +1768,24 @@ async fn test_following_into_excluded_file(
.unwrap();
// Client A updates their selections in those editors
editor_for_regular.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
editor_for_regular.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_all(&Default::default(), cx);
editor.handle_input("new commit message", cx);
editor.select_left(&Default::default(), cx);
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_all(&Default::default(), window, cx);
editor.handle_input("new commit message", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![18..17]);
});
// When client B starts following client A, currently visible file is replicated
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -1755,15 +1805,15 @@ async fn test_following_into_excluded_file(
vec![18..17]
);
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_right(&Default::default(), cx);
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_right(&Default::default(), window, cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
// Changes from B to the excluded file are replicated in A's editor
editor_for_excluded_b.update(cx_b, |editor, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
editor_for_excluded_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", window, cx);
});
executor.run_until_parked();
editor_for_excluded_a.update(cx_a, |editor, cx| {
@@ -1774,13 +1824,11 @@ async fn test_following_into_excluded_file(
});
}
fn visible_push_notifications(
cx: &mut TestAppContext,
) -> Vec<gpui::View<ProjectSharedNotification>> {
fn visible_push_notifications(cx: &mut TestAppContext) -> Vec<Model<ProjectSharedNotification>> {
let mut ret = Vec::new();
for window in cx.windows() {
window
.update(cx, |window, _| {
.update(cx, |window, _, _| {
if let Ok(handle) = window.downcast::<ProjectSharedNotification>() {
ret.push(handle)
}
@@ -1821,7 +1869,7 @@ fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec
})
}
fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
fn pane_summaries(workspace: &Model<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
workspace.update(cx, |workspace, cx| {
let active_pane = workspace.active_pane();
workspace
@@ -1924,14 +1972,14 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 1.
let channel_notes_1_a = cx_a
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
channel_notes_1_a.update(cx_a, |notes, cx| {
channel_notes_1_a.update_in(cx_a, |notes, window, cx| {
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
notes.editor.update(cx, |editor, cx| {
editor.insert("Hello from A.", cx);
editor.change_selections(None, cx, |selections| {
editor.insert("Hello from A.", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![3..4]);
});
});
@@ -1939,9 +1987,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client B follows client A.
workspace_b
.update(cx_b, |workspace, cx| {
.update_in(cx_b, |workspace, window, cx| {
workspace
.start_following(client_a.peer_id().unwrap(), cx)
.start_following(client_a.peer_id().unwrap(), window, cx)
.unwrap()
})
.await
@@ -1971,7 +2019,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 2.
let channel_notes_2_a = cx_a
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
channel_notes_2_a.update(cx_a, |notes, cx| {
@@ -1997,8 +2045,8 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens a local buffer in their unshared project.
let _unshared_editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@@ -2027,7 +2075,7 @@ pub(crate) async fn join_channel(
}
async fn share_workspace(
workspace: &View<Workspace>,
workspace: &Model<Workspace>,
cx: &mut VisualTestContext,
) -> anyhow::Result<u64> {
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
@@ -2069,9 +2117,9 @@ async fn test_following_to_channel_notes_other_workspace(
// a opens a second workspace and the channel notes
let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|cx| cx.activate_window());
cx_a2.update(|window, _| window.activate_window());
cx_a2
.update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
.update(|window, cx| ChannelView::open(channel, None, workspace_a2, window, cx))
.await
.unwrap();
cx_a2.run_until_parked();
@@ -2083,7 +2131,7 @@ async fn test_following_to_channel_notes_other_workspace(
});
// a returns to the shared project
cx_a.update(|cx| cx.activate_window());
cx_a.update(|window, _| window.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {
@@ -2141,7 +2189,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
// a opens a file in a new window
let (_, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|cx| cx.activate_window());
cx_a2.update(|window, _| window.activate_window());
cx_a2.simulate_keystrokes("cmd-p");
cx_a2.run_until_parked();
cx_a2.simulate_keystrokes("3 enter");
@@ -2152,7 +2200,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
cx_a.run_until_parked();
// a returns to the shared project
cx_a.update(|cx| cx.activate_window());
cx_a.update(|window, _| window.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {

View File

@@ -6174,7 +6174,7 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
cx.simulate_resize(size(px(300.), px(300.)));
cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
cx.update(|cx| cx.refresh());
cx.update(|window, _cx| window.refresh());
let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
@@ -6273,8 +6273,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Opening item 3 as a "permanent" tab
workspace
.update(cx, |workspace, cx| {
workspace.open_path(path_3.clone(), None, false, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path(path_3.clone(), None, false, window, cx)
})
.await
.unwrap();
@@ -6290,8 +6290,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 1 as preview
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@@ -6311,8 +6311,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@@ -6332,7 +6332,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.await
.unwrap();
@@ -6350,10 +6352,11 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Closing item 1
pane.update(cx, |pane, cx| {
pane.update_in(cx, |pane, window, cx| {
pane.close_item_by_id(
pane.active_item().unwrap().item_id(),
workspace::SaveIntent::Skip,
window,
cx,
)
})
@@ -6371,7 +6374,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.await
.unwrap();
@@ -6389,9 +6394,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Close permanent tab
pane.update(cx, |pane, cx| {
pane.update_in(cx, |pane, window, cx| {
let id = pane.items().next().unwrap().item_id();
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
})
.await
.unwrap();
@@ -6438,8 +6443,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@@ -6470,14 +6475,14 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Focus left pane
workspace.update(cx, |workspace, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx)
workspace.update_in(cx, |workspace, window, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
});
// Open item 2 as preview in left pane
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();

View File

@@ -21,14 +21,14 @@ async fn test_notifications(
let notification_events_b = Arc::new(Mutex::new(Vec::new()));
client_a.notification_store().update(cx_a, |_, cx| {
let events = notification_events_a.clone();
cx.subscribe(&cx.handle(), move |_, _, event, _| {
cx.subscribe(&cx.model(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()
});
client_b.notification_store().update(cx_b, |_, cx| {
let events = notification_events_b.clone();
cx.subscribe(&cx.handle(), move |_, _, event, _| {
cx.subscribe(&cx.model(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()

View File

@@ -17,7 +17,7 @@ use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use git::GitHostingProviderRegistry;
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, VisualTestContext};
use http_client::FakeHttpClient;
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
@@ -752,17 +752,17 @@ impl TestClient {
pub async fn host_workspace(
&self,
workspace: &View<Workspace>,
workspace: &Model<Workspace>,
channel_id: ChannelId,
cx: &mut VisualTestContext,
) {
cx.update(|cx| {
cx.update(|_, cx| {
let active_call = ActiveCall::global(cx);
active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
})
.await
.unwrap();
cx.update(|cx| {
cx.update(|_, cx| {
let active_call = ActiveCall::global(cx);
let project = workspace.read(cx).project().clone();
active_call.update(cx, |call, cx| call.share_project(project, cx))
@@ -776,7 +776,7 @@ impl TestClient {
&'a self,
channel_id: ChannelId,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Model<Workspace>, &'a mut VisualTestContext) {
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
.await
.unwrap();
@@ -822,31 +822,31 @@ impl TestClient {
&'a self,
project: &Model<Project>,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
) -> (Model<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
})
}
pub async fn build_test_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Model<Workspace>, &'a mut VisualTestContext) {
let project = self.build_test_project(cx).await;
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
})
}
pub fn active_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Model<Workspace>, &'a mut VisualTestContext) {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_view(cx).unwrap();
let view = window.root_model(cx).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
// it might be nice to try and cleanup these at the end of each test.
(view, cx)
@@ -856,11 +856,11 @@ impl TestClient {
pub fn open_channel_notes(
channel_id: ChannelId,
cx: &mut VisualTestContext,
) -> Task<anyhow::Result<View<ChannelView>>> {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_view(cx).unwrap();
) -> Task<anyhow::Result<Model<ChannelView>>> {
let window = cx.update(|_, cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_model(cx).unwrap();
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
cx.update(|window, cx| ChannelView::open(channel_id, None, view.clone(), window, cx))
}
impl Drop for TestClient {

View File

@@ -11,9 +11,8 @@ use editor::{
EditorEvent,
};
use gpui::{
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, FocusableView, Model,
Pixels, Point, Render, Subscription, Task, View, ViewContext, VisualContext as _, WeakView,
WindowContext,
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, Focusable, Model,
ModelContext, Pixels, Point, Render, Subscription, Task, VisualContext as _, WeakModel, Window,
};
use project::Project;
use rpc::proto::ChannelVisibility;
@@ -38,8 +37,8 @@ pub fn init(cx: &mut AppContext) {
}
pub struct ChannelView {
pub editor: View<Editor>,
workspace: WeakView<Workspace>,
pub editor: Model<Editor>,
workspace: WeakModel<Workspace>,
project: Model<Project>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
@@ -52,20 +51,22 @@ impl ChannelView {
pub fn open(
channel_id: ChannelId,
link_position: Option<String>,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let pane = workspace.read(cx).active_pane().clone();
let channel_view = Self::open_in_pane(
channel_id,
link_position,
pane.clone(),
workspace.clone(),
window,
cx,
);
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
pane.update_in(&mut cx, |pane, window, cx| {
telemetry::event!(
"Channel Notes Opened",
channel_id,
@@ -74,7 +75,7 @@ impl ChannelView {
.room()
.map(|r| r.read(cx).id())
);
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
pane.add_item(Box::new(channel_view.clone()), true, true, None, window, cx);
})?;
anyhow::Ok(channel_view)
})
@@ -83,15 +84,16 @@ impl ChannelView {
pub fn open_in_pane(
channel_id: ChannelId,
link_position: Option<String>,
pane: View<Pane>,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let channel_view = Self::load(channel_id, workspace, cx);
cx.spawn(|mut cx| async move {
pane: Model<Pane>,
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let channel_view = Self::load(channel_id, workspace, window, cx);
window.spawn(cx, |mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
pane.update_in(&mut cx, |pane, window, cx| {
let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
@@ -104,7 +106,12 @@ impl ChannelView {
{
if let Some(link_position) = link_position {
existing_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
channel_view.focus_position_from_link(
link_position,
true,
window,
cx,
)
});
}
return existing_view;
@@ -115,15 +122,27 @@ impl ChannelView {
// replace that.
if let Some(existing_item) = existing_view {
if let Some(ix) = pane.index_for_item(&existing_item) {
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
.detach();
pane.add_item(Box::new(channel_view.clone()), true, true, Some(ix), cx);
pane.close_item_by_id(
existing_item.entity_id(),
SaveIntent::Skip,
window,
cx,
)
.detach();
pane.add_item(
Box::new(channel_view.clone()),
true,
true,
Some(ix),
window,
cx,
);
}
}
if let Some(link_position) = link_position {
channel_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
channel_view.focus_position_from_link(link_position, true, window, cx)
});
}
@@ -134,9 +153,10 @@ impl ChannelView {
pub fn load(
channel_id: ChannelId,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let weak_workspace = workspace.downgrade();
let workspace = workspace.read(cx);
let project = workspace.project().to_owned();
@@ -146,7 +166,7 @@ impl ChannelView {
let channel_buffer =
channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let channel_buffer = channel_buffer.await?;
let markdown = markdown.await.log_err();
@@ -160,9 +180,15 @@ impl ChannelView {
})
})?;
cx.new_view(|cx| {
let mut this =
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
cx.new_window_model(|window, cx| {
let mut this = Self::new(
project,
weak_workspace,
channel_store,
channel_buffer,
window,
cx,
);
this.acknowledge_buffer_version(cx);
this
})
@@ -171,24 +197,27 @@ impl ChannelView {
pub fn new(
project: Model<Project>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let buffer = channel_buffer.read(cx).buffer();
let this = cx.view().downgrade();
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
let this = cx.model().downgrade();
let editor = cx.new_model(|cx| {
let mut editor = Editor::for_buffer(buffer, None, window, cx);
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(),
)));
editor.set_custom_context_menu(move |_, position, cx| {
editor.set_custom_context_menu(move |_, position, window, cx| {
let this = this.clone();
Some(ui::ContextMenu::build(cx, move |menu, _| {
menu.entry("Copy link to section", None, move |cx| {
this.update(cx, |this, cx| this.copy_link_for_position(position, cx))
.ok();
Some(ui::ContextMenu::build(window, cx, move |menu, _, _| {
menu.entry("Copy link to section", None, move |window, cx| {
this.update(cx, |this, cx| {
this.copy_link_for_position(position, window, cx)
})
.ok();
})
}))
});
@@ -197,7 +226,7 @@ impl ChannelView {
let _editor_event_subscription =
cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
cx.subscribe_in(&channel_buffer, window, Self::handle_channel_buffer_event)
.detach();
Self {
@@ -216,10 +245,13 @@ impl ChannelView {
&mut self,
position: String,
first_attempt: bool,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let position = Channel::slug(&position).to_lowercase();
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
if let Some(item) = outline
@@ -228,7 +260,7 @@ impl ChannelView {
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
{
self.editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
})
});
@@ -239,12 +271,13 @@ impl ChannelView {
if !first_attempt {
return;
}
self._reparse_subscription = Some(cx.subscribe(
self._reparse_subscription = Some(cx.subscribe_in(
&self.editor,
move |this, _, e: &EditorEvent, cx| {
window,
move |this, _, e: &EditorEvent, window, cx| {
match e {
EditorEvent::Reparsed(_) => {
this.focus_position_from_link(position.clone(), false, cx);
this.focus_position_from_link(position.clone(), false, window, cx);
this._reparse_subscription.take();
}
EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
@@ -256,15 +289,22 @@ impl ChannelView {
));
}
fn copy_link(&mut self, _: &CopyLink, cx: &mut ViewContext<Self>) {
fn copy_link(&mut self, _: &CopyLink, window: &mut Window, cx: &mut ModelContext<Self>) {
let position = self
.editor
.update(cx, |editor, cx| editor.selections.newest_display(cx).start);
self.copy_link_for_position(position, cx)
self.copy_link_for_position(position, window, cx)
}
fn copy_link_for_position(&self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
fn copy_link_for_position(
&self,
position: DisplayPoint,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
let mut closest_heading = None;
@@ -304,9 +344,10 @@ impl ChannelView {
fn handle_channel_buffer_event(
&mut self,
_: Model<ChannelBuffer>,
_: &Model<ChannelBuffer>,
event: &ChannelBufferEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
@@ -320,7 +361,7 @@ impl ChannelView {
});
}
ChannelBufferEvent::BufferEdited => {
if self.editor.read(cx).is_focused(cx) {
if self.editor.read(cx).is_focused(window) {
self.acknowledge_buffer_version(cx);
} else {
self.channel_store.update(cx, |store, cx| {
@@ -338,7 +379,7 @@ impl ChannelView {
}
}
fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<ChannelView>) {
self.channel_store.update(cx, |store, cx| {
let channel_buffer = self.channel_buffer.read(cx);
store.acknowledge_notes_version(
@@ -357,7 +398,7 @@ impl ChannelView {
impl EventEmitter<EditorEvent> for ChannelView {}
impl Render for ChannelView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
div()
.size_full()
.on_action(cx.listener(Self::copy_link))
@@ -365,7 +406,7 @@ impl Render for ChannelView {
}
}
impl FocusableView for ChannelView {
impl Focusable for ChannelView {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
@@ -377,7 +418,7 @@ impl Item for ChannelView {
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
self_handle: &'a Model<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
@@ -389,7 +430,7 @@ impl Item for ChannelView {
}
}
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
fn tab_icon(&self, _: &Window, cx: &AppContext) -> Option<Icon> {
let channel = self.channel(cx)?;
let icon = match channel.visibility {
ChannelVisibility::Public => IconName::Public,
@@ -399,7 +440,12 @@ impl Item for ChannelView {
Some(Icon::new(icon))
}
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> gpui::AnyElement {
fn tab_content(
&self,
params: TabContentParams,
_: &Window,
cx: &AppContext,
) -> gpui::AnyElement {
let (channel_name, status) = if let Some(channel) = self.channel(cx) {
let status = match (
self.channel_buffer.read(cx).buffer().read(cx).read_only(),
@@ -439,14 +485,16 @@ impl Item for ChannelView {
fn clone_on_split(
&self,
_: Option<WorkspaceId>,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>> {
Some(cx.new_view(|cx| {
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<Model<Self>> {
Some(cx.new_model(|cx| {
Self::new(
self.project.clone(),
self.workspace.clone(),
self.channel_store.clone(),
self.channel_buffer.clone(),
window,
cx,
)
}))
@@ -456,21 +504,33 @@ impl Item for ChannelView {
false
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx))
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, Item::deactivated)
}
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
fn deactivated(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.editor
.update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
.update(cx, |item, cx| item.deactivated(window, cx))
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
fn set_nav_history(
&mut self,
history: ItemNavHistory,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
Item::set_nav_history(editor, history, window, cx)
})
}
fn as_searchable(&self, _: &Model<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
@@ -492,7 +552,7 @@ impl FollowableItem for ChannelView {
self.remote_id
}
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
fn to_state_proto(&self, window: &Window, cx: &AppContext) -> Option<proto::view::Variant> {
let channel_buffer = self.channel_buffer.read(cx);
if !channel_buffer.is_connected() {
return None;
@@ -502,7 +562,7 @@ impl FollowableItem for ChannelView {
proto::view::ChannelView {
channel_id: channel_buffer.channel_id.0,
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx)
self.editor.read(cx).to_state_proto(window, cx)
{
Some(proto)
} else {
@@ -513,11 +573,12 @@ impl FollowableItem for ChannelView {
}
fn from_state_proto(
workspace: View<workspace::Workspace>,
workspace: Model<workspace::Workspace>,
remote_id: workspace::ViewId,
state: &mut Option<proto::view::Variant>,
cx: &mut WindowContext,
) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
window: &mut Window,
cx: &mut AppContext,
) -> Option<gpui::Task<anyhow::Result<Model<Self>>>> {
let Some(proto::view::Variant::ChannelView(_)) = state else {
return None;
};
@@ -525,12 +586,12 @@ impl FollowableItem for ChannelView {
unreachable!()
};
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
let open = ChannelView::load(ChannelId(state.channel_id), workspace, window, cx);
Some(cx.spawn(|mut cx| async move {
Some(window.spawn(cx, |mut cx| async move {
let this = open.await?;
let task = this.update(&mut cx, |this, cx| {
let task = this.update_in(&mut cx, |this, window, cx| {
this.remote_id = Some(remote_id);
if let Some(state) = state.editor {
@@ -545,6 +606,7 @@ impl FollowableItem for ChannelView {
scroll_y: state.scroll_y,
..Default::default()
}),
window,
cx,
)
}))
@@ -565,31 +627,38 @@ impl FollowableItem for ChannelView {
&self,
event: &EditorEvent,
update: &mut Option<proto::update_view::Variant>,
cx: &WindowContext,
window: &Window,
cx: &AppContext,
) -> bool {
self.editor
.read(cx)
.add_event_to_update_proto(event, update, cx)
.add_event_to_update_proto(event, update, window, cx)
}
fn apply_update_proto(
&mut self,
project: &Model<Project>,
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> gpui::Task<anyhow::Result<()>> {
self.editor.update(cx, |editor, cx| {
editor.apply_update_proto(project, message, cx)
editor.apply_update_proto(project, message, window, cx)
})
}
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
fn set_leader_peer_id(
&mut self,
leader_peer_id: Option<PeerId>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.set_leader_peer_id(leader_peer_id, cx)
editor.set_leader_peer_id(leader_peer_id, window, cx)
})
}
fn is_project_item(&self, _cx: &WindowContext) -> bool {
fn is_project_item(&self, _window: &Window, _cx: &AppContext) -> bool {
false
}
@@ -597,7 +666,7 @@ impl FollowableItem for ChannelView {
Editor::to_follow_event(event)
}
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
fn dedup(&self, existing: &Self, _: &Window, cx: &AppContext) -> Option<Dedup> {
let existing = existing.channel_buffer.read(cx);
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
if existing.is_connected() {

View File

@@ -8,9 +8,9 @@ use db::kvp::KEY_VALUE_STORE;
use editor::{actions, Editor};
use gpui::{
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight,
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, Stateful, Subscription,
Task, View, ViewContext, VisualContext, WeakView,
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, Focusable, FontWeight,
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, ModelContext, Render, Stateful,
Subscription, Task, WeakModel, Window,
};
use language::LanguageRegistry;
use menu::Confirm;
@@ -37,9 +37,9 @@ const MESSAGE_LOADING_THRESHOLD: usize = 50;
const CHAT_PANEL_KEY: &str = "ChatPanel";
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<ChatPanel>(cx);
cx.observe_new_models(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<ChatPanel>(window, cx);
});
})
.detach();
@@ -51,7 +51,7 @@ pub struct ChatPanel {
languages: Arc<LanguageRegistry>,
message_list: ListState,
active_chat: Option<(Model<ChannelChat>, Subscription)>,
message_editor: View<MessageEditor>,
message_editor: Model<MessageEditor>,
local_timezone: UtcOffset,
fs: Arc<dyn Fs>,
width: Option<Pixels>,
@@ -74,37 +74,46 @@ struct SerializedChatPanel {
actions!(chat_panel, [ToggleFocus]);
impl ChatPanel {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
pub fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<Self> {
let fs = workspace.app_state().fs.clone();
let client = workspace.app_state().client.clone();
let channel_store = ChannelStore::global(cx);
let user_store = workspace.app_state().user_store.clone();
let languages = workspace.app_state().languages.clone();
let input_editor = cx.new_view(|cx| {
let input_editor = cx.new_model(|cx| {
MessageEditor::new(
languages.clone(),
user_store.clone(),
None,
cx.new_view(|cx| Editor::auto_height(4, cx)),
cx.new_model(|cx| Editor::auto_height(4, window, cx)),
window,
cx,
)
});
cx.new_view(|cx: &mut ViewContext<Self>| {
let view = cx.view().downgrade();
let message_list =
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
cx.new_model(|cx| {
let view = cx.model().downgrade();
let message_list = ListState::new(
0,
gpui::ListAlignment::Bottom,
px(1000.),
move |ix, window, cx| {
if let Some(view) = view.upgrade() {
view.update(cx, |view, cx| {
view.render_message(ix, cx).into_any_element()
view.update(cx, |view: &mut Self, cx| {
view.render_message(ix, window, cx).into_any_element()
})
} else {
div().into_any()
}
});
},
);
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, _, cx| {
if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
this.load_more_messages(cx);
}
@@ -187,9 +196,9 @@ impl ChatPanel {
}
pub fn load(
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
@@ -203,8 +212,8 @@ impl ChatPanel {
None
};
workspace.update(&mut cx, |workspace, cx| {
let panel = Self::new(workspace, cx);
workspace.update_in(&mut cx, |workspace, window, cx| {
let panel = Self::new(workspace, window, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|r| r.round());
@@ -216,7 +225,7 @@ impl ChatPanel {
})
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
fn serialize(&mut self, cx: &mut ModelContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
@@ -232,7 +241,7 @@ impl ChatPanel {
);
}
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ModelContext<Self>) {
if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
self.markdown_data.clear();
self.message_list.reset(chat.read(cx).message_count());
@@ -251,7 +260,7 @@ impl ChatPanel {
&mut self,
_: Model<ChannelChat>,
event: &ChannelChatEvent,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) {
match event {
ChannelChatEvent::MessagesUpdated {
@@ -284,7 +293,7 @@ impl ChatPanel {
cx.notify();
}
fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
if self.active && self.is_scrolled_to_bottom {
if let Some((chat, _)) = &self.active_chat {
if let Some(channel_id) = self.channel_id(cx) {
@@ -305,7 +314,7 @@ impl ChatPanel {
&mut self,
message_id: Option<ChannelMessageId>,
reply_to_message: &Option<ChannelMessage>,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
let reply_to_message = match reply_to_message {
None => {
@@ -369,8 +378,8 @@ impl ChatPanel {
),
)
.cursor(CursorStyle::PointingHand)
.tooltip(|cx| Tooltip::text("Go to message", cx))
.on_click(cx.listener(move |chat_panel, _, cx| {
.tooltip(Tooltip::text("Go to message"))
.on_click(cx.listener(move |chat_panel, _, _, cx| {
if let Some(channel_id) = current_channel_id {
chat_panel
.select_channel(channel_id, reply_to_message_id.into(), cx)
@@ -380,7 +389,12 @@ impl ChatPanel {
)
}
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_message(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
let active_chat = &self.active_chat.as_ref().unwrap().0;
let (message, is_continuation_from_previous, is_admin) =
active_chat.update(cx, |active_chat, cx| {
@@ -530,7 +544,7 @@ impl ChatPanel {
.w_full()
.text_ui_sm(cx)
.id(element_id)
.child(text.element("body".into(), cx)),
.child(text.element("body".into(), window, cx)),
)
.when(self.has_open_menu(message_id), |el| {
el.bg(cx.theme().colors().element_selected)
@@ -560,7 +574,7 @@ impl ChatPanel {
},
)
.child(
self.render_popover_buttons(cx, message_id, can_delete_message, can_edit_message)
self.render_popover_buttons(message_id, can_delete_message, can_edit_message, cx)
.mt_neg_2p5(),
)
}
@@ -572,7 +586,7 @@ impl ChatPanel {
}
}
fn render_popover_button(&self, cx: &ViewContext<Self>, child: Stateful<Div>) -> Div {
fn render_popover_button(&self, cx: &mut ModelContext<Self>, child: Stateful<Div>) -> Div {
div()
.w_6()
.bg(cx.theme().colors().element_background)
@@ -582,10 +596,10 @@ impl ChatPanel {
fn render_popover_buttons(
&self,
cx: &ViewContext<Self>,
message_id: Option<u64>,
can_delete_message: bool,
can_edit_message: bool,
cx: &mut ModelContext<Self>,
) -> Div {
h_flex()
.absolute()
@@ -606,16 +620,16 @@ impl ChatPanel {
.id("reply")
.child(
IconButton::new(("reply", message_id), IconName::ReplyArrowRight)
.on_click(cx.listener(move |this, _, cx| {
.on_click(cx.listener(move |this, _, window, cx| {
this.cancel_edit_message(cx);
this.message_editor.update(cx, |editor, cx| {
editor.set_reply_to_message_id(message_id);
editor.focus_handle(cx).focus(cx);
window.focus(&editor.focus_handle(cx));
})
})),
)
.tooltip(|cx| Tooltip::text("Reply", cx)),
.tooltip(Tooltip::text("Reply")),
),
)
})
@@ -628,7 +642,7 @@ impl ChatPanel {
.id("edit")
.child(
IconButton::new(("edit", message_id), IconName::Pencil)
.on_click(cx.listener(move |this, _, cx| {
.on_click(cx.listener(move |this, _, window, cx| {
this.message_editor.update(cx, |editor, cx| {
editor.clear_reply_to_message_id();
@@ -655,18 +669,18 @@ impl ChatPanel {
});
editor.set_edit_message_id(message_id);
editor.focus_handle(cx).focus(cx);
editor.focus_handle(cx).focus(window);
}
})
})),
)
.tooltip(|cx| Tooltip::text("Edit", cx)),
.tooltip(Tooltip::text("Edit")),
),
)
})
})
.when_some(message_id, |el, message_id| {
let this = cx.view().clone();
let this = cx.model().clone();
el.child(
self.render_popover_button(
@@ -678,34 +692,36 @@ impl ChatPanel {
("trigger", message_id),
IconName::Ellipsis,
))
.menu(move |cx| {
.menu(move |window, cx| {
Some(Self::render_message_menu(
&this,
message_id,
can_delete_message,
window,
cx,
))
}),
)
.id("more")
.tooltip(|cx| Tooltip::text("More", cx)),
.tooltip(Tooltip::text("More")),
),
)
})
}
fn render_message_menu(
this: &View<Self>,
this: &Model<Self>,
message_id: u64,
can_delete_message: bool,
cx: &mut WindowContext,
) -> View<ContextMenu> {
window: &mut Window,
cx: &mut AppContext,
) -> Model<ContextMenu> {
let menu = {
ContextMenu::build(cx, move |menu, cx| {
ContextMenu::build(window, cx, move |menu, window, _| {
menu.entry(
"Copy message text",
None,
cx.handler_for(this, move |this, cx| {
window.handler_for(this, move |this, _, cx| {
if let Some(message) = this.active_chat().and_then(|active_chat| {
active_chat.read(cx).find_loaded_message(message_id)
}) {
@@ -718,15 +734,21 @@ impl ChatPanel {
menu.entry(
"Delete message",
None,
cx.handler_for(this, move |this, cx| this.remove_message(message_id, cx)),
window.handler_for(this, move |this, _, cx| {
this.remove_message(message_id, cx)
}),
)
})
})
};
this.update(cx, |this, cx| {
let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| {
this.open_context_menu = None;
});
let subscription = cx.subscribe_in(
&menu,
window,
|this: &mut Self, _, _: &DismissEvent, _, _| {
this.open_context_menu = None;
},
);
this.open_context_menu = Some((message_id, subscription));
});
menu
@@ -777,19 +799,19 @@ impl ChatPanel {
);
rich_text.custom_ranges.push(range);
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, cx| {
Some(Tooltip::text(edit_timestamp_text.clone(), cx))
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, _, cx| {
Some(Tooltip::simple(edit_timestamp_text.clone(), cx))
})
}
}
rich_text
}
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
fn send(&mut self, _: &Confirm, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
let message = self
.message_editor
.update(cx, |editor, cx| editor.take_message(cx));
.update(cx, |editor, cx| editor.take_message(window, cx));
if let Some(id) = self.message_editor.read(cx).edit_message_id() {
self.message_editor.update(cx, |editor, _| {
@@ -811,13 +833,13 @@ impl ChatPanel {
}
}
fn remove_message(&mut self, id: u64, cx: &mut ViewContext<Self>) {
fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
chat.update(cx, |chat, cx| chat.remove_message(id, cx).detach())
}
}
fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
chat.update(cx, |channel, cx| {
if let Some(task) = channel.load_more_messages(cx) {
@@ -831,7 +853,7 @@ impl ChatPanel {
&mut self,
selected_channel_id: ChannelId,
scroll_to_message_id: Option<u64>,
cx: &mut ViewContext<ChatPanel>,
cx: &mut ModelContext<ChatPanel>,
) -> Task<Result<()>> {
let open_chat = self
.active_chat
@@ -857,20 +879,18 @@ impl ChatPanel {
if let Some(message_id) = scroll_to_message_id {
if let Some(item_ix) =
ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone())
.await
{
this.update(&mut cx, |this, cx| {
if let Some(highlight_message_id) = highlight_message_id {
let task = cx.spawn({
|this, mut cx| async move {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(&mut cx, |this, cx| {
this.highlighted_message.take();
cx.notify();
})
.ok();
}
let task = cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(&mut cx, |this, cx| {
this.highlighted_message.take();
cx.notify();
})
.ok();
});
this.highlighted_message = Some((highlight_message_id, task));
@@ -891,12 +911,12 @@ impl ChatPanel {
})
}
fn close_reply_preview(&mut self, cx: &mut ViewContext<Self>) {
fn close_reply_preview(&mut self, cx: &mut ModelContext<Self>) {
self.message_editor
.update(cx, |editor, _| editor.clear_reply_to_message_id());
}
fn cancel_edit_message(&mut self, cx: &mut ViewContext<Self>) {
fn cancel_edit_message(&mut self, cx: &mut ModelContext<Self>) {
self.message_editor.update(cx, |editor, cx| {
// only clear the editor input if we were editing a message
if editor.edit_message_id().is_none() {
@@ -919,7 +939,7 @@ impl ChatPanel {
}
impl Render for ChatPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let channel_id = self
.active_chat
.as_ref()
@@ -971,11 +991,12 @@ impl Render for ChatPanel {
.full_width()
.key_binding(KeyBinding::for_action(
&collab_panel::ToggleFocus,
cx,
window,
))
.on_click(|_, cx| {
cx.dispatch_action(
.on_click(|_, window, cx| {
window.dispatch_action(
collab_panel::ToggleFocus.boxed_clone(),
cx,
)
}),
),
@@ -999,8 +1020,8 @@ impl Render for ChatPanel {
.child(
IconButton::new("cancel-edit-message", IconName::Close)
.shape(ui::IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel edit message", cx))
.on_click(cx.listener(move |this, _, cx| {
.tooltip(Tooltip::text("Cancel edit message"))
.on_click(cx.listener(move |this, _, _, cx| {
this.cancel_edit_message(cx);
})),
),
@@ -1045,7 +1066,7 @@ impl Render for ChatPanel {
)
.when_some(channel_id, |this, channel_id| {
this.cursor_pointer().on_click(cx.listener(
move |chat_panel, _, cx| {
move |chat_panel, _, _, cx| {
chat_panel
.select_channel(
channel_id,
@@ -1061,8 +1082,8 @@ impl Render for ChatPanel {
.child(
IconButton::new("close-reply-preview", IconName::Close)
.shape(ui::IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close reply", cx))
.on_click(cx.listener(move |this, _, cx| {
.tooltip(Tooltip::text("Close reply"))
.on_click(cx.listener(move |this, _, _, cx| {
this.close_reply_preview(cx);
})),
),
@@ -1073,7 +1094,7 @@ impl Render for ChatPanel {
Some(
h_flex()
.p_2()
.on_action(cx.listener(|this, _: &actions::Cancel, cx| {
.on_action(cx.listener(|this, _: &actions::Cancel, _, cx| {
this.cancel_edit_message(cx);
this.close_reply_preview(cx);
}))
@@ -1085,7 +1106,7 @@ impl Render for ChatPanel {
}
}
impl FocusableView for ChatPanel {
impl Focusable for ChatPanel {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
if self.active_chat.is_some() {
self.message_editor.read(cx).focus_handle(cx)
@@ -1096,7 +1117,7 @@ impl FocusableView for ChatPanel {
}
impl Panel for ChatPanel {
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, _: &Window, cx: &AppContext) -> DockPosition {
ChatPanelSettings::get_global(cx).dock
}
@@ -1104,7 +1125,12 @@ impl Panel for ChatPanel {
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 ModelContext<Self>,
) {
settings::update_settings_file::<ChatPanelSettings>(
self.fs.clone(),
cx,
@@ -1112,18 +1138,18 @@ impl Panel for ChatPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, _: &Window, cx: &AppContext) -> Pixels {
self.width
.unwrap_or_else(|| ChatPanelSettings::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 ModelContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut ModelContext<Self>) {
self.active = active;
if active {
self.acknowledge_last_message(cx);
@@ -1134,7 +1160,7 @@ impl Panel for ChatPanel {
"ChatPanel"
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
fn icon(&self, _: &Window, cx: &AppContext) -> Option<ui::IconName> {
match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => None,
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
@@ -1146,7 +1172,7 @@ impl Panel for ChatPanel {
}
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
fn icon_tooltip(&self, _: &Window, _: &AppContext) -> Option<&'static str> {
Some("Chat Panel")
}
@@ -1154,7 +1180,7 @@ impl Panel for ChatPanel {
Box::new(ToggleFocus)
}
fn starts_open(&self, cx: &WindowContext) -> bool {
fn starts_open(&self, _: &Window, cx: &AppContext) -> bool {
ActiveCall::global(cx)
.read(cx)
.room()

View File

@@ -5,8 +5,8 @@ use collections::HashSet;
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
Render, Task, TextStyle, View, ViewContext, WeakView,
AsyncWindowContext, Focusable, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
ModelContext, Render, Task, TextStyle, WeakModel, Window,
};
use language::{
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
@@ -42,7 +42,7 @@ static MENTIONS_SEARCH: LazyLock<SearchQuery> = LazyLock::new(|| {
});
pub struct MessageEditor {
pub editor: View<Editor>,
pub editor: Model<Editor>,
user_store: Model<UserStore>,
channel_chat: Option<Model<ChannelChat>>,
mentions: Vec<UserId>,
@@ -51,7 +51,7 @@ pub struct MessageEditor {
edit_message_id: Option<u64>,
}
struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
struct MessageEditorCompletionProvider(WeakModel<MessageEditor>);
impl CompletionProvider for MessageEditorCompletionProvider {
fn completions(
@@ -59,13 +59,14 @@ impl CompletionProvider for MessageEditorCompletionProvider {
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
_: editor::CompletionContext,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Task<anyhow::Result<Vec<Completion>>> {
let Some(handle) = self.0.upgrade() else {
return Task::ready(Ok(Vec::new()));
};
handle.update(cx, |message_editor, cx| {
message_editor.completions(buffer, buffer_position, cx)
message_editor.completions(buffer, buffer_position, window, cx)
})
}
@@ -74,7 +75,8 @@ impl CompletionProvider for MessageEditorCompletionProvider {
_buffer: Model<Buffer>,
_completion_indices: Vec<usize>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_cx: &mut ViewContext<Editor>,
_window: &mut Window,
_cx: &mut ModelContext<Editor>,
) -> Task<anyhow::Result<bool>> {
Task::ready(Ok(false))
}
@@ -85,7 +87,8 @@ impl CompletionProvider for MessageEditorCompletionProvider {
_position: language::Anchor,
text: &str,
_trigger_in_words: bool,
_cx: &mut ViewContext<Editor>,
_window: &mut Window,
_cx: &mut ModelContext<Editor>,
) -> bool {
text == "@"
}
@@ -96,10 +99,11 @@ impl MessageEditor {
language_registry: Arc<LanguageRegistry>,
user_store: Model<UserStore>,
channel_chat: Option<Model<ChannelChat>>,
editor: View<Editor>,
cx: &mut ViewContext<Self>,
editor: Model<Editor>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let this = cx.view().downgrade();
let this = cx.model().downgrade();
editor.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_use_autoclose(false);
@@ -121,7 +125,8 @@ impl MessageEditor {
.as_singleton()
.expect("message editor must be singleton");
cx.subscribe(&buffer, Self::on_buffer_event).detach();
cx.subscribe_in(&buffer, window, Self::on_buffer_event)
.detach();
cx.observe_global::<settings::SettingsStore>(|view, cx| {
view.editor.update(cx, |editor, cx| {
editor.set_auto_replace_emoji_shortcode(
@@ -134,7 +139,7 @@ impl MessageEditor {
.detach();
let markdown = language_registry.language_for_name("Markdown");
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
let markdown = markdown.await.context("failed to load Markdown language")?;
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(Some(markdown), cx)
@@ -177,7 +182,7 @@ impl MessageEditor {
self.edit_message_id = None;
}
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ModelContext<Self>) {
let channel_id = chat.read(cx).channel_id;
self.channel_chat = Some(chat);
let channel_name = ChannelStore::global(cx)
@@ -193,7 +198,11 @@ impl MessageEditor {
});
}
pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> MessageParams {
pub fn take_message(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> MessageParams {
self.editor.update(cx, |editor, cx| {
let highlights = editor.text_highlights::<Self>(cx);
let text = editor.text(cx);
@@ -208,7 +217,7 @@ impl MessageEditor {
Vec::new()
};
editor.clear(cx);
editor.clear(window, cx);
self.mentions.clear();
let reply_to_message_id = std::mem::take(&mut self.reply_to_message_id);
@@ -222,13 +231,14 @@ impl MessageEditor {
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
buffer: &Model<Buffer>,
event: &language::BufferEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
let buffer = buffer.read(cx).snapshot();
self.mentions_task = Some(cx.spawn(|this, cx| async move {
self.mentions_task = Some(cx.spawn_in(window, |this, cx| async move {
cx.background_executor()
.timer(MENTIONS_DEBOUNCE_INTERVAL)
.await;
@@ -241,13 +251,14 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Completion>>> {
if let Some((start_anchor, query, candidates)) =
self.collect_mention_candidates(buffer, end_anchor, cx)
{
if !candidates.is_empty() {
return cx.spawn(|_, cx| async move {
return cx.spawn_in(window, |_, cx| async move {
Ok(Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
@@ -264,7 +275,7 @@ impl MessageEditor {
self.collect_emoji_candidates(buffer, end_anchor, cx)
{
if !candidates.is_empty() {
return cx.spawn(|_, cx| async move {
return cx.spawn_in(window, |_, cx| async move {
Ok(Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
@@ -338,7 +349,7 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
let end_offset = end_anchor.to_offset(buffer.read(cx));
@@ -387,7 +398,7 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> Option<(Anchor, String, &'static [StringMatchCandidate])> {
static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock<Vec<StringMatchCandidate>> =
LazyLock::new(|| {
@@ -445,7 +456,7 @@ impl MessageEditor {
}
async fn find_mentions(
this: WeakView<MessageEditor>,
this: WeakModel<MessageEditor>,
buffer: BufferSnapshot,
mut cx: AsyncWindowContext,
) {
@@ -505,7 +516,7 @@ impl MessageEditor {
}
impl Render for MessageEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ use client::{
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, anchored, deferred, div, AppContext, ClipboardItem, DismissEvent, EventEmitter,
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
VisualContext, WeakView,
Focusable, Model, ModelContext, ParentElement, Render, Styled, Subscription, Task, WeakModel,
Window,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
@@ -26,7 +26,7 @@ actions!(
);
pub struct ChannelModal {
picker: View<Picker<ChannelModalDelegate>>,
picker: Model<Picker<ChannelModalDelegate>>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
}
@@ -37,11 +37,12 @@ impl ChannelModal {
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
mode: Mode,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
let channel_modal = cx.view().downgrade();
let picker = cx.new_view(|cx| {
let channel_modal = cx.model().downgrade();
let picker = cx.new_model(|cx| {
Picker::uniform_list(
ChannelModalDelegate {
channel_modal,
@@ -57,6 +58,7 @@ impl ChannelModal {
has_all_members: false,
mode,
},
window,
cx,
)
.modal(false)
@@ -69,27 +71,32 @@ impl ChannelModal {
}
}
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
fn toggle_mode(&mut self, _: &ToggleMode, window: &mut Window, cx: &mut ModelContext<Self>) {
let mode = match self.picker.read(cx).delegate.mode {
Mode::ManageMembers => Mode::InviteMembers,
Mode::InviteMembers => Mode::ManageMembers,
};
self.set_mode(mode, cx);
self.set_mode(mode, window, cx);
}
fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
fn set_mode(&mut self, mode: Mode, window: &mut Window, cx: &mut ModelContext<Self>) {
self.picker.update(cx, |picker, cx| {
let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", cx);
picker.update_matches(picker.query(cx), cx);
picker.set_query("", window, cx);
picker.update_matches(picker.query(cx), window, cx);
cx.notify()
});
cx.notify()
}
fn set_channel_visibility(&mut self, selection: &ToggleState, cx: &mut ViewContext<Self>) {
fn set_channel_visibility(
&mut self,
selection: &ToggleState,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.channel_store.update(cx, |channel_store, cx| {
channel_store
.set_channel_visibility(
@@ -105,7 +112,7 @@ impl ChannelModal {
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
cx.emit(DismissEvent);
}
}
@@ -113,14 +120,14 @@ impl ChannelModal {
impl EventEmitter<DismissEvent> for ChannelModal {}
impl ModalView for ChannelModal {}
impl FocusableView for ChannelModal {
impl Focusable for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ChannelModal {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let channel_store = self.channel_store.read(cx);
let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
return div();
@@ -169,7 +176,7 @@ impl Render for ChannelModal {
Some(
Button::new("copy-link", "Copy Link")
.label_size(LabelSize::Small)
.on_click(cx.listener(move |this, _, cx| {
.on_click(cx.listener(move |this, _, _, cx| {
if let Some(channel) = this
.channel_store
.read(cx)
@@ -197,8 +204,8 @@ impl Render for ChannelModal {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Manage Members"))
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::ManageMembers, cx);
.on_click(cx.listener(|this, _, window, cx| {
this.set_mode(Mode::ManageMembers, window, cx);
})),
)
.child(
@@ -212,8 +219,8 @@ impl Render for ChannelModal {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Invite Members"))
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::InviteMembers, cx);
.on_click(cx.listener(|this, _, window, cx| {
this.set_mode(Mode::InviteMembers, window, cx);
})),
),
),
@@ -229,7 +236,7 @@ pub enum Mode {
}
pub struct ChannelModalDelegate {
channel_modal: WeakView<ChannelModal>,
channel_modal: WeakModel<ChannelModal>,
matching_users: Vec<Arc<User>>,
matching_member_indices: Vec<usize>,
user_store: Model<UserStore>,
@@ -240,13 +247,13 @@ pub struct ChannelModalDelegate {
match_candidates: Vec<StringMatchCandidate>,
members: Vec<ChannelMembership>,
has_all_members: bool,
context_menu: Option<(View<ContextMenu>, Subscription)>,
context_menu: Option<(Model<ContextMenu>, Subscription)>,
}
impl PickerDelegate for ChannelModalDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search collaborator by username...".into()
}
@@ -261,11 +268,21 @@ impl PickerDelegate for ChannelModalDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
}
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 ModelContext<Picker<Self>>,
) -> Task<()> {
match self.mode {
Mode::ManageMembers => {
if self.has_all_members {
@@ -284,7 +301,7 @@ impl PickerDelegate for ChannelModalDelegate {
cx.background_executor().clone(),
));
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
let delegate = &mut picker.delegate;
@@ -300,7 +317,7 @@ impl PickerDelegate for ChannelModalDelegate {
let search_members = self.channel_store.update(cx, |store, cx| {
store.fuzzy_search_members(self.channel_id, query.clone(), 100, cx)
});
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
async {
let members = search_members.await?;
picker.update(&mut cx, |picker, cx| {
@@ -322,7 +339,7 @@ impl PickerDelegate for ChannelModalDelegate {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
@@ -338,26 +355,26 @@ impl PickerDelegate for ChannelModalDelegate {
}
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
if let Some(selected_user) = self.user_at_index(self.selected_index) {
if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id)
{
return;
}
match self.mode {
Mode::ManageMembers => self.show_context_menu(self.selected_index, cx),
Mode::ManageMembers => self.show_context_menu(self.selected_index, window, cx),
Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
Some(proto::channel_member::Kind::Invitee) => {
self.remove_member(selected_user.id, cx);
self.remove_member(selected_user.id, window, cx);
}
Some(proto::channel_member::Kind::Member) => {}
None => self.invite_member(selected_user, cx),
None => self.invite_member(selected_user, window, cx),
},
}
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
if self.context_menu.is_none() {
self.channel_modal
.update(cx, |_, cx| {
@@ -371,7 +388,8 @@ impl PickerDelegate for ChannelModalDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = self.user_at_index(ix)?;
let membership = self.member_at_index(ix);
@@ -470,33 +488,39 @@ impl ChannelModalDelegate {
&mut self,
user_id: UserId,
new_role: ChannelRole,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<()> {
let update = self.channel_store.update(cx, |store, cx| {
store.set_member_role(self.channel_id, user_id, new_role, cx)
});
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
picker.update_in(&mut cx, |picker, window, cx| {
let this = &mut picker.delegate;
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user_id) {
member.role = new_role;
}
cx.focus_self();
cx.focus_self(window);
cx.notify();
})
})
.detach_and_prompt_err("Failed to update role", cx, |_, _| None);
.detach_and_prompt_err("Failed to update role", window, cx, |_, _, _| None);
Some(())
}
fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
fn remove_member(
&mut self,
user_id: UserId,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<()> {
let update = self.channel_store.update(cx, |store, cx| {
store.remove_member(self.channel_id, user_id, cx)
});
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
picker.update_in(&mut cx, |picker, window, cx| {
let this = &mut picker.delegate;
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
this.members.remove(ix);
@@ -514,20 +538,25 @@ impl ChannelModalDelegate {
.selected_index
.min(this.matching_member_indices.len().saturating_sub(1));
picker.focus(cx);
picker.focus(window, cx);
cx.notify();
})
})
.detach_and_prompt_err("Failed to remove member", cx, |_, _| None);
.detach_and_prompt_err("Failed to remove member", window, cx, |_, _, _| None);
Some(())
}
fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
fn invite_member(
&mut self,
user: Arc<User>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let invite_member = self.channel_store.update(cx, |store, cx| {
store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
});
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
invite_member.await?;
this.update(&mut cx, |this, cx| {
@@ -544,25 +573,30 @@ impl ChannelModalDelegate {
cx.notify();
})
})
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
.detach_and_prompt_err("Failed to invite member", window, cx, |_, _, _| None);
}
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn show_context_menu(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
let Some(membership) = self.member_at_index(ix) else {
return;
};
let user_id = membership.user.id;
let picker = cx.view().clone();
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
let picker = cx.model().clone();
let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
let role = membership.role;
if role == ChannelRole::Admin || role == ChannelRole::Member {
let picker = picker.clone();
menu = menu.entry("Demote to Guest", None, move |cx| {
menu = menu.entry("Demote to Guest", None, move |window, cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Guest, cx);
.set_user_role(user_id, ChannelRole::Guest, window, cx);
})
});
}
@@ -575,22 +609,22 @@ impl ChannelModalDelegate {
"Demote to Member"
};
menu = menu.entry(label, None, move |cx| {
menu = menu.entry(label, None, move |window, cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Member, cx);
.set_user_role(user_id, ChannelRole::Member, window, cx);
})
});
}
if role == ChannelRole::Member || role == ChannelRole::Guest {
let picker = picker.clone();
menu = menu.entry("Promote to Admin", None, move |cx| {
menu = menu.entry("Promote to Admin", None, move |window, cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Admin, cx);
.set_user_role(user_id, ChannelRole::Admin, window, cx);
})
});
};
@@ -598,20 +632,24 @@ impl ChannelModalDelegate {
menu = menu.separator();
menu = menu.entry("Remove from Channel", None, {
let picker = picker.clone();
move |cx| {
move |window, cx| {
picker.update(cx, |picker, cx| {
picker.delegate.remove_member(user_id, cx);
picker.delegate.remove_member(user_id, window, cx);
})
}
});
menu
});
cx.focus_view(&context_menu);
let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
picker.delegate.context_menu = None;
picker.focus(cx);
cx.notify();
});
window.focus(&context_menu.focus_handle(cx));
let subscription = cx.subscribe_in(
&context_menu,
window,
|picker, _, _: &DismissEvent, window, cx| {
picker.delegate.context_menu = None;
picker.focus(window, cx);
cx.notify();
},
);
self.context_menu = Some((context_menu, subscription));
}
}

View File

@@ -1,7 +1,7 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
AppContext, DismissEvent, EventEmitter, FocusHandle, Focusable, Model, ModelContext,
ParentElement as _, Render, Styled, Task, WeakModel, Window,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
@@ -10,31 +10,35 @@ use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
pub struct ContactFinder {
picker: View<Picker<ContactFinderDelegate>>,
picker: Model<Picker<ContactFinderDelegate>>,
}
impl ContactFinder {
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(
user_store: Model<UserStore>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let delegate = ContactFinderDelegate {
parent: cx.view().downgrade(),
parent: cx.model().downgrade(),
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false));
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
Self { picker }
}
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
pub fn set_query(&mut self, query: String, window: &mut Window, cx: &mut ModelContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.set_query(query, cx);
picker.set_query(query, window, cx);
});
}
}
impl Render for ContactFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex()
.elevation_3(cx)
.child(
@@ -53,7 +57,7 @@ impl Render for ContactFinder {
}
pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
parent: WeakModel<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: Model<UserStore>,
selected_index: usize,
@@ -62,7 +66,7 @@ pub struct ContactFinderDelegate {
impl EventEmitter<DismissEvent> for ContactFinder {}
impl ModalView for ContactFinder {}
impl FocusableView for ContactFinder {
impl Focusable for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
@@ -79,20 +83,30 @@ impl PickerDelegate for ContactFinderDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Search collaborator by username...".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 ModelContext<Picker<Self>>,
) -> Task<()> {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
@@ -106,7 +120,7 @@ impl PickerDelegate for ContactFinderDelegate {
})
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
if let Some(user) = self.potential_contacts.get(self.selected_index) {
let user_store = self.user_store.read(cx);
match user_store.contact_request_status(user) {
@@ -125,7 +139,7 @@ impl PickerDelegate for ContactFinderDelegate {
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
@@ -135,7 +149,8 @@ impl PickerDelegate for ContactFinderDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user);

View File

@@ -7,10 +7,9 @@ use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt;
use gpui::{
actions, div, img, list, px, AnyElement, AppContext, AsyncWindowContext, CursorStyle,
DismissEvent, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
IntoElement, ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render,
StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, WeakView,
WindowContext,
DismissEvent, Element, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement,
ListAlignment, ListScrollEvent, ListState, Model, ModelContext, ParentElement, Render,
StatefulInteractiveElement, Styled, Task, WeakModel, Window,
};
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
use project::Fs;
@@ -45,7 +44,7 @@ pub struct NotificationPanel {
notification_list: ListState,
pending_serialization: Task<Option<()>>,
subscriptions: Vec<gpui::Subscription>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
current_notification_toast: Option<(u64, Task<()>)>,
local_timezone: UtcOffset,
focus_handle: FocusHandle,
@@ -76,27 +75,31 @@ pub struct NotificationPresenter {
actions!(notification_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<NotificationPanel>(cx);
cx.observe_new_models(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<NotificationPanel>(window, cx);
});
})
.detach();
}
impl NotificationPanel {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
pub fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<Self> {
let fs = workspace.app_state().fs.clone();
let client = workspace.app_state().client.clone();
let user_store = workspace.app_state().user_store.clone();
let workspace_handle = workspace.weak_handle();
cx.new_view(|cx: &mut ViewContext<Self>| {
cx.new_model(|cx| {
let mut status = client.status();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
while (status.next().await).is_some() {
if this
.update(&mut cx, |_, cx| {
.update(&mut cx, |_: &mut Self, cx| {
cx.notify();
})
.is_err()
@@ -107,17 +110,17 @@ impl NotificationPanel {
})
.detach();
let view = cx.view().downgrade();
let view = cx.model().downgrade();
let notification_list =
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, window, cx| {
view.upgrade()
.and_then(|view| {
view.update(cx, |this, cx| this.render_notification(ix, cx))
view.update(cx, |this, cx| this.render_notification(ix, window, cx))
})
.unwrap_or_else(|| div().into_any())
});
notification_list.set_scroll_handler(cx.listener(
|this, event: &ListScrollEvent, cx| {
|this, event: &ListScrollEvent, _, cx| {
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
if let Some(task) = this
.notification_store
@@ -149,27 +152,34 @@ impl NotificationPanel {
unseen_notifications: Vec::new(),
};
let mut old_dock_position = this.position(cx);
let mut old_dock_position = this.position(window, cx);
this.subscriptions.extend([
cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
cx.subscribe(&this.notification_store, Self::on_notification_event),
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
let new_dock_position = this.position(cx);
if new_dock_position != old_dock_position {
old_dock_position = new_dock_position;
cx.emit(Event::DockPositionChanged);
}
cx.notify();
}),
cx.subscribe_in(
&this.notification_store,
window,
Self::on_notification_event,
),
cx.observe_global_in::<SettingsStore>(
window,
move |this: &mut Self, window, cx| {
let new_dock_position = this.position(window, cx);
if new_dock_position != old_dock_position {
old_dock_position = new_dock_position;
cx.emit(Event::DockPositionChanged);
}
cx.notify();
},
),
]);
this
})
}
pub fn load(
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
@@ -183,8 +193,8 @@ impl NotificationPanel {
None
};
workspace.update(&mut cx, |workspace, cx| {
let panel = Self::new(workspace, cx);
workspace.update_in(&mut cx, |workspace, window, cx| {
let panel = Self::new(workspace, window, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|w| w.round());
@@ -196,7 +206,7 @@ impl NotificationPanel {
})
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
fn serialize(&mut self, cx: &mut ModelContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
@@ -212,7 +222,12 @@ impl NotificationPanel {
);
}
fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
fn render_notification(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<AnyElement> {
let entry = self.notification_store.read(cx).notification_at(ix)?;
let notification_id = entry.id;
let now = OffsetDateTime::now_utc();
@@ -229,7 +244,7 @@ impl NotificationPanel {
let notification = entry.notification.clone();
if self.active && !entry.is_read {
self.did_render_notification(notification_id, &notification, cx);
self.did_render_notification(notification_id, &notification, window, cx);
}
let relative_timestamp = time_format::format_localized_timestamp(
@@ -259,8 +274,8 @@ impl NotificationPanel {
.when(can_navigate, |el| {
el.cursor(CursorStyle::PointingHand).on_click({
let notification = notification.clone();
cx.listener(move |this, _, cx| {
this.did_click_notification(&notification, cx)
cx.listener(move |this, _, window, cx| {
this.did_click_notification(&notification, window, cx)
})
})
})
@@ -288,8 +303,8 @@ impl NotificationPanel {
.rounded_md()
})
.child(Label::new(relative_timestamp).color(Color::Muted))
.tooltip(move |cx| {
Tooltip::text(absolute_timestamp.clone(), cx)
.tooltip(move |_, cx| {
Tooltip::simple(absolute_timestamp.clone(), cx)
}),
)
.children(if let Some(is_accepted) = response {
@@ -307,8 +322,8 @@ impl NotificationPanel {
.justify_end()
.child(Button::new("decline", "Decline").on_click({
let notification = notification.clone();
let view = cx.view().clone();
move |_, cx| {
let view = cx.model().clone();
move |_, _, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
@@ -320,8 +335,8 @@ impl NotificationPanel {
}))
.child(Button::new("accept", "Accept").on_click({
let notification = notification.clone();
let view = cx.view().clone();
move |_, cx| {
let view = cx.model().clone();
move |_, _, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
@@ -415,7 +430,8 @@ impl NotificationPanel {
&mut self,
notification_id: u64,
notification: &Notification,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let should_mark_as_read = match notification {
Notification::ContactRequestAccepted { .. } => true,
@@ -429,7 +445,7 @@ impl NotificationPanel {
.entry(notification_id)
.or_insert_with(|| {
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
cx.background_executor().timer(MARK_AS_READ_DELAY).await;
client
.request(proto::MarkNotificationRead { notification_id })
@@ -443,7 +459,12 @@ impl NotificationPanel {
}
}
fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
fn did_click_notification(
&mut self,
notification: &Notification,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Notification::ChannelMessageMention {
message_id,
channel_id,
@@ -451,9 +472,9 @@ impl NotificationPanel {
} = notification.clone()
{
if let Some(workspace) = self.workspace.upgrade() {
cx.window_context().defer(move |cx| {
window.defer(cx, move |window, cx| {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(window, cx) {
panel.update(cx, |panel, cx| {
panel
.select_channel(ChannelId(channel_id), Some(message_id), cx)
@@ -466,7 +487,11 @@ impl NotificationPanel {
}
}
fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
fn is_showing_notification(
&self,
notification: &Notification,
cx: &mut ModelContext<Self>,
) -> bool {
if !self.active {
return false;
}
@@ -490,16 +515,17 @@ impl NotificationPanel {
fn on_notification_event(
&mut self,
_: Model<NotificationStore>,
_: &Model<NotificationStore>,
event: &NotificationEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
match event {
NotificationEvent::NewNotification { entry } => {
if !self.is_showing_notification(&entry.notification, cx) {
self.unseen_notifications.push(entry.clone());
}
self.add_toast(entry, cx);
self.add_toast(entry, window, cx);
}
NotificationEvent::NotificationRemoved { entry }
| NotificationEvent::NotificationRead { entry } => {
@@ -516,7 +542,12 @@ impl NotificationPanel {
}
}
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
fn add_toast(
&mut self,
entry: &NotificationEntry,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if self.is_showing_notification(&entry.notification, cx) {
return;
}
@@ -529,7 +560,7 @@ impl NotificationPanel {
let notification_id = entry.id;
self.current_notification_toast = Some((
notification_id,
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
cx.background_executor().timer(TOAST_DURATION).await;
this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
.ok();
@@ -542,8 +573,8 @@ impl NotificationPanel {
workspace.dismiss_notification(&id, cx);
workspace.show_notification(id, cx, |cx| {
let workspace = cx.view().downgrade();
cx.new_view(|_| NotificationToast {
let workspace = cx.model().downgrade();
cx.new_model(|_| NotificationToast {
notification_id,
actor,
text,
@@ -554,7 +585,7 @@ impl NotificationPanel {
.ok();
}
fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
fn remove_toast(&mut self, notification_id: u64, cx: &mut ModelContext<Self>) {
if let Some((current_id, _)) = &self.current_notification_toast {
if *current_id == notification_id {
self.current_notification_toast.take();
@@ -572,7 +603,8 @@ impl NotificationPanel {
&mut self,
notification: Notification,
response: bool,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) {
self.notification_store.update(cx, |store, cx| {
store.respond_to_notification(notification, response, cx);
@@ -581,7 +613,7 @@ impl NotificationPanel {
}
impl Render for NotificationPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex()
.size_full()
.child(
@@ -611,15 +643,16 @@ impl Render for NotificationPanel {
.full_width()
.on_click({
let client = self.client.clone();
move |_, cx| {
move |_, window, cx| {
let client = client.clone();
cx.spawn(move |cx| async move {
client
.authenticate_and_connect(true, &cx)
.log_err()
.await;
})
.detach()
window
.spawn(cx, move |cx| async move {
client
.authenticate_and_connect(true, &cx)
.log_err()
.await;
})
.detach()
}
}),
)
@@ -648,7 +681,7 @@ impl Render for NotificationPanel {
}
}
impl FocusableView for NotificationPanel {
impl Focusable for NotificationPanel {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
@@ -662,7 +695,7 @@ impl Panel for NotificationPanel {
"NotificationPanel"
}
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, _: &Window, cx: &AppContext) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}
@@ -670,7 +703,12 @@ impl Panel for NotificationPanel {
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 ModelContext<Self>,
) {
settings::update_settings_file::<NotificationPanelSettings>(
self.fs.clone(),
cx,
@@ -678,18 +716,18 @@ impl Panel for NotificationPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, _: &Window, cx: &AppContext) -> Pixels {
self.width
.unwrap_or_else(|| NotificationPanelSettings::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 ModelContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut ModelContext<Self>) {
self.active = active;
if self.active {
@@ -702,7 +740,7 @@ impl Panel for NotificationPanel {
}
}
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
fn icon(&self, _: &Window, cx: &AppContext) -> Option<IconName> {
let show_button = NotificationPanelSettings::get_global(cx).button;
if !show_button {
return None;
@@ -715,11 +753,11 @@ impl Panel for NotificationPanel {
Some(IconName::BellDot)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
fn icon_tooltip(&self, _window: &Window, _cx: &AppContext) -> Option<&'static str> {
Some("Notification Panel")
}
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
fn icon_label(&self, _window: &Window, cx: &AppContext) -> Option<String> {
let count = self.notification_store.read(cx).unread_notification_count();
if count == 0 {
None
@@ -741,21 +779,25 @@ pub struct NotificationToast {
notification_id: u64,
actor: Option<Arc<User>>,
text: String,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
}
impl NotificationToast {
fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
fn focus_notification_panel(&self, window: &mut Window, cx: &mut ModelContext<Self>) {
let workspace = self.workspace.clone();
let notification_id = self.notification_id;
cx.window_context().defer(move |cx| {
window.defer(cx, move |window, cx| {
workspace
.update(cx, |workspace, cx| {
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(window, cx) {
panel.update(cx, |panel, cx| {
let store = panel.notification_store.read(cx);
if let Some(entry) = store.notification_for_id(notification_id) {
panel.did_click_notification(&entry.clone().notification, cx);
panel.did_click_notification(
&entry.clone().notification,
window,
cx,
);
}
});
}
@@ -766,7 +808,7 @@ impl NotificationToast {
}
impl Render for NotificationToast {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let user = self.actor.clone();
h_flex()
@@ -778,10 +820,10 @@ impl Render for NotificationToast {
.child(Label::new(self.text.clone()))
.child(
IconButton::new("close", IconName::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
.on_click(cx.listener(|this, _, cx| {
this.focus_notification_panel(cx);
.on_click(cx.listener(|this, _, window, cx| {
this.focus_notification_panel(window, cx);
cx.emit(DismissEvent);
}))
}

View File

@@ -32,7 +32,7 @@ impl ParentElement for CollabNotification {
}
impl RenderOnce for CollabNotification {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _: &mut Window, cx: &mut AppContext) -> impl IntoElement {
h_flex()
.text_ui(cx)
.justify_between()

View File

@@ -17,8 +17,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
while let Some(incoming_call) = incoming_call.next().await {
for window in notification_windows.drain(..) {
window
.update(&mut cx, |_, cx| {
cx.remove_window();
.update(&mut cx, |_, window, _| {
window.remove_window();
})
.log_err();
}
@@ -36,8 +36,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
.log_err()
{
let window = cx
.open_window(options, |cx| {
cx.new_view(|_| {
.open_window(options, |_, cx| {
cx.new_model(|_| {
IncomingCallNotification::new(
incoming_call.clone(),
app_state.clone(),
@@ -111,19 +111,19 @@ impl IncomingCallNotification {
}
impl Render for IncomingCallNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
div().size_full().font(ui_font).child(
CollabNotification::new(
self.state.call.calling_user.avatar_uri.clone(),
Button::new("accept", "Accept").on_click({
let state = self.state.clone();
move |_, cx| state.respond(true, cx)
move |_, _, cx| state.respond(true, cx)
}),
Button::new("decline", "Decline").on_click({
let state = self.state.clone();
move |_, cx| state.respond(false, cx)
move |_, _, cx| state.respond(false, cx)
}),
)
.child(v_flex().overflow_hidden().child(Label::new(format!(

View File

@@ -28,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.displays() {
let options = notification_window_options(screen, window_size, cx);
let Some(window) = cx
.open_window(options, |cx| {
cx.new_view(|_| {
.open_window(options, |_, cx| {
cx.new_model(|_| {
ProjectSharedNotification::new(
owner.clone(),
*project_id,
@@ -55,8 +55,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
if let Some(windows) = notification_windows.remove(project_id) {
for window in windows {
window
.update(cx, |_, cx| {
cx.remove_window();
.update(cx, |_, window, _| {
window.remove_window();
})
.ok();
}
@@ -67,8 +67,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for (_, windows) in notification_windows.drain() {
for window in windows {
window
.update(cx, |_, cx| {
cx.remove_window();
.update(cx, |_, window, _| {
window.remove_window();
})
.ok();
}
@@ -101,14 +101,14 @@ impl ProjectSharedNotification {
}
}
fn join(&mut self, cx: &mut ViewContext<Self>) {
fn join(&mut self, cx: &mut ModelContext<Self>) {
if let Some(app_state) = self.app_state.upgrade() {
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
.detach_and_log_err(cx);
}
}
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
fn dismiss(&mut self, cx: &mut ModelContext<Self>) {
if let Some(active_room) =
ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
{
@@ -122,18 +122,20 @@ impl ProjectSharedNotification {
}
impl Render for ProjectSharedNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
div().size_full().font(ui_font).child(
CollabNotification::new(
self.owner.avatar_uri.clone(),
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| {
this.join(cx);
})),
Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
this.dismiss(cx);
})),
Button::new("dismiss", "Dismiss").on_click(cx.listener(
move |this, _event, _, cx| {
this.dismiss(cx);
},
)),
)
.child(Label::new(self.owner.github_login.clone()))
.child(Label::new(format!(

View File

@@ -7,7 +7,7 @@ use crate::notifications::collab_notification::CollabNotification;
pub struct CollabNotificationStory;
impl Render for CollabNotificationStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
let window_container = |width, height| div().w(px(width)).h(px(height));
Story::container()

View File

@@ -11,8 +11,8 @@ use command_palette_hooks::{
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
ParentElement, Render, Styled, Task, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
Action, AppContext, DismissEvent, EventEmitter, FocusHandle, Focusable, Global, Model,
ModelContext, ParentElement, Render, Styled, Task, UpdateGlobal, WeakModel, Window,
};
use picker::{Picker, PickerDelegate};
@@ -27,13 +27,13 @@ pub fn init(cx: &mut AppContext) {
client::init_settings(cx);
cx.set_global(HitCounts::default());
command_palette_hooks::init(cx);
cx.observe_new_views(CommandPalette::register).detach();
cx.observe_new_models(CommandPalette::register).detach();
}
impl ModalView for CommandPalette {}
pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
picker: Model<Picker<CommandPaletteDelegate>>,
}
/// Removes subsequent whitespace characters and double colons from the query.
@@ -60,24 +60,40 @@ fn normalize_query(input: &str) -> String {
}
impl CommandPalette {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &Toggle, cx| Self::toggle(workspace, "", cx));
}
pub fn toggle(workspace: &mut Workspace, query: &str, cx: &mut ViewContext<Workspace>) {
let Some(previous_focus_handle) = cx.focused() else {
return;
};
workspace.toggle_modal(cx, move |cx| {
CommandPalette::new(previous_focus_handle, query, cx)
fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut ModelContext<Workspace>,
) {
workspace.register_action(|workspace, _: &Toggle, window, cx| {
Self::toggle(workspace, "", window, cx)
});
}
fn new(previous_focus_handle: FocusHandle, query: &str, cx: &mut ViewContext<Self>) -> Self {
pub fn toggle(
workspace: &mut Workspace,
query: &str,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) {
let Some(previous_focus_handle) = window.focused(cx) else {
return;
};
workspace.toggle_modal(window, cx, move |window, cx| {
CommandPalette::new(previous_focus_handle, query, window, cx)
});
}
fn new(
previous_focus_handle: FocusHandle,
query: &str,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let filter = CommandPaletteFilter::try_global(cx);
let commands = cx
.available_actions()
let commands = window
.available_actions(cx)
.into_iter()
.filter_map(|action| {
if filter.is_some_and(|filter| filter.is_hidden(&*action)) {
@@ -92,38 +108,38 @@ impl CommandPalette {
.collect();
let delegate =
CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
CommandPaletteDelegate::new(cx.model().downgrade(), commands, previous_focus_handle);
let picker = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx);
picker.set_query(query, cx);
let picker = cx.new_model(|cx| {
let picker = Picker::uniform_list(delegate, window, cx);
picker.set_query(query, window, cx);
picker
});
Self { picker }
}
pub fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
pub fn set_query(&mut self, query: &str, window: &mut Window, cx: &mut ModelContext<Self>) {
self.picker
.update(cx, |picker, cx| picker.set_query(query, cx))
.update(cx, |picker, cx| picker.set_query(query, window, cx))
}
}
impl EventEmitter<DismissEvent> for CommandPalette {}
impl FocusableView for CommandPalette {
impl Focusable for CommandPalette {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for CommandPalette {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
pub struct CommandPaletteDelegate {
command_palette: WeakView<CommandPalette>,
command_palette: WeakModel<CommandPalette>,
all_commands: Vec<Command>,
commands: Vec<Command>,
matches: Vec<StringMatch>,
@@ -159,7 +175,7 @@ impl Global for HitCounts {}
impl CommandPaletteDelegate {
fn new(
command_palette: WeakView<CommandPalette>,
command_palette: WeakModel<CommandPalette>,
commands: Vec<Command>,
previous_focus_handle: FocusHandle,
) -> Self {
@@ -179,7 +195,7 @@ impl CommandPaletteDelegate {
query: String,
mut commands: Vec<Command>,
mut matches: Vec<StringMatch>,
cx: &mut ViewContext<Picker<Self>>,
cx: &mut ModelContext<Picker<Self>>,
) {
self.updating_matches.take();
@@ -233,7 +249,7 @@ impl CommandPaletteDelegate {
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
"Execute a command...".into()
}
@@ -245,14 +261,20 @@ impl PickerDelegate for CommandPaletteDelegate {
self.selected_ix
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) {
self.selected_ix = ix;
}
fn update_matches(
&mut self,
mut query: String,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> gpui::Task<()> {
let settings = WorkspaceSettings::get_global(cx);
if let Some(alias) = settings.command_aliases.get(&query) {
@@ -305,7 +327,7 @@ impl PickerDelegate for CommandPaletteDelegate {
});
self.updating_matches = Some((task, rx.clone()));
cx.spawn(move |picker, mut cx| async move {
cx.spawn_in(window, move |picker, mut cx| async move {
let Some((commands, matches)) = rx.recv().await else {
return;
};
@@ -324,7 +346,8 @@ impl PickerDelegate for CommandPaletteDelegate {
&mut self,
query: String,
duration: Duration,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> bool {
let Some((task, rx)) = self.updating_matches.take() else {
return true;
@@ -345,20 +368,19 @@ impl PickerDelegate for CommandPaletteDelegate {
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
self.command_palette
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
if self.matches.is_empty() {
self.dismissed(cx);
self.dismissed(window, cx);
return;
}
let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix);
telemetry::event!(
"Action Invoked",
source = "command palette",
@@ -370,16 +392,17 @@ impl PickerDelegate for CommandPaletteDelegate {
*hit_counts.0.entry(command.name).or_default() += 1;
});
let action = command.action;
cx.focus(&self.previous_focus_handle);
self.dismissed(cx);
cx.dispatch_action(action);
window.focus(&self.previous_focus_handle);
self.dismissed(window, cx);
window.dispatch_action(action, cx);
}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let r#match = self.matches.get(ix)?;
let command = self.commands.get(r#match.candidate_id)?;
@@ -400,7 +423,7 @@ impl PickerDelegate for CommandPaletteDelegate {
.children(KeyBinding::for_action_in(
&*command.action,
&self.previous_focus_handle,
cx,
window,
)),
),
)
@@ -491,17 +514,18 @@ mod tests {
async fn test_command_palette(cx: &mut TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_text("abc", cx);
let editor = cx.new_window_model(|window, cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_text("abc", window, cx);
editor
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
editor.update(cx, |editor, cx| editor.focus(cx))
workspace.update_in(cx, |workspace, window, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
editor.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)))
});
cx.simulate_keystrokes("cmd-shift-p");
@@ -536,7 +560,7 @@ mod tests {
});
// Add namespace filter, and redeploy the palette
cx.update(|cx| {
cx.update(|_window, cx| {
CommandPaletteFilter::update_global(cx, |filter, _| {
filter.hide_namespace("editor");
});
@@ -561,17 +585,18 @@ mod tests {
async fn test_normalized_matches(cx: &mut TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_text("abc", cx);
let editor = cx.new_window_model(|window, cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_text("abc", window, cx);
editor
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
editor.update(cx, |editor, cx| editor.focus(cx))
workspace.update_in(cx, |workspace, window, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
editor.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)))
});
// Test normalize (trimming whitespace and double colons)
@@ -596,14 +621,17 @@ mod tests {
async fn test_go_to_line(cx: &mut TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
cx.simulate_keystrokes("cmd-n");
let editor = workspace.update(cx, |workspace, cx| {
workspace.active_item_as::<Editor>(cx).unwrap()
});
editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
editor.update_in(cx, |editor, window, cx| {
editor.set_text("1\n2\n3\n4\n5\n6\n", window, cx)
});
cx.simulate_keystrokes("cmd-shift-p");
cx.simulate_input("go to line: Toggle");
@@ -615,8 +643,8 @@ mod tests {
cx.simulate_keystrokes("3 enter");
editor.update(cx, |editor, cx| {
assert!(editor.focus_handle(cx).is_focused(cx));
editor.update_in(cx, |editor, window, cx| {
assert!(editor.focus_handle(cx).is_focused(window));
assert_eq!(
editor.selections.last::<Point>(cx).range().start,
Point::new(2, 0)

View File

@@ -93,6 +93,7 @@ impl CommandPaletteFilter {
}
/// The result of intercepting a command palette command.
#[derive(Debug)]
pub struct CommandInterceptResult {
/// The action produced as a result of the interception.
pub action: Box<dyn Action>,

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use gpui::{Model, Task, WindowContext};
use gpui::{AppContext, Model, Task, Window};
use crate::manager::ContextServerManager;
use crate::types;
@@ -51,8 +51,9 @@ impl Tool for ContextServerTool {
fn run(
self: std::sync::Arc<Self>,
input: serde_json::Value,
_workspace: gpui::WeakView<workspace::Workspace>,
cx: &mut WindowContext,
_workspace: gpui::WeakModel<workspace::Workspace>,
_: &mut Window,
cx: &mut AppContext,
) -> gpui::Task<gpui::Result<String>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
cx.foreground_executor().spawn({

View File

@@ -300,8 +300,8 @@ mod tests {
)
.await;
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -329,7 +329,7 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.context_menu_visible());
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion());
@@ -340,7 +340,7 @@ mod tests {
// Confirming a non-copilot completion inserts it and hides the context menu, without showing
// the copilot suggestion afterwards.
editor
.confirm_completion(&Default::default(), cx)
.confirm_completion(&Default::default(), window, cx)
.unwrap()
.detach();
assert!(!editor.context_menu_visible());
@@ -376,7 +376,7 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
// Since only the copilot is available, it's shown inline
@@ -387,7 +387,7 @@ mod tests {
// Ensure existing inline completion is interpolated when inserting again.
cx.simulate_keystroke("c");
executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(!editor.context_menu_visible());
assert!(!editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
@@ -406,7 +406,7 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert!(!editor.context_menu_contains_inline_completion());
@@ -414,19 +414,19 @@ mod tests {
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
// Canceling should remove the active Copilot suggestion.
editor.cancel(&Default::default(), cx);
editor.cancel(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
// After canceling, tabbing shouldn't insert the previously shown suggestion.
editor.tab(&Default::default(), cx);
editor.tab(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
// When undoing the previously active suggestion is shown again.
editor.undo(&Default::default(), cx);
editor.undo(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
@@ -434,25 +434,25 @@ mod tests {
// If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
// AcceptInlineCompletion when there is an active suggestion inserts it.
editor.accept_inline_completion(&Default::default(), cx);
editor.accept_inline_completion(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
// When undoing the previously active suggestion is shown again.
editor.undo(&Default::default(), cx);
editor.undo(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
// Hide suggestion.
editor.cancel(&Default::default(), cx);
editor.cancel(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
@@ -461,16 +461,16 @@ mod tests {
// If an edit occurs outside of this editor but no suggestion is being shown,
// we won't make it visible.
cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
});
// Reset the editor to verify how suggestions behave when tabbing on leading indentation.
cx.update_editor(|editor, cx| {
editor.set_text("fn foo() {\n \n}", cx);
editor.change_selections(None, cx, |s| {
cx.update_editor(|editor, window, cx| {
editor.set_text("fn foo() {\n \n}", window, cx);
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
});
});
@@ -484,21 +484,23 @@ mod tests {
vec![],
);
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
// Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
editor.tab(&Default::default(), cx);
editor.tab(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
// Using AcceptInlineCompletion again accepts the suggestion.
editor.accept_inline_completion(&Default::default(), cx);
editor.accept_inline_completion(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
@@ -526,8 +528,8 @@ mod tests {
)
.await;
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
});
// Setup the editor with a completion request.
@@ -556,17 +558,17 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_inline_completion());
// Accepting the first word of the suggestion should only accept the first word and still show the rest.
editor.accept_partial_inline_completion(&Default::default(), cx);
editor.accept_partial_inline_completion(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
// Accepting next word should accept the non-word and copilot suggestion should be gone
editor.accept_partial_inline_completion(&Default::default(), cx);
editor.accept_partial_inline_completion(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
@@ -598,11 +600,11 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_inline_completion());
// Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
editor.accept_partial_inline_completion(&Default::default(), cx);
editor.accept_partial_inline_completion(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
assert_eq!(
@@ -611,7 +613,7 @@ mod tests {
);
// Accepting next word should accept the next word and copilot suggestion should still exist
editor.accept_partial_inline_completion(&Default::default(), cx);
editor.accept_partial_inline_completion(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
assert_eq!(
@@ -620,7 +622,7 @@ mod tests {
);
// Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
editor.accept_partial_inline_completion(&Default::default(), cx);
editor.accept_partial_inline_completion(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
assert_eq!(
@@ -650,8 +652,8 @@ mod tests {
)
.await;
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -669,31 +671,33 @@ mod tests {
}],
vec![],
);
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
editor.backspace(&Default::default(), cx);
editor.backspace(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\nt\nthree\n");
editor.backspace(&Default::default(), cx);
editor.backspace(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\n\nthree\n");
// Deleting across the original suggestion range invalidates it.
editor.backspace(&Default::default(), cx);
editor.backspace(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\nthree\n");
assert_eq!(editor.text(cx), "one\nthree\n");
// Undoing the deletion restores the suggestion.
editor.undo(&Default::default(), cx);
editor.undo(&Default::default(), window, cx);
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\n\nthree\n");
@@ -728,12 +732,18 @@ mod tests {
);
multibuffer
});
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
editor.update(cx, |editor, cx| editor.focus(cx)).unwrap();
let editor = cx
.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, true, window, cx));
editor
.update(cx, |editor, window, cx| {
use gpui::Focusable;
window.focus(&editor.focus_handle(cx));
})
.unwrap();
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
})
.unwrap();
@@ -746,15 +756,15 @@ mod tests {
}],
vec![],
);
_ = editor.update(cx, |editor, cx| {
_ = editor.update(cx, |editor, window, cx| {
// Ensure copilot suggestions are shown for the first excerpt.
editor.change_selections(None, cx, |s| {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
});
editor.next_inline_completion(&Default::default(), cx);
editor.next_inline_completion(&Default::default(), window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
_ = editor.update(cx, |editor, cx| {
_ = editor.update(cx, |editor, _, cx| {
assert!(editor.has_active_inline_completion());
assert_eq!(
editor.display_text(cx),
@@ -772,9 +782,9 @@ mod tests {
}],
vec![],
);
_ = editor.update(cx, |editor, cx| {
_ = editor.update(cx, |editor, window, cx| {
// Move to another excerpt, ensuring the suggestion gets cleared.
editor.change_selections(None, cx, |s| {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
});
assert!(!editor.has_active_inline_completion());
@@ -785,7 +795,7 @@ mod tests {
assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
// Type a character, ensuring we don't even try to interpolate the previous suggestion.
editor.handle_input(" ", cx);
editor.handle_input(" ", window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(
editor.display_text(cx),
@@ -796,7 +806,7 @@ mod tests {
// Ensure the new suggestion is displayed when the debounce timeout expires.
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
_ = editor.update(cx, |editor, cx| {
_ = editor.update(cx, |editor, _, cx| {
assert!(editor.has_active_inline_completion());
assert_eq!(
editor.display_text(cx),
@@ -826,8 +836,8 @@ mod tests {
)
.await;
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -854,9 +864,11 @@ mod tests {
}],
vec![],
);
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
@@ -883,7 +895,7 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
@@ -910,7 +922,7 @@ mod tests {
vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
assert!(editor.context_menu_visible());
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion(),);
@@ -973,12 +985,18 @@ mod tests {
);
multibuffer
});
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
editor.update(cx, |editor, cx| editor.focus(cx)).unwrap();
let editor = cx
.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, true, window, cx));
editor
.update(cx, |editor, window, cx| {
use gpui::Focusable;
window.focus(&editor.focus_handle(cx))
})
.unwrap();
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), cx)
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
})
.unwrap();
@@ -998,21 +1016,21 @@ mod tests {
},
);
_ = editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |selections| {
_ = editor.update(cx, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
});
editor.refresh_inline_completion(true, false, cx);
editor.refresh_inline_completion(true, false, window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
assert!(copilot_requests.try_next().is_err());
_ = editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| {
_ = editor.update(cx, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(5, 0)..Point::new(5, 0)])
});
editor.refresh_inline_completion(true, false, cx);
editor.refresh_inline_completion(true, false, window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);

View File

@@ -1,8 +1,8 @@
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
div, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Render,
Styled, Subscription, ViewContext,
div, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, Model, ModelContext, MouseDownEvent, ParentElement, Render,
Styled, Subscription, Window,
};
use ui::{prelude::*, Button, Label, Vector, VectorName};
use util::ResultExt as _;
@@ -13,21 +13,21 @@ const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
struct CopilotStartingToast;
pub fn initiate_sign_in(cx: &mut WindowContext) {
pub fn initiate_sign_in(window: &mut Window, cx: &mut AppContext) {
let Some(copilot) = Copilot::global(cx) else {
return;
};
let status = copilot.read(cx).status();
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
return;
};
match status {
Status::Starting { task } => {
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
return;
};
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
let Ok(workspace) = workspace.update(cx, |workspace, _window, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
@@ -70,8 +70,10 @@ pub fn initiate_sign_in(cx: &mut WindowContext) {
_ => {
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
workspace
.update(cx, |this, cx| {
this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx));
.update(cx, |this, window, cx| {
this.toggle_modal(window, cx, |_, cx| {
CopilotCodeVerification::new(&copilot, cx)
});
})
.ok();
}
@@ -85,7 +87,7 @@ pub struct CopilotCodeVerification {
_subscription: Subscription,
}
impl FocusableView for CopilotCodeVerification {
impl Focusable for CopilotCodeVerification {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
@@ -95,7 +97,7 @@ impl EventEmitter<DismissEvent> for CopilotCodeVerification {}
impl ModalView for CopilotCodeVerification {}
impl CopilotCodeVerification {
pub fn new(copilot: &Model<Copilot>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(copilot: &Model<Copilot>, cx: &mut ModelContext<Self>) -> Self {
let status = copilot.read(cx).status();
Self {
status,
@@ -113,14 +115,14 @@ impl CopilotCodeVerification {
}
}
pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
pub fn set_status(&mut self, status: Status, cx: &mut ModelContext<Self>) {
self.status = status;
cx.notify();
}
fn render_device_code(
data: &PromptUserDeviceFlow,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
let copied = cx
.read_from_clipboard()
@@ -136,9 +138,9 @@ impl CopilotCodeVerification {
.justify_between()
.on_mouse_down(gpui::MouseButton::Left, {
let user_code = data.user_code.clone();
move |_, cx| {
move |_, window, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(user_code.clone()));
cx.refresh();
window.refresh();
}
})
.child(div().flex_1().child(Label::new(data.user_code.clone())))
@@ -152,7 +154,8 @@ impl CopilotCodeVerification {
fn render_prompting_modal(
connect_clicked: bool,
data: &PromptUserDeviceFlow,
cx: &mut ViewContext<Self>,
cx: &mut ModelContext<Self>,
) -> impl Element {
let connect_button_label = if connect_clicked {
"Waiting for connection..."
@@ -177,7 +180,7 @@ impl CopilotCodeVerification {
Button::new("connect-button", connect_button_label)
.on_click({
let verification_uri = data.verification_uri.clone();
cx.listener(move |this, _, cx| {
cx.listener(move |this, _, _window, cx| {
cx.open_url(&verification_uri);
this.connect_clicked = true;
})
@@ -188,10 +191,10 @@ impl CopilotCodeVerification {
.child(
Button::new("copilot-enable-cancel-button", "Cancel")
.full_width()
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
}
fn render_enabled_modal(cx: &mut ViewContext<Self>) -> impl Element {
fn render_enabled_modal(cx: &mut ModelContext<Self>) -> impl Element {
v_flex()
.gap_2()
.child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large))
@@ -201,11 +204,11 @@ impl CopilotCodeVerification {
.child(
Button::new("copilot-enabled-done-button", "Done")
.full_width()
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
}
fn render_unauthorized_modal(cx: &mut ViewContext<Self>) -> impl Element {
fn render_unauthorized_modal(cx: &mut ModelContext<Self>) -> impl Element {
v_flex()
.child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large))
@@ -215,12 +218,12 @@ impl CopilotCodeVerification {
.child(
Button::new("copilot-subscribe-button", "Subscribe on GitHub")
.full_width()
.on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
.on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
)
.child(
Button::new("copilot-subscribe-cancel-button", "Cancel")
.full_width()
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
}
@@ -232,7 +235,7 @@ impl CopilotCodeVerification {
}
impl Render for CopilotCodeVerification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let prompt = match &self.status {
Status::SigningIn {
prompt: Some(prompt),
@@ -260,11 +263,11 @@ impl Render for CopilotCodeVerification {
.items_center()
.p_4()
.gap_2()
.on_action(cx.listener(|_, _: &menu::Cancel, cx| {
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
cx.emit(DismissEvent);
}))
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, cx| {
cx.focus(&this.focus_handle);
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _| {
window.focus(&this.focus_handle);
}))
.child(
Vector::new(VectorName::ZedXCopilot, rems(8.), rems(4.))

View File

@@ -17,9 +17,8 @@ use editor::{
use feature_flags::FeatureFlagAppExt;
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
FocusableView, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement,
Render, SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
WeakView, WindowContext,
Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext,
ParentElement, Render, SharedString, Styled, StyledText, Subscription, Task, WeakModel, Window,
};
use language::{
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
@@ -55,15 +54,15 @@ impl Global for IncludeWarnings {}
pub fn init(cx: &mut AppContext) {
ProjectDiagnosticsSettings::register(cx);
cx.observe_new_views(ProjectDiagnosticsEditor::register)
cx.observe_new_models(ProjectDiagnosticsEditor::register)
.detach();
}
struct ProjectDiagnosticsEditor {
project: Model<Project>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
focus_handle: FocusHandle,
editor: View<Editor>,
editor: Model<Editor>,
summary: DiagnosticSummary,
excerpts: Model<MultiBuffer>,
path_states: Vec<PathState>,
@@ -93,7 +92,7 @@ impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
impl Render for ProjectDiagnosticsEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let child = if self.path_states.is_empty() {
div()
.bg(cx.theme().colors().editor_background)
@@ -118,7 +117,11 @@ impl Render for ProjectDiagnosticsEditor {
}
impl ProjectDiagnosticsEditor {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut ModelContext<Workspace>,
) {
workspace.register_action(Self::deploy);
}
@@ -126,17 +129,18 @@ impl ProjectDiagnosticsEditor {
context: u32,
include_warnings: bool,
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
workspace: WeakModel<Workspace>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
let project_event_subscription =
cx.subscribe(&project_handle, |this, project, event, cx| match event {
cx.subscribe_in(&project_handle, window, |this, project, event, window, cx| match event {
project::Event::DiskBasedDiagnosticsStarted { .. } => {
cx.notify();
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
log::debug!("disk based diagnostics finished for server {language_server_id}");
this.update_stale_excerpts(cx);
this.update_stale_excerpts(window, cx);
}
project::Event::DiagnosticsUpdated {
language_server_id,
@@ -147,45 +151,58 @@ impl ProjectDiagnosticsEditor {
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
if this.editor.focus_handle(cx).contains_focused(window, cx) || this.focus_handle.contains_focused(window, cx) {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
} else {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
this.update_stale_excerpts(cx);
this.update_stale_excerpts(window, cx);
}
}
_ => {}
});
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, _event, cx| this.focus_out(cx))
.detach();
cx.on_focus_in(&focus_handle, window, |this, window, cx| {
this.focus_in(window, cx)
})
.detach();
cx.on_focus_out(&focus_handle, window, |this, _event, window, cx| {
this.focus_out(window, cx)
})
.detach();
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
let editor = cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
let editor = cx.new_model(|cx| {
let mut editor = Editor::for_multibuffer(
excerpts.clone(),
Some(project_handle.clone()),
true,
window,
cx,
);
editor.set_vertical_scroll_margin(5, cx);
editor
});
cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
cx.emit(event.clone());
match event {
EditorEvent::Focused => {
if this.path_states.is_empty() {
cx.focus(&this.focus_handle);
cx.subscribe_in(
&editor,
window,
|this, _editor, event: &EditorEvent, window, cx| {
cx.emit(event.clone());
match event {
EditorEvent::Focused => {
if this.path_states.is_empty() {
window.focus(&this.focus_handle);
}
}
EditorEvent::Blurred => this.update_stale_excerpts(window, cx),
_ => {}
}
EditorEvent::Blurred => this.update_stale_excerpts(cx),
_ => {}
}
})
},
)
.detach();
cx.observe_global::<IncludeWarnings>(|this, cx| {
cx.observe_global_in::<IncludeWarnings>(window, |this, window, cx| {
this.include_warnings = cx.global::<IncludeWarnings>().0;
this.update_all_excerpts(cx);
this.update_all_excerpts(window, cx);
})
.detach();
@@ -204,16 +221,16 @@ impl ProjectDiagnosticsEditor {
update_excerpts_task: None,
_subscription: project_event_subscription,
};
this.update_all_excerpts(cx);
this.update_all_excerpts(window, cx);
this
}
fn update_stale_excerpts(&mut self, cx: &mut ViewContext<Self>) {
fn update_stale_excerpts(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if self.update_excerpts_task.is_some() {
return;
}
let project_handle = self.project.clone();
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
self.update_excerpts_task = Some(cx.spawn_in(window, |this, mut cx| async move {
cx.background_executor()
.timer(DIAGNOSTICS_UPDATE_DEBOUNCE)
.await;
@@ -234,8 +251,8 @@ impl ProjectDiagnosticsEditor {
.await
.log_err()
{
this.update(&mut cx, |this, cx| {
this.update_excerpts(path, language_server_id, buffer, cx);
this.update_in(&mut cx, |this, window, cx| {
this.update_excerpts(path, language_server_id, buffer, window, cx);
})?;
}
}
@@ -246,63 +263,77 @@ impl ProjectDiagnosticsEditor {
fn new(
project_handle: Model<Project>,
include_warnings: bool,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
workspace: WeakModel<Workspace>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
Self::new_with_context(
editor::DEFAULT_MULTIBUFFER_CONTEXT,
include_warnings,
project_handle,
workspace,
window,
cx,
)
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
fn deploy(
workspace: &mut Workspace,
_: &Deploy,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, true, true, cx);
workspace.activate_item(&existing, true, true, window, cx);
} else {
let workspace_handle = cx.view().downgrade();
let workspace_handle = cx.model().downgrade();
let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0,
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
};
let diagnostics = cx.new_view(|cx| {
let diagnostics = cx.new_model(|cx| {
ProjectDiagnosticsEditor::new(
workspace.project().clone(),
include_warnings,
workspace_handle,
window,
cx,
)
});
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx);
}
}
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
fn toggle_warnings(
&mut self,
_: &ToggleWarnings,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.include_warnings = !self.include_warnings;
cx.set_global(IncludeWarnings(self.include_warnings));
self.update_all_excerpts(cx);
self.update_all_excerpts(window, cx);
cx.notify();
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
self.editor.focus_handle(cx).focus(cx)
fn focus_in(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if self.focus_handle.is_focused(window) && !self.path_states.is_empty() {
self.editor.focus_handle(cx).focus(window)
}
}
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
self.update_stale_excerpts(cx);
fn focus_out(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if !self.focus_handle.is_focused(window) && !self.editor.focus_handle(cx).is_focused(window)
{
self.update_stale_excerpts(window, cx);
}
}
/// Enqueue an update of all excerpts. Updates all paths that either
/// currently have diagnostics or are currently present in this view.
fn update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.project.update(cx, |project, cx| {
let mut paths = project
.diagnostic_summaries(false, cx)
@@ -317,7 +348,7 @@ impl ProjectDiagnosticsEditor {
paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None)));
self.paths_to_update = paths;
});
self.update_stale_excerpts(cx);
self.update_stale_excerpts(window, cx);
}
fn update_excerpts(
@@ -325,7 +356,8 @@ impl ProjectDiagnosticsEditor {
path_to_update: ProjectPath,
server_to_update: Option<LanguageServerId>,
buffer: Model<Buffer>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let was_empty = self.path_states.is_empty();
let snapshot = buffer.read(cx).snapshot();
@@ -581,12 +613,12 @@ impl ProjectDiagnosticsEditor {
} else {
groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id =
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.refresh());
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.refresh());
selections = editor.selections.all::<usize>(cx);
}
// If any selection has lost its position, move it to start of the next primary diagnostic.
let snapshot = editor.snapshot(cx);
let snapshot = editor.snapshot(window, cx);
for selection in &mut selections {
if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
let group_ix = match groups.binary_search_by(|probe| {
@@ -612,19 +644,19 @@ impl ProjectDiagnosticsEditor {
}
}
}
editor.change_selections(None, cx, |s| {
editor.change_selections(None, window, cx, |s| {
s.select(selections);
});
Some(())
});
if self.path_states.is_empty() {
if self.editor.focus_handle(cx).is_focused(cx) {
cx.focus(&self.focus_handle);
if self.editor.focus_handle(cx).is_focused(window) {
window.focus(&self.focus_handle);
}
} else if self.focus_handle.is_focused(cx) {
} else if self.focus_handle.is_focused(window) {
let focus_handle = self.editor.focus_handle(cx);
cx.focus(&focus_handle);
window.focus(&focus_handle);
}
#[cfg(test)]
@@ -634,7 +666,7 @@ impl ProjectDiagnosticsEditor {
}
#[cfg(test)]
fn check_invariants(&self, cx: &mut ViewContext<Self>) {
fn check_invariants(&self, cx: &mut ModelContext<Self>) {
let mut excerpts = Vec::new();
for (id, buffer, _) in self.excerpts.read(cx).snapshot(cx).excerpts() {
if let Some(file) = buffer.file() {
@@ -654,7 +686,7 @@ impl ProjectDiagnosticsEditor {
}
}
impl FocusableView for ProjectDiagnosticsEditor {
impl Focusable for ProjectDiagnosticsEditor {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
@@ -667,20 +699,31 @@ impl Item for ProjectDiagnosticsEditor {
Editor::to_item_events(event, f)
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| editor.deactivated(cx));
fn deactivated(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.editor
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx))
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
Some("Project Diagnostics".into())
}
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
fn tab_content(
&self,
params: TabContentParams,
_window: &Window,
_: &AppContext,
) -> AnyElement {
h_flex()
.gap_1()
.when(
@@ -735,7 +778,12 @@ impl Item for ProjectDiagnosticsEditor {
false
}
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, _| {
editor.set_nav_history(Some(nav_history));
});
@@ -744,16 +792,18 @@ impl Item for ProjectDiagnosticsEditor {
fn clone_on_split(
&self,
_workspace_id: Option<workspace::WorkspaceId>,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>>
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<Model<Self>>
where
Self: Sized,
{
Some(cx.new_view(|cx| {
Some(cx.new_model(|cx| {
ProjectDiagnosticsEditor::new(
self.project.clone(),
self.include_warnings,
self.workspace.clone(),
window,
cx,
)
}))
@@ -779,28 +829,35 @@ impl Item for ProjectDiagnosticsEditor {
&mut self,
format: bool,
project: Model<Project>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
self.editor.save(format, project, cx)
self.editor.save(format, project, window, cx)
}
fn save_as(
&mut self,
_: Model<Project>,
_: ProjectPath,
_: &mut ViewContext<Self>,
_window: &mut Window,
_: &mut ModelContext<Self>,
) -> Task<Result<()>> {
unreachable!()
}
fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
self.editor.reload(project, cx)
fn reload(
&mut self,
project: Model<Project>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
self.editor.reload(project, window, cx)
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
self_handle: &'a Model<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
@@ -812,7 +869,7 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Model<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
@@ -824,9 +881,15 @@ impl Item for ProjectDiagnosticsEditor {
self.editor.breadcrumbs(theme, cx)
}
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
fn added_to_workspace(
&mut self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.added_to_workspace(workspace, window, cx)
});
}
}
@@ -854,7 +917,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.child(
h_flex()
.block_mouse_down()
.h(2. * cx.line_height())
.h(2. * cx.window.line_height())
.pl_10()
.pr_5()
.w_full()
@@ -864,24 +927,27 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
h_flex()
.gap_3()
.map(|stack| {
stack.child(svg().size(cx.text_style().font_size).flex_none().map(
|icon| {
if diagnostic.severity == DiagnosticSeverity::ERROR {
icon.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::Warning.path())
.text_color(Color::Warning.color(cx))
}
},
))
stack.child(
svg()
.size(cx.window.text_style().font_size)
.flex_none()
.map(|icon| {
if diagnostic.severity == DiagnosticSeverity::ERROR {
icon.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::Warning.path())
.text_color(Color::Warning.color(cx))
}
}),
)
})
.child(
h_flex()
.gap_1()
.child(
StyledText::new(message.clone()).with_highlights(
&cx.text_style(),
&cx.window.text_style(),
code_ranges
.iter()
.map(|range| (range.clone(), highlight_style)),

View File

@@ -61,7 +61,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
let workspace = window.root(cx).unwrap();
@@ -150,12 +150,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
});
// Open the project diagnostics view while there are already diagnostics.
let view = window.build_view(cx, |cx| {
let view = window.build_model(cx, |window, cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
true,
project.clone(),
workspace.downgrade(),
window,
cx,
)
});
@@ -477,16 +478,17 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
let workspace = window.root(cx).unwrap();
let view = window.build_view(cx, |cx| {
let view = window.build_model(cx, |window, cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
true,
project.clone(),
workspace.downgrade(),
window,
cx,
)
});
@@ -754,25 +756,26 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
let workspace = window.root(cx).unwrap();
let mutated_view = window.build_view(cx, |cx| {
let mutated_view = window.build_model(cx, |window, cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
true,
project.clone(),
workspace.downgrade(),
window,
cx,
)
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_center(Box::new(mutated_view.clone()), cx);
workspace.update_in(cx, |workspace, window, cx| {
workspace.add_item_to_center(Box::new(mutated_view.clone()), window, cx);
});
mutated_view.update(cx, |view, cx| {
assert!(view.focus_handle.is_focused(cx));
mutated_view.update_in(cx, |view, window, _cx| {
assert!(view.focus_handle.is_focused(window));
});
let mut next_group_id = 0;
@@ -858,16 +861,19 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
}
log::info!("updating mutated diagnostics view");
mutated_view.update(cx, |view, cx| view.update_stale_excerpts(cx));
mutated_view.update_in(cx, |view, window, cx| {
view.update_stale_excerpts(window, cx)
});
cx.run_until_parked();
log::info!("constructing reference diagnostics view");
let reference_view = window.build_view(cx, |cx| {
let reference_view = window.build_model(cx, |window, cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
true,
project.clone(),
workspace.downgrade(),
window,
cx,
)
});
@@ -917,7 +923,7 @@ struct ExcerptInfo {
}
fn get_diagnostics_excerpts(
view: &View<ProjectDiagnosticsEditor>,
view: &Model<ProjectDiagnosticsEditor>,
cx: &mut VisualTestContext,
) -> Vec<ExcerptInfo> {
view.update(cx, |view, cx| {
@@ -1043,58 +1049,63 @@ const FILE_HEADER: &str = "file header";
const EXCERPT_HEADER: &str = "excerpt header";
fn editor_blocks(
editor: &View<Editor>,
editor: &Model<Editor>,
cx: &mut VisualTestContext,
) -> Vec<(DisplayRow, SharedString)> {
let mut blocks = Vec::new();
cx.draw(gpui::Point::default(), AvailableSpace::min_size(), |cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
blocks.extend(
snapshot
.blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
.filter_map(|(row, block)| {
let block_id = block.id();
let name: SharedString = match block {
Block::Custom(block) => {
let mut element = block.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id,
selected: false,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
element
.interactivity()
.element_id
.clone()?
.try_into()
.ok()?
}
Block::FoldedBuffer { .. } => FILE_HEADER.into(),
Block::ExcerptBoundary {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
FILE_HEADER.into()
} else {
EXCERPT_HEADER.into()
cx.draw(
gpui::Point::default(),
AvailableSpace::min_size(),
|window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
blocks.extend(
snapshot
.blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
.filter_map(|(row, block)| {
let block_id = block.id();
let name: SharedString = match block {
Block::Custom(block) => {
let mut element = block.render(&mut BlockContext {
app: cx,
window,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id,
selected: false,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
element
.interactivity()
.element_id
.clone()?
.try_into()
.ok()?
}
}
};
Some((row, name))
}),
)
});
Block::FoldedBuffer { .. } => FILE_HEADER.into(),
Block::ExcerptBoundary {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
FILE_HEADER.into()
} else {
EXCERPT_HEADER.into()
}
}
};
div().into_any()
});
Some((row, name))
}),
)
});
div().into_any()
},
);
blocks
}

View File

@@ -2,8 +2,8 @@ use std::time::Duration;
use editor::Editor;
use gpui::{
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView,
EventEmitter, IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription,
Task, WeakModel, Window,
};
use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
@@ -13,15 +13,15 @@ use crate::{Deploy, ProjectDiagnosticsEditor};
pub struct DiagnosticIndicator {
summary: project::DiagnosticSummary,
active_editor: Option<WeakView<Editor>>,
workspace: WeakView<Workspace>,
active_editor: Option<WeakModel<Editor>>,
workspace: WeakModel<Workspace>,
current_diagnostic: Option<Diagnostic>,
_observe_active_editor: Option<Subscription>,
diagnostics_update: Task<()>,
}
impl Render for DiagnosticIndicator {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
(0, 0) => h_flex().map(|this| {
this.child(
@@ -67,11 +67,16 @@ impl Render for DiagnosticIndicator {
Some(
Button::new("diagnostic_message", message)
.label_size(LabelSize::Small)
.tooltip(|cx| {
Tooltip::for_action("Next Diagnostic", &editor::actions::GoToDiagnostic, cx)
.tooltip(|window, cx| {
Tooltip::for_action(
"Next Diagnostic",
&editor::actions::GoToDiagnostic,
window,
cx,
)
})
.on_click(cx.listener(|this, _, cx| {
this.go_to_next_diagnostic(cx);
.on_click(cx.listener(|this, _, window, cx| {
this.go_to_next_diagnostic(window, cx);
}))
.into_any_element(),
)
@@ -87,11 +92,18 @@ impl Render for DiagnosticIndicator {
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)
.tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
.on_click(cx.listener(|this, _, cx| {
.tooltip(|window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, window, cx)
})
.on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
ProjectDiagnosticsEditor::deploy(
workspace,
&Default::default(),
window,
cx,
)
})
}
})),
@@ -101,7 +113,7 @@ impl Render for DiagnosticIndicator {
}
impl DiagnosticIndicator {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
pub fn new(workspace: &Workspace, cx: &mut ModelContext<Self>) -> Self {
let project = workspace.project();
cx.subscribe(project, |this, project, event, cx| match event {
project::Event::DiskBasedDiagnosticsStarted { .. } => {
@@ -133,15 +145,15 @@ impl DiagnosticIndicator {
}
}
fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
fn go_to_next_diagnostic(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
editor.update(cx, |editor, cx| {
editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
editor.go_to_diagnostic_impl(editor::Direction::Next, window, cx);
})
}
}
fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
fn update(&mut self, editor: Model<Editor>, window: &mut Window, cx: &mut ModelContext<Self>) {
let (buffer, cursor_position) = editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let cursor_position = editor.selections.newest::<usize>(cx).head();
@@ -153,17 +165,18 @@ impl DiagnosticIndicator {
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic);
if new_diagnostic != self.current_diagnostic {
self.diagnostics_update = cx.spawn(|diagnostics_indicator, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(50))
.await;
diagnostics_indicator
.update(&mut cx, |diagnostics_indicator, cx| {
diagnostics_indicator.current_diagnostic = new_diagnostic;
cx.notify();
})
.ok();
});
self.diagnostics_update =
cx.spawn_in(window, |diagnostics_indicator, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(50))
.await;
diagnostics_indicator
.update(&mut cx, |diagnostics_indicator, cx| {
diagnostics_indicator.current_diagnostic = new_diagnostic;
cx.notify();
})
.ok();
});
}
}
}
@@ -174,12 +187,13 @@ impl StatusItemView for DiagnosticIndicator {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
self.active_editor = Some(editor.downgrade());
self._observe_active_editor = Some(cx.observe(&editor, Self::update));
self.update(editor, cx);
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
self.update(editor, window, cx);
} else {
self.active_editor = None;
self.current_diagnostic = None;

View File

@@ -1,15 +1,15 @@
use crate::ProjectDiagnosticsEditor;
use gpui::{EventEmitter, ParentElement, Render, View, ViewContext, WeakView};
use gpui::{EventEmitter, Model, ModelContext, ParentElement, Render, WeakModel, Window};
use ui::prelude::*;
use ui::{IconButton, IconButtonShape, IconName, Tooltip};
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub struct ToolbarControls {
editor: Option<WeakView<ProjectDiagnosticsEditor>>,
editor: Option<WeakModel<ProjectDiagnosticsEditor>>,
}
impl Render for ToolbarControls {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let mut include_warnings = false;
let mut has_stale_excerpts = false;
let mut is_updating = false;
@@ -47,11 +47,11 @@ impl Render for ToolbarControls {
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.disabled(is_updating)
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
.on_click(cx.listener(|this, _, cx| {
.tooltip(Tooltip::text("Update excerpts"))
.on_click(cx.listener(|this, _, window, cx| {
if let Some(diagnostics) = this.diagnostics() {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.update_all_excerpts(cx);
diagnostics.update_all_excerpts(window, cx);
});
}
})),
@@ -61,11 +61,11 @@ impl Render for ToolbarControls {
IconButton::new("toggle-warnings", IconName::Warning)
.icon_color(warning_color)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text(tooltip, cx))
.on_click(cx.listener(|this, _, cx| {
.tooltip(Tooltip::text(tooltip))
.on_click(cx.listener(|this, _, window, cx| {
if let Some(editor) = this.diagnostics() {
editor.update(cx, |editor, cx| {
editor.toggle_warnings(&Default::default(), cx);
editor.toggle_warnings(&Default::default(), window, cx);
});
}
})),
@@ -79,7 +79,8 @@ impl ToolbarItemView for ToolbarControls {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut ViewContext<Self>,
_window: &mut Window,
_: &mut ModelContext<Self>,
) -> ToolbarItemLocation {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
@@ -105,7 +106,7 @@ impl ToolbarControls {
ToolbarControls { editor: None }
}
fn diagnostics(&self) -> Option<View<ProjectDiagnosticsEditor>> {
fn diagnostics(&self) -> Option<Model<ProjectDiagnosticsEditor>> {
self.editor.as_ref()?.upgrade()
}
}

View File

@@ -3,7 +3,7 @@ use git::blame::BlameEntry;
use git::Oid;
use gpui::{
AppContext, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
StatefulInteractiveElement, WeakView,
StatefulInteractiveElement, WeakModel,
};
use settings::Settings;
use std::hash::Hash;
@@ -27,7 +27,11 @@ impl<'a> CommitAvatar<'a> {
}
impl<'a> CommitAvatar<'a> {
fn render(&'a self, cx: &mut ViewContext<BlameEntryTooltip>) -> Option<impl IntoElement> {
fn render(
&'a self,
window: &mut Window,
cx: &mut ModelContext<BlameEntryTooltip>,
) -> Option<impl IntoElement> {
let remote = self
.details
.and_then(|details| details.remote.as_ref())
@@ -35,7 +39,7 @@ impl<'a> CommitAvatar<'a> {
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
let element = match cx.use_asset::<CommitAvatarAsset>(&avatar_url) {
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
// Loading or no avatar found
None | Some(None) => Icon::new(IconName::Person)
.color(Color::Muted)
@@ -91,7 +95,7 @@ pub(crate) struct BlameEntryTooltip {
blame_entry: BlameEntry,
details: Option<CommitDetails>,
editor_style: EditorStyle,
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
scroll_handle: ScrollHandle,
}
@@ -100,7 +104,7 @@ impl BlameEntryTooltip {
blame_entry: BlameEntry,
details: Option<CommitDetails>,
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakModel<Workspace>>,
) -> Self {
Self {
editor_style: style.clone(),
@@ -113,8 +117,9 @@ impl BlameEntryTooltip {
}
impl Render for BlameEntryTooltip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let avatar = CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(cx);
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let avatar =
CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(window, cx);
let author = self
.blame_entry
@@ -149,11 +154,11 @@ impl Render for BlameEntryTooltip {
.and_then(|details| details.pull_request.clone());
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let message_max_height = cx.line_height() * 12 + (ui_font_size / 0.4);
let message_max_height = window.line_height() * 12 + (ui_font_size / 0.4);
tooltip_container(cx, move |this, cx| {
tooltip_container(window, cx, move |this, _, cx| {
this.occlude()
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.child(
v_flex()
.w(gpui::rems(30.))
@@ -208,7 +213,7 @@ impl Render for BlameEntryTooltip {
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
.on_click(move |_, _, cx| {
cx.stop_propagation();
cx.open_url(pr.url.as_str())
}),
@@ -235,7 +240,7 @@ impl Render for BlameEntryTooltip {
.as_ref()
.and_then(|details| details.permalink.clone()),
|this, url| {
this.on_click(move |_, cx| {
this.on_click(move |_, _, cx| {
cx.stop_propagation();
cx.open_url(url.as_str())
})
@@ -247,7 +252,7 @@ impl Render for BlameEntryTooltip {
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.on_click(move |_, cx| {
.on_click(move |_, _, cx| {
cx.stop_propagation();
cx.write_to_clipboard(
ClipboardItem::new_string(full_sha.clone()),

View File

@@ -1,5 +1,5 @@
use anyhow::Context as _;
use gpui::{View, ViewContext, WindowContext};
use gpui::{AppContext, Model, ModelContext, Window};
use language::Language;
use url::Url;
@@ -16,7 +16,8 @@ fn is_c_language(language: &Language) -> bool {
pub fn switch_source_header(
editor: &mut Editor,
_: &SwitchSourceHeader,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let Some(project) = &editor.project else {
return;
@@ -49,7 +50,7 @@ pub fn switch_source_header(
cx,
)
});
cx.spawn(|_editor, mut cx| async move {
cx.spawn_in(window, |_editor, mut cx| async move {
let switch_source_header = switch_source_header_task
.await
.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?;
@@ -70,8 +71,8 @@ pub fn switch_source_header(
})?;
workspace
.update(&mut cx, |workspace, view_cx| {
workspace.open_abs_path(path, false, view_cx)
.update_in(&mut cx, |workspace, window, cx| {
workspace.open_abs_path(path, false, window, cx)
})
.with_context(|| {
format!(
@@ -84,11 +85,11 @@ pub fn switch_source_header(
.detach_and_log_err(cx);
}
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
pub fn apply_related_actions(editor: &Model<Editor>, window: &mut Window, cx: &mut AppContext) {
if editor.update(cx, |e, cx| {
find_specific_language_server_in_selection(e, cx, is_c_language, CLANGD_SERVER_NAME)
.is_some()
}) {
register_action(editor, cx, switch_source_header);
register_action(editor, window, switch_source_header);
}
}

View File

@@ -1,8 +1,8 @@
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
Model, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
UniformListScrollHandle, ViewContext, WeakView,
Model, ModelContext, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
UniformListScrollHandle, WeakModel, Window,
};
use language::Buffer;
use language::{CodeLabel, Documentation};
@@ -44,12 +44,13 @@ impl CodeContextMenu {
pub fn select_first(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
if self.visible() {
match self {
CodeContextMenu::Completions(menu) => menu.select_first(provider, cx),
CodeContextMenu::CodeActions(menu) => menu.select_first(cx),
CodeContextMenu::Completions(menu) => menu.select_first(provider, window, cx),
CodeContextMenu::CodeActions(menu) => menu.select_first(window, cx),
}
true
} else {
@@ -60,12 +61,13 @@ impl CodeContextMenu {
pub fn select_prev(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
if self.visible() {
match self {
CodeContextMenu::Completions(menu) => menu.select_prev(provider, cx),
CodeContextMenu::CodeActions(menu) => menu.select_prev(cx),
CodeContextMenu::Completions(menu) => menu.select_prev(provider, window, cx),
CodeContextMenu::CodeActions(menu) => menu.select_prev(window, cx),
}
true
} else {
@@ -76,11 +78,12 @@ impl CodeContextMenu {
pub fn select_next(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
if self.visible() {
match self {
CodeContextMenu::Completions(menu) => menu.select_next(provider, cx),
CodeContextMenu::Completions(menu) => menu.select_next(provider, window, cx),
CodeContextMenu::CodeActions(menu) => menu.select_next(cx),
}
true
@@ -92,11 +95,12 @@ impl CodeContextMenu {
pub fn select_last(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
if self.visible() {
match self {
CodeContextMenu::Completions(menu) => menu.select_last(provider, cx),
CodeContextMenu::Completions(menu) => menu.select_last(provider, window, cx),
CodeContextMenu::CodeActions(menu) => menu.select_last(cx),
}
true
@@ -123,11 +127,16 @@ impl CodeContextMenu {
&self,
style: &EditorStyle,
max_height_in_lines: u32,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> AnyElement {
match self {
CodeContextMenu::Completions(menu) => menu.render(style, max_height_in_lines, cx),
CodeContextMenu::CodeActions(menu) => menu.render(style, max_height_in_lines, cx),
CodeContextMenu::Completions(menu) => {
menu.render(style, max_height_in_lines, window, cx)
}
CodeContextMenu::CodeActions(menu) => {
menu.render(style, max_height_in_lines, window, cx)
}
}
}
@@ -135,8 +144,8 @@ impl CodeContextMenu {
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
workspace: Option<WeakModel<Workspace>>,
cx: &mut ModelContext<Editor>,
) -> Option<AnyElement> {
match self {
CodeContextMenu::Completions(menu) => menu.render_aside(style, max_size, workspace, cx),
@@ -264,46 +273,51 @@ impl CompletionsMenu {
fn select_first(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
self.update_selection_index(0, provider, cx);
self.update_selection_index(0, provider, window, cx);
}
fn select_prev(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
self.update_selection_index(self.prev_match_index(), provider, cx);
self.update_selection_index(self.prev_match_index(), provider, window, cx);
}
fn select_next(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
self.update_selection_index(self.next_match_index(), provider, cx);
self.update_selection_index(self.next_match_index(), provider, window, cx);
}
fn select_last(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
self.update_selection_index(self.entries.len() - 1, provider, cx);
self.update_selection_index(self.entries.len() - 1, provider, window, cx);
}
fn update_selection_index(
&mut self,
match_index: usize,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
if self.selected_item != match_index {
self.selected_item = match_index;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_visible_completions(provider, cx);
self.resolve_visible_completions(provider, window, cx);
cx.notify();
}
}
@@ -349,7 +363,8 @@ impl CompletionsMenu {
pub fn resolve_visible_completions(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
if !self.resolve_completions {
return;
@@ -413,10 +428,11 @@ impl CompletionsMenu {
self.buffer.clone(),
candidate_ids,
self.completions.clone(),
window,
cx,
);
cx.spawn(move |editor, mut cx| async move {
cx.spawn_in(window, move |editor, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
editor.update(&mut cx, |_, cx| cx.notify()).ok();
}
@@ -443,7 +459,8 @@ impl CompletionsMenu {
&self,
style: &EditorStyle,
max_height_in_lines: u32,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> AnyElement {
let completions = self.completions.borrow_mut();
let show_completion_documentation = self.show_completion_documentation;
@@ -479,10 +496,10 @@ impl CompletionsMenu {
let last_rendered_range = self.last_rendered_range.clone();
let style = style.clone();
let list = uniform_list(
cx.view().clone(),
cx.model().clone(),
"completions",
matches.len(),
move |_editor, range, cx| {
move |_editor, range, _, cx| {
last_rendered_range.borrow_mut().replace(range.clone());
let start_ix = range.start;
let completions_guard = completions.borrow_mut();
@@ -559,12 +576,13 @@ impl CompletionsMenu {
ListItem::new(mat.candidate_id)
.inset(true)
.toggle_state(item_ix == selected_item)
.on_click(cx.listener(move |editor, _event, cx| {
.on_click(cx.listener(move |editor, _event, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_completion(
&ConfirmCompletion {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
@@ -590,10 +608,11 @@ impl CompletionsMenu {
))
.with_highlights(&style.text, None),
)
.on_click(cx.listener(move |editor, _event, cx| {
.on_click(cx.listener(move |editor, _event, window, cx| {
cx.stop_propagation();
editor.accept_inline_completion(
&AcceptInlineCompletion {},
window,
cx,
);
})),
@@ -604,7 +623,7 @@ impl CompletionsMenu {
},
)
.occlude()
.max_h(max_height_in_lines as f32 * cx.line_height())
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(self.scroll_handle.clone())
.with_width_from_item(widest_completion_ix)
.with_sizing_behavior(ListSizingBehavior::Infer);
@@ -616,8 +635,8 @@ impl CompletionsMenu {
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
workspace: Option<WeakModel<Workspace>>,
cx: &mut ModelContext<Editor>,
) -> Option<AnyElement> {
if !self.show_completion_documentation {
return None;
@@ -910,14 +929,14 @@ pub struct CodeActionsMenu {
}
impl CodeActionsMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
fn select_first(&mut self, _: &mut Window, cx: &mut ModelContext<Editor>) {
self.selected_item = 0;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
cx.notify()
}
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
fn select_prev(&mut self, _: &mut Window, cx: &mut ModelContext<Editor>) {
if self.selected_item > 0 {
self.selected_item -= 1;
} else {
@@ -928,7 +947,7 @@ impl CodeActionsMenu {
cx.notify();
}
fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
fn select_next(&mut self, cx: &mut ModelContext<Editor>) {
if self.selected_item + 1 < self.actions.len() {
self.selected_item += 1;
} else {
@@ -939,7 +958,7 @@ impl CodeActionsMenu {
cx.notify();
}
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
fn select_last(&mut self, cx: &mut ModelContext<Editor>) {
self.selected_item = self.actions.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
@@ -962,15 +981,16 @@ impl CodeActionsMenu {
&self,
_style: &EditorStyle,
max_height_in_lines: u32,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> AnyElement {
let actions = self.actions.clone();
let selected_item = self.selected_item;
let list = uniform_list(
cx.view().clone(),
cx.model().clone(),
"code_actions_menu",
self.actions.len(),
move |_this, range, cx| {
move |_this, range, _, cx| {
actions
.iter()
.skip(range.start)
@@ -985,12 +1005,13 @@ impl CodeActionsMenu {
.inset(true)
.toggle_state(selected)
.when_some(action.as_code_action(), |this, action| {
this.on_click(cx.listener(move |editor, _, cx| {
this.on_click(cx.listener(move |editor, _, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
@@ -1009,12 +1030,13 @@ impl CodeActionsMenu {
)
})
.when_some(action.as_task(), |this, task| {
this.on_click(cx.listener(move |editor, _, cx| {
this.on_click(cx.listener(move |editor, _, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
@@ -1035,7 +1057,7 @@ impl CodeActionsMenu {
},
)
.occlude()
.max_h(max_height_in_lines as f32 * cx.line_height())
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(self.scroll_handle.clone())
.with_width_from_item(
self.actions

View File

@@ -68,7 +68,7 @@ use std::{
use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot};
use text::LineIndent;
use ui::{px, SharedString, WindowContext};
use ui::{px, AppContext, SharedString, Window};
use unicode_segmentation::UnicodeSegmentation;
use wrap_map::{WrapMap, WrapSnapshot};
@@ -78,7 +78,7 @@ pub enum FoldStatus {
Foldable,
}
pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut Window, &mut AppContext) -> AnyElement>;
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
@@ -1749,9 +1749,9 @@ pub mod tests {
let editor = cx.editor.clone();
let window = cx.window;
_ = cx.update_window(window, |_, cx| {
_ = cx.update_window(window, |_, window, cx| {
let text_layout_details =
editor.update(cx, |editor, cx| editor.text_layout_details(cx));
editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
let font_size = px(12.0);
let wrap_width = Some(px(64.));
@@ -2624,8 +2624,8 @@ pub mod tests {
[Crease::inline(
range,
FoldPlaceholder::test(),
|_row, _status, _toggle, _cx| div(),
|_row, _status, _cx| div(),
|_row, _status, _toggle, _window, _cx| div(),
|_row, _status, _window, _cx| div(),
)],
&map.buffer.read(cx).snapshot(cx),
);

View File

@@ -4,7 +4,7 @@ use super::{
};
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
use gpui::{AnyElement, AppContext, EntityId, Pixels, Window};
use language::{Chunk, Patch, Point};
use multi_buffer::{
Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToOffset,
@@ -226,7 +226,8 @@ pub enum BlockStyle {
}
pub struct BlockContext<'a, 'b> {
pub context: &'b mut WindowContext<'a>,
pub window: &'a mut Window,
pub app: &'b mut AppContext,
pub anchor_x: Pixels,
pub max_width: Pixels,
pub gutter_dimensions: &'b GutterDimensions,
@@ -1930,16 +1931,16 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
}
impl<'a> Deref for BlockContext<'a, '_> {
type Target = WindowContext<'a>;
type Target = AppContext;
fn deref(&self) -> &Self::Target {
self.context
self.app
}
}
impl DerefMut for BlockContext<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
self.app
}
}

View File

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
use sum_tree::{Bias, SeekTarget, SumTree};
use text::Point;
use ui::{IconName, SharedString, WindowContext};
use ui::{AppContext, IconName, SharedString, Window};
use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
@@ -117,12 +117,13 @@ type RenderToggleFn = Arc<
+ Fn(
MultiBufferRow,
bool,
Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
&mut WindowContext,
Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut AppContext)>,
&mut Window,
&mut AppContext,
) -> AnyElement,
>;
type RenderTrailerFn =
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut Window, &mut AppContext) -> AnyElement>;
#[derive(Clone)]
pub enum Crease<T> {
@@ -185,26 +186,27 @@ impl<T> Crease<T> {
+ Fn(
MultiBufferRow,
bool,
Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
&mut WindowContext,
Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut AppContext)>,
&mut Window,
&mut AppContext,
) -> ToggleElement
+ 'static,
ToggleElement: IntoElement,
RenderTrailer: 'static
+ Send
+ Sync
+ Fn(MultiBufferRow, bool, &mut WindowContext) -> TrailerElement
+ Fn(MultiBufferRow, bool, &mut Window, &mut AppContext) -> TrailerElement
+ 'static,
TrailerElement: IntoElement,
{
Crease::Inline {
range,
placeholder,
render_toggle: Some(Arc::new(move |row, folded, toggle, cx| {
render_toggle(row, folded, toggle, cx).into_any_element()
render_toggle: Some(Arc::new(move |row, folded, toggle, window, cx| {
render_toggle(row, folded, toggle, window, cx).into_any_element()
})),
render_trailer: Some(Arc::new(move |row, folded, cx| {
render_trailer(row, folded, cx).into_any_element()
render_trailer: Some(Arc::new(move |row, folded, window, cx| {
render_trailer(row, folded, window, cx).into_any_element()
})),
metadata: None,
}
@@ -402,14 +404,14 @@ mod test {
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
];
let crease_ids = crease_map.insert(creases, &snapshot);
@@ -448,20 +450,20 @@ mod test {
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
];
crease_map.insert(creases, &snapshot);

View File

@@ -2,7 +2,7 @@ use super::{
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
Highlights,
};
use gpui::{AnyElement, ElementId, WindowContext};
use gpui::{AnyElement, AppContext, ElementId, Window};
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
use std::{
@@ -19,7 +19,9 @@ use util::post_inc;
#[derive(Clone)]
pub struct FoldPlaceholder {
/// Creates an element to represent this fold's placeholder.
pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement>,
pub render: Arc<
dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut AppContext) -> AnyElement,
>,
/// If true, the element is constrained to the shaped width of an ellipsis.
pub constrain_width: bool,
/// If true, merges the fold with an adjacent one.
@@ -31,7 +33,7 @@ pub struct FoldPlaceholder {
impl Default for FoldPlaceholder {
fn default() -> Self {
Self {
render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
render: Arc::new(|_, _, _, _| gpui::Empty.into_any_element()),
constrain_width: true,
merge_adjacent: true,
type_tag: None,
@@ -43,7 +45,7 @@ impl FoldPlaceholder {
#[cfg(any(test, feature = "test-support"))]
pub fn test() -> Self {
Self {
render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
render: Arc::new(|_id, _range, _window, _cx| gpui::Empty.into_any_element()),
constrain_width: true,
merge_adjacent: true,
type_tag: None,
@@ -485,7 +487,8 @@ impl FoldMap {
(fold.placeholder.render)(
fold_id,
fold.range.0.clone(),
cx,
cx.window,
cx.context,
)
}),
constrain_width: fold.placeholder.constrain_width,

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@ impl EditorSettingsControls {
}
impl RenderOnce for EditorSettingsControls {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, _cx: &mut AppContext) -> impl IntoElement {
SettingsContainer::new()
.child(
SettingsGroup::new("Font")
@@ -80,7 +80,7 @@ impl EditableSettingControl for BufferFontFamilyControl {
}
impl RenderOnce for BufferFontFamilyControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
@@ -89,18 +89,18 @@ impl RenderOnce for BufferFontFamilyControl {
.child(DropdownMenu::new(
"buffer-font-family",
value.clone(),
ContextMenu::build(cx, |mut menu, cx| {
ContextMenu::build(window, cx, |mut menu, _, cx| {
let font_family_cache = FontFamilyCache::global(cx);
for font_name in font_family_cache.list_font_families(cx) {
menu = menu.custom_entry(
{
let font_name = font_name.clone();
move |_cx| Label::new(font_name.clone()).into_any_element()
move |_window, _cx| Label::new(font_name.clone()).into_any_element()
},
{
let font_name = font_name.clone();
move |cx| {
move |_window, cx| {
Self::write(font_name.clone(), cx);
}
},
@@ -139,7 +139,7 @@ impl EditableSettingControl for BufferFontSizeControl {
}
impl RenderOnce for BufferFontSizeControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
@@ -148,10 +148,10 @@ impl RenderOnce for BufferFontSizeControl {
.child(NumericStepper::new(
"buffer-font-size",
value.to_string(),
move |_, cx| {
move |_, _, cx| {
Self::write(value - px(1.), cx);
},
move |_, cx| {
move |_, _, cx| {
Self::write(value + px(1.), cx);
},
))
@@ -184,7 +184,7 @@ impl EditableSettingControl for BufferFontWeightControl {
}
impl RenderOnce for BufferFontWeightControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
@@ -193,12 +193,12 @@ impl RenderOnce for BufferFontWeightControl {
.child(DropdownMenu::new(
"buffer-font-weight",
value.0.to_string(),
ContextMenu::build(cx, |mut menu, _cx| {
ContextMenu::build(window, cx, |mut menu, _window, _cx| {
for weight in FontWeight::ALL {
menu = menu.custom_entry(
move |_cx| Label::new(weight.0.to_string()).into_any_element(),
move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
{
move |cx| {
move |_, cx| {
Self::write(weight, cx);
}
},
@@ -255,14 +255,14 @@ impl EditableSettingControl for BufferFontLigaturesControl {
}
impl RenderOnce for BufferFontLigaturesControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"buffer-font-ligatures",
Label::new(self.name()),
value.into(),
|selection, cx| {
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
@@ -308,14 +308,14 @@ impl EditableSettingControl for InlineGitBlameControl {
}
impl RenderOnce for InlineGitBlameControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"inline-git-blame",
Label::new(self.name()),
value.into(),
|selection, cx| {
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
@@ -361,14 +361,14 @@ impl EditableSettingControl for LineNumbersControl {
}
impl RenderOnce for LineNumbersControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"line-numbers",
Label::new(self.name()),
value.into(),
|selection, cx| {
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
@@ -407,20 +407,20 @@ impl EditableSettingControl for RelativeLineNumbersControl {
}
impl RenderOnce for RelativeLineNumbersControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
let value = Self::read(cx);
DropdownMenu::new(
"relative-line-numbers",
if value { "Relative" } else { "Ascending" },
ContextMenu::build(cx, |menu, _cx| {
ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.custom_entry(
|_cx| Label::new("Ascending").into_any_element(),
move |cx| Self::write(false, cx),
|_window, _cx| Label::new("Ascending").into_any_element(),
move |_, cx| Self::write(false, cx),
)
.custom_entry(
|_cx| Label::new("Relative").into_any_element(),
move |cx| Self::write(true, cx),
|_window, _cx| Label::new("Relative").into_any_element(),
move |_, cx| Self::write(true, cx),
)
}),
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,8 @@ use git::{
repository::GitFileStatus,
};
use gpui::{
actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, Model, Render, Subscription, Task, View, WeakView,
actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, Focusable,
InteractiveElement, Model, Render, Subscription, Task, WeakModel,
};
use language::{Buffer, BufferRow};
use multi_buffer::{ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer};
@@ -34,7 +34,7 @@ use crate::{Editor, EditorEvent, DEFAULT_MULTIBUFFER_CONTEXT};
actions!(project_diff, [Deploy]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(ProjectDiffEditor::register).detach();
cx.observe_new_models(ProjectDiffEditor::register).detach();
}
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
@@ -43,10 +43,10 @@ struct ProjectDiffEditor {
buffer_changes: BTreeMap<WorktreeId, HashMap<ProjectEntryId, Changes>>,
entry_order: HashMap<WorktreeId, Vec<(ProjectPath, ProjectEntryId)>>,
excerpts: Model<MultiBuffer>,
editor: View<Editor>,
editor: Model<Editor>,
project: Model<Project>,
workspace: WeakView<Workspace>,
workspace: WeakModel<Workspace>,
focus_handle: FocusHandle,
worktree_rescans: HashMap<WorktreeId, Task<()>>,
_subscriptions: Vec<Subscription>,
@@ -60,35 +60,46 @@ struct Changes {
}
impl ProjectDiffEditor {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut ModelContext<Workspace>,
) {
workspace.register_action(Self::deploy);
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
fn deploy(
workspace: &mut Workspace,
_: &Deploy,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) {
if !cx.is_staff() {
return;
}
if let Some(existing) = workspace.item_of_type::<Self>(cx) {
workspace.activate_item(&existing, true, true, cx);
workspace.activate_item(&existing, true, true, window, cx);
} else {
let workspace_handle = cx.view().downgrade();
let project_diff =
cx.new_view(|cx| Self::new(workspace.project().clone(), workspace_handle, cx));
workspace.add_item_to_active_pane(Box::new(project_diff), None, true, cx);
let workspace_handle = cx.model().downgrade();
let project_diff = cx.new_model(|cx| {
Self::new(workspace.project().clone(), workspace_handle, window, cx)
});
workspace.add_item_to_active_pane(Box::new(project_diff), None, true, window, cx);
}
}
fn new(
project: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
workspace: WeakModel<Workspace>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
// TODO diff change subscriptions. For that, needed:
// * `-20/+50` stats retrieval: some background process that reacts on file changes
let focus_handle = cx.focus_handle();
let changed_entries_subscription =
cx.subscribe(&project, |project_diff_editor, _, e, cx| {
cx.subscribe_in(&project, window, |project_diff_editor, _, e, window, cx| {
let mut worktree_to_rescan = None;
match e {
project::Event::WorktreeAdded(id) => {
@@ -141,15 +152,15 @@ impl ProjectDiffEditor {
}
if let Some(worktree_to_rescan) = worktree_to_rescan {
project_diff_editor.schedule_worktree_rescan(worktree_to_rescan, cx);
project_diff_editor.schedule_worktree_rescan(worktree_to_rescan, window, cx);
}
});
let excerpts = cx.new_model(|cx| MultiBuffer::new(project.read(cx).capability()));
let editor = cx.new_view(|cx| {
let editor = cx.new_model(|cx| {
let mut diff_display_editor =
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, window, cx);
diff_display_editor.set_expand_all_diff_hunks();
diff_display_editor
});
@@ -165,16 +176,16 @@ impl ProjectDiffEditor {
excerpts,
_subscriptions: vec![changed_entries_subscription],
};
new_self.schedule_rescan_all(cx);
new_self.schedule_rescan_all(window, cx);
new_self
}
fn schedule_rescan_all(&mut self, cx: &mut ViewContext<Self>) {
fn schedule_rescan_all(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
let mut current_worktrees = HashSet::<WorktreeId>::default();
for worktree in self.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
let worktree_id = worktree.read(cx).id();
current_worktrees.insert(worktree_id);
self.schedule_worktree_rescan(worktree_id, cx);
self.schedule_worktree_rescan(worktree_id, window, cx);
}
self.worktree_rescans
@@ -185,11 +196,16 @@ impl ProjectDiffEditor {
.retain(|worktree_id, _| current_worktrees.contains(worktree_id));
}
fn schedule_worktree_rescan(&mut self, id: WorktreeId, cx: &mut ViewContext<Self>) {
fn schedule_worktree_rescan(
&mut self,
id: WorktreeId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let project = self.project.clone();
self.worktree_rescans.insert(
id,
cx.spawn(|project_diff_editor, mut cx| async move {
cx.spawn_in(window, |project_diff_editor, mut cx| async move {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
let open_tasks = project
.update(&mut cx, |project, cx| {
@@ -257,7 +273,7 @@ impl ProjectDiffEditor {
continue;
};
cx.update(|cx| {
cx.update(|_, cx| {
buffers.insert(
entry_id,
(
@@ -308,11 +324,11 @@ impl ProjectDiffEditor {
.await;
project_diff_editor
.update(&mut cx, |project_diff_editor, cx| {
.update_in(&mut cx, |project_diff_editor, window, cx| {
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
project_diff_editor.editor.update(cx, |editor, cx| {
for change_set in change_sets {
editor.diff_map.add_change_set(change_set, cx)
editor.diff_map.add_change_set(change_set, window, cx)
}
});
})
@@ -326,7 +342,8 @@ impl ProjectDiffEditor {
worktree_id: WorktreeId,
new_changes: HashMap<ProjectEntryId, Changes>,
new_entry_order: Vec<(ProjectPath, ProjectEntryId)>,
cx: &mut ViewContext<ProjectDiffEditor>,
cx: &mut ModelContext<ProjectDiffEditor>,
) {
if let Some(current_order) = self.entry_order.get(&worktree_id) {
let current_entries = self.buffer_changes.entry(worktree_id).or_default();
@@ -901,7 +918,7 @@ impl ProjectDiffEditor {
impl EventEmitter<EditorEvent> for ProjectDiffEditor {}
impl FocusableView for ProjectDiffEditor {
impl Focusable for ProjectDiffEditor {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
@@ -914,20 +931,31 @@ impl Item for ProjectDiffEditor {
Editor::to_item_events(event, f)
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| editor.deactivated(cx));
fn deactivated(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
self.editor
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx))
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
Some("Project Diff".into())
}
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
fn tab_content(
&self,
params: TabContentParams,
_window: &Window,
_: &AppContext,
) -> AnyElement {
if self.buffer_changes.is_empty() {
Label::new("No changes")
.color(if params.selected {
@@ -987,7 +1015,12 @@ impl Item for ProjectDiffEditor {
false
}
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, _| {
editor.set_nav_history(Some(nav_history));
});
@@ -996,13 +1029,14 @@ impl Item for ProjectDiffEditor {
fn clone_on_split(
&self,
_workspace_id: Option<workspace::WorkspaceId>,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>>
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<Model<Self>>
where
Self: Sized,
{
Some(cx.new_view(|cx| {
ProjectDiffEditor::new(self.project.clone(), self.workspace.clone(), cx)
Some(cx.new_model(|cx| {
ProjectDiffEditor::new(self.project.clone(), self.workspace.clone(), window, cx)
}))
}
@@ -1022,16 +1056,18 @@ impl Item for ProjectDiffEditor {
&mut self,
format: bool,
project: Model<Project>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
self.editor.save(format, project, cx)
self.editor.save(format, project, window, cx)
}
fn save_as(
&mut self,
_: Model<Project>,
_: ProjectPath,
_: &mut ViewContext<Self>,
_window: &mut Window,
_: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
unreachable!()
}
@@ -1039,15 +1075,16 @@ impl Item for ProjectDiffEditor {
fn reload(
&mut self,
project: Model<Project>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
self.editor.reload(project, cx)
self.editor.reload(project, window, cx)
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
self_handle: &'a Model<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
@@ -1067,14 +1104,20 @@ impl Item for ProjectDiffEditor {
self.editor.breadcrumbs(theme, cx)
}
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
fn added_to_workspace(
&mut self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.added_to_workspace(workspace, window, cx)
});
}
}
impl Render for ProjectDiffEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let child = if self.buffer_changes.is_empty() {
div()
.bg(cx.theme().colors().editor_background)
@@ -1138,14 +1181,15 @@ mod tests {
.await;
let project = Project::test(fs.clone(), [Path::new("/root")], cx).await;
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let file_a_editor = workspace
.update(cx, |workspace, cx| {
.update(cx, |workspace, window, cx| {
let file_a_editor =
workspace.open_abs_path(PathBuf::from("/root/file_a"), true, cx);
ProjectDiffEditor::deploy(workspace, &Deploy, cx);
workspace.open_abs_path(PathBuf::from("/root/file_a"), true, window, cx);
ProjectDiffEditor::deploy(workspace, &Deploy, window, cx);
file_a_editor
})
.unwrap()
@@ -1154,7 +1198,7 @@ mod tests {
.downcast::<Editor>()
.expect("did not open an editor for file_a");
let project_diff_editor = workspace
.update(cx, |workspace, cx| {
.update(cx, |workspace, _, cx| {
workspace
.active_pane()
.read(cx)
@@ -1173,13 +1217,13 @@ mod tests {
let old_text = file_a_editor.update(cx, |editor, cx| editor.text(cx));
let change = "an edit after git add";
file_a_editor
.update(cx, |file_a_editor, cx| {
file_a_editor.insert(change, cx);
file_a_editor.save(false, project.clone(), cx)
.update_in(cx, |file_a_editor, window, cx| {
file_a_editor.insert(change, window, cx);
file_a_editor.save(false, project.clone(), window, cx)
})
.await
.expect("failed to save a file");
file_a_editor.update(cx, |file_a_editor, cx| {
file_a_editor.update_in(cx, |file_a_editor, window, cx| {
let change_set = cx.new_model(|cx| {
BufferChangeSet::new_with_base_text(
old_text.clone(),
@@ -1195,7 +1239,7 @@ mod tests {
});
file_a_editor
.diff_map
.add_change_set(change_set.clone(), cx);
.add_change_set(change_set.clone(), window, cx);
project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.set_change_set(

View File

@@ -1,11 +1,14 @@
use gpui::ViewContext;
use language::CursorShape;
use crate::{Editor, RangeToAnchorExt};
use gpui::{ModelContext, Window};
use language::CursorShape;
enum MatchingBracketHighlight {}
pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
pub fn refresh_matching_bracket_highlights(
editor: &mut Editor,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
let newest_selection = editor.selections.newest::<usize>(cx);
@@ -14,7 +17,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
return;
}
let snapshot = editor.snapshot(cx);
let snapshot = editor.snapshot(window, cx);
let head = newest_selection.head();
let mut tail = head;
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)

View File

@@ -5,7 +5,7 @@ use crate::{
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
};
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
use gpui::{px, AppContext, AsyncWindowContext, Model, ModelContext, Modifiers, Task, Window};
use language::{Bias, ToOffset};
use linkify::{LinkFinder, LinkKind};
use lsp::LanguageServerId;
@@ -117,7 +117,8 @@ impl Editor {
point_for_position: PointForPosition,
snapshot: &EditorSnapshot,
modifiers: Modifiers,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let hovered_link_modifier = match multi_cursor_setting {
@@ -137,7 +138,7 @@ impl Editor {
.anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left)),
);
show_link_definition(modifiers.shift, self, trigger_point, snapshot, cx);
show_link_definition(modifiers.shift, self, trigger_point, snapshot, window, cx);
}
None => {
update_inlay_link_and_hover_points(
@@ -146,13 +147,14 @@ impl Editor {
self,
hovered_link_modifier,
modifiers.shift,
window,
cx,
);
}
}
}
pub(crate) fn hide_hovered_link(&mut self, cx: &mut ViewContext<Self>) {
pub(crate) fn hide_hovered_link(&mut self, cx: &mut ModelContext<Self>) {
self.hovered_link_state.take();
self.clear_highlights::<HoveredLinkState>(cx);
}
@@ -161,17 +163,18 @@ impl Editor {
&mut self,
point: PointForPosition,
modifiers: Modifiers,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let reveal_task = self.cmd_click_reveal_task(point, modifiers, cx);
cx.spawn(|editor, mut cx| async move {
let reveal_task = self.cmd_click_reveal_task(point, modifiers, window, cx);
cx.spawn_in(window, |editor, mut cx| async move {
let definition_revealed = reveal_task.await.log_err().unwrap_or(Navigated::No);
let find_references = editor
.update(&mut cx, |editor, cx| {
.update_in(&mut cx, |editor, window, cx| {
if definition_revealed == Navigated::Yes {
return None;
}
editor.find_all_references(&FindAllReferences, cx)
editor.find_all_references(&FindAllReferences, window, cx)
})
.ok()
.flatten();
@@ -182,9 +185,14 @@ impl Editor {
.detach();
}
pub fn scroll_hover(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) -> bool {
pub fn scroll_hover(
&mut self,
amount: &ScrollAmount,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> bool {
let selection = self.selections.newest_anchor().head();
let snapshot = self.snapshot(cx);
let snapshot = self.snapshot(window, cx);
let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
popover
@@ -193,7 +201,7 @@ impl Editor {
}) else {
return false;
};
popover.scroll(amount, cx);
popover.scroll(amount, window, cx);
true
}
@@ -201,19 +209,20 @@ impl Editor {
&mut self,
point: PointForPosition,
modifiers: Modifiers,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Task<anyhow::Result<Navigated>> {
if let Some(hovered_link_state) = self.hovered_link_state.take() {
self.hide_hovered_link(cx);
if !hovered_link_state.links.is_empty() {
if !self.focus_handle.is_focused(cx) {
cx.focus(&self.focus_handle);
if !self.focus_handle.is_focused(window) {
window.focus(&self.focus_handle);
}
// exclude links pointing back to the current anchor
let current_position = point
.next_valid
.to_point(&self.snapshot(cx).display_snapshot);
.to_point(&self.snapshot(window, cx).display_snapshot);
let Some((buffer, anchor)) = self
.buffer()
.read(cx)
@@ -233,7 +242,7 @@ impl Editor {
})
.collect();
return self.navigate_to_hover_links(None, links, modifiers.alt, cx);
return self.navigate_to_hover_links(None, links, modifiers.alt, window, cx);
}
}
@@ -245,14 +254,15 @@ impl Editor {
add: false,
click_count: 1,
},
window,
cx,
);
if point.as_valid().is_some() {
if modifiers.shift {
self.go_to_type_definition(&GoToTypeDefinition, cx)
self.go_to_type_definition(&GoToTypeDefinition, window, cx)
} else {
self.go_to_definition(&GoToDefinition, cx)
self.go_to_definition(&GoToDefinition, window, cx)
}
} else {
Task::ready(Ok(Navigated::No))
@@ -266,7 +276,8 @@ pub fn update_inlay_link_and_hover_points(
editor: &mut Editor,
secondary_held: bool,
shift_held: bool,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
@@ -310,6 +321,7 @@ pub fn update_inlay_link_and_hover_points(
buffer_id,
excerpt_id,
hovered_hint.id,
window,
cx,
);
}
@@ -349,6 +361,7 @@ pub fn update_inlay_link_and_hover_points(
..hovered_hint.text.len() + extra_shift_right,
},
},
window,
cx,
);
hover_updated = true;
@@ -393,6 +406,7 @@ pub fn update_inlay_link_and_hover_points(
},
range: highlight.clone(),
},
window,
cx,
);
hover_updated = true;
@@ -413,6 +427,7 @@ pub fn update_inlay_link_and_hover_points(
language_server_id,
),
snapshot,
window,
cx,
);
}
@@ -431,7 +446,7 @@ pub fn update_inlay_link_and_hover_points(
editor.hide_hovered_link(cx)
}
if !hover_updated {
hover_popover::hover_at(editor, None, cx);
hover_popover::hover_at(editor, None, window, cx);
}
}
@@ -440,7 +455,8 @@ pub fn show_link_definition(
editor: &mut Editor,
trigger_point: TriggerPoint,
snapshot: &EditorSnapshot,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let preferred_kind = match trigger_point {
TriggerPoint::Text(_) if !shift_held => GotoDefinitionKind::Symbol,
@@ -509,7 +525,7 @@ pub fn show_link_definition(
let provider = editor.semantics_provider.clone();
let snapshot = snapshot.buffer_snapshot.clone();
hovered_link_state.task = Some(cx.spawn(|this, mut cx| {
hovered_link_state.task = Some(cx.spawn_in(window, |this, mut cx| {
async move {
let result = match &trigger_point {
TriggerPoint::Text(_) => {
@@ -536,7 +552,7 @@ pub fn show_link_definition(
Some((range, vec![HoverLink::File(filename)]))
} else if let Some(provider) = provider {
let task = cx.update(|cx| {
let task = cx.update(|_, cx| {
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
})?;
if let Some(task) = task {
@@ -926,7 +942,7 @@ mod tests {
struct A;
let vˇariable = A;
"});
let screen_coord = cx.editor(|editor, cx| editor.pixel_position_of_cursor(cx));
let screen_coord = cx.editor(|editor, _, cx| editor.pixel_position_of_cursor(cx));
// Basic hold cmd+shift, expect highlight in region if response contains type definition
let symbol_range = cx.lsp_range(indoc! {"
@@ -1226,11 +1242,11 @@ mod tests {
fn do_work() { test(); }
"})[0]
.clone();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_range = snapshot.anchor_before(selection_range.start)
..snapshot.anchor_after(selection_range.end);
editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| {
editor.change_selections(Some(crate::Autoscroll::fit()), window, cx, |s| {
s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
});
});
@@ -1319,7 +1335,7 @@ mod tests {
.next()
.await;
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
let expected_layers = vec![hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
@@ -1336,8 +1352,8 @@ mod tests {
.first()
.cloned()
.unwrap();
let midpoint = cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let midpoint = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let previous_valid = inlay_range.start.to_display_point(&snapshot);
let next_valid = inlay_range.end.to_display_point(&snapshot);
assert_eq!(previous_valid.row(), next_valid.row());
@@ -1351,8 +1367,8 @@ mod tests {
let hover_point = cx.pixel_position_for(midpoint);
cx.simulate_mouse_move(hover_point, None, Modifiers::secondary_key());
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let actual_highlights = snapshot
.inlay_highlights::<HoveredLinkState>()
.into_iter()
@@ -1370,8 +1386,8 @@ mod tests {
cx.simulate_mouse_move(hover_point, None, Modifiers::none());
// Assert no link highlights
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let actual_ranges = snapshot
.text_highlight_ranges::<HoveredLinkState>()
.map(|ranges| ranges.as_ref().clone().1)
@@ -1515,7 +1531,7 @@ mod tests {
for (input, expected) in test_cases {
cx.set_state(input);
let (position, snapshot) = cx.editor(|editor, cx| {
let (position, snapshot) = cx.editor(|editor, _, cx| {
let positions = editor.selections.newest_anchor().head().text_anchor;
let snapshot = editor
.buffer()
@@ -1556,7 +1572,7 @@ mod tests {
.await;
// Insert a new file
let fs = cx.update_workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
.await;
@@ -1579,9 +1595,9 @@ mod tests {
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
// No highlight
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor
.snapshot(cx)
.snapshot(window, cx)
.text_highlight_ranges::<HoveredLinkState>()
.unwrap_or_default()
.1
@@ -1662,8 +1678,8 @@ mod tests {
cx.simulate_click(screen_coord, Modifiers::secondary_key());
cx.update_workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.update_workspace(|workspace, cx| {
cx.update_workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.update_workspace(|workspace, _, cx| {
let active_editor = workspace.active_item_as::<Editor>(cx).unwrap();
let buffer = active_editor
@@ -1692,7 +1708,7 @@ mod tests {
.await;
// Insert a new file
let fs = cx.update_workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
.await;
@@ -1708,9 +1724,9 @@ mod tests {
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
// No highlight
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
assert!(editor
.snapshot(cx)
.snapshot(window, cx)
.text_highlight_ranges::<HoveredLinkState>()
.unwrap_or_default()
.1
@@ -1719,6 +1735,6 @@ mod tests {
// Does not open the directory
cx.simulate_click(screen_coord, Modifiers::secondary_key());
cx.update_workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
cx.update_workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 1));
}
}

View File

@@ -6,9 +6,10 @@ use crate::{
Hover, RangeToAnchorExt,
};
use gpui::{
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
div, px, AnyElement, AsyncWindowContext, Focusable as _, FontWeight, Hsla, InteractiveElement,
IntoElement, Model, ModelContext, MouseButton, ParentElement, Pixels, ScrollHandle, Size,
Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Task, TextStyleRefinement,
Window,
};
use itertools::Itertools;
use language::{Diagnostic, DiagnosticEntry, Language, LanguageRegistry};
@@ -21,7 +22,7 @@ use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent, Scrollbar, ScrollbarState};
use ui::{prelude::*, theme_is_transparent, Scrollbar, ScrollbarState};
use util::TryFutureExt;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@@ -30,33 +31,42 @@ pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
pub const HOVER_POPOVER_GAP: Pixels = px(10.);
/// Bindable action which uses the most recent selection head to trigger a hover
pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
pub fn hover(editor: &mut Editor, _: &Hover, window: &mut Window, cx: &mut ModelContext<Editor>) {
let head = editor.selections.newest_anchor().head();
show_hover(editor, head, true, cx);
show_hover(editor, head, true, window, cx);
}
/// The internal hover action dispatches between `show_hover` or `hide_hover`
/// depending on whether a point to hover over is provided.
pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {
pub fn hover_at(
editor: &mut Editor,
anchor: Option<Anchor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
if EditorSettings::get_global(cx).hover_popover_enabled {
if show_keyboard_hover(editor, cx) {
if show_keyboard_hover(editor, window, cx) {
return;
}
if let Some(anchor) = anchor {
show_hover(editor, anchor, false, cx);
show_hover(editor, anchor, false, window, cx);
} else {
hide_hover(editor, cx);
}
}
}
pub fn show_keyboard_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
pub fn show_keyboard_hover(
editor: &mut Editor,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> bool {
let info_popovers = editor.hover_state.info_popovers.clone();
for p in info_popovers {
let keyboard_grace = p.keyboard_grace.borrow();
if *keyboard_grace {
if let Some(anchor) = p.anchor {
show_hover(editor, anchor, false, cx);
show_hover(editor, anchor, false, window, cx);
return true;
}
}
@@ -67,7 +77,7 @@ pub fn show_keyboard_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) ->
let keyboard_grace = d.keyboard_grace.borrow();
if *keyboard_grace {
if let Some(anchor) = d.anchor {
show_hover(editor, anchor, false, cx);
show_hover(editor, anchor, false, window, cx);
return true;
}
}
@@ -103,7 +113,12 @@ pub fn find_hovered_hint_part(
None
}
pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
pub fn hover_at_inlay(
editor: &mut Editor,
inlay_hover: InlayHover,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
if EditorSettings::get_global(cx).hover_popover_enabled {
if editor.pending_rename.is_some() {
return;
@@ -132,7 +147,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
let task = cx.spawn(|this, mut cx| {
let task = cx.spawn_in(window, |this, mut cx| {
async move {
cx.background_executor()
.timer(Duration::from_millis(hover_popover_delay))
@@ -173,7 +188,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
/// Hides the type information popup.
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
/// selections changed.
pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
pub fn hide_hover(editor: &mut Editor, cx: &mut ModelContext<Editor>) -> bool {
let info_popovers = editor.hover_state.info_popovers.drain(..);
let diagnostics_popover = editor.hover_state.diagnostic_popover.take();
let did_hide = info_popovers.count() > 0 || diagnostics_popover.is_some();
@@ -197,13 +212,14 @@ fn show_hover(
editor: &mut Editor,
anchor: Anchor,
ignore_timeout: bool,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Option<()> {
if editor.pending_rename.is_some() {
return None;
}
let snapshot = editor.snapshot(cx);
let snapshot = editor.snapshot(window, cx);
let (buffer, buffer_position) = editor
.buffer
@@ -239,7 +255,7 @@ fn show_hover(
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
let task = cx.spawn(|this, mut cx| {
let task = cx.spawn_in(window, |this, mut cx| {
async move {
// If we need to delay, delay a set amount initially before making the lsp request
let delay = if ignore_timeout {
@@ -257,7 +273,7 @@ fn show_hover(
total_delay
};
let hover_request = cx.update(|cx| provider.hover(&buffer, buffer_position, cx))?;
let hover_request = cx.update(|_, cx| provider.hover(&buffer, buffer_position, cx))?;
if let Some(delay) = delay {
delay.await;
@@ -333,7 +349,7 @@ fn show_hover(
let mut background_color: Option<Hsla> = None;
let parsed_content = cx
.new_view(|cx| {
.new_window_model(|window, cx| {
let status_colors = cx.theme().status();
match local_diagnostic.diagnostic.severity {
@@ -359,7 +375,7 @@ fn show_hover(
}
};
let settings = ThemeSettings::get_global(cx);
let mut base_text_style = cx.text_style();
let mut base_text_style = window.text_style();
base_text_style.refine(&TextStyleRefinement {
font_family: Some(settings.ui_font.family.clone()),
font_fallbacks: settings.ui_font.fallbacks.clone(),
@@ -382,7 +398,7 @@ fn show_hover(
},
..Default::default()
};
Markdown::new_text(text, markdown_style.clone(), None, None, cx)
Markdown::new_text(text, markdown_style.clone(), None, None, window, cx)
})
.ok();
@@ -408,7 +424,7 @@ fn show_hover(
} else {
Vec::new()
};
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
let snapshot = this.update_in(&mut cx, |this, window, cx| this.snapshot(window, cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(hovers_response.len());
let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
@@ -459,7 +475,7 @@ fn show_hover(
info_popovers.push(info_popover);
}
this.update(&mut cx, |editor, cx| {
this.update_in(&mut cx, |editor, window, cx| {
if hover_highlights.is_empty() {
editor.clear_background_highlights::<HoverState>(cx);
} else {
@@ -473,7 +489,7 @@ fn show_hover(
editor.hover_state.info_popovers = info_popovers;
cx.notify();
cx.refresh();
window.refresh();
})?;
anyhow::Ok(())
@@ -527,7 +543,7 @@ async fn parse_blocks(
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
cx: &mut AsyncWindowContext,
) -> Option<View<Markdown>> {
) -> Option<Model<Markdown>> {
let fallback_language_name = if let Some(ref l) = language {
let l = Arc::clone(l);
Some(l.lsp_id().clone())
@@ -548,14 +564,14 @@ async fn parse_blocks(
.join("\n\n");
let rendered_block = cx
.new_view(|cx| {
.new_window_model(|window, cx| {
let settings = ThemeSettings::get_global(cx);
let ui_font_family = settings.ui_font.family.clone();
let ui_font_fallbacks = settings.ui_font.fallbacks.clone();
let buffer_font_family = settings.buffer_font.family.clone();
let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone();
let mut base_text_style = cx.text_style();
let mut base_text_style = window.text_style();
base_text_style.refine(&TextStyleRefinement {
font_family: Some(ui_font_family.clone()),
font_fallbacks: ui_font_fallbacks,
@@ -602,6 +618,7 @@ async fn parse_blocks(
markdown_style.clone(),
Some(language_registry.clone()),
fallback_language_name,
window,
cx,
)
})
@@ -628,7 +645,7 @@ impl HoverState {
snapshot: &EditorSnapshot,
visible_rows: Range<DisplayRow>,
max_size: Size<Pixels>,
cx: &mut ViewContext<Editor>,
cx: &mut ModelContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement>)> {
// If there is a diagnostic, position the popovers based on that.
// Otherwise use the start of the hover range
@@ -671,18 +688,18 @@ impl HoverState {
Some((point, elements))
}
pub fn focused(&self, cx: &mut ViewContext<Editor>) -> bool {
pub fn focused(&self, window: &mut Window, cx: &mut ModelContext<Editor>) -> bool {
let mut hover_popover_is_focused = false;
for info_popover in &self.info_popovers {
if let Some(markdown_view) = &info_popover.parsed_content {
if markdown_view.focus_handle(cx).is_focused(cx) {
if markdown_view.focus_handle(cx).is_focused(window) {
hover_popover_is_focused = true;
}
}
}
if let Some(diagnostic_popover) = &self.diagnostic_popover {
if let Some(markdown_view) = &diagnostic_popover.parsed_content {
if markdown_view.focus_handle(cx).is_focused(cx) {
if markdown_view.focus_handle(cx).is_focused(window) {
hover_popover_is_focused = true;
}
}
@@ -694,7 +711,7 @@ impl HoverState {
#[derive(Debug, Clone)]
pub(crate) struct InfoPopover {
pub(crate) symbol_range: RangeInEditor,
pub(crate) parsed_content: Option<View<Markdown>>,
pub(crate) parsed_content: Option<Model<Markdown>>,
pub(crate) scroll_handle: ScrollHandle,
pub(crate) scrollbar_state: ScrollbarState,
pub(crate) keyboard_grace: Rc<RefCell<bool>>,
@@ -705,7 +722,7 @@ impl InfoPopover {
pub(crate) fn render(
&mut self,
max_size: Size<Pixels>,
cx: &mut ViewContext<Editor>,
cx: &mut ModelContext<Editor>,
) -> AnyElement {
let keyboard_grace = Rc::clone(&self.keyboard_grace);
let mut d = div()
@@ -713,8 +730,8 @@ impl InfoPopover {
.elevation_2(cx)
// Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, move |_, cx| {
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, move |_, _, cx| {
let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false;
cx.stop_propagation();
@@ -737,36 +754,41 @@ impl InfoPopover {
d.into_any_element()
}
pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
pub fn scroll(
&self,
amount: &ScrollAmount,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let mut current = self.scroll_handle.offset();
current.y -= amount.pixels(
cx.line_height(),
window.line_height(),
self.scroll_handle.bounds().size.height - px(16.),
) / 2.0;
cx.notify();
self.scroll_handle.set_offset(current);
}
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
fn render_vertical_scrollbar(&self, cx: &mut ModelContext<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("info-popover-vertical-scroll")
.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(|_, _, cx| {
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, cx| {
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
@@ -784,7 +806,7 @@ impl InfoPopover {
pub struct DiagnosticPopover {
local_diagnostic: DiagnosticEntry<Anchor>,
primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
parsed_content: Option<View<Markdown>>,
parsed_content: Option<Model<Markdown>>,
border_color: Option<Hsla>,
background_color: Option<Hsla>,
pub keyboard_grace: Rc<RefCell<bool>>,
@@ -792,7 +814,7 @@ pub struct DiagnosticPopover {
}
impl DiagnosticPopover {
pub fn render(&self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
pub fn render(&self, max_size: Size<Pixels>, cx: &mut ModelContext<Editor>) -> AnyElement {
let keyboard_grace = Rc::clone(&self.keyboard_grace);
let mut markdown_div = div().py_1().px_2();
if let Some(markdown) = &self.parsed_content {
@@ -819,15 +841,15 @@ impl DiagnosticPopover {
.elevation_2_borderless(cx)
// Don't draw the background color if the theme
// allows transparent surfaces.
.when(window_is_transparent(cx), |this| {
.when(theme_is_transparent(cx), |this| {
this.bg(gpui::transparent_black())
})
// Prevent a mouse move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_move(|_, _, cx| cx.stop_propagation())
// Prevent a mouse down on the popover from being propagated to the editor,
// because that would move the cursor.
.on_mouse_down(MouseButton::Left, move |_, cx| {
.on_mouse_down(MouseButton::Left, move |_, _, cx| {
let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false;
cx.stop_propagation();
@@ -943,14 +965,14 @@ mod tests {
three
fn test() { printˇln!(); }
"});
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), cx)
hover_at(editor, Some(anchor), window, cx)
});
assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
assert!(!cx.editor(|editor, _window, _cx| editor.hover_state.visible()));
// After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {"
@@ -973,7 +995,7 @@ mod tests {
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
requests.next().await;
cx.editor(|editor, cx| {
cx.editor(|editor, _window, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -991,14 +1013,14 @@ mod tests {
});
// check that the completion menu is still visible and that there still has only been 1 completion request
cx.editor(|editor, _| assert!(editor.context_menu_visible()));
cx.editor(|editor, _, _| assert!(editor.context_menu_visible()));
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
//apply a completion and check it was successfully applied
let _apply_additional_edits = cx.update_editor(|editor, cx| {
editor.context_menu_next(&Default::default(), cx);
let _apply_additional_edits = cx.update_editor(|editor, window, cx| {
editor.context_menu_next(&Default::default(), window, cx);
editor
.confirm_completion(&ConfirmCompletion::default(), cx)
.confirm_completion(&ConfirmCompletion::default(), window, cx)
.unwrap()
});
cx.assert_editor_state(indoc! {"
@@ -1009,11 +1031,11 @@ mod tests {
"});
// check that the completion menu is no longer visible and that there still has only been 1 completion request
cx.editor(|editor, _| assert!(!editor.context_menu_visible()));
cx.editor(|editor, _, _| assert!(!editor.context_menu_visible()));
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
//verify the information popover is still visible and unchanged
cx.editor(|editor, cx| {
cx.editor(|editor, _, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -1041,19 +1063,19 @@ mod tests {
let mut request = cx
.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), cx)
hover_at(editor, Some(anchor), window, cx)
});
cx.background_executor
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
request.next().await;
// verify that the information popover is no longer visible
cx.editor(|editor, _| {
cx.editor(|editor, _, _| {
assert!(!editor.hover_state.visible());
});
}
@@ -1079,14 +1101,14 @@ mod tests {
fn test() { printˇln!(); }
"});
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), cx)
hover_at(editor, Some(anchor), window, cx)
});
assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
assert!(!cx.editor(|editor, _window, _cx| editor.hover_state.visible()));
// After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {"
@@ -1106,7 +1128,7 @@ mod tests {
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
requests.next().await;
cx.editor(|editor, cx| {
cx.editor(|editor, _, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -1131,17 +1153,17 @@ mod tests {
let mut request = cx
.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), cx)
hover_at(editor, Some(anchor), window, cx)
});
cx.background_executor
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
request.next().await;
cx.editor(|editor, _| {
cx.editor(|editor, _, _| {
assert!(!editor.hover_state.visible());
});
}
@@ -1163,12 +1185,12 @@ mod tests {
cx.set_state(indoc! {"
fˇn test() { println!(); }
"});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
cx.update_editor(|editor, window, cx| hover(editor, &Hover, window, cx));
let symbol_range = cx.lsp_range(indoc! {"
«fn» test() { println!(); }
"});
cx.editor(|editor, _cx| {
cx.editor(|editor, _window, _cx| {
assert!(!editor.hover_state.visible());
assert_eq!(
@@ -1194,7 +1216,7 @@ mod tests {
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
@@ -1230,7 +1252,7 @@ mod tests {
cx.set_state(indoc! {"
fˇn test() { println!(); }
"});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
cx.update_editor(|editor, window, cx| hover(editor, &Hover, window, cx));
let symbol_range = cx.lsp_range(indoc! {"
«fn» test() { println!(); }
"});
@@ -1252,7 +1274,7 @@ mod tests {
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
@@ -1291,7 +1313,7 @@ mod tests {
cx.set_state(indoc! {"
fˇn test() { println!(); }
"});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
cx.update_editor(|editor, window, cx| hover(editor, &Hover, window, cx));
let symbol_range = cx.lsp_range(indoc! {"
«fn» test() { println!(); }
"});
@@ -1318,7 +1340,7 @@ mod tests {
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
@@ -1378,10 +1400,10 @@ mod tests {
});
// Hover pops diagnostic immediately
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
cx.update_editor(|editor, window, cx| hover(editor, &Hover, window, cx));
cx.background_executor.run_until_parked();
cx.editor(|Editor { hover_state, .. }, _| {
cx.editor(|Editor { hover_state, .. }, _, _| {
assert!(
hover_state.diagnostic_popover.is_some() && hover_state.info_popovers.is_empty()
)
@@ -1404,7 +1426,7 @@ mod tests {
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
cx.background_executor.run_until_parked();
cx.editor(|Editor { hover_state, .. }, _| {
cx.editor(|Editor { hover_state, .. }, _, _| {
hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
});
}
@@ -1453,10 +1475,10 @@ mod tests {
}))
}
});
cx.update_editor(|editor, cx| hover(editor, &Default::default(), cx));
cx.update_editor(|editor, window, cx| hover(editor, &Default::default(), window, cx));
cx.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
let popover = editor.hover_state.info_popovers.first().unwrap();
let content = popover.get_rendered_text(cx);
@@ -1568,7 +1590,7 @@ mod tests {
.next()
.await;
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
let expected_layers = vec![entire_hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
@@ -1589,8 +1611,8 @@ mod tests {
.first()
.cloned()
.unwrap();
let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let new_type_hint_part_hover_position = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let previous_valid = inlay_range.start.to_display_point(&snapshot);
let next_valid = inlay_range.end.to_display_point(&snapshot);
assert_eq!(previous_valid.row(), next_valid.row());
@@ -1608,13 +1630,14 @@ mod tests {
column_overshoot_after_line_end: 0,
}
});
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
&editor.snapshot(cx),
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
cx,
);
});
@@ -1678,20 +1701,21 @@ mod tests {
.await;
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
&editor.snapshot(cx),
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
cx,
);
});
cx.background_executor
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
let hover_state = &editor.hover_state;
assert!(
hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1
@@ -1713,8 +1737,8 @@ mod tests {
);
});
let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let struct_hint_part_hover_position = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let previous_valid = inlay_range.start.to_display_point(&snapshot);
let next_valid = inlay_range.end.to_display_point(&snapshot);
assert_eq!(previous_valid.row(), next_valid.row());
@@ -1732,20 +1756,21 @@ mod tests {
column_overshoot_after_line_end: 0,
}
});
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
&editor.snapshot(cx),
&editor.snapshot(window, cx),
struct_hint_part_hover_position,
editor,
true,
false,
window,
cx,
);
});
cx.background_executor
.advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
cx.background_executor.run_until_parked();
cx.update_editor(|editor, cx| {
cx.update_editor(|editor, _, cx| {
let hover_state = &editor.hover_state;
assert!(
hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1

View File

@@ -1,7 +1,8 @@
use collections::{HashMap, HashSet};
use git::diff::DiffHunkStatus;
use gpui::{
Action, AppContext, Corner, CursorStyle, Hsla, Model, MouseButton, Subscription, Task, View,
Action, AppContext, Corner, CursorStyle, Focusable as _, Hsla, Model, MouseButton,
Subscription, Task,
};
use language::{Buffer, BufferId, Point};
use multi_buffer::{
@@ -14,7 +15,7 @@ use sum_tree::TreeMap;
use text::OffsetRangeExt;
use ui::{
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
ModelContext, ParentElement, PopoverMenu, Styled, Tooltip, Window,
};
use util::RangeExt;
use workspace::Item;
@@ -82,7 +83,8 @@ impl DiffMap {
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let buffer_id = change_set.read(cx).buffer_id;
self.snapshot
@@ -92,18 +94,27 @@ impl DiffMap {
buffer_id,
DiffBaseState {
last_version: None,
_subscription: cx.observe(&change_set, move |editor, change_set, cx| {
editor
.diff_map
.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
}),
_subscription: cx.observe_in(
&change_set,
window,
move |editor, change_set, window, cx| {
editor
.diff_map
.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
Editor::sync_expanded_diff_hunks(
&mut editor.diff_map,
buffer_id,
window,
cx,
);
},
),
change_set,
},
);
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
Editor::sync_expanded_diff_hunks(self, buffer_id, window, cx);
}
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
@@ -204,23 +215,34 @@ impl Editor {
pub(super) fn toggle_hovered_hunk(
&mut self,
hovered_hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) {
let editor_snapshot = self.snapshot(cx);
let editor_snapshot = self.snapshot(window, cx);
if let Some(diff_hunk) = to_diff_hunk(hovered_hunk, &editor_snapshot.buffer_snapshot) {
self.toggle_hunks_expanded(vec![diff_hunk], cx);
self.change_selections(None, cx, |selections| selections.refresh());
self.toggle_hunks_expanded(vec![diff_hunk], window, cx);
self.change_selections(None, window, cx, |selections| selections.refresh());
}
}
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
pub fn toggle_hunk_diff(
&mut self,
_: &ToggleHunkDiff,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.snapshot(window, cx);
let selections = self.selections.all(cx);
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), window, cx);
}
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
pub fn expand_all_hunk_diffs(
&mut self,
_: &ExpandAllHunkDiffs,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.snapshot(window, cx);
let display_rows_with_expanded_hunks = self
.diff_map
.hunks(false)
@@ -251,27 +273,28 @@ impl Editor {
display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
});
self.toggle_hunks_expanded(hunks.collect(), cx);
self.toggle_hunks_expanded(hunks.collect(), window, cx);
}
fn toggle_hunks_expanded(
&mut self,
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
if self.diff_map.expand_all {
return;
}
let previous_toggle_task = self.diff_map.hunk_update_tasks.remove(&None);
let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
let new_toggle_task = cx.spawn_in(window, move |editor, mut cx| async move {
if let Some(task) = previous_toggle_task {
task.await;
}
editor
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
.update_in(&mut cx, |editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();
let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
let mut blocks_to_remove = HashSet::default();
@@ -349,7 +372,7 @@ impl Editor {
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx);
for hunk in hunks_to_expand {
editor.expand_diff_hunk(None, &hunk, cx);
editor.expand_diff_hunk(None, &hunk, window, cx);
}
cx.notify();
})
@@ -365,7 +388,8 @@ impl Editor {
&mut self,
diff_base_buffer: Option<Model<Buffer>>,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Option<()> {
let buffer = self.buffer.clone();
let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -405,7 +429,13 @@ impl Editor {
blocks = self.insert_blocks(
[
self.hunk_header_block(&hunk, cx),
Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
Self::deleted_text_block(
hunk,
diff_base_buffer,
deleted_text_lines,
window,
cx,
),
],
None,
cx,
@@ -430,7 +460,13 @@ impl Editor {
blocks = self.insert_blocks(
[
self.hunk_header_block(&hunk, cx),
Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
Self::deleted_text_block(
hunk,
diff_base_buffer,
deleted_text_lines,
window,
cx,
),
],
None,
cx,
@@ -454,7 +490,8 @@ impl Editor {
fn apply_diff_hunks_in_range(
&mut self,
range: Range<Anchor>,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Option<()> {
let (buffer, range, _) = self
.buffer
@@ -468,7 +505,7 @@ impl Editor {
});
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
self.save(true, project, window, cx).detach_and_log_err(cx);
}
None
@@ -477,7 +514,8 @@ impl Editor {
pub(crate) fn apply_all_diff_hunks(
&mut self,
_: &ApplyAllDiffHunks,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let buffers = self.buffer.read(cx).all_buffers();
for branch_buffer in buffers {
@@ -487,19 +525,20 @@ impl Editor {
}
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
self.save(true, project, window, cx).detach_and_log_err(cx);
}
}
pub(crate) fn apply_selected_diff_hunks(
&mut self,
_: &ApplyDiffHunk,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.snapshot(cx);
let snapshot = self.snapshot(window, cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
let mut ranges_by_buffer = HashMap::default();
self.transact(cx, |editor, cx| {
self.transact(window, cx, |editor, _, cx| {
for hunk in hunks {
if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
ranges_by_buffer
@@ -517,7 +556,7 @@ impl Editor {
});
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
self.save(true, project, window, cx).detach_and_log_err(cx);
}
}
@@ -530,7 +569,7 @@ impl Editor {
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
cx: &mut ModelContext<Editor>,
) -> BlockProperties<Anchor> {
let is_branch_buffer = self
.buffer
@@ -554,7 +593,7 @@ impl Editor {
style: BlockStyle::Sticky,
priority: 0,
render: Arc::new({
let editor = cx.view().clone();
let editor = cx.model().clone();
let hunk = hunk.clone();
let has_multiple_hunks = self.has_multiple_hunks(cx);
@@ -565,7 +604,7 @@ impl Editor {
h_flex()
.id(cx.block_id)
.block_mouse_down()
.h(cx.line_height())
.h(cx.window.line_height())
.w_full()
.border_t_1()
.border_color(border_color)
@@ -573,16 +612,18 @@ impl Editor {
.child(
div()
.id("gutter-strip")
.w(EditorElement::diff_hunk_strip_width(cx.line_height()))
.w(EditorElement::diff_hunk_strip_width(
cx.window.line_height(),
))
.h_full()
.bg(gutter_color)
.cursor(CursorStyle::PointingHand)
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
editor.toggle_hovered_hunk(&hunk, window, cx);
});
}
}),
@@ -603,11 +644,12 @@ impl Editor {
.disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
@@ -615,10 +657,11 @@ impl Editor {
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.go_to_subsequent_hunk(
hunk.multi_buffer_range.end,
window,
cx,
);
});
@@ -632,11 +675,12 @@ impl Editor {
.disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPrevHunk,
&focus_handle,
window,
cx,
)
}
@@ -644,10 +688,11 @@ impl Editor {
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.go_to_preceding_hunk(
hunk.multi_buffer_range.start,
window,
cx,
);
});
@@ -661,11 +706,12 @@ impl Editor {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Discard Hunk",
&RevertSelectedHunks,
&focus_handle,
window,
cx,
)
}
@@ -673,9 +719,13 @@ impl Editor {
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.revert_hunk(hunk.clone(), cx);
editor.revert_hunk(
hunk.clone(),
window,
cx,
);
});
}
}),
@@ -689,11 +739,12 @@ impl Editor {
.tooltip({
let focus_handle =
editor.focus_handle(cx);
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Apply Hunk",
&ApplyDiffHunk,
&focus_handle,
window,
cx,
)
}
@@ -701,12 +752,13 @@ impl Editor {
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor
.apply_diff_hunks_in_range(
hunk.multi_buffer_range
.clone(),
window,
cx,
);
});
@@ -733,8 +785,8 @@ impl Editor {
!hunk_controls_menu_handle
.is_deployed(),
|this| {
this.tooltip(|cx| {
Tooltip::text(
this.tooltip(|_, cx| {
Tooltip::simple(
"Hunk Controls",
cx,
)
@@ -744,11 +796,12 @@ impl Editor {
)
.anchor(Corner::TopRight)
.with_handle(hunk_controls_menu_handle)
.menu(move |cx| {
.menu(move |window, cx| {
let focus = focus.clone();
let menu = ContextMenu::build(
window,
cx,
move |menu, _| {
move |menu, _, _| {
menu.context(focus.clone())
.action(
"Discard All Hunks",
@@ -770,11 +823,12 @@ impl Editor {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
move |window, cx| {
Tooltip::for_action_in(
"Collapse Hunk",
&ToggleHunkDiff,
&focus_handle,
window,
cx,
)
}
@@ -782,9 +836,10 @@ impl Editor {
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
editor
.toggle_hovered_hunk(&hunk, window, cx);
});
}
}),
@@ -801,7 +856,8 @@ impl Editor {
hunk: &HoveredHunk,
diff_base_buffer: Model<Buffer>,
deleted_text_height: u32,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> BlockProperties<Anchor> {
let gutter_color = match hunk.status {
DiffHunkStatus::Added => unreachable!(),
@@ -810,8 +866,8 @@ impl Editor {
};
let deleted_hunk_color = deleted_hunk_color(cx);
let (editor_height, editor_with_deleted_text) =
editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
let editor = cx.view().clone();
editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, window, cx);
let editor = cx.model().clone();
let hunk = hunk.clone();
let height = editor_height.max(deleted_text_height);
BlockProperties {
@@ -820,14 +876,14 @@ impl Editor {
style: BlockStyle::Flex,
priority: 0,
render: Arc::new(move |cx| {
let width = EditorElement::diff_hunk_strip_width(cx.line_height());
let gutter_dimensions = editor.read(cx.context).gutter_dimensions;
let width = EditorElement::diff_hunk_strip_width(cx.window.line_height());
let gutter_dimensions = editor.read(cx.app).gutter_dimensions;
h_flex()
.id(cx.block_id)
.block_mouse_down()
.bg(deleted_hunk_color)
.h(height as f32 * cx.line_height())
.h(height as f32 * cx.window.line_height())
.w_full()
.child(
h_flex()
@@ -850,9 +906,9 @@ impl Editor {
.on_mouse_down(MouseButton::Left, {
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
editor.toggle_hovered_hunk(&hunk, window, cx);
});
}
}),
@@ -864,7 +920,7 @@ impl Editor {
}
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<Editor>) -> bool {
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ModelContext<Editor>) -> bool {
if self.diff_map.expand_all {
return false;
}
@@ -887,7 +943,8 @@ impl Editor {
pub(super) fn sync_expanded_diff_hunks(
diff_map: &mut DiffMap,
buffer_id: BufferId,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
let mut diff_base_buffer = None;
@@ -904,10 +961,10 @@ impl Editor {
diff_map.hunk_update_tasks.remove(&Some(buffer_id));
let new_sync_task = cx.spawn(move |editor, mut cx| async move {
let new_sync_task = cx.spawn_in(window, move |editor, mut cx| async move {
editor
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
.update_in(&mut cx, |editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let mut recalculated_hunks = snapshot
.diff_map
.diff_hunks(&snapshot.buffer_snapshot)
@@ -1037,7 +1094,12 @@ impl Editor {
if let Some(diff_base_buffer) = &diff_base_buffer {
for hunk in hunks_to_reexpand {
editor.expand_diff_hunk(Some(diff_base_buffer.clone()), &hunk, cx);
editor.expand_diff_hunk(
Some(diff_base_buffer.clone()),
&hunk,
window,
cx,
);
}
}
})
@@ -1050,10 +1112,15 @@ impl Editor {
);
}
fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
fn go_to_subsequent_hunk(
&mut self,
position: Anchor,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.snapshot(window, cx);
let position = position.to_point(&snapshot.buffer_snapshot);
if let Some(hunk) = self.go_to_hunk_after_position(&snapshot, position, cx) {
if let Some(hunk) = self.go_to_hunk_after_position(&snapshot, position, window, cx) {
let multi_buffer_start = snapshot
.buffer_snapshot
.anchor_before(Point::new(hunk.row_range.start.0, 0));
@@ -1067,15 +1134,21 @@ impl Editor {
status: hunk_status(&hunk),
diff_base_byte_range: hunk.diff_base_byte_range,
},
window,
cx,
);
}
}
fn go_to_preceding_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
fn go_to_preceding_hunk(
&mut self,
position: Anchor,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.snapshot(window, cx);
let position = position.to_point(&snapshot.buffer_snapshot);
let hunk = self.go_to_hunk_before_position(&snapshot, position, cx);
let hunk = self.go_to_hunk_before_position(&snapshot, position, window, cx);
if let Some(hunk) = hunk {
let multi_buffer_start = snapshot
.buffer_snapshot
@@ -1090,6 +1163,7 @@ impl Editor {
status: hunk_status(&hunk),
diff_base_byte_range: hunk.diff_base_byte_range,
},
window,
cx,
);
}
@@ -1134,10 +1208,11 @@ fn editor_with_deleted_text(
diff_base_buffer: Model<Buffer>,
deleted_color: Hsla,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
) -> (u32, View<Editor>) {
let parent_editor = cx.view().downgrade();
let editor = cx.new_view(|cx| {
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> (u32, Model<Editor>) {
let parent_editor = cx.model().downgrade();
let editor = cx.new_model(|cx| {
let multi_buffer =
cx.new_model(|_| MultiBuffer::without_headers(language::Capability::ReadOnly));
multi_buffer.update(cx, |multi_buffer, cx| {
@@ -1151,7 +1226,7 @@ fn editor_with_deleted_text(
);
});
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, window, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
@@ -1162,7 +1237,7 @@ fn editor_with_deleted_text(
editor.set_show_code_actions(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.set_show_inline_completions(Some(false), window, cx);
enum DeletedBlockRowHighlight {}
editor.highlight_rows::<DeletedBlockRowHighlight>(
@@ -1172,21 +1247,25 @@ fn editor_with_deleted_text(
cx,
);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor
._subscriptions
.extend([cx.on_blur(&editor.focus_handle, |editor, cx| {
editor.change_selections(None, cx, |s| {
editor._subscriptions.extend([cx.on_blur(
&editor.focus_handle,
window,
|editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.try_cancel();
});
})]);
},
)]);
editor
.register_action::<RevertSelectedHunks>({
let hunk = hunk.clone();
let parent_editor = parent_editor.clone();
move |_, cx| {
move |_, window, cx| {
parent_editor
.update(cx, |editor, cx| editor.revert_hunk(hunk.clone(), cx))
.update(cx, |editor, cx| {
editor.revert_hunk(hunk.clone(), window, cx)
})
.ok();
}
})
@@ -1194,10 +1273,10 @@ fn editor_with_deleted_text(
editor
.register_action::<ToggleHunkDiff>({
let hunk = hunk.clone();
move |_, cx| {
move |_, window, cx| {
parent_editor
.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
editor.toggle_hovered_hunk(&hunk, window, cx);
})
.ok();
}
@@ -1409,9 +1488,10 @@ mod tests {
multibuffer
});
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, false, cx));
let editor = cx
.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, false, window, cx));
editor
.update(cx, |editor, cx| {
.update(cx, |editor, window, cx| {
for (buffer, diff_base) in [
(buffer_1.clone(), diff_base_1),
(buffer_2.clone(), diff_base_2),
@@ -1423,13 +1503,15 @@ mod tests {
cx,
)
});
editor.diff_map.add_change_set(change_set, cx)
editor.diff_map.add_change_set(change_set, window, cx)
}
})
.unwrap();
cx.background_executor.run_until_parked();
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
let snapshot = editor
.update(cx, |editor, window, cx| editor.snapshot(window, cx))
.unwrap();
assert_eq!(
snapshot.buffer_snapshot.text(),

View File

@@ -1,11 +1,10 @@
use std::{ops::Range, time::Duration};
use collections::HashSet;
use gpui::{AppContext, Task};
use gpui::{AppContext, ModelContext, Task, Window};
use language::{language_settings::language_settings, BufferRow};
use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};
use text::{BufferId, LineIndent, Point};
use ui::ViewContext;
use util::ResultExt;
use crate::{DisplaySnapshot, Editor};
@@ -35,7 +34,7 @@ impl Editor {
&self,
visible_buffer_range: Range<MultiBufferRow>,
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
cx: &mut ModelContext<Editor>,
) -> Option<Vec<MultiBufferIndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
@@ -68,7 +67,8 @@ impl Editor {
&mut self,
indent_guides: &[MultiBufferIndentGuide],
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut ModelContext<Editor>,
) -> Option<HashSet<usize>> {
let selection = self.selections.newest::<Point>(cx);
let cursor_row = MultiBufferRow(selection.head().row);
@@ -114,15 +114,16 @@ impl Editor {
{
Ok(result) => state.active_indent_range = result,
Err(future) => {
state.pending_refresh = Some(cx.spawn(|editor, mut cx| async move {
let result = cx.background_executor().spawn(future).await;
editor
.update(&mut cx, |editor, _| {
editor.active_indent_guides_state.active_indent_range = result;
editor.active_indent_guides_state.pending_refresh = None;
})
.log_err();
}));
state.pending_refresh =
Some(cx.spawn_in(window, |editor, mut cx| async move {
let result = cx.background_executor().spawn(future).await;
editor
.update(&mut cx, |editor, _| {
editor.active_indent_guides_state.active_indent_range = result;
editor.active_indent_guides_state.pending_refresh = None;
})
.log_err();
}));
return None;
}
}

Some files were not shown because too many files have changed in this diff Show More