Compare commits
108 Commits
v0.202.8
...
remove-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b4d9ffd96 | ||
|
|
9626de34f7 | ||
|
|
764af2fd87 | ||
|
|
a5fa81586e | ||
|
|
4d4ea61b12 | ||
|
|
9f56b9208a | ||
|
|
1cedbc04db | ||
|
|
643f2e1e45 | ||
|
|
6f6e995603 | ||
|
|
b43fcb7b09 | ||
|
|
de6abee965 | ||
|
|
93ad5c38f0 | ||
|
|
16e9304af6 | ||
|
|
71a6e7e677 | ||
|
|
f92da24fae | ||
|
|
4b3ed1152f | ||
|
|
fc43ec805d | ||
|
|
3738d9c34a | ||
|
|
2776d382c3 | ||
|
|
8e002b1e5a | ||
|
|
898bc8cb8c | ||
|
|
991345ed4e | ||
|
|
55d2e26fa5 | ||
|
|
2813cc077f | ||
|
|
696530b1e5 | ||
|
|
1c6b2b9dd0 | ||
|
|
f4d76abf39 | ||
|
|
4034691669 | ||
|
|
904e1ef8f7 | ||
|
|
9f73fb6ddc | ||
|
|
339ed1bad2 | ||
|
|
59fce2924d | ||
|
|
8d42bc011d | ||
|
|
47f46fe786 | ||
|
|
56d9e60ad7 | ||
|
|
b94279da00 | ||
|
|
f971c9eb5f | ||
|
|
be4ecd9e21 | ||
|
|
6a3834fc91 | ||
|
|
952d10239d | ||
|
|
8fdc5fa6ad | ||
|
|
79ec03cd14 | ||
|
|
df29eaf897 | ||
|
|
e3bccd7f0d | ||
|
|
4737c81c44 | ||
|
|
603b398d32 | ||
|
|
db387de6b3 | ||
|
|
a5fbde69ac | ||
|
|
cc5c668744 | ||
|
|
953586f532 | ||
|
|
b1485631cd | ||
|
|
55c46bbddf | ||
|
|
9f409de5fe | ||
|
|
426ae3339a | ||
|
|
fb05ab846b | ||
|
|
591352cd9e | ||
|
|
54945b922a | ||
|
|
16fa413d2d | ||
|
|
0de4094cc1 | ||
|
|
60ecf8345e | ||
|
|
2a3a9d06ad | ||
|
|
2aa3bd7099 | ||
|
|
bdc85d780e | ||
|
|
df4c51e9ea | ||
|
|
cb3bd8d442 | ||
|
|
d219bfc10f | ||
|
|
d768bfdce1 | ||
|
|
6c9d544341 | ||
|
|
ab1359b315 | ||
|
|
c641bbf0b1 | ||
|
|
6864b3e86d | ||
|
|
25416ff303 | ||
|
|
5e3ec9d81c | ||
|
|
5a3b9d8820 | ||
|
|
ec72020cb1 | ||
|
|
f2d41ca066 | ||
|
|
7510afccb8 | ||
|
|
ab8f8dde43 | ||
|
|
41de86f87f | ||
|
|
0ebd08871b | ||
|
|
dbb74ca28d | ||
|
|
8e987c273f | ||
|
|
859da6b13a | ||
|
|
54fe194f7b | ||
|
|
d07e1d2141 | ||
|
|
c9a477350f | ||
|
|
c49c33bf61 | ||
|
|
772489499c | ||
|
|
1d73e6646c | ||
|
|
efba1cb141 | ||
|
|
19ac46a252 | ||
|
|
cc95b79913 | ||
|
|
267df69b44 | ||
|
|
1de66771d6 | ||
|
|
d7b266ce62 | ||
|
|
61aa1d8f08 | ||
|
|
d4428c7c4b | ||
|
|
021e8a17f6 | ||
|
|
296fe144cb | ||
|
|
a7e5d5a298 | ||
|
|
838ebe90cd | ||
|
|
dcd2d85127 | ||
|
|
b05f723e95 | ||
|
|
30ffadfe32 | ||
|
|
04ffcab87d | ||
|
|
5a3233ebe4 | ||
|
|
a037f71432 | ||
|
|
d625a3e6ba |
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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,
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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![];
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, ¬ification, cx);
|
||||
self.did_render_notification(notification_id, ¬ification, 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(¬ification, cx)
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.did_click_notification(¬ification, 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);
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.))
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user