Detailed changes
@@ -19,6 +19,25 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "activity_indicator2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "auto_update2",
+ "editor2",
+ "futures 0.3.28",
+ "gpui2",
+ "language2",
+ "project2",
+ "settings2",
+ "smallvec",
+ "theme2",
+ "ui2",
+ "util",
+ "workspace2",
+]
+
[[package]]
name = "addr2line"
version = "0.17.0"
@@ -1076,6 +1095,23 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "breadcrumbs2"
+version = "0.1.0"
+dependencies = [
+ "collections",
+ "editor2",
+ "gpui2",
+ "itertools 0.10.5",
+ "language2",
+ "project2",
+ "search2",
+ "settings2",
+ "theme2",
+ "ui2",
+ "workspace2",
+]
+
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
@@ -1669,7 +1705,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.29.0"
+version = "0.29.1"
dependencies = [
"anyhow",
"async-trait",
@@ -9951,7 +9987,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.10"
-source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1"
+source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0"
dependencies = [
"cc",
"regex",
@@ -11697,6 +11733,7 @@ dependencies = [
name = "zed2"
version = "0.109.0"
dependencies = [
+ "activity_indicator2",
"ai2",
"anyhow",
"async-compression",
@@ -11706,6 +11743,7 @@ dependencies = [
"audio2",
"auto_update2",
"backtrace",
+ "breadcrumbs2",
"call2",
"channel2",
"chrono",
@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/activity_indicator",
+ "crates/activity_indicator2",
"crates/ai",
"crates/assistant",
"crates/audio",
@@ -8,6 +9,7 @@ members = [
"crates/auto_update",
"crates/auto_update2",
"crates/breadcrumbs",
+ "crates/breadcrumbs2",
"crates/call",
"crates/call2",
"crates/channel",
@@ -200,7 +202,7 @@ tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", re
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io]
-tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
+tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" }
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
@@ -530,12 +530,17 @@
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
- "space": "project_panel::Open",
"backspace": "project_panel::Delete",
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
+ {
+ "context": "ProjectPanel && not_editing",
+ "bindings": {
+ "space": "project_panel::Open"
+ }
+ },
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -0,0 +1,28 @@
+[package]
+name = "activity_indicator2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/activity_indicator.rs"
+doctest = false
+
+[dependencies]
+auto_update = { path = "../auto_update2", package = "auto_update2" }
+editor = { path = "../editor2", package = "editor2" }
+language = { path = "../language2", package = "language2" }
+gpui = { path = "../gpui2", package = "gpui2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+ui = { path = "../ui2", package = "ui2" }
+util = { path = "../util" }
+theme = { path = "../theme2", package = "theme2" }
+workspace = { path = "../workspace2", package = "workspace2" }
+
+anyhow.workspace = true
+futures.workspace = true
+smallvec.workspace = true
+
+[dev-dependencies]
+editor = { path = "../editor2", package = "editor2", features = ["test-support"] }
@@ -0,0 +1,333 @@
+use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
+use editor::Editor;
+use futures::StreamExt;
+use gpui::{
+ actions, svg, AppContext, CursorStyle, Div, EventEmitter, InteractiveElement as _, Model,
+ ParentElement as _, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, View,
+ ViewContext, VisualContext as _,
+};
+use language::{LanguageRegistry, LanguageServerBinaryStatus};
+use project::{LanguageServerProgress, Project};
+use smallvec::SmallVec;
+use std::{cmp::Reverse, fmt::Write, sync::Arc};
+use ui::h_stack;
+use util::ResultExt;
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
+
+actions!(ShowErrorMessage);
+
+const DOWNLOAD_ICON: &str = "icons/download.svg";
+const WARNING_ICON: &str = "icons/warning.svg";
+
+pub enum Event {
+ ShowError { lsp_name: Arc<str>, error: String },
+}
+
+pub struct ActivityIndicator {
+ statuses: Vec<LspStatus>,
+ project: Model<Project>,
+ auto_updater: Option<Model<AutoUpdater>>,
+}
+
+struct LspStatus {
+ name: Arc<str>,
+ status: LanguageServerBinaryStatus,
+}
+
+struct PendingWork<'a> {
+ language_server_name: &'a str,
+ progress_token: &'a str,
+ progress: &'a LanguageServerProgress,
+}
+
+#[derive(Default)]
+struct Content {
+ icon: Option<&'static str>,
+ message: String,
+ on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
+}
+
+impl ActivityIndicator {
+ pub fn new(
+ workspace: &mut Workspace,
+ languages: Arc<LanguageRegistry>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> View<ActivityIndicator> {
+ let project = workspace.project().clone();
+ let auto_updater = AutoUpdater::get(cx);
+ let this = cx.build_view(|cx: &mut ViewContext<Self>| {
+ let mut status_events = languages.language_server_binary_statuses();
+ cx.spawn(|this, mut cx| async move {
+ while let Some((language, event)) = status_events.next().await {
+ this.update(&mut cx, |this, cx| {
+ this.statuses.retain(|s| s.name != language.name());
+ this.statuses.push(LspStatus {
+ name: language.name(),
+ status: event,
+ });
+ cx.notify();
+ })?;
+ }
+ anyhow::Ok(())
+ })
+ .detach();
+ cx.observe(&project, |_, _, cx| cx.notify()).detach();
+
+ if let Some(auto_updater) = auto_updater.as_ref() {
+ cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
+ }
+
+ // cx.observe_active_labeled_tasks(|_, cx| cx.notify())
+ // .detach();
+
+ Self {
+ statuses: Default::default(),
+ project: project.clone(),
+ auto_updater,
+ }
+ });
+
+ cx.subscribe(&this, move |workspace, _, event, cx| match event {
+ Event::ShowError { lsp_name, error } => {
+ if let Some(buffer) = project
+ .update(cx, |project, cx| project.create_buffer(error, None, cx))
+ .log_err()
+ {
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ [(0..0, format!("Language server error: {}\n\n", lsp_name))],
+ None,
+ cx,
+ );
+ });
+ workspace.add_item(
+ Box::new(cx.build_view(|cx| {
+ Editor::for_buffer(buffer, Some(project.clone()), cx)
+ })),
+ cx,
+ );
+ }
+ }
+ })
+ .detach();
+ this
+ }
+
+ fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
+ self.statuses.retain(|status| {
+ if let LanguageServerBinaryStatus::Failed { error } = &status.status {
+ cx.emit(Event::ShowError {
+ lsp_name: status.name.clone(),
+ error: error.clone(),
+ });
+ false
+ } else {
+ true
+ }
+ });
+
+ cx.notify();
+ }
+
+ fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
+ if let Some(updater) = &self.auto_updater {
+ updater.update(cx, |updater, cx| {
+ updater.dismiss_error(cx);
+ });
+ }
+ cx.notify();
+ }
+
+ fn pending_language_server_work<'a>(
+ &self,
+ cx: &'a AppContext,
+ ) -> impl Iterator<Item = PendingWork<'a>> {
+ self.project
+ .read(cx)
+ .language_server_statuses()
+ .rev()
+ .filter_map(|status| {
+ if status.pending_work.is_empty() {
+ None
+ } else {
+ let mut pending_work = status
+ .pending_work
+ .iter()
+ .map(|(token, progress)| PendingWork {
+ language_server_name: status.name.as_str(),
+ progress_token: token.as_str(),
+ progress,
+ })
+ .collect::<SmallVec<[_; 4]>>();
+ pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
+ Some(pending_work)
+ }
+ })
+ .flatten()
+ }
+
+ fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
+ // Show any language server has pending activity.
+ let mut pending_work = self.pending_language_server_work(cx);
+ if let Some(PendingWork {
+ language_server_name,
+ progress_token,
+ progress,
+ }) = pending_work.next()
+ {
+ let mut message = language_server_name.to_string();
+
+ message.push_str(": ");
+ if let Some(progress_message) = progress.message.as_ref() {
+ message.push_str(progress_message);
+ } else {
+ message.push_str(progress_token);
+ }
+
+ if let Some(percentage) = progress.percentage {
+ write!(&mut message, " ({}%)", percentage).unwrap();
+ }
+
+ let additional_work_count = pending_work.count();
+ if additional_work_count > 0 {
+ write!(&mut message, " + {} more", additional_work_count).unwrap();
+ }
+
+ return Content {
+ icon: None,
+ message,
+ on_click: None,
+ };
+ }
+
+ // Show any language server installation info.
+ let mut downloading = SmallVec::<[_; 3]>::new();
+ let mut checking_for_update = SmallVec::<[_; 3]>::new();
+ let mut failed = SmallVec::<[_; 3]>::new();
+ for status in &self.statuses {
+ let name = status.name.clone();
+ match status.status {
+ LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
+ LanguageServerBinaryStatus::Downloading => downloading.push(name),
+ LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
+ LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
+ }
+ }
+
+ if !downloading.is_empty() {
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
+ "Downloading {} language server{}...",
+ downloading.join(", "),
+ if downloading.len() > 1 { "s" } else { "" }
+ ),
+ on_click: None,
+ };
+ } else if !checking_for_update.is_empty() {
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
+ "Checking for updates to {} language server{}...",
+ checking_for_update.join(", "),
+ if checking_for_update.len() > 1 {
+ "s"
+ } else {
+ ""
+ }
+ ),
+ on_click: None,
+ };
+ } else if !failed.is_empty() {
+ return Content {
+ icon: Some(WARNING_ICON),
+ message: format!(
+ "Failed to download {} language server{}. Click to show error.",
+ failed.join(", "),
+ if failed.len() > 1 { "s" } else { "" }
+ ),
+ on_click: Some(Arc::new(|this, cx| {
+ this.show_error_message(&Default::default(), cx)
+ })),
+ };
+ }
+
+ // Show any application auto-update info.
+ if let Some(updater) = &self.auto_updater {
+ return match &updater.read(cx).status() {
+ AutoUpdateStatus::Checking => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Checking for Zed updatesβ¦".to_string(),
+ on_click: None,
+ },
+ AutoUpdateStatus::Downloading => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Downloading Zed updateβ¦".to_string(),
+ on_click: None,
+ },
+ AutoUpdateStatus::Installing => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Installing Zed updateβ¦".to_string(),
+ on_click: None,
+ },
+ AutoUpdateStatus::Updated => Content {
+ icon: None,
+ message: "Click to restart and update Zed".to_string(),
+ on_click: Some(Arc::new(|_, cx| {
+ workspace::restart(&Default::default(), cx)
+ })),
+ },
+ AutoUpdateStatus::Errored => Content {
+ icon: Some(WARNING_ICON),
+ message: "Auto update failed".to_string(),
+ on_click: Some(Arc::new(|this, cx| {
+ this.dismiss_error_message(&Default::default(), cx)
+ })),
+ },
+ AutoUpdateStatus::Idle => Default::default(),
+ };
+ }
+
+ // todo!(show active tasks)
+ // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
+ // return Content {
+ // icon: None,
+ // message: most_recent_active_task.to_string(),
+ // on_click: None,
+ // };
+ // }
+
+ Default::default()
+ }
+}
+
+impl EventEmitter<Event> for ActivityIndicator {}
+
+impl Render for ActivityIndicator {
+ type Element = Stateful<Div>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ let content = self.content_to_render(cx);
+
+ let mut result = h_stack()
+ .id("activity-indicator")
+ .on_action(cx.listener(Self::show_error_message))
+ .on_action(cx.listener(Self::dismiss_error_message));
+
+ if let Some(on_click) = content.on_click {
+ result = result
+ .cursor(CursorStyle::PointingHand)
+ .on_click(cx.listener(move |this, _, cx| {
+ on_click(this, cx);
+ }))
+ }
+
+ result
+ .children(content.icon.map(|icon| svg().path(icon)))
+ .child(SharedString::from(content.message))
+ }
+}
+
+impl StatusItemView for ActivityIndicator {
+ fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
+}
@@ -1218,6 +1218,31 @@ impl View for AssistantPanel {
let style = &theme.assistant;
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
Flex::column()
+ .with_child(
+ Text::new(
+ "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " - Having a subscription for another service like GitHub Copilot won't work.",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " - You can create a api key at: platform.openai.com/api-keys",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " ",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
.with_child(
Text::new(
"Paste your OpenAI API key and press Enter to use the assistant",
@@ -1231,6 +1256,20 @@ impl View for AssistantPanel {
.with_style(style.api_key_editor.container)
.aligned(),
)
+ .with_child(
+ Text::new(
+ " ",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
+ .with_child(
+ Text::new(
+ "Click on the Z button in the status bar to close this panel.",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
.contained()
.with_style(style.api_key_prompt.container)
.aligned()
@@ -102,7 +102,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
})
.detach();
- if let Some(version) = *ZED_APP_VERSION {
+ if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
let auto_updater = cx.build_model(|cx| {
let updater = AutoUpdater::new(version, http_client, server_url);
@@ -0,0 +1,28 @@
+[package]
+name = "breadcrumbs2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/breadcrumbs.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+ui = { package = "ui2", path = "../ui2" }
+language = { package = "language2", path = "../language2" }
+project = { package = "project2", path = "../project2" }
+search = { package = "search2", path = "../search2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+# outline = { path = "../outline" }
+itertools = "0.10"
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
@@ -0,0 +1,204 @@
+use gpui::{
+ Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
+ ViewContext, WeakView,
+};
+use itertools::Itertools;
+use theme::ActiveTheme;
+use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
+use workspace::{
+ item::{ItemEvent, ItemHandle},
+ ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+pub enum Event {
+ UpdateLocation,
+}
+
+pub struct Breadcrumbs {
+ pane_focused: bool,
+ active_item: Option<Box<dyn ItemHandle>>,
+ subscription: Option<Subscription>,
+ _workspace: WeakView<Workspace>,
+}
+
+impl Breadcrumbs {
+ pub fn new(workspace: &Workspace) -> Self {
+ Self {
+ pane_focused: false,
+ active_item: Default::default(),
+ subscription: Default::default(),
+ _workspace: workspace.weak_handle(),
+ }
+ }
+}
+
+impl EventEmitter<Event> for Breadcrumbs {}
+impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
+
+impl Render for Breadcrumbs {
+ type Element = Component<ButtonLike>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ let button = ButtonLike::new("breadcrumbs")
+ .style(ButtonStyle::Transparent)
+ .disabled(true);
+
+ let active_item = match &self.active_item {
+ Some(active_item) => active_item,
+ None => return button.into_element(),
+ };
+ let not_editor = active_item.downcast::<editor::Editor>().is_none();
+
+ let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
+ Some(breadcrumbs) => breadcrumbs,
+ None => return button.into_element(),
+ }
+ .into_iter()
+ .map(|breadcrumb| {
+ StyledText::new(breadcrumb.text)
+ .with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
+ .into_any()
+ });
+
+ let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
+ Label::new(" βΊ ").into_any_element()
+ }));
+
+ if not_editor || !self.pane_focused {
+ return button.into_element();
+ }
+
+ // let this = cx.view().downgrade();
+ button
+ .style(ButtonStyle::Filled)
+ .disabled(false)
+ .on_click(move |_, _cx| {
+ todo!("outline::toggle");
+ // this.update(cx, |this, cx| {
+ // if let Some(workspace) = this.workspace.upgrade() {
+ // workspace.update(cx, |_workspace, _cx| {
+ // outline::toggle(workspace, &Default::default(), cx)
+ // })
+ // }
+ // })
+ // .ok();
+ })
+ .into_element()
+ }
+}
+
+// impl View for Breadcrumbs {
+// fn ui_name() -> &'static str {
+// "Breadcrumbs"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// let active_item = match &self.active_item {
+// Some(active_item) => active_item,
+// None => return Empty::new().into_any(),
+// };
+// let not_editor = active_item.downcast::<editor::Editor>().is_none();
+
+// let theme = theme::current(cx).clone();
+// let style = &theme.workspace.toolbar.breadcrumbs;
+
+// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
+// Some(breadcrumbs) => breadcrumbs,
+// None => return Empty::new().into_any(),
+// }
+// .into_iter()
+// .map(|breadcrumb| {
+// Text::new(
+// breadcrumb.text,
+// theme.workspace.toolbar.breadcrumbs.default.text.clone(),
+// )
+// .with_highlights(breadcrumb.highlights.unwrap_or_default())
+// .into_any()
+// });
+
+// let crumbs = Flex::row()
+// .with_children(Itertools::intersperse_with(breadcrumbs, || {
+// Label::new(" βΊ ", style.default.text.clone()).into_any()
+// }))
+// .constrained()
+// .with_height(theme.workspace.toolbar.breadcrumb_height)
+// .contained();
+
+// if not_editor || !self.pane_focused {
+// return crumbs
+// .with_style(style.default.container)
+// .aligned()
+// .left()
+// .into_any();
+// }
+
+// MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
+// let style = style.style_for(state);
+// crumbs.with_style(style.container)
+// })
+// .on_click(MouseButton::Left, |_, this, cx| {
+// if let Some(workspace) = this.workspace.upgrade(cx) {
+// workspace.update(cx, |workspace, cx| {
+// outline::toggle(workspace, &Default::default(), cx)
+// })
+// }
+// })
+// .with_tooltip::<Breadcrumbs>(
+// 0,
+// "Show symbol outline".to_owned(),
+// Some(Box::new(outline::Toggle)),
+// theme.tooltip.clone(),
+// cx,
+// )
+// .aligned()
+// .left()
+// .into_any()
+// }
+// }
+
+impl ToolbarItemView for Breadcrumbs {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ ) -> ToolbarItemLocation {
+ cx.notify();
+ self.active_item = None;
+ if let Some(item) = active_pane_item {
+ let this = cx.view().downgrade();
+ self.subscription = Some(item.subscribe_to_item_events(
+ cx,
+ Box::new(move |event, cx| {
+ if let ItemEvent::UpdateBreadcrumbs = event {
+ this.update(cx, |_, cx| {
+ cx.emit(Event::UpdateLocation);
+ cx.notify();
+ })
+ .ok();
+ }
+ }),
+ ));
+ self.active_item = Some(item.boxed_clone());
+ item.breadcrumb_location(cx)
+ } else {
+ ToolbarItemLocation::Hidden
+ }
+ }
+
+ // fn location_for_event(
+ // &self,
+ // _: &Event,
+ // current_location: ToolbarItemLocation,
+ // cx: &AppContext,
+ // ) -> ToolbarItemLocation {
+ // if let Some(active_item) = self.active_item.as_ref() {
+ // active_item.breadcrumb_location(cx)
+ // } else {
+ // current_location
+ // }
+ // }
+
+ fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
+ self.pane_focused = pane_focused;
+ }
+}
@@ -15,7 +15,7 @@ use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
- Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowHandle,
+ Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
};
pub use participant::ParticipantLocation;
use postage::watch;
@@ -557,24 +557,17 @@ pub fn report_call_event_for_channel(
pub struct Call {
active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
- parent_workspace: WeakView<Workspace>,
}
impl Call {
- pub fn new(
- parent_workspace: WeakView<Workspace>,
- cx: &mut ViewContext<'_, Workspace>,
- ) -> Box<dyn CallHandler> {
+ pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
let mut active_call = None;
if cx.has_global::<Model<ActiveCall>>() {
let call = cx.global::<Model<ActiveCall>>().clone();
let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
active_call = Some((call, subscriptions));
}
- Box::new(Self {
- active_call,
- parent_workspace,
- })
+ Box::new(Self { active_call })
}
fn on_active_call_event(
workspace: &mut Workspace,
@@ -597,6 +590,7 @@ impl CallHandler for Call {
fn peer_state(
&mut self,
leader_id: PeerId,
+ project: &Model<Project>,
cx: &mut ViewContext<Workspace>,
) -> Option<(bool, bool)> {
let (call, _) = self.active_call.as_ref()?;
@@ -608,12 +602,7 @@ impl CallHandler for Call {
match participant.location {
ParticipantLocation::SharedProject { project_id } => {
leader_in_this_app = true;
- leader_in_this_project = Some(project_id)
- == self
- .parent_workspace
- .update(cx, |this, cx| this.project().read(cx).remote_id())
- .log_err()
- .flatten();
+ leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
}
ParticipantLocation::UnsharedProject => {
leader_in_this_app = true;
@@ -3,10 +3,12 @@ use anyhow::Result;
use client::{proto::PeerId, User};
use futures::StreamExt;
use gpui::{
- div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render,
- SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+ div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
+ InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
+ VisualContext, WindowContext,
};
use std::sync::{Arc, Weak};
+use ui::{h_stack, Icon, IconElement};
use workspace::{item::Item, ItemNavHistory, WorkspaceId};
pub enum Event {
@@ -16,8 +18,6 @@ pub enum Event {
pub struct SharedScreen {
track: Weak<RemoteVideoTrack>,
frame: Option<Frame>,
- // temporary addition just to render something interactive.
- current_frame_id: usize,
pub peer_id: PeerId,
user: Arc<User>,
nav_history: Option<ItemNavHistory>,
@@ -51,7 +51,6 @@ impl SharedScreen {
Ok(())
}),
focus: cx.focus_handle(),
- current_frame_id: 0,
}
}
}
@@ -65,50 +64,16 @@ impl FocusableView for SharedScreen {
}
}
impl Render for SharedScreen {
- type Element = Div;
+ type Element = Focusable<Div>;
+
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
- let frame = self.frame.clone();
- let frame_id = self.current_frame_id;
- self.current_frame_id = self.current_frame_id.wrapping_add(1);
- div().children(frame.map(|_| {
- ui::Label::new(frame_id.to_string()).color(ui::Color::Error)
- // img().data(Arc::new(ImageData::new(image::ImageBuffer::new(
- // frame.width() as u32,
- // frame.height() as u32,
- // ))))
- }))
+ div().track_focus(&self.focus).size_full().children(
+ self.frame
+ .as_ref()
+ .map(|frame| img(frame.image()).size_full()),
+ )
}
}
-// impl View for SharedScreen {
-// fn ui_name() -> &'static str {
-// "SharedScreen"
-// }
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// enum Focus {}
-
-// let frame = self.frame.clone();
-// MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
-// Canvas::new(move |bounds, _, _, cx| {
-// if let Some(frame) = frame.clone() {
-// let size = constrain_size_preserving_aspect_ratio(
-// bounds.size(),
-// vec2f(frame.width() as f32, frame.height() as f32),
-// );
-// let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-// cx.scene().push_surface(gpui::platform::mac::Surface {
-// bounds: RectF::new(origin, size),
-// image_buffer: frame.image(),
-// });
-// }
-// })
-// .contained()
-// .with_style(theme::current(cx).shared_screen)
-// })
-// .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
-// .into_any()
-// }
-// }
impl Item for SharedScreen {
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
@@ -121,25 +86,14 @@ impl Item for SharedScreen {
}
fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
- div().child("Shared screen").into_any()
- // Flex::row()
- // .with_child(
- // Svg::new("icons/desktop.svg")
- // .with_color(style.label.text.color)
- // .constrained()
- // .with_width(style.type_icon_width)
- // .aligned()
- // .contained()
- // .with_margin_right(style.spacing),
- // )
- // .with_child(
- // Label::new(
- // format!("{}'s screen", self.user.github_login),
- // style.label.clone(),
- // )
- // .aligned(),
- // )
- // .into_any()
+ h_stack()
+ .gap_1()
+ .child(IconElement::new(Icon::Screen))
+ .child(SharedString::from(format!(
+ "{}'s screen",
+ self.user.github_login
+ )))
+ .into_any()
}
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
@@ -346,7 +346,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
}
}
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct TelemetrySettings {
pub diagnostics: bool,
pub metrics: bool,
@@ -350,6 +350,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
+ dbg!(telemetry_settings);
self.report_clickhouse_event(event, telemetry_settings, true)
}
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
-version = "0.29.0"
+version = "0.29.1"
publish = false
[[bin]]
@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
+ if new_parent
+ .ancestors_including_self()
+ .any(|id| id == channel.id)
+ {
+ Err(anyhow!("cannot move a channel into one of its descendants"))?;
+ }
+
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {
@@ -450,6 +450,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
+
+ // Can't move a channel into its ancestor
+ db.move_channel(projects_id, Some(livestreaming_id), user_id)
+ .await
+ .unwrap_err();
+ let result = db.get_channels_for_user(user_id).await.unwrap();
+ assert_channel_tree(
+ result.channels,
+ &[
+ (zed_id, &[]),
+ (projects_id, &[]),
+ (livestreaming_id, &[projects_id]),
+ ],
+ );
}
test_both_dbs!(
@@ -3941,7 +3941,7 @@ async fn test_collaborating_with_diagnostics(
// Ensure client B observes the new diagnostics.
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3961,14 +3961,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
- project.diagnostic_summaries(cx).collect::<Vec<_>>()
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
- *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+ *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -4018,7 +4018,7 @@ async fn test_collaborating_with_diagnostics(
deterministic.run_until_parked();
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4034,7 +4034,7 @@ async fn test_collaborating_with_diagnostics(
});
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4097,13 +4097,22 @@ async fn test_collaborating_with_diagnostics(
);
deterministic.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_b.read_with(cx_b, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_c.read_with(cx_c, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
}
@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
+ if new_parent
+ .ancestors_including_self()
+ .any(|id| id == channel.id)
+ {
+ Err(anyhow!("cannot move a channel into one of its descendants"))?;
+ }
+
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {
@@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
.await
.unwrap();
- // Dag is: zed - projects - livestreaming
-
// Move to same parent should be a no-op
assert!(db
.move_channel(projects_id, Some(zed_id), user_id)
@@ -450,6 +448,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
+
+ // Can't move a channel into its ancestor
+ db.move_channel(projects_id, Some(livestreaming_id), user_id)
+ .await
+ .unwrap_err();
+ let result = db.get_channels_for_user(user_id).await.unwrap();
+ assert_channel_tree(
+ result.channels,
+ &[
+ (zed_id, &[]),
+ (projects_id, &[]),
+ (livestreaming_id, &[projects_id]),
+ ],
+ );
}
test_both_dbs!(
@@ -3688,7 +3688,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3708,14 +3708,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
- project.diagnostic_summaries(cx).collect::<Vec<_>>()
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
- *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+ *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -3766,7 +3766,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3783,7 +3783,7 @@ async fn test_collaborating_with_diagnostics(
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3844,15 +3844,24 @@ async fn test_collaborating_with_diagnostics(
executor.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_b.read_with(cx_b, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_c.read_with(cx_c, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
}
@@ -221,7 +221,7 @@ impl TestServer {
fs: fs.clone(),
build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(),
- call_factory: |_, _| Box::new(workspace::TestCallHandler),
+ call_factory: |_| Box::new(workspace::TestCallHandler),
});
cx.update(|cx| {
@@ -17,8 +17,9 @@ mod contact_finder;
// Client, Contact, User, UserStore,
// };
use contact_finder::ContactFinder;
-use menu::Confirm;
+use menu::{Cancel, Confirm, SelectNext, SelectPrev};
use rpc::proto;
+use theme::{ActiveTheme, ThemeSettings};
// use context_menu::{ContextMenu, ContextMenuItem};
// use db::kvp::KEY_VALUE_STORE;
// use drag_and_drop::{DragAndDrop, Draggable};
@@ -151,10 +152,10 @@ actions!(
// ]
// );
-// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-// struct ChannelMoveClipboard {
-// channel_id: ChannelId,
-// }
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+struct ChannelMoveClipboard {
+ channel_id: ChannelId,
+}
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
@@ -168,17 +169,19 @@ use editor::Editor;
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
- actions, div, img, prelude::*, serde_json, Action, AppContext, AsyncWindowContext, Div,
- EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model,
- ParentElement, PromptLevel, Render, RenderOnce, SharedString, Styled, Subscription, Task, View,
- ViewContext, VisualContext, WeakView,
+ actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
+ AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle,
+ Focusable, FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent,
+ ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, ScrollHandle, SharedString,
+ Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
};
-use project::Fs;
+use project::{Fs, Project};
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
+use ui::prelude::*;
use ui::{
- h_stack, v_stack, Avatar, Button, Color, Icon, IconButton, IconElement, Label, List,
- ListHeader, ListItem, Toggle, Tooltip,
+ h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
+ Label, List, ListHeader, ListItem, Tooltip,
};
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -230,26 +233,6 @@ pub fn init(cx: &mut AppContext) {
// },
// );
- // cx.add_action(
- // |panel: &mut CollabPanel,
- // action: &StartMoveChannelFor,
- // _: &mut ViewContext<CollabPanel>| {
- // panel.channel_clipboard = Some(ChannelMoveClipboard {
- // channel_id: action.channel_id,
- // });
- // },
- // );
-
- // cx.add_action(
- // |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
- // if let Some(channel) = panel.selected_channel() {
- // panel.channel_clipboard = Some(ChannelMoveClipboard {
- // channel_id: channel.id,
- // })
- // }
- // },
- // );
-
// cx.add_action(
// |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
// let Some(clipboard) = panel.channel_clipboard.take() else {
@@ -303,12 +286,12 @@ impl ChannelEditingState {
}
pub struct CollabPanel {
- width: Option<f32>,
+ width: Option<Pixels>,
fs: Arc<dyn Fs>,
focus_handle: FocusHandle,
- // channel_clipboard: Option<ChannelMoveClipboard>,
+ channel_clipboard: Option<ChannelMoveClipboard>,
pending_serialization: Task<Option<()>>,
- // context_menu: ViewHandle<ContextMenu>,
+ context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
filter_editor: View<Editor>,
channel_name_editor: View<Editor>,
channel_editing_state: Option<ChannelEditingState>,
@@ -317,9 +300,9 @@ pub struct CollabPanel {
channel_store: Model<ChannelStore>,
user_store: Model<UserStore>,
client: Arc<Client>,
- // project: ModelHandle<Project>,
+ project: Model<Project>,
match_candidates: Vec<StringMatchCandidate>,
- // list_state: ListState<Self>,
+ scroll_handle: ScrollHandle,
subscriptions: Vec<Subscription>,
collapsed_sections: Vec<Section>,
collapsed_channels: Vec<ChannelId>,
@@ -337,7 +320,7 @@ enum ChannelDragTarget {
#[derive(Serialize, Deserialize)]
struct SerializedCollabPanel {
- width: Option<f32>,
+ width: Option<Pixels>,
collapsed_channels: Option<Vec<u64>>,
}
@@ -401,10 +384,6 @@ enum ListEntry {
ContactPlaceholder,
}
-// impl Entity for CollabPanel {
-// type Event = Event;
-// }
-
impl CollabPanel {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
cx.build_view(|cx| {
@@ -416,28 +395,28 @@ impl CollabPanel {
editor
});
- // cx.subscribe(&filter_editor, |this, _, event, cx| {
- // if let editor::Event::BufferEdited = event {
- // let query = this.filter_editor.read(cx).text(cx);
- // if !query.is_empty() {
- // this.selection.take();
- // }
- // this.update_entries(true, cx);
- // if !query.is_empty() {
- // this.selection = this
- // .entries
- // .iter()
- // .position(|entry| !matches!(entry, ListEntry::Header(_)));
- // }
- // } else if let editor::Event::Blurred = event {
- // let query = this.filter_editor.read(cx).text(cx);
- // if query.is_empty() {
- // this.selection.take();
- // this.update_entries(true, cx);
- // }
- // }
- // })
- // .detach();
+ cx.subscribe(&filter_editor, |this: &mut Self, _, event, cx| {
+ if let editor::EditorEvent::BufferEdited = event {
+ let query = this.filter_editor.read(cx).text(cx);
+ if !query.is_empty() {
+ this.selection.take();
+ }
+ this.update_entries(true, cx);
+ if !query.is_empty() {
+ this.selection = this
+ .entries
+ .iter()
+ .position(|entry| !matches!(entry, ListEntry::Header(_)));
+ }
+ } else if let editor::EditorEvent::Blurred = event {
+ let query = this.filter_editor.read(cx).text(cx);
+ if query.is_empty() {
+ this.selection.take();
+ this.update_entries(true, cx);
+ }
+ }
+ })
+ .detach();
let channel_name_editor = cx.build_view(|cx| Editor::single_line(cx));
@@ -589,10 +568,10 @@ impl CollabPanel {
let mut this = Self {
width: None,
focus_handle: cx.focus_handle(),
- // channel_clipboard: None,
+ channel_clipboard: None,
fs: workspace.app_state().fs.clone(),
pending_serialization: Task::ready(None),
- // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+ context_menu: None,
channel_name_editor,
filter_editor,
entries: Vec::default(),
@@ -600,16 +579,16 @@ impl CollabPanel {
selection: None,
channel_store: ChannelStore::global(cx),
user_store: workspace.user_store().clone(),
- // project: workspace.project().clone(),
+ project: workspace.project().clone(),
subscriptions: Vec::default(),
match_candidates: Vec::default(),
+ scroll_handle: ScrollHandle::new(),
collapsed_sections: vec![Section::Offline],
collapsed_channels: Vec::default(),
workspace: workspace.weak_handle(),
client: workspace.app_state().client.clone(),
// context_menu_on_selected: true,
drag_target_channel: ChannelDragTarget::None,
- // list_state,
};
this.update_entries(false, cx);
@@ -725,9 +704,9 @@ impl CollabPanel {
let query = self.filter_editor.read(cx).text(cx);
let executor = cx.background_executor().clone();
- // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
- let _old_entries = mem::take(&mut self.entries);
- // let mut scroll_to_top = false;
+ let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
+ let old_entries = mem::take(&mut self.entries);
+ let scroll_to_top = false;
// if let Some(room) = ActiveCall::global(cx).read(cx).room() {
// self.entries.push(ListEntry::Header(Section::ActiveCall));
@@ -1094,71 +1073,62 @@ impl CollabPanel {
self.entries.push(ListEntry::ContactPlaceholder);
}
- // if select_same_item {
- // if let Some(prev_selected_entry) = prev_selected_entry {
- // self.selection.take();
- // for (ix, entry) in self.entries.iter().enumerate() {
- // if *entry == prev_selected_entry {
- // self.selection = Some(ix);
- // break;
- // }
- // }
- // }
- // } else {
- // self.selection = self.selection.and_then(|prev_selection| {
- // if self.entries.is_empty() {
- // None
- // } else {
- // Some(prev_selection.min(self.entries.len() - 1))
- // }
- // });
- // }
-
- // let old_scroll_top = self.list_state.logical_scroll_top();
-
- // self.list_state.reset(self.entries.len());
+ if select_same_item {
+ if let Some(prev_selected_entry) = prev_selected_entry {
+ self.selection.take();
+ for (ix, entry) in self.entries.iter().enumerate() {
+ if *entry == prev_selected_entry {
+ self.selection = Some(ix);
+ self.scroll_handle.scroll_to_item(ix);
+ break;
+ }
+ }
+ }
+ } else {
+ self.selection = self.selection.and_then(|prev_selection| {
+ if self.entries.is_empty() {
+ None
+ } else {
+ let ix = prev_selection.min(self.entries.len() - 1);
+ self.scroll_handle.scroll_to_item(ix);
+ Some(ix)
+ }
+ });
+ }
- // if scroll_to_top {
- // self.list_state.scroll_to(ListOffset::default());
- // } else {
- // // Attempt to maintain the same scroll position.
- // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
- // let new_scroll_top = self
- // .entries
- // .iter()
- // .position(|entry| entry == old_top_entry)
- // .map(|item_ix| ListOffset {
- // item_ix,
- // offset_in_item: old_scroll_top.offset_in_item,
- // })
- // .or_else(|| {
- // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
- // let item_ix = self
- // .entries
- // .iter()
- // .position(|entry| entry == entry_after_old_top)?;
- // Some(ListOffset {
- // item_ix,
- // offset_in_item: 0.,
- // })
- // })
- // .or_else(|| {
- // let entry_before_old_top =
- // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
- // let item_ix = self
- // .entries
- // .iter()
- // .position(|entry| entry == entry_before_old_top)?;
- // Some(ListOffset {
- // item_ix,
- // offset_in_item: 0.,
- // })
- // });
+ if scroll_to_top {
+ self.scroll_handle.scroll_to_item(0)
+ } else {
+ let (old_index, old_offset) = self.scroll_handle.logical_scroll_top();
+ // Attempt to maintain the same scroll position.
+ if let Some(old_top_entry) = old_entries.get(old_index) {
+ let (new_index, new_offset) = self
+ .entries
+ .iter()
+ .position(|entry| entry == old_top_entry)
+ .map(|item_ix| (item_ix, old_offset))
+ .or_else(|| {
+ let entry_after_old_top = old_entries.get(old_index + 1)?;
+ let item_ix = self
+ .entries
+ .iter()
+ .position(|entry| entry == entry_after_old_top)?;
+ Some((item_ix, px(0.)))
+ })
+ .or_else(|| {
+ let entry_before_old_top = old_entries.get(old_index.saturating_sub(1))?;
+ let item_ix = self
+ .entries
+ .iter()
+ .position(|entry| entry == entry_before_old_top)?;
+ Some((item_ix, px(0.)))
+ })
+ .unwrap_or_else(|| (old_index, old_offset));
- // self.list_state
- // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
- // }
- // }
+ self.scroll_handle
+ .set_logical_scroll_top(new_index, new_offset);
+ }
+ }
cx.notify();
}
@@ -1685,280 +1655,238 @@ impl CollabPanel {
// .into_any()
// }
- // fn has_subchannels(&self, ix: usize) -> bool {
- // self.entries.get(ix).map_or(false, |entry| {
- // if let ListEntry::Channel { has_children, .. } = entry {
- // *has_children
- // } else {
- // false
- // }
- // })
- // }
-
- // fn deploy_channel_context_menu(
- // &mut self,
- // position: Option<Vector2F>,
- // channel: &Channel,
- // ix: usize,
- // cx: &mut ViewContext<Self>,
- // ) {
- // self.context_menu_on_selected = position.is_none();
-
- // let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
- // self.channel_store
- // .read(cx)
- // .channel_for_id(clipboard.channel_id)
- // .map(|channel| channel.name.clone())
- // });
+ fn has_subchannels(&self, ix: usize) -> bool {
+ self.entries.get(ix).map_or(false, |entry| {
+ if let ListEntry::Channel { has_children, .. } = entry {
+ *has_children
+ } else {
+ false
+ }
+ })
+ }
- // self.context_menu.update(cx, |context_menu, cx| {
- // context_menu.set_position_mode(if self.context_menu_on_selected {
- // OverlayPositionMode::Local
- // } else {
- // OverlayPositionMode::Window
- // });
+ fn deploy_channel_context_menu(
+ &mut self,
+ position: Point<Pixels>,
+ channel_id: ChannelId,
+ ix: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
+ self.channel_store
+ .read(cx)
+ .channel_for_id(clipboard.channel_id)
+ .map(|channel| channel.name.clone())
+ });
+ let this = cx.view().clone();
- // let mut items = Vec::new();
+ let context_menu = ContextMenu::build(cx, |mut context_menu, cx| {
+ if self.has_subchannels(ix) {
+ let expand_action_name = if self.is_channel_collapsed(channel_id) {
+ "Expand Subchannels"
+ } else {
+ "Collapse Subchannels"
+ };
+ context_menu = context_menu.entry(
+ expand_action_name,
+ cx.handler_for(&this, move |this, cx| {
+ this.toggle_channel_collapsed(channel_id, cx)
+ }),
+ );
+ }
- // let select_action_name = if self.selection == Some(ix) {
- // "Unselect"
- // } else {
- // "Select"
- // };
+ context_menu = context_menu
+ .entry(
+ "Open Notes",
+ cx.handler_for(&this, move |this, cx| {
+ this.open_channel_notes(channel_id, cx)
+ }),
+ )
+ .entry(
+ "Open Chat",
+ cx.handler_for(&this, move |this, cx| {
+ this.join_channel_chat(channel_id, cx)
+ }),
+ )
+ .entry(
+ "Copy Channel Link",
+ cx.handler_for(&this, move |this, cx| {
+ this.copy_channel_link(channel_id, cx)
+ }),
+ );
- // items.push(ContextMenuItem::action(
- // select_action_name,
- // ToggleSelectedIx { ix },
- // ));
+ if self.channel_store.read(cx).is_channel_admin(channel_id) {
+ context_menu = context_menu
+ .separator()
+ .entry(
+ "New Subchannel",
+ cx.handler_for(&this, move |this, cx| this.new_subchannel(channel_id, cx)),
+ )
+ .entry(
+ "Rename",
+ cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
+ )
+ .entry(
+ "Move this channel",
+ cx.handler_for(&this, move |this, cx| {
+ this.start_move_channel(channel_id, cx)
+ }),
+ );
- // if self.has_subchannels(ix) {
- // let expand_action_name = if self.is_channel_collapsed(channel.id) {
- // "Expand Subchannels"
- // } else {
- // "Collapse Subchannels"
- // };
- // items.push(ContextMenuItem::action(
- // expand_action_name,
- // ToggleCollapse {
- // location: channel.id,
- // },
- // ));
- // }
+ if let Some(channel_name) = clipboard_channel_name {
+ context_menu = context_menu.separator().entry(
+ format!("Move '#{}' here", channel_name),
+ cx.handler_for(&this, move |this, cx| {
+ this.move_channel_on_clipboard(channel_id, cx)
+ }),
+ );
+ }
- // items.push(ContextMenuItem::action(
- // "Open Notes",
- // OpenChannelNotes {
- // channel_id: channel.id,
- // },
- // ));
-
- // items.push(ContextMenuItem::action(
- // "Open Chat",
- // JoinChannelChat {
- // channel_id: channel.id,
- // },
- // ));
-
- // items.push(ContextMenuItem::action(
- // "Copy Channel Link",
- // CopyChannelLink {
- // channel_id: channel.id,
- // },
- // ));
-
- // if self.channel_store.read(cx).is_channel_admin(channel.id) {
- // items.extend([
- // ContextMenuItem::Separator,
- // ContextMenuItem::action(
- // "New Subchannel",
- // NewChannel {
- // location: channel.id,
- // },
- // ),
- // ContextMenuItem::action(
- // "Rename",
- // RenameChannel {
- // channel_id: channel.id,
- // },
- // ),
- // ContextMenuItem::action(
- // "Move this channel",
- // StartMoveChannelFor {
- // channel_id: channel.id,
- // },
- // ),
- // ]);
-
- // if let Some(channel_name) = clipboard_channel_name {
- // items.push(ContextMenuItem::Separator);
- // items.push(ContextMenuItem::action(
- // format!("Move '#{}' here", channel_name),
- // MoveChannel { to: channel.id },
- // ));
- // }
+ context_menu = context_menu
+ .separator()
+ .entry(
+ "Invite Members",
+ cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)),
+ )
+ .entry(
+ "Manage Members",
+ cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
+ )
+ .entry(
+ "Delete",
+ cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
+ );
+ }
- // items.extend([
- // ContextMenuItem::Separator,
- // ContextMenuItem::action(
- // "Invite Members",
- // InviteMembers {
- // channel_id: channel.id,
- // },
- // ),
- // ContextMenuItem::action(
- // "Manage Members",
- // ManageMembers {
- // channel_id: channel.id,
- // },
- // ),
- // ContextMenuItem::Separator,
- // ContextMenuItem::action(
- // "Delete",
- // RemoveChannel {
- // channel_id: channel.id,
- // },
- // ),
- // ]);
- // }
+ context_menu
+ });
- // context_menu.show(
- // position.unwrap_or_default(),
- // if self.context_menu_on_selected {
- // gpui::elements::AnchorCorner::TopRight
- // } else {
- // gpui::elements::AnchorCorner::BottomLeft
- // },
- // items,
- // cx,
- // );
- // });
+ cx.focus_view(&context_menu);
+ let subscription =
+ cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
+ if this.context_menu.as_ref().is_some_and(|context_menu| {
+ context_menu.0.focus_handle(cx).contains_focused(cx)
+ }) {
+ cx.focus_self();
+ }
+ this.context_menu.take();
+ cx.notify();
+ });
+ self.context_menu = Some((context_menu, position, subscription));
- // cx.notify();
- // }
+ cx.notify();
+ }
- // fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- // if self.take_editing_state(cx) {
- // cx.focus(&self.filter_editor);
- // } else {
- // self.filter_editor.update(cx, |editor, cx| {
- // if editor.buffer().read(cx).len(cx) > 0 {
- // editor.set_text("", cx);
- // }
- // });
- // }
+ fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+ if self.take_editing_state(cx) {
+ cx.focus_view(&self.filter_editor);
+ } else {
+ self.filter_editor.update(cx, |editor, cx| {
+ if editor.buffer().read(cx).len(cx) > 0 {
+ editor.set_text("", cx);
+ }
+ });
+ }
- // self.update_entries(false, cx);
- // }
+ self.update_entries(false, cx);
+ }
- // fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
- // let ix = self.selection.map_or(0, |ix| ix + 1);
- // if ix < self.entries.len() {
- // self.selection = Some(ix);
- // }
+ fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+ let ix = self.selection.map_or(0, |ix| ix + 1);
+ if ix < self.entries.len() {
+ self.selection = Some(ix);
+ }
- // self.list_state.reset(self.entries.len());
- // if let Some(ix) = self.selection {
- // self.list_state.scroll_to(ListOffset {
- // item_ix: ix,
- // offset_in_item: 0.,
- // });
- // }
- // cx.notify();
- // }
+ if let Some(ix) = self.selection {
+ self.scroll_handle.scroll_to_item(ix)
+ }
+ cx.notify();
+ }
- // fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
- // let ix = self.selection.take().unwrap_or(0);
- // if ix > 0 {
- // self.selection = Some(ix - 1);
- // }
+ fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
+ let ix = self.selection.take().unwrap_or(0);
+ if ix > 0 {
+ self.selection = Some(ix - 1);
+ }
- // self.list_state.reset(self.entries.len());
- // if let Some(ix) = self.selection {
- // self.list_state.scroll_to(ListOffset {
- // item_ix: ix,
- // offset_in_item: 0.,
- // });
- // }
- // cx.notify();
- // }
+ if let Some(ix) = self.selection {
+ self.scroll_handle.scroll_to_item(ix)
+ }
+ cx.notify();
+ }
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if self.confirm_channel_edit(cx) {
return;
}
- // if let Some(selection) = self.selection {
- // if let Some(entry) = self.entries.get(selection) {
- // match entry {
- // ListEntry::Header(section) => match section {
- // Section::ActiveCall => Self::leave_call(cx),
- // Section::Channels => self.new_root_channel(cx),
- // Section::Contacts => self.toggle_contact_finder(cx),
- // Section::ContactRequests
- // | Section::Online
- // | Section::Offline
- // | Section::ChannelInvites => {
- // self.toggle_section_expanded(*section, cx);
- // }
- // },
- // ListEntry::Contact { contact, calling } => {
- // if contact.online && !contact.busy && !calling {
- // self.call(contact.user.id, Some(self.project.clone()), cx);
- // }
- // }
- // ListEntry::ParticipantProject {
- // project_id,
- // host_user_id,
- // ..
- // } => {
- // if let Some(workspace) = self.workspace.upgrade(cx) {
- // let app_state = workspace.read(cx).app_state().clone();
- // workspace::join_remote_project(
- // *project_id,
- // *host_user_id,
- // app_state,
- // cx,
- // )
- // .detach_and_log_err(cx);
- // }
- // }
- // ListEntry::ParticipantScreen { peer_id, .. } => {
- // let Some(peer_id) = peer_id else {
- // return;
- // };
- // if let Some(workspace) = self.workspace.upgrade(cx) {
- // workspace.update(cx, |workspace, cx| {
- // workspace.open_shared_screen(*peer_id, cx)
- // });
- // }
- // }
- // ListEntry::Channel { channel, .. } => {
- // let is_active = maybe!({
- // let call_channel = ActiveCall::global(cx)
- // .read(cx)
- // .room()?
- // .read(cx)
- // .channel_id()?;
-
- // Some(call_channel == channel.id)
- // })
- // .unwrap_or(false);
- // if is_active {
- // self.open_channel_notes(
- // &OpenChannelNotes {
- // channel_id: channel.id,
- // },
- // cx,
- // )
- // } else {
- // self.join_channel(channel.id, cx)
- // }
- // }
- // ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
- // _ => {}
- // }
- // }
- // }
+ if let Some(selection) = self.selection {
+ if let Some(entry) = self.entries.get(selection) {
+ match entry {
+ ListEntry::Header(section) => match section {
+ Section::ActiveCall => Self::leave_call(cx),
+ Section::Channels => self.new_root_channel(cx),
+ Section::Contacts => self.toggle_contact_finder(cx),
+ Section::ContactRequests
+ | Section::Online
+ | Section::Offline
+ | Section::ChannelInvites => {
+ self.toggle_section_expanded(*section, cx);
+ }
+ },
+ ListEntry::Contact { contact, calling } => {
+ if contact.online && !contact.busy && !calling {
+ self.call(contact.user.id, cx);
+ }
+ }
+ // ListEntry::ParticipantProject {
+ // project_id,
+ // host_user_id,
+ // ..
+ // } => {
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // let app_state = workspace.read(cx).app_state().clone();
+ // workspace::join_remote_project(
+ // *project_id,
+ // *host_user_id,
+ // app_state,
+ // cx,
+ // )
+ // .detach_and_log_err(cx);
+ // }
+ // }
+ // ListEntry::ParticipantScreen { peer_id, .. } => {
+ // let Some(peer_id) = peer_id else {
+ // return;
+ // };
+ // if let Some(workspace) = self.workspace.upgrade(cx) {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.open_shared_screen(*peer_id, cx)
+ // });
+ // }
+ // }
+ ListEntry::Channel { channel, .. } => {
+ let is_active = maybe!({
+ let call_channel = ActiveCall::global(cx)
+ .read(cx)
+ .room()?
+ .read(cx)
+ .channel_id()?;
+
+ Some(call_channel == channel.id)
+ })
+ .unwrap_or(false);
+ if is_active {
+ self.open_channel_notes(channel.id, cx)
+ } else {
+ self.join_channel(channel.id, cx)
+ }
+ }
+ ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
+ _ => {}
+ }
+ }
+ }
}
fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
@@ -2025,33 +1953,33 @@ impl CollabPanel {
self.update_entries(false, cx);
}
- // fn collapse_selected_channel(
- // &mut self,
- // _: &CollapseSelectedChannel,
- // cx: &mut ViewContext<Self>,
- // ) {
- // let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
- // return;
- // };
+ fn collapse_selected_channel(
+ &mut self,
+ _: &CollapseSelectedChannel,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+ return;
+ };
- // if self.is_channel_collapsed(channel_id) {
- // return;
- // }
+ if self.is_channel_collapsed(channel_id) {
+ return;
+ }
- // self.toggle_channel_collapsed(channel_id, cx);
- // }
+ self.toggle_channel_collapsed(channel_id, cx);
+ }
- // fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
- // let Some(id) = self.selected_channel().map(|channel| channel.id) else {
- // return;
- // };
+ fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
+ let Some(id) = self.selected_channel().map(|channel| channel.id) else {
+ return;
+ };
- // if !self.is_channel_collapsed(id) {
- // return;
- // }
+ if !self.is_channel_collapsed(id) {
+ return;
+ }
- // self.toggle_channel_collapsed(id, cx)
- // }
+ self.toggle_channel_collapsed(id, cx)
+ }
// fn toggle_channel_collapsed_action(
// &mut self,
@@ -2080,11 +2008,11 @@ impl CollabPanel {
self.collapsed_channels.binary_search(&channel_id).is_ok()
}
- // fn leave_call(cx: &mut ViewContext<Self>) {
- // ActiveCall::global(cx)
- // .update(cx, |call, cx| call.hang_up(cx))
- // .detach_and_log_err(cx);
- // }
+ fn leave_call(cx: &mut ViewContext<Self>) {
+ ActiveCall::global(cx)
+ .update(cx, |call, cx| call.hang_up(cx))
+ .detach_and_log_err(cx);
+ }
fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
if let Some(workspace) = self.workspace.upgrade() {
@@ -2116,78 +2044,108 @@ impl CollabPanel {
});
}
- // fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
- // self.collapsed_channels
- // .retain(|channel| *channel != action.location);
- // self.channel_editing_state = Some(ChannelEditingState::Create {
- // location: Some(action.location.to_owned()),
- // pending_name: None,
- // });
- // self.update_entries(false, cx);
- // self.select_channel_editor();
- // cx.focus(self.channel_name_editor.as_any());
- // cx.notify();
- // }
+ fn new_subchannel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ self.collapsed_channels
+ .retain(|channel| *channel != channel_id);
+ self.channel_editing_state = Some(ChannelEditingState::Create {
+ location: Some(channel_id),
+ pending_name: None,
+ });
+ self.update_entries(false, cx);
+ self.select_channel_editor();
+ cx.focus_view(&self.channel_name_editor);
+ cx.notify();
+ }
- // fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
- // self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
- // }
+ fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ todo!();
+ // self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
+ }
- // fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
- // self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
- // }
+ fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ todo!();
+ // self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
+ }
- // fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
- // if let Some(channel) = self.selected_channel() {
- // self.remove_channel(channel.id, cx)
- // }
- // }
+ fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
+ if let Some(channel) = self.selected_channel() {
+ self.remove_channel(channel.id, cx)
+ }
+ }
- // fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
- // if let Some(channel) = self.selected_channel() {
- // self.rename_channel(
- // &RenameChannel {
- // channel_id: channel.id,
- // },
- // cx,
- // );
- // }
- // }
+ fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+ if let Some(channel) = self.selected_channel() {
+ self.rename_channel(channel.id, cx);
+ }
+ }
- // fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
- // let channel_store = self.channel_store.read(cx);
- // if !channel_store.is_channel_admin(action.channel_id) {
- // return;
- // }
- // if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
- // self.channel_editing_state = Some(ChannelEditingState::Rename {
- // location: action.channel_id.to_owned(),
- // pending_name: None,
- // });
- // self.channel_name_editor.update(cx, |editor, cx| {
- // editor.set_text(channel.name.clone(), cx);
- // editor.select_all(&Default::default(), cx);
- // });
- // cx.focus(self.channel_name_editor.as_any());
- // self.update_entries(false, cx);
- // self.select_channel_editor();
- // }
- // }
+ fn rename_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ let channel_store = self.channel_store.read(cx);
+ if !channel_store.is_channel_admin(channel_id) {
+ return;
+ }
+ if let Some(channel) = channel_store.channel_for_id(channel_id).cloned() {
+ self.channel_editing_state = Some(ChannelEditingState::Rename {
+ location: channel_id,
+ pending_name: None,
+ });
+ self.channel_name_editor.update(cx, |editor, cx| {
+ editor.set_text(channel.name.clone(), cx);
+ editor.select_all(&Default::default(), cx);
+ });
+ cx.focus_view(&self.channel_name_editor);
+ self.update_entries(false, cx);
+ self.select_channel_editor();
+ }
+ }
+
+ fn start_move_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
+ }
+
+ fn start_move_selected_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+ if let Some(channel) = self.selected_channel() {
+ self.channel_clipboard = Some(ChannelMoveClipboard {
+ channel_id: channel.id,
+ })
+ }
+ }
+
+ fn move_channel_on_clipboard(
+ &mut self,
+ to_channel_id: ChannelId,
+ cx: &mut ViewContext<CollabPanel>,
+ ) {
+ if let Some(clipboard) = self.channel_clipboard.take() {
+ self.channel_store.update(cx, |channel_store, cx| {
+ channel_store
+ .move_channel(clipboard.channel_id, Some(to_channel_id), cx)
+ .detach_and_log_err(cx)
+ })
+ }
+ }
- fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+ fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
if let Some(workspace) = self.workspace.upgrade() {
todo!();
// ChannelView::open(action.channel_id, workspace, cx).detach();
}
}
- // fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
- // let Some(channel) = self.selected_channel() else {
- // return;
- // };
+ fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
+ let Some(channel) = self.selected_channel() else {
+ return;
+ };
+ let Some(bounds) = self
+ .selection
+ .and_then(|ix| self.scroll_handle.bounds_for_item(ix))
+ else {
+ return;
+ };
- // self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
- // }
+ self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx);
+ cx.stop_propagation();
+ }
fn selected_channel(&self) -> Option<&Arc<Channel>> {
self.selection
@@ -185,7 +185,7 @@ impl PickerDelegate for ContactFinderDelegate {
div()
.flex_1()
.justify_between()
- .children(user.avatar.clone().map(|avatar| img().data(avatar)))
+ .children(user.avatar.clone().map(|avatar| img(avatar)))
.child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))),
)
@@ -35,19 +35,19 @@ use gpui::{
ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
ViewContext, VisualContext, WeakView, WindowBounds,
};
-use project::Project;
+use project::{Project, RepositoryEntry};
use theme::ActiveTheme;
use ui::{
- h_stack, Avatar, Button, ButtonCommon, ButtonLike, ButtonVariant, Clickable, Color, IconButton,
- IconElement, IconSize, KeyBinding, Tooltip,
+ h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
+ IconButton, IconElement, KeyBinding, Tooltip,
};
use util::ResultExt;
use workspace::{notifications::NotifyResultExt, Workspace};
use crate::face_pile::FacePile;
-// const MAX_PROJECT_NAME_LENGTH: usize = 40;
-// const MAX_BRANCH_NAME_LENGTH: usize = 40;
+const MAX_PROJECT_NAME_LENGTH: usize = 40;
+const MAX_BRANCH_NAME_LENGTH: usize = 40;
// actions!(
// collab,
@@ -103,17 +103,18 @@ impl Render for CollabTitlebarItem {
.update(cx, |this, cx| this.call_state().remote_participants(cx))
.log_err()
.flatten();
- let mic_icon = if self
+ let is_muted = self
.workspace
.update(cx, |this, cx| this.call_state().is_muted(cx))
.log_err()
.flatten()
- .unwrap_or_default()
- {
- ui::Icon::MicMute
- } else {
- ui::Icon::Mic
- };
+ .unwrap_or_default();
+ let is_deafened = self
+ .workspace
+ .update(cx, |this, cx| this.call_state().is_deafened(cx))
+ .log_err()
+ .flatten()
+ .unwrap_or_default();
let speakers_icon = if self
.workspace
.update(cx, |this, cx| this.call_state().is_deafened(cx))
@@ -149,53 +150,11 @@ impl Render for CollabTitlebarItem {
.child(
h_stack()
.gap_1()
- // TODO - Add player menu
- .child(
- div()
- .border()
- .border_color(gpui::red())
- .id("project_owner_indicator")
- .child(
- Button::new("player")
- .variant(ButtonVariant::Ghost)
- .color(Some(Color::Player(0))),
- )
- .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
- )
- // TODO - Add project menu
- .child(
- div()
- .border()
- .border_color(gpui::red())
- .id("titlebar_project_menu_button")
- .child(Button::new("project_name").variant(ButtonVariant::Ghost))
- .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
- )
- // TODO - Add git menu
- .child(
- div()
- .border()
- .border_color(gpui::red())
- .id("titlebar_git_menu_button")
- .child(
- Button::new("branch_name")
- .variant(ButtonVariant::Ghost)
- .color(Some(Color::Muted)),
- )
- .tooltip(move |cx| {
- cx.build_view(|_| {
- Tooltip::new("Recent Branches")
- .key_binding(KeyBinding::new(gpui::KeyBinding::new(
- "cmd-b",
- // todo!() Replace with real action.
- gpui::NoAction,
- None,
- )))
- .meta("Only local branches shown")
- })
- .into()
- }),
- ),
+ .when(is_in_room, |this| {
+ this.children(self.render_project_owner(cx))
+ })
+ .child(self.render_project_name(cx))
+ .children(self.render_project_branch(cx)),
)
.when_some(
users.zip(current_user.clone()),
@@ -236,62 +195,129 @@ impl Render for CollabTitlebarItem {
.when(is_in_room, |this| {
this.child(
h_stack()
+ .gap_1()
.child(
h_stack()
- .child(Button::new(if is_shared { "Unshare" } else { "Share" }))
- .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().hang_up(cx).detach();
- })
- .log_err();
- }
- })),
+ .gap_1()
+ .child(
+ Button::new(
+ "toggle_sharing",
+ if is_shared { "Unshare" } else { "Share" },
+ )
+ .style(ButtonStyle::Subtle),
+ )
+ .child(
+ IconButton::new("leave-call", ui::Icon::Exit)
+ .style(ButtonStyle::Subtle)
+ .on_click({
+ let workspace = workspace.clone();
+ move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.call_state().hang_up(cx).detach();
+ })
+ .log_err();
+ }
+ }),
+ ),
)
.child(
h_stack()
- .child(IconButton::new("mute-microphone", mic_icon).on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_mute(cx);
- })
- .log_err();
- }
- }))
- .child(IconButton::new("mute-sound", speakers_icon).on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_deafen(cx);
- })
- .log_err();
- }
- }))
- .child(IconButton::new("screen-share", ui::Icon::Screen).on_click(
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_screen_share(cx);
- })
- .log_err();
- },
- ))
+ .gap_1()
+ .child(
+ IconButton::new(
+ "mute-microphone",
+ if is_muted {
+ ui::Icon::MicMute
+ } else {
+ ui::Icon::Mic
+ },
+ )
+ .style(ButtonStyle::Subtle)
+ .selected(is_muted)
+ .on_click({
+ let workspace = workspace.clone();
+ move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.call_state().toggle_mute(cx);
+ })
+ .log_err();
+ }
+ }),
+ )
+ .child(
+ IconButton::new("mute-sound", speakers_icon)
+ .style(ButtonStyle::Subtle)
+ .selected(is_deafened.clone())
+ .tooltip(move |cx| {
+ Tooltip::with_meta(
+ "Deafen Audio",
+ None,
+ "Mic will be muted",
+ cx,
+ )
+ })
+ .on_click({
+ let workspace = workspace.clone();
+ move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.call_state().toggle_deafen(cx);
+ })
+ .log_err();
+ }
+ }),
+ )
+ .child(
+ IconButton::new("screen-share", ui::Icon::Screen)
+ .style(ButtonStyle::Subtle)
+ .on_click(move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.call_state().toggle_screen_share(cx);
+ })
+ .log_err();
+ }),
+ )
.pl_2(),
),
)
})
- .map(|this| {
+ .child(h_stack().px_1p5().map(|this| {
if let Some(user) = current_user {
this.when_some(user.avatar.clone(), |this, avatar| {
- this.child(ui::Avatar::data(avatar))
+ // TODO: Finish implementing user menu popover
+ //
+ this.child(
+ popover_menu("user-menu")
+ .menu(|cx| ContextMenu::build(cx, |menu, _| menu.header("ADADA")))
+ .trigger(
+ ButtonLike::new("user-menu")
+ .child(
+ h_stack().gap_0p5().child(Avatar::data(avatar)).child(
+ IconElement::new(Icon::ChevronDown)
+ .color(Color::Muted),
+ ),
+ )
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
+ )
+ .anchor(gpui::AnchorCorner::TopRight),
+ )
+ // this.child(
+ // ButtonLike::new("user-menu")
+ // .child(
+ // h_stack().gap_0p5().child(Avatar::data(avatar)).child(
+ // IconElement::new(Icon::ChevronDown).color(Color::Muted),
+ // ),
+ // )
+ // .style(ButtonStyle::Subtle)
+ // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
+ // )
})
} else {
- this.child(Button::new("Sign in").on_click(move |_, cx| {
+ this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
let client = client.clone();
cx.spawn(move |mut cx| async move {
client
@@ -301,29 +327,8 @@ impl Render for CollabTitlebarItem {
})
.detach();
}))
- // Temporary, will be removed when the last part of button2 is merged
- .child(
- div().border().border_color(gpui::blue()).child(
- ButtonLike::new("test-button")
- .children([
- Avatar::uri(
- "https://avatars.githubusercontent.com/u/1714999?v=4",
- )
- .into_element()
- .into_any(),
- IconElement::new(ui::Icon::ChevronDown)
- .size(IconSize::Small)
- .into_element()
- .into_any(),
- ])
- .on_click(move |event, _cx| {
- dbg!(format!("clicked: {:?}", event.down.position));
- })
- .tooltip(|cx| Tooltip::text("Test tooltip", cx)),
- ),
- )
}
- })
+ }))
}
}
@@ -442,6 +447,110 @@ impl CollabTitlebarItem {
}
}
+ // resolve if you are in a room -> render_project_owner
+ // render_project_owner -> resolve if you are in a room -> Option<foo>
+
+ pub fn render_project_owner(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+ // TODO: We can't finish implementing this until project sharing works
+ // - [ ] Show the project owner when the project is remote (maybe done)
+ // - [x] Show the project owner when the project is local
+ // - [ ] Show the project owner with a lock icon when the project is local and unshared
+
+ let remote_id = self.project.read(cx).remote_id();
+ let is_local = remote_id.is_none();
+ let is_shared = self.project.read(cx).is_shared();
+ let (user_name, participant_index) = {
+ if let Some(host) = self.project.read(cx).host() {
+ debug_assert!(!is_local);
+ let (Some(host_user), Some(participant_index)) = (
+ self.user_store.read(cx).get_cached_user(host.user_id),
+ self.user_store
+ .read(cx)
+ .participant_indices()
+ .get(&host.user_id),
+ ) else {
+ return None;
+ };
+ (host_user.github_login.clone(), participant_index.0)
+ } else {
+ debug_assert!(is_local);
+ let name = self
+ .user_store
+ .read(cx)
+ .current_user()
+ .map(|user| user.github_login.clone())?;
+ (name, 0)
+ }
+ };
+ Some(
+ div().border().border_color(gpui::red()).child(
+ Button::new(
+ "project_owner_trigger",
+ format!("{user_name} ({})", !is_shared),
+ )
+ .color(Color::Player(participant_index))
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
+ ),
+ )
+ }
+
+ pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
+ let name = {
+ let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
+ let worktree = worktree.read(cx);
+ worktree.root_name()
+ });
+
+ names.next().unwrap_or("")
+ };
+
+ let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
+
+ div().border().border_color(gpui::red()).child(
+ Button::new("project_name_trigger", name)
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
+ )
+ }
+
+ pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+ let entry = {
+ let mut names_and_branches =
+ self.project.read(cx).visible_worktrees(cx).map(|worktree| {
+ let worktree = worktree.read(cx);
+ worktree.root_git_entry()
+ });
+
+ names_and_branches.next().flatten()
+ };
+
+ let branch_name = entry
+ .as_ref()
+ .and_then(RepositoryEntry::branch)
+ .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
+
+ Some(
+ div().border().border_color(gpui::red()).child(
+ Button::new("project_branch_trigger", branch_name)
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| {
+ cx.build_view(|_| {
+ Tooltip::new("Recent Branches")
+ .key_binding(KeyBinding::new(gpui::KeyBinding::new(
+ "cmd-b",
+ // todo!() Replace with real action.
+ gpui::NoAction,
+ None,
+ )))
+ .meta("Local branches only")
+ })
+ .into()
+ }),
+ ),
+ )
+ }
+
// fn collect_title_root_names(
// &self,
// theme: Arc<Theme>,
@@ -2,10 +2,11 @@ use crate::notification_window_options;
use call::{ActiveCall, IncomingCall};
use futures::StreamExt;
use gpui::{
- div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce,
- StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle,
+ div, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, Styled, ViewContext,
+ VisualContext as _, WindowHandle,
};
use std::sync::{Arc, Weak};
+use ui::prelude::*;
use ui::{h_stack, v_stack, Avatar, Button, Label};
use util::ResultExt;
use workspace::AppState;
@@ -199,14 +200,24 @@ impl IncomingCallNotification {
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
h_stack()
- .child(Button::new("Accept").render(cx).bg(green()).on_click({
- let state = self.state.clone();
- move |_, cx| state.respond(true, cx)
- }))
- .child(Button::new("Decline").render(cx).bg(red()).on_click({
- let state = self.state.clone();
- move |_, cx| state.respond(false, cx)
- }))
+ .child(
+ Button::new("accept", "Accept")
+ .render(cx)
+ // .bg(green())
+ .on_click({
+ let state = self.state.clone();
+ move |_, cx| state.respond(true, cx)
+ }),
+ )
+ .child(
+ Button::new("decline", "Decline")
+ .render(cx)
+ // .bg(red())
+ .on_click({
+ let state = self.state.clone();
+ move |_, cx| state.respond(false, cx)
+ }),
+ )
// enum Accept {}
// enum Decline {}
@@ -11,7 +11,7 @@ use gpui::{
};
use picker::{Picker, PickerDelegate};
-use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -126,7 +126,7 @@ impl View for ProjectDiagnosticsEditor {
json!({
"project": json!({
"language_servers": project.language_server_statuses().collect::<Vec<_>>(),
- "summary": project.diagnostic_summary(cx),
+ "summary": project.diagnostic_summary(false, cx),
}),
"summary": self.summary,
"paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
@@ -195,7 +195,7 @@ impl ProjectDiagnosticsEditor {
});
let project = project_handle.read(cx);
- let summary = project.diagnostic_summary(cx);
+ let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -241,7 +241,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
- .diagnostic_summaries(cx)
+ .diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -320,7 +320,7 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
- this.summary = this.project.read(cx).diagnostic_summary(cx);
+ this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(Event::TitleChanged);
})?;
anyhow::Ok(())
@@ -34,19 +34,19 @@ impl DiagnosticIndicator {
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
_ => {}
})
.detach();
Self {
- summary: project.read(cx).diagnostic_summary(cx),
+ summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()
@@ -165,7 +165,7 @@ impl ProjectDiagnosticsEditor {
});
let project = project_handle.read(cx);
- let summary = project.diagnostic_summary(cx);
+ let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -252,7 +252,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
- .diagnostic_summaries(cx)
+ .diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -332,7 +332,7 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
- this.summary = this.project.read(cx).diagnostic_summary(cx);
+ this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(ItemEvent::UpdateTab);
cx.emit(ItemEvent::UpdateBreadcrumbs);
})?;
@@ -77,13 +77,13 @@ impl DiagnosticIndicator {
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
@@ -92,7 +92,7 @@ impl DiagnosticIndicator {
.detach();
Self {
- summary: project.read(cx).diagnostic_summary(cx),
+ summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()
@@ -1,5 +1,6 @@
use crate::ProjectDiagnosticsEditor;
use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
+use ui::prelude::*;
use ui::{Icon, IconButton, Tooltip};
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
@@ -162,7 +162,7 @@ impl WrapMap {
{
let tab_snapshot = new_snapshot.tab_snapshot.clone();
let range = TabPoint::zero()..tab_snapshot.max_point();
- let edits = new_snapshot
+ edits = new_snapshot
.update(
tab_snapshot,
&[TabEdit {
@@ -63,6 +63,7 @@ use language::{
use lazy_static::lazy_static;
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use lsp::{DiagnosticSeverity, LanguageServerId};
+use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
use multi_buffer::ToOffsetUtf16;
pub use multi_buffer::{
@@ -73,7 +74,7 @@ use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock};
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*;
-use rpc::proto::*;
+use rpc::proto::{self, *};
use scroll::{
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
};
@@ -99,7 +100,8 @@ use text::{OffsetUtf16, Rope};
use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
};
-use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
+use ui::prelude::*;
+use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, Tooltip};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{
item::{ItemEvent, ItemHandle},
@@ -152,9 +154,7 @@ pub fn render_parsed_markdown(
}
}),
);
- let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
- // todo!("add the ability to change cursor style for link ranges")
let mut links = Vec::new();
let mut link_ranges = Vec::new();
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
@@ -166,7 +166,7 @@ pub fn render_parsed_markdown(
InteractiveText::new(
element_id,
- StyledText::new(parsed.text.clone()).with_runs(runs),
+ StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
)
.on_click(link_ranges, move |clicked_range_ix, cx| {
match &links[clicked_range_ix] {
@@ -407,133 +407,17 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(cx: &mut AppContext) {
init_settings(cx);
- // cx.register_action_type(Editor::new_file);
- // cx.register_action_type(Editor::new_file_in_direction);
- // cx.register_action_type(Editor::cancel);
- // cx.register_action_type(Editor::newline);
- // cx.register_action_type(Editor::newline_above);
- // cx.register_action_type(Editor::newline_below);
- // cx.register_action_type(Editor::backspace);
- // cx.register_action_type(Editor::delete);
- // cx.register_action_type(Editor::tab);
- // cx.register_action_type(Editor::tab_prev);
- // cx.register_action_type(Editor::indent);
- // cx.register_action_type(Editor::outdent);
- // cx.register_action_type(Editor::delete_line);
- // cx.register_action_type(Editor::join_lines);
- // cx.register_action_type(Editor::sort_lines_case_sensitive);
- // cx.register_action_type(Editor::sort_lines_case_insensitive);
- // cx.register_action_type(Editor::reverse_lines);
- // cx.register_action_type(Editor::shuffle_lines);
- // cx.register_action_type(Editor::convert_to_upper_case);
- // cx.register_action_type(Editor::convert_to_lower_case);
- // cx.register_action_type(Editor::convert_to_title_case);
- // cx.register_action_type(Editor::convert_to_snake_case);
- // cx.register_action_type(Editor::convert_to_kebab_case);
- // cx.register_action_type(Editor::convert_to_upper_camel_case);
- // cx.register_action_type(Editor::convert_to_lower_camel_case);
- // cx.register_action_type(Editor::delete_to_previous_word_start);
- // cx.register_action_type(Editor::delete_to_previous_subword_start);
- // cx.register_action_type(Editor::delete_to_next_word_end);
- // cx.register_action_type(Editor::delete_to_next_subword_end);
- // cx.register_action_type(Editor::delete_to_beginning_of_line);
- // cx.register_action_type(Editor::delete_to_end_of_line);
- // cx.register_action_type(Editor::cut_to_end_of_line);
- // cx.register_action_type(Editor::duplicate_line);
- // cx.register_action_type(Editor::move_line_up);
- // cx.register_action_type(Editor::move_line_down);
- // cx.register_action_type(Editor::transpose);
- // cx.register_action_type(Editor::cut);
- // cx.register_action_type(Editor::copy);
- // cx.register_action_type(Editor::paste);
- // cx.register_action_type(Editor::undo);
- // cx.register_action_type(Editor::redo);
- // cx.register_action_type(Editor::move_page_up);
- // cx.register_action_type::<MoveDown>();
- // cx.register_action_type(Editor::move_page_down);
- // cx.register_action_type(Editor::next_screen);
- // cx.register_action_type::<MoveLeft>();
- // cx.register_action_type::<MoveRight>();
- // cx.register_action_type(Editor::move_to_previous_word_start);
- // cx.register_action_type(Editor::move_to_previous_subword_start);
- // cx.register_action_type(Editor::move_to_next_word_end);
- // cx.register_action_type(Editor::move_to_next_subword_end);
- // cx.register_action_type(Editor::move_to_beginning_of_line);
- // cx.register_action_type(Editor::move_to_end_of_line);
- // cx.register_action_type(Editor::move_to_start_of_paragraph);
- // cx.register_action_type(Editor::move_to_end_of_paragraph);
- // cx.register_action_type(Editor::move_to_beginning);
- // cx.register_action_type(Editor::move_to_end);
- // cx.register_action_type(Editor::select_up);
- // cx.register_action_type(Editor::select_down);
- // cx.register_action_type(Editor::select_left);
- // cx.register_action_type(Editor::select_right);
- // cx.register_action_type(Editor::select_to_previous_word_start);
- // cx.register_action_type(Editor::select_to_previous_subword_start);
- // cx.register_action_type(Editor::select_to_next_word_end);
- // cx.register_action_type(Editor::select_to_next_subword_end);
- // cx.register_action_type(Editor::select_to_beginning_of_line);
- // cx.register_action_type(Editor::select_to_end_of_line);
- // cx.register_action_type(Editor::select_to_start_of_paragraph);
- // cx.register_action_type(Editor::select_to_end_of_paragraph);
- // cx.register_action_type(Editor::select_to_beginning);
- // cx.register_action_type(Editor::select_to_end);
- // cx.register_action_type(Editor::select_all);
- // cx.register_action_type(Editor::select_all_matches);
- // cx.register_action_type(Editor::select_line);
- // cx.register_action_type(Editor::split_selection_into_lines);
- // cx.register_action_type(Editor::add_selection_above);
- // cx.register_action_type(Editor::add_selection_below);
- // cx.register_action_type(Editor::select_next);
- // cx.register_action_type(Editor::select_previous);
- // cx.register_action_type(Editor::toggle_comments);
- // cx.register_action_type(Editor::select_larger_syntax_node);
- // cx.register_action_type(Editor::select_smaller_syntax_node);
- // cx.register_action_type(Editor::move_to_enclosing_bracket);
- // cx.register_action_type(Editor::undo_selection);
- // cx.register_action_type(Editor::redo_selection);
- // cx.register_action_type(Editor::go_to_diagnostic);
- // cx.register_action_type(Editor::go_to_prev_diagnostic);
- // cx.register_action_type(Editor::go_to_hunk);
- // cx.register_action_type(Editor::go_to_prev_hunk);
- // cx.register_action_type(Editor::go_to_definition);
- // cx.register_action_type(Editor::go_to_definition_split);
- // cx.register_action_type(Editor::go_to_type_definition);
- // cx.register_action_type(Editor::go_to_type_definition_split);
- // cx.register_action_type(Editor::fold);
- // cx.register_action_type(Editor::fold_at);
- // cx.register_action_type(Editor::unfold_lines);
- // cx.register_action_type(Editor::unfold_at);
- // cx.register_action_type(Editor::gutter_hover);
- // cx.register_action_type(Editor::fold_selected_ranges);
- // cx.register_action_type(Editor::show_completions);
- // cx.register_action_type(Editor::toggle_code_actions);
- // cx.register_action_type(Editor::open_excerpts);
- // cx.register_action_type(Editor::toggle_soft_wrap);
- // cx.register_action_type(Editor::toggle_inlay_hints);
- // cx.register_action_type(Editor::reveal_in_finder);
- // cx.register_action_type(Editor::copy_path);
- // cx.register_action_type(Editor::copy_relative_path);
- // cx.register_action_type(Editor::copy_highlight_json);
- // cx.add_async_action(Editor::format);
- // cx.register_action_type(Editor::restart_language_server);
- // cx.register_action_type(Editor::show_character_palette);
- // cx.add_async_action(Editor::confirm_completion);
- // cx.add_async_action(Editor::confirm_code_action);
- // cx.add_async_action(Editor::rename);
- // cx.add_async_action(Editor::confirm_rename);
- // cx.add_async_action(Editor::find_all_references);
- // cx.register_action_type(Editor::next_copilot_suggestion);
- // cx.register_action_type(Editor::previous_copilot_suggestion);
- // cx.register_action_type(Editor::copilot_suggest);
- // cx.register_action_type(Editor::context_menu_first);
- // cx.register_action_type(Editor::context_menu_prev);
- // cx.register_action_type(Editor::context_menu_next);
- // cx.register_action_type(Editor::context_menu_last);
workspace::register_project_item::<Editor>(cx);
workspace::register_followable_item::<Editor>(cx);
workspace::register_deserializable_item::<Editor>(cx);
+ cx.observe_new_views(
+ |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
+ workspace.register_action(Editor::new_file);
+ workspace.register_action(Editor::new_file_in_direction);
+ },
+ )
+ .detach();
}
trait InvalidationRegion {
@@ -621,8 +505,6 @@ pub struct Editor {
ime_transaction: Option<TransactionId>,
active_diagnostics: Option<ActiveDiagnosticGroup>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
- // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
- // override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<Model<Project>>,
collaboration_hub: Option<Box<dyn CollaborationHub>>,
blink_manager: Model<BlinkManager>,
@@ -636,7 +518,7 @@ pub struct Editor {
inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
nav_history: Option<ItemNavHistory>,
context_menu: RwLock<Option<ContextMenu>>,
- // mouse_context_menu: View<context_menu::ContextMenu>,
+ mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
next_completion_id: CompletionId,
available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
@@ -971,95 +853,94 @@ impl CompletionsMenu {
fn pre_resolve_completion_documentation(
&self,
- _editor: &Editor,
- _cx: &mut ViewContext<Editor>,
+ editor: &Editor,
+ cx: &mut ViewContext<Editor>,
) -> Option<Task<()>> {
- // todo!("implementation below ");
- None
- }
- // {
- // let settings = EditorSettings::get_global(cx);
- // if !settings.show_completion_documentation {
- // return None;
- // }
+ let settings = EditorSettings::get_global(cx);
+ if !settings.show_completion_documentation {
+ return None;
+ }
- // let Some(project) = editor.project.clone() else {
- // return None;
- // };
-
- // let client = project.read(cx).client();
- // let language_registry = project.read(cx).languages().clone();
-
- // let is_remote = project.read(cx).is_remote();
- // let project_id = project.read(cx).remote_id();
-
- // let completions = self.completions.clone();
- // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
-
- // Some(cx.spawn(move |this, mut cx| async move {
- // if is_remote {
- // let Some(project_id) = project_id else {
- // log::error!("Remote project without remote_id");
- // return;
- // };
-
- // for completion_index in completion_indices {
- // let completions_guard = completions.read();
- // let completion = &completions_guard[completion_index];
- // if completion.documentation.is_some() {
- // continue;
- // }
-
- // let server_id = completion.server_id;
- // let completion = completion.lsp_completion.clone();
- // drop(completions_guard);
-
- // Self::resolve_completion_documentation_remote(
- // project_id,
- // server_id,
- // completions.clone(),
- // completion_index,
- // completion,
- // client.clone(),
- // language_registry.clone(),
- // )
- // .await;
-
- // _ = this.update(&mut cx, |_, cx| cx.notify());
- // }
- // } else {
- // for completion_index in completion_indices {
- // let completions_guard = completions.read();
- // let completion = &completions_guard[completion_index];
- // if completion.documentation.is_some() {
- // continue;
- // }
-
- // let server_id = completion.server_id;
- // let completion = completion.lsp_completion.clone();
- // drop(completions_guard);
-
- // let server = project.read_with(&mut cx, |project, _| {
- // project.language_server_for_id(server_id)
- // });
- // let Some(server) = server else {
- // return;
- // };
-
- // Self::resolve_completion_documentation_local(
- // server,
- // completions.clone(),
- // completion_index,
- // completion,
- // language_registry.clone(),
- // )
- // .await;
-
- // _ = this.update(&mut cx, |_, cx| cx.notify());
- // }
- // }
- // }))
- // }
+ let Some(project) = editor.project.clone() else {
+ return None;
+ };
+
+ let client = project.read(cx).client();
+ let language_registry = project.read(cx).languages().clone();
+
+ let is_remote = project.read(cx).is_remote();
+ let project_id = project.read(cx).remote_id();
+
+ let completions = self.completions.clone();
+ let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
+
+ Some(cx.spawn(move |this, mut cx| async move {
+ if is_remote {
+ let Some(project_id) = project_id else {
+ log::error!("Remote project without remote_id");
+ return;
+ };
+
+ for completion_index in completion_indices {
+ let completions_guard = completions.read();
+ let completion = &completions_guard[completion_index];
+ if completion.documentation.is_some() {
+ continue;
+ }
+
+ let server_id = completion.server_id;
+ let completion = completion.lsp_completion.clone();
+ drop(completions_guard);
+
+ Self::resolve_completion_documentation_remote(
+ project_id,
+ server_id,
+ completions.clone(),
+ completion_index,
+ completion,
+ client.clone(),
+ language_registry.clone(),
+ )
+ .await;
+
+ _ = this.update(&mut cx, |_, cx| cx.notify());
+ }
+ } else {
+ for completion_index in completion_indices {
+ let completions_guard = completions.read();
+ let completion = &completions_guard[completion_index];
+ if completion.documentation.is_some() {
+ continue;
+ }
+
+ let server_id = completion.server_id;
+ let completion = completion.lsp_completion.clone();
+ drop(completions_guard);
+
+ let server = project
+ .read_with(&mut cx, |project, _| {
+ project.language_server_for_id(server_id)
+ })
+ .ok()
+ .flatten();
+ let Some(server) = server else {
+ return;
+ };
+
+ Self::resolve_completion_documentation_local(
+ server,
+ completions.clone(),
+ completion_index,
+ completion,
+ language_registry.clone(),
+ )
+ .await;
+
+ _ = this.update(&mut cx, |_, cx| cx.notify());
+ }
+ }
+ }))
+ }
fn attempt_resolve_selected_completion_documentation(
&mut self,
@@ -1080,10 +961,9 @@ impl CompletionsMenu {
let completions = self.completions.clone();
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
- // todo!()
- // if completion.documentation.is_some() {
- // return;
- // }
+ if completion.documentation.is_some() {
+ return;
+ }
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
@@ -1142,41 +1022,40 @@ impl CompletionsMenu {
client: Arc<Client>,
language_registry: Arc<LanguageRegistry>,
) {
- // todo!()
- // let request = proto::ResolveCompletionDocumentation {
- // project_id,
- // language_server_id: server_id.0 as u64,
- // lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
- // };
-
- // let Some(response) = client
- // .request(request)
- // .await
- // .context("completion documentation resolve proto request")
- // .log_err()
- // else {
- // return;
- // };
-
- // if response.text.is_empty() {
- // let mut completions = completions.write();
- // let completion = &mut completions[completion_index];
- // completion.documentation = Some(Documentation::Undocumented);
- // }
-
- // let documentation = if response.is_markdown {
- // Documentation::MultiLineMarkdown(
- // markdown::parse_markdown(&response.text, &language_registry, None).await,
- // )
- // } else if response.text.lines().count() <= 1 {
- // Documentation::SingleLine(response.text)
- // } else {
- // Documentation::MultiLinePlainText(response.text)
- // };
-
- // let mut completions = completions.write();
- // let completion = &mut completions[completion_index];
- // completion.documentation = Some(documentation);
+ let request = proto::ResolveCompletionDocumentation {
+ project_id,
+ language_server_id: server_id.0 as u64,
+ lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
+ };
+
+ let Some(response) = client
+ .request(request)
+ .await
+ .context("completion documentation resolve proto request")
+ .log_err()
+ else {
+ return;
+ };
+
+ if response.text.is_empty() {
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(Documentation::Undocumented);
+ }
+
+ let documentation = if response.is_markdown {
+ Documentation::MultiLineMarkdown(
+ markdown::parse_markdown(&response.text, &language_registry, None).await,
+ )
+ } else if response.text.lines().count() <= 1 {
+ Documentation::SingleLine(response.text)
+ } else {
+ Documentation::MultiLinePlainText(response.text)
+ };
+
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(documentation);
}
async fn resolve_completion_documentation_local(
@@ -1186,38 +1065,37 @@ impl CompletionsMenu {
completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>,
) {
- // todo!()
- // let can_resolve = server
- // .capabilities()
- // .completion_provider
- // .as_ref()
- // .and_then(|options| options.resolve_provider)
- // .unwrap_or(false);
- // if !can_resolve {
- // return;
- // }
-
- // let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
- // let Some(completion_item) = request.await.log_err() else {
- // return;
- // };
-
- // if let Some(lsp_documentation) = completion_item.documentation {
- // let documentation = language::prepare_completion_documentation(
- // &lsp_documentation,
- // &language_registry,
- // None, // TODO: Try to reasonably work out which language the completion is for
- // )
- // .await;
-
- // let mut completions = completions.write();
- // let completion = &mut completions[completion_index];
- // completion.documentation = Some(documentation);
- // } else {
- // let mut completions = completions.write();
- // let completion = &mut completions[completion_index];
- // completion.documentation = Some(Documentation::Undocumented);
- // }
+ let can_resolve = server
+ .capabilities()
+ .completion_provider
+ .as_ref()
+ .and_then(|options| options.resolve_provider)
+ .unwrap_or(false);
+ if !can_resolve {
+ return;
+ }
+
+ let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
+ let Some(completion_item) = request.await.log_err() else {
+ return;
+ };
+
+ if let Some(lsp_documentation) = completion_item.documentation {
+ let documentation = language::prepare_completion_documentation(
+ &lsp_documentation,
+ &language_registry,
+ None, // TODO: Try to reasonably work out which language the completion is for
+ )
+ .await;
+
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(documentation);
+ } else {
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(Documentation::Undocumented);
+ }
}
fn visible(&self) -> bool {
@@ -1320,11 +1198,7 @@ impl CompletionsMenu {
),
);
let completion_label = StyledText::new(completion.label.text.clone())
- .with_runs(text_runs_for_highlights(
- &completion.label.text,
- &style.text,
- highlights,
- ));
+ .with_highlights(&style.text, highlights);
let documentation_label =
if let Some(Documentation::SingleLine(text)) = documentation {
Some(SharedString::from(text.clone()))
@@ -1738,21 +1612,11 @@ impl Editor {
// Self::new(EditorMode::Full, buffer, None, field_editor_style, cx)
// }
- // pub fn auto_height(
- // max_lines: usize,
- // field_editor_style: Option<Arc<GetFieldEditorTheme>>,
- // cx: &mut ViewContext<Self>,
- // ) -> Self {
- // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
- // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
- // Self::new(
- // EditorMode::AutoHeight { max_lines },
- // buffer,
- // None,
- // field_editor_style,
- // cx,
- // )
- // }
+ pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
+ let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+ let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+ Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
+ }
pub fn for_buffer(
buffer: Model<Buffer>,
@@ -1772,14 +1636,7 @@ impl Editor {
}
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
- let mut clone = Self::new(
- self.mode,
- self.buffer.clone(),
- self.project.clone(),
- // todo!
- // self.get_field_editor_theme.clone(),
- cx,
- );
+ let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
self.display_map.update(cx, |display_map, cx| {
let snapshot = display_map.snapshot(cx);
clone.display_map.update(cx, |display_map, cx| {
@@ -1796,17 +1653,11 @@ impl Editor {
mode: EditorMode,
buffer: Model<MultiBuffer>,
project: Option<Model<Project>>,
- // todo!()
- // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
- // let editor_view_id = cx.view_id();
let style = cx.text_style();
let font_size = style.font_size.to_pixels(cx.rem_size());
let display_map = cx.build_model(|cx| {
- // todo!()
- // let settings = settings::get::<ThemeSettings>(cx);
- // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
});
@@ -1862,7 +1713,6 @@ impl Editor {
ime_transaction: Default::default(),
active_diagnostics: None,
soft_wrap_mode_override,
- // get_field_editor_theme,
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
project,
blink_manager: blink_manager.clone(),
@@ -1876,8 +1726,7 @@ impl Editor {
inlay_background_highlights: Default::default(),
nav_history: None,
context_menu: RwLock::new(None),
- // mouse_context_menu: cx
- // .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
+ mouse_context_menu: None,
completion_tasks: Default::default(),
next_completion_id: 0,
next_inlay_id: 0,
@@ -1886,7 +1735,6 @@ impl Editor {
document_highlights_task: Default::default(),
pending_rename: Default::default(),
searchable: true,
- // override_text_style: None,
cursor_shape: Default::default(),
autoindent_mode: Some(AutoindentMode::EachLine),
collapse_matches: false,
@@ -2004,25 +1852,25 @@ impl Editor {
}
}
- // pub fn new_file_in_direction(
- // workspace: &mut Workspace,
- // action: &workspace::NewFileInDirection,
- // cx: &mut ViewContext<Workspace>,
- // ) {
- // let project = workspace.project().clone();
- // if project.read(cx).is_remote() {
- // cx.propagate();
- // } else if let Some(buffer) = project
- // .update(cx, |project, cx| project.create_buffer("", None, cx))
- // .log_err()
- // {
- // workspace.split_item(
- // action.0,
- // Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
- // cx,
- // );
- // }
- // }
+ pub fn new_file_in_direction(
+ workspace: &mut Workspace,
+ action: &workspace::NewFileInDirection,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ let project = workspace.project().clone();
+ if project.read(cx).is_remote() {
+ cx.propagate();
+ } else if let Some(buffer) = project
+ .update(cx, |project, cx| project.create_buffer("", None, cx))
+ .log_err()
+ {
+ workspace.split_item(
+ action.0,
+ Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
+ cx,
+ );
+ }
+ }
pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
self.buffer.read(cx).replica_id()
@@ -4391,7 +4239,7 @@ impl Editor {
editor.fold_at(&FoldAt { buffer_row }, cx);
}
}))
- .color(ui::Color::Muted)
+ .icon_color(ui::Color::Muted)
})
})
.flatten()
@@ -8378,6 +8226,18 @@ impl Editor {
cx.notify();
}
+ pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
+ let rem_size = cx.rem_size();
+ self.display_map.update(cx, |map, cx| {
+ map.set_font(
+ style.text.font(),
+ style.text.font_size.to_pixels(rem_size),
+ cx,
+ )
+ });
+ self.style = Some(style);
+ }
+
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
self.display_map
.update(cx, |map, cx| map.set_wrap_width(width, cx))
@@ -8800,62 +8660,56 @@ impl Editor {
// self.searchable
// }
- // fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {
- // let active_item = workspace.active_item(cx);
- // let editor_handle = if let Some(editor) = active_item
- // .as_ref()
- // .and_then(|item| item.act_as::<Self>(cx))
- // {
- // editor
- // } else {
- // cx.propagate();
- // return;
- // };
-
- // let editor = editor_handle.read(cx);
- // let buffer = editor.buffer.read(cx);
- // if buffer.is_singleton() {
- // cx.propagate();
- // return;
- // }
+ fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
+ let buffer = self.buffer.read(cx);
+ if buffer.is_singleton() {
+ cx.propagate();
+ return;
+ }
- // let mut new_selections_by_buffer = HashMap::default();
- // for selection in editor.selections.all::<usize>(cx) {
- // for (buffer, mut range, _) in
- // buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
- // {
- // if selection.reversed {
- // mem::swap(&mut range.start, &mut range.end);
- // }
- // new_selections_by_buffer
- // .entry(buffer)
- // .or_insert(Vec::new())
- // .push(range)
- // }
- // }
+ let Some(workspace) = self.workspace() else {
+ cx.propagate();
+ return;
+ };
- // editor_handle.update(cx, |editor, cx| {
- // editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx);
- // });
- // let pane = workspace.active_pane().clone();
- // pane.update(cx, |pane, _| pane.disable_history());
-
- // // We defer the pane interaction because we ourselves are a workspace item
- // // and activating a new item causes the pane to call a method on us reentrantly,
- // // which panics if we're on the stack.
- // cx.defer(move |workspace, cx| {
- // for (buffer, ranges) in new_selections_by_buffer.into_iter() {
- // let editor = workspace.open_project_item::<Self>(buffer, cx);
- // editor.update(cx, |editor, cx| {
- // editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
- // s.select_ranges(ranges);
- // });
- // });
- // }
-
- // pane.update(cx, |pane, _| pane.enable_history());
- // });
- // }
+ let mut new_selections_by_buffer = HashMap::default();
+ for selection in self.selections.all::<usize>(cx) {
+ for (buffer, mut range, _) in
+ buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
+ {
+ if selection.reversed {
+ mem::swap(&mut range.start, &mut range.end);
+ }
+ new_selections_by_buffer
+ .entry(buffer)
+ .or_insert(Vec::new())
+ .push(range)
+ }
+ }
+
+ self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
+
+ // We defer the pane interaction because we ourselves are a workspace item
+ // and activating a new item causes the pane to call a method on us reentrantly,
+ // which panics if we're on the stack.
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ let pane = workspace.active_pane().clone();
+ pane.update(cx, |pane, _| pane.disable_history());
+
+ for (buffer, ranges) in new_selections_by_buffer.into_iter() {
+ let editor = workspace.open_project_item::<Self>(buffer, cx);
+ editor.update(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
+ s.select_ranges(ranges);
+ });
+ });
+ }
+
+ pane.update(cx, |pane, _| pane.enable_history());
+ })
+ });
+ }
fn jump(
&mut self,
@@ -9401,7 +9255,7 @@ impl Render for Editor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let settings = ThemeSettings::get_global(cx);
let text_style = match self.mode {
- EditorMode::SingleLine => TextStyle {
+ EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
@@ -9414,8 +9268,6 @@ impl Render for Editor {
white_space: WhiteSpace::Normal,
},
- EditorMode::AutoHeight { max_lines } => todo!(),
-
EditorMode::Full => TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
@@ -9450,106 +9302,6 @@ impl Render for Editor {
}
}
-// impl View for Editor {
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// let style = self.style(cx);
-// let font_changed = self.display_map.update(cx, |map, cx| {
-// map.set_fold_ellipses_color(style.folds.ellipses.text_color);
-// map.set_font_with_size(style.text.font_id, style.text.font_size, cx)
-// });
-
-// if font_changed {
-// cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
-// hide_hover(editor, cx);
-// hide_link_definition(editor, cx);
-// });
-// }
-
-// Stack::new()
-// .with_child(EditorElement::new(style.clone()))
-// .with_child(ChildView::new(&self.mouse_context_menu, cx))
-// .into_any()
-// }
-
-// fn ui_name() -> &'static str {
-// "Editor"
-// }
-
-// fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext<Self>) {
-// if cx.is_self_focused() {
-// let focused_event = EditorFocused(cx.handle());
-// cx.emit(Event::Focused);
-// cx.emit_global(focused_event);
-// }
-// if let Some(rename) = self.pending_rename.as_ref() {
-// cx.focus(&rename.editor);
-// } else if cx.is_self_focused() || !focused.is::<Editor>() {
-// if !self.focused {
-// self.blink_manager.update(cx, BlinkManager::enable);
-// }
-// self.focused = true;
-// self.buffer.update(cx, |buffer, cx| {
-// buffer.finalize_last_transaction(cx);
-// if self.leader_peer_id.is_none() {
-// buffer.set_active_selections(
-// &self.selections.disjoint_anchors(),
-// self.selections.line_mode,
-// self.cursor_shape,
-// cx,
-// );
-// }
-// });
-// }
-// }
-
-// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-// let blurred_event = EditorBlurred(cx.handle());
-// cx.emit_global(blurred_event);
-// self.focused = false;
-// self.blink_manager.update(cx, BlinkManager::disable);
-// self.buffer
-// .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-// self.hide_context_menu(cx);
-// hide_hover(self, cx);
-// cx.emit(Event::Blurred);
-// cx.notify();
-// }
-
-// fn modifiers_changed(
-// &mut self,
-// event: &gpui::platform::ModifiersChangedEvent,
-// cx: &mut ViewContext<Self>,
-// ) -> bool {
-// let pending_selection = self.has_pending_selection();
-
-// if let Some(point) = &self.link_go_to_definition_state.last_trigger_point {
-// if event.cmd && !pending_selection {
-// let point = point.clone();
-// let snapshot = self.snapshot(cx);
-// let kind = point.definition_kind(event.shift);
-
-// show_link_definition(kind, self, point, snapshot, cx);
-// return false;
-// }
-// }
-
-// {
-// if self.link_go_to_definition_state.symbol_range.is_some()
-// || !self.link_go_to_definition_state.definitions.is_empty()
-// {
-// self.link_go_to_definition_state.symbol_range.take();
-// self.link_go_to_definition_state.definitions.clear();
-// cx.notify();
-// }
-
-// self.link_go_to_definition_state.task = None;
-
-// self.clear_highlights::<LinkGoToDefinitionState>(cx);
-// }
-
-// false
-// }
-
impl InputHandler for Editor {
fn text_for_range(
&mut self,
@@ -9796,72 +9548,6 @@ impl InputHandler for Editor {
}
}
-// fn build_style(
-// settings: &ThemeSettings,
-// get_field_editor_theme: Option<&GetFieldEditorTheme>,
-// override_text_style: Option<&OverrideTextStyle>,
-// cx: &mut AppContext,
-// ) -> EditorStyle {
-// let font_cache = cx.font_cache();
-// let line_height_scalar = settings.line_height();
-// let theme_id = settings.theme.meta.id;
-// let mut theme = settings.theme.editor.clone();
-// let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme {
-// let field_editor_theme = get_field_editor_theme(&settings.theme);
-// theme.text_color = field_editor_theme.text.color;
-// theme.selection = field_editor_theme.selection;
-// theme.background = field_editor_theme
-// .container
-// .background_color
-// .unwrap_or_default();
-// EditorStyle {
-// text: field_editor_theme.text,
-// placeholder_text: field_editor_theme.placeholder_text,
-// line_height_scalar,
-// theme,
-// theme_id,
-// }
-// } else {
-// todo!();
-// // let font_family_id = settings.buffer_font_family;
-// // let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
-// // let font_properties = Default::default();
-// // let font_id = font_cache
-// // .select_font(font_family_id, &font_properties)
-// // .unwrap();
-// // let font_size = settings.buffer_font_size(cx);
-// // EditorStyle {
-// // text: TextStyle {
-// // color: settings.theme.editor.text_color,
-// // font_family_name,
-// // font_family_id,
-// // font_id,
-// // font_size,
-// // font_properties,
-// // underline: Default::default(),
-// // soft_wrap: false,
-// // },
-// // placeholder_text: None,
-// // line_height_scalar,
-// // theme,
-// // theme_id,
-// // }
-// };
-
-// if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) {
-// if let Some(highlighted) = style
-// .text
-// .clone()
-// .highlight(highlight_style, font_cache)
-// .log_err()
-// {
-// style.text = highlighted;
-// }
-// }
-
-// style
-// }
-
trait SelectionExt {
fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
@@ -5427,178 +5427,177 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
);
}
-//todo!(completion)
-// #[gpui::test]
-// async fn test_completion(cx: &mut gpui::TestAppContext) {
-// init_test(cx, |_| {});
+#[gpui::test]
+async fn test_completion(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
-// let mut cx = EditorLspTestContext::new_rust(
-// lsp::ServerCapabilities {
-// completion_provider: Some(lsp::CompletionOptions {
-// trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-// resolve_provider: Some(true),
-// ..Default::default()
-// }),
-// ..Default::default()
-// },
-// cx,
-// )
-// .await;
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+ resolve_provider: Some(true),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
-// cx.set_state(indoc! {"
-// oneΛ
-// two
-// three
-// "});
-// cx.simulate_keystroke(".");
-// handle_completion_request(
-// &mut cx,
-// indoc! {"
-// one.|<>
-// two
-// three
-// "},
-// vec!["first_completion", "second_completion"],
-// )
-// .await;
-// cx.condition(|editor, _| editor.context_menu_visible())
-// .await;
-// let apply_additional_edits = cx.update_editor(|editor, cx| {
-// editor.context_menu_next(&Default::default(), cx);
-// editor
-// .confirm_completion(&ConfirmCompletion::default(), cx)
-// .unwrap()
-// });
-// cx.assert_editor_state(indoc! {"
-// one.second_completionΛ
-// two
-// three
-// "});
+ cx.set_state(indoc! {"
+ oneΛ
+ two
+ three
+ "});
+ cx.simulate_keystroke(".");
+ handle_completion_request(
+ &mut cx,
+ indoc! {"
+ one.|<>
+ two
+ three
+ "},
+ vec!["first_completion", "second_completion"],
+ )
+ .await;
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
+ let apply_additional_edits = cx.update_editor(|editor, cx| {
+ editor.context_menu_next(&Default::default(), cx);
+ editor
+ .confirm_completion(&ConfirmCompletion::default(), cx)
+ .unwrap()
+ });
+ cx.assert_editor_state(indoc! {"
+ one.second_completionΛ
+ two
+ three
+ "});
-// handle_resolve_completion_request(
-// &mut cx,
-// Some(vec![
-// (
-// //This overlaps with the primary completion edit which is
-// //misbehavior from the LSP spec, test that we filter it out
-// indoc! {"
-// one.second_Λcompletion
-// two
-// threeΛ
-// "},
-// "overlapping additional edit",
-// ),
-// (
-// indoc! {"
-// one.second_completion
-// two
-// threeΛ
-// "},
-// "\nadditional edit",
-// ),
-// ]),
-// )
-// .await;
-// apply_additional_edits.await.unwrap();
-// cx.assert_editor_state(indoc! {"
-// one.second_completionΛ
-// two
-// three
-// additional edit
-// "});
+ handle_resolve_completion_request(
+ &mut cx,
+ Some(vec![
+ (
+ //This overlaps with the primary completion edit which is
+ //misbehavior from the LSP spec, test that we filter it out
+ indoc! {"
+ one.second_Λcompletion
+ two
+ threeΛ
+ "},
+ "overlapping additional edit",
+ ),
+ (
+ indoc! {"
+ one.second_completion
+ two
+ threeΛ
+ "},
+ "\nadditional edit",
+ ),
+ ]),
+ )
+ .await;
+ apply_additional_edits.await.unwrap();
+ cx.assert_editor_state(indoc! {"
+ one.second_completionΛ
+ two
+ three
+ additional edit
+ "});
-// cx.set_state(indoc! {"
-// one.second_completion
-// twoΛ
-// threeΛ
-// additional edit
-// "});
-// cx.simulate_keystroke(" ");
-// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-// cx.simulate_keystroke("s");
-// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+ cx.set_state(indoc! {"
+ one.second_completion
+ twoΛ
+ threeΛ
+ additional edit
+ "});
+ cx.simulate_keystroke(" ");
+ assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+ cx.simulate_keystroke("s");
+ assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-// cx.assert_editor_state(indoc! {"
-// one.second_completion
-// two sΛ
-// three sΛ
-// additional edit
-// "});
-// handle_completion_request(
-// &mut cx,
-// indoc! {"
-// one.second_completion
-// two s
-// three <s|>
-// additional edit
-// "},
-// vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-// )
-// .await;
-// cx.condition(|editor, _| editor.context_menu_visible())
-// .await;
+ cx.assert_editor_state(indoc! {"
+ one.second_completion
+ two sΛ
+ three sΛ
+ additional edit
+ "});
+ handle_completion_request(
+ &mut cx,
+ indoc! {"
+ one.second_completion
+ two s
+ three <s|>
+ additional edit
+ "},
+ vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+ )
+ .await;
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
-// cx.simulate_keystroke("i");
+ cx.simulate_keystroke("i");
-// handle_completion_request(
-// &mut cx,
-// indoc! {"
-// one.second_completion
-// two si
-// three <si|>
-// additional edit
-// "},
-// vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-// )
-// .await;
-// cx.condition(|editor, _| editor.context_menu_visible())
-// .await;
+ handle_completion_request(
+ &mut cx,
+ indoc! {"
+ one.second_completion
+ two si
+ three <si|>
+ additional edit
+ "},
+ vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+ )
+ .await;
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
-// let apply_additional_edits = cx.update_editor(|editor, cx| {
-// editor
-// .confirm_completion(&ConfirmCompletion::default(), cx)
-// .unwrap()
-// });
-// cx.assert_editor_state(indoc! {"
-// one.second_completion
-// two sixth_completionΛ
-// three sixth_completionΛ
-// additional edit
-// "});
+ let apply_additional_edits = cx.update_editor(|editor, cx| {
+ editor
+ .confirm_completion(&ConfirmCompletion::default(), cx)
+ .unwrap()
+ });
+ cx.assert_editor_state(indoc! {"
+ one.second_completion
+ two sixth_completionΛ
+ three sixth_completionΛ
+ additional edit
+ "});
-// handle_resolve_completion_request(&mut cx, None).await;
-// apply_additional_edits.await.unwrap();
+ handle_resolve_completion_request(&mut cx, None).await;
+ apply_additional_edits.await.unwrap();
-// cx.update(|cx| {
-// cx.update_global::<SettingsStore, _, _>(|settings, cx| {
-// settings.update_user_settings::<EditorSettings>(cx, |settings| {
-// settings.show_completions_on_input = Some(false);
-// });
-// })
-// });
-// cx.set_state("editorΛ");
-// cx.simulate_keystroke(".");
-// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-// cx.simulate_keystroke("c");
-// cx.simulate_keystroke("l");
-// cx.simulate_keystroke("o");
-// cx.assert_editor_state("editor.cloΛ");
-// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-// cx.update_editor(|editor, cx| {
-// editor.show_completions(&ShowCompletions, cx);
-// });
-// handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
-// cx.condition(|editor, _| editor.context_menu_visible())
-// .await;
-// let apply_additional_edits = cx.update_editor(|editor, cx| {
-// editor
-// .confirm_completion(&ConfirmCompletion::default(), cx)
-// .unwrap()
-// });
-// cx.assert_editor_state("editor.closeΛ");
-// handle_resolve_completion_request(&mut cx, None).await;
-// apply_additional_edits.await.unwrap();
-// }
+ cx.update(|cx| {
+ cx.update_global::<SettingsStore, _>(|settings, cx| {
+ settings.update_user_settings::<EditorSettings>(cx, |settings| {
+ settings.show_completions_on_input = Some(false);
+ });
+ })
+ });
+ cx.set_state("editorΛ");
+ cx.simulate_keystroke(".");
+ assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+ cx.simulate_keystroke("c");
+ cx.simulate_keystroke("l");
+ cx.simulate_keystroke("o");
+ cx.assert_editor_state("editor.cloΛ");
+ assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+ cx.update_editor(|editor, cx| {
+ editor.show_completions(&ShowCompletions, cx);
+ });
+ handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
+ let apply_additional_edits = cx.update_editor(|editor, cx| {
+ editor
+ .confirm_completion(&ConfirmCompletion::default(), cx)
+ .unwrap()
+ });
+ cx.assert_editor_state("editor.closeΛ");
+ handle_resolve_completion_request(&mut cx, None).await;
+ apply_additional_edits.await.unwrap();
+}
#[gpui::test]
async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
@@ -7803,197 +7802,196 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
);
}
-//todo!(completions)
-// #[gpui::test]
-// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
-// init_test(cx, |_| {});
+#[gpui::test]
+async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
-// let mut cx = EditorLspTestContext::new_rust(
-// lsp::ServerCapabilities {
-// completion_provider: Some(lsp::CompletionOptions {
-// trigger_characters: Some(vec![".".to_string()]),
-// resolve_provider: Some(true),
-// ..Default::default()
-// }),
-// ..Default::default()
-// },
-// cx,
-// )
-// .await;
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string()]),
+ resolve_provider: Some(true),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
-// cx.set_state(indoc! {"fn main() { let a = 2Λ; }"});
-// cx.simulate_keystroke(".");
-// let completion_item = lsp::CompletionItem {
-// label: "some".into(),
-// kind: Some(lsp::CompletionItemKind::SNIPPET),
-// detail: Some("Wrap the expression in an `Option::Some`".to_string()),
-// documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
-// kind: lsp::MarkupKind::Markdown,
-// value: "```rust\nSome(2)\n```".to_string(),
-// })),
-// deprecated: Some(false),
-// sort_text: Some("fffffff2".to_string()),
-// filter_text: Some("some".to_string()),
-// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-// range: lsp::Range {
-// start: lsp::Position {
-// line: 0,
-// character: 22,
-// },
-// end: lsp::Position {
-// line: 0,
-// character: 22,
-// },
-// },
-// new_text: "Some(2)".to_string(),
-// })),
-// additional_text_edits: Some(vec![lsp::TextEdit {
-// range: lsp::Range {
-// start: lsp::Position {
-// line: 0,
-// character: 20,
-// },
-// end: lsp::Position {
-// line: 0,
-// character: 22,
-// },
-// },
-// new_text: "".to_string(),
-// }]),
-// ..Default::default()
-// };
-
-// let closure_completion_item = completion_item.clone();
-// let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
-// let task_completion_item = closure_completion_item.clone();
-// async move {
-// Ok(Some(lsp::CompletionResponse::Array(vec![
-// task_completion_item,
-// ])))
-// }
-// });
+ cx.set_state(indoc! {"fn main() { let a = 2Λ; }"});
+ cx.simulate_keystroke(".");
+ let completion_item = lsp::CompletionItem {
+ label: "some".into(),
+ kind: Some(lsp::CompletionItemKind::SNIPPET),
+ detail: Some("Wrap the expression in an `Option::Some`".to_string()),
+ documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
+ kind: lsp::MarkupKind::Markdown,
+ value: "```rust\nSome(2)\n```".to_string(),
+ })),
+ deprecated: Some(false),
+ sort_text: Some("fffffff2".to_string()),
+ filter_text: Some("some".to_string()),
+ insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+ text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 22,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 22,
+ },
+ },
+ new_text: "Some(2)".to_string(),
+ })),
+ additional_text_edits: Some(vec![lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 20,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 22,
+ },
+ },
+ new_text: "".to_string(),
+ }]),
+ ..Default::default()
+ };
-// request.next().await;
+ let closure_completion_item = completion_item.clone();
+ let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
+ let task_completion_item = closure_completion_item.clone();
+ async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ task_completion_item,
+ ])))
+ }
+ });
-// cx.condition(|editor, _| editor.context_menu_visible())
-// .await;
-// let apply_additional_edits = cx.update_editor(|editor, cx| {
-// editor
-// .confirm_completion(&ConfirmCompletion::default(), cx)
-// .unwrap()
-// });
-// cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)Λ; }"});
-
-// cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
-// let task_completion_item = completion_item.clone();
-// async move { Ok(task_completion_item) }
-// })
-// .next()
-// .await
-// .unwrap();
-// apply_additional_edits.await.unwrap();
-// cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)Λ; }"});
-// }
+ request.next().await;
-// #[gpui::test]
-// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
-// init_test(cx, |_| {});
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
+ let apply_additional_edits = cx.update_editor(|editor, cx| {
+ editor
+ .confirm_completion(&ConfirmCompletion::default(), cx)
+ .unwrap()
+ });
+ cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)Λ; }"});
+
+ cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+ let task_completion_item = completion_item.clone();
+ async move { Ok(task_completion_item) }
+ })
+ .next()
+ .await
+ .unwrap();
+ apply_additional_edits.await.unwrap();
+ cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)Λ; }"});
+}
-// let mut cx = EditorLspTestContext::new(
-// Language::new(
-// LanguageConfig {
-// path_suffixes: vec!["jsx".into()],
-// overrides: [(
-// "element".into(),
-// LanguageConfigOverride {
-// word_characters: Override::Set(['-'].into_iter().collect()),
-// ..Default::default()
-// },
-// )]
-// .into_iter()
-// .collect(),
-// ..Default::default()
-// },
-// Some(tree_sitter_typescript::language_tsx()),
-// )
-// .with_override_query("(jsx_self_closing_element) @element")
-// .unwrap(),
-// lsp::ServerCapabilities {
-// completion_provider: Some(lsp::CompletionOptions {
-// trigger_characters: Some(vec![":".to_string()]),
-// ..Default::default()
-// }),
-// ..Default::default()
-// },
-// cx,
-// )
-// .await;
+#[gpui::test]
+async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
-// cx.lsp
-// .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
-// Ok(Some(lsp::CompletionResponse::Array(vec![
-// lsp::CompletionItem {
-// label: "bg-blue".into(),
-// ..Default::default()
-// },
-// lsp::CompletionItem {
-// label: "bg-red".into(),
-// ..Default::default()
-// },
-// lsp::CompletionItem {
-// label: "bg-yellow".into(),
-// ..Default::default()
-// },
-// ])))
-// });
+ let mut cx = EditorLspTestContext::new(
+ Language::new(
+ LanguageConfig {
+ path_suffixes: vec!["jsx".into()],
+ overrides: [(
+ "element".into(),
+ LanguageConfigOverride {
+ word_characters: Override::Set(['-'].into_iter().collect()),
+ ..Default::default()
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ..Default::default()
+ },
+ Some(tree_sitter_typescript::language_tsx()),
+ )
+ .with_override_query("(jsx_self_closing_element) @element")
+ .unwrap(),
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![":".to_string()]),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
-// cx.set_state(r#"<p class="bgΛ" />"#);
+ cx.lsp
+ .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ lsp::CompletionItem {
+ label: "bg-blue".into(),
+ ..Default::default()
+ },
+ lsp::CompletionItem {
+ label: "bg-red".into(),
+ ..Default::default()
+ },
+ lsp::CompletionItem {
+ label: "bg-yellow".into(),
+ ..Default::default()
+ },
+ ])))
+ });
-// // Trigger completion when typing a dash, because the dash is an extra
-// // word character in the 'element' scope, which contains the cursor.
-// cx.simulate_keystroke("-");
-// cx.executor().run_until_parked();
-// cx.update_editor(|editor, _| {
-// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-// assert_eq!(
-// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-// &["bg-red", "bg-blue", "bg-yellow"]
-// );
-// } else {
-// panic!("expected completion menu to be open");
-// }
-// });
+ cx.set_state(r#"<p class="bgΛ" />"#);
-// cx.simulate_keystroke("l");
-// cx.executor().run_until_parked();
-// cx.update_editor(|editor, _| {
-// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-// assert_eq!(
-// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-// &["bg-blue", "bg-yellow"]
-// );
-// } else {
-// panic!("expected completion menu to be open");
-// }
-// });
+ // Trigger completion when typing a dash, because the dash is an extra
+ // word character in the 'element' scope, which contains the cursor.
+ cx.simulate_keystroke("-");
+ cx.executor().run_until_parked();
+ cx.update_editor(|editor, _| {
+ if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+ assert_eq!(
+ menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ &["bg-red", "bg-blue", "bg-yellow"]
+ );
+ } else {
+ panic!("expected completion menu to be open");
+ }
+ });
-// // When filtering completions, consider the character after the '-' to
-// // be the start of a subword.
-// cx.set_state(r#"<p class="yelΛ" />"#);
-// cx.simulate_keystroke("l");
-// cx.executor().run_until_parked();
-// cx.update_editor(|editor, _| {
-// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-// assert_eq!(
-// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-// &["bg-yellow"]
-// );
-// } else {
-// panic!("expected completion menu to be open");
-// }
-// });
-// }
+ cx.simulate_keystroke("l");
+ cx.executor().run_until_parked();
+ cx.update_editor(|editor, _| {
+ if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+ assert_eq!(
+ menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ &["bg-blue", "bg-yellow"]
+ );
+ } else {
+ panic!("expected completion menu to be open");
+ }
+ });
+
+ // When filtering completions, consider the character after the '-' to
+ // be the start of a subword.
+ cx.set_state(r#"<p class="yelΛ" />"#);
+ cx.simulate_keystroke("l");
+ cx.executor().run_until_parked();
+ cx.update_editor(|editor, _| {
+ if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+ assert_eq!(
+ menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ &["bg-yellow"]
+ );
+ } else {
+ panic!("expected completion menu to be open");
+ }
+ });
+}
#[gpui::test]
async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
@@ -9,9 +9,11 @@ use crate::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
link_go_to_definition::{
- go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
- update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+ go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
+ update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+ LinkGoToDefinitionState,
},
+ mouse_context_menu,
scroll::scroll_amount::ScrollAmount,
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point,
@@ -19,14 +21,15 @@ use crate::{
};
use anyhow::Result;
use collections::{BTreeMap, HashMap};
+use git::diff::DiffHunkStatus;
use gpui::{
- div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
- BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
- ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
- IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
- StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
- ViewContext, WeakView, WindowContext, WrappedLine,
+ div, overlay, point, px, relative, size, transparent_black, Action, AnchorCorner, AnyElement,
+ AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle,
+ DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, EntityId, Hsla,
+ InteractiveBounds, InteractiveElement, IntoElement, LineLayout, ModifiersChangedEvent,
+ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
+ ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
+ Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -48,6 +51,7 @@ use std::{
};
use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor};
+use ui::prelude::*;
use ui::{h_stack, IconButton, Tooltip};
use util::ResultExt;
use workspace::item::Item;
@@ -138,8 +142,6 @@ impl EditorElement {
register_action(view, cx, Editor::move_right);
register_action(view, cx, Editor::move_down);
register_action(view, cx, Editor::move_up);
- // on_action(cx, Editor::new_file); todo!()
- // on_action(cx, Editor::new_file_in_direction); todo!()
register_action(view, cx, Editor::cancel);
register_action(view, cx, Editor::newline);
register_action(view, cx, Editor::newline_above);
@@ -262,7 +264,7 @@ impl EditorElement {
register_action(view, cx, Editor::fold_selected_ranges);
register_action(view, cx, Editor::show_completions);
register_action(view, cx, Editor::toggle_code_actions);
- // on_action(cx, Editor::open_excerpts); todo!()
+ register_action(view, cx, Editor::open_excerpts);
register_action(view, cx, Editor::toggle_soft_wrap);
register_action(view, cx, Editor::toggle_inlay_hints);
register_action(view, cx, hover_popover::hover);
@@ -311,7 +313,57 @@ impl EditorElement {
register_action(view, cx, Editor::context_menu_last);
}
- fn mouse_down(
+ fn register_key_listeners(&self, cx: &mut WindowContext) {
+ cx.on_key_event({
+ let editor = self.editor.clone();
+ move |event: &ModifiersChangedEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
+ cx.stop_propagation();
+ }
+ }
+ });
+ }
+
+ fn modifiers_changed(
+ editor: &mut Editor,
+ event: &ModifiersChangedEvent,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ let pending_selection = editor.has_pending_selection();
+
+ if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
+ if event.command && !pending_selection {
+ let point = point.clone();
+ let snapshot = editor.snapshot(cx);
+ let kind = point.definition_kind(event.shift);
+
+ show_link_definition(kind, editor, point, snapshot, cx);
+ return false;
+ }
+ }
+
+ {
+ if editor.link_go_to_definition_state.symbol_range.is_some()
+ || !editor.link_go_to_definition_state.definitions.is_empty()
+ {
+ editor.link_go_to_definition_state.symbol_range.take();
+ editor.link_go_to_definition_state.definitions.clear();
+ cx.notify();
+ }
+
+ editor.link_go_to_definition_state.task = None;
+
+ editor.clear_highlights::<LinkGoToDefinitionState>(cx);
+ }
+
+ false
+ }
+
+ fn mouse_left_down(
editor: &mut Editor,
event: &MouseDownEvent,
position_map: &PositionMap,
@@ -364,25 +416,25 @@ impl EditorElement {
true
}
- // fn mouse_right_down(
- // editor: &mut Editor,
- // position: gpui::Point<Pixels>,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // cx: &mut EventContext<Editor>,
- // ) -> bool {
- // if !text_bounds.contains_point(position) {
- // return false;
- // }
- // let point_for_position = position_map.point_for_position(text_bounds, position);
- // mouse_context_menu::deploy_context_menu(
- // editor,
- // position,
- // point_for_position.previous_valid,
- // cx,
- // );
- // true
- // }
+ fn mouse_right_down(
+ editor: &mut Editor,
+ event: &MouseDownEvent,
+ position_map: &PositionMap,
+ text_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ if !text_bounds.contains_point(&event.position) {
+ return false;
+ }
+ let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ mouse_context_menu::deploy_context_menu(
+ editor,
+ event.position,
+ point_for_position.previous_valid,
+ cx,
+ );
+ true
+ }
fn mouse_up(
editor: &mut Editor,
@@ -724,87 +776,85 @@ impl EditorElement {
}
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
- // todo!()
- // let diff_style = &theme::current(cx).editor.diff.clone();
- // let line_height = layout.position_map.line_height;
-
- // let scroll_position = layout.position_map.snapshot.scroll_position();
- // let scroll_top = scroll_position.y * line_height;
-
- // for hunk in &layout.display_hunks {
- // let (display_row_range, status) = match hunk {
- // //TODO: This rendering is entirely a horrible hack
- // &DisplayDiffHunk::Folded { display_row: row } => {
- // let start_y = row as f32 * line_height - scroll_top;
- // let end_y = start_y + line_height;
-
- // let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(diff_style.modified),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (1. * line_height).into(),
- // });
-
- // continue;
- // }
-
- // DisplayDiffHunk::Unfolded {
- // display_row_range,
- // status,
- // } => (display_row_range, status),
- // };
-
- // let color = match status {
- // DiffHunkStatus::Added => diff_style.inserted,
- // DiffHunkStatus::Modified => diff_style.modified,
-
- // //TODO: This rendering is entirely a horrible hack
- // DiffHunkStatus::Removed => {
- // let row = display_row_range.start;
-
- // let offset = line_height / 2.;
- // let start_y = row as f32 * line_height - offset - scroll_top;
- // let end_y = start_y + line_height;
-
- // let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(diff_style.deleted),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (1. * line_height).into(),
- // });
-
- // continue;
- // }
- // };
-
- // let start_row = display_row_range.start;
- // let end_row = display_row_range.end;
-
- // let start_y = start_row as f32 * line_height - scroll_top;
- // let end_y = end_row as f32 * line_height - scroll_top;
-
- // let width = diff_style.width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(color),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (diff_style.corner_radius * line_height).into(),
- // });
- // }
+ let line_height = layout.position_map.line_height;
+
+ let scroll_position = layout.position_map.snapshot.scroll_position();
+ let scroll_top = scroll_position.y * line_height;
+
+ for hunk in &layout.display_hunks {
+ let (display_row_range, status) = match hunk {
+ //TODO: This rendering is entirely a horrible hack
+ &DisplayDiffHunk::Folded { display_row: row } => {
+ let start_y = row as f32 * line_height - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ gpui::yellow(), // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+
+ continue;
+ }
+
+ DisplayDiffHunk::Unfolded {
+ display_row_range,
+ status,
+ } => (display_row_range, status),
+ };
+
+ let color = match status {
+ DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color")
+ DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
+
+ //TODO: This rendering is entirely a horrible hack
+ DiffHunkStatus::Removed => {
+ let row = display_row_range.start;
+
+ let offset = line_height / 2.;
+ let start_y = row as f32 * line_height - offset - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ gpui::red(), // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+
+ continue;
+ }
+ };
+
+ let start_row = display_row_range.start;
+ let end_row = display_row_range.end;
+
+ let start_y = start_row as f32 * line_height - scroll_top;
+ let end_y = end_row as f32 * line_height - scroll_top;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(0.05 * line_height),
+ color, // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+ }
}
fn paint_text(
@@ -830,15 +880,19 @@ impl EditorElement {
bounds: text_bounds,
}),
|cx| {
- // todo!("cursor region")
- // cx.scene().push_cursor_region(CursorRegion {
- // bounds,
- // style: if !editor.link_go_to_definition_state.definitions.is_empty {
- // CursorStyle::PointingHand
- // } else {
- // CursorStyle::IBeam
- // },
- // });
+ if text_bounds.contains_point(&cx.mouse_position()) {
+ if self
+ .editor
+ .read(cx)
+ .link_go_to_definition_state
+ .definitions
+ .is_empty()
+ {
+ cx.set_cursor_style(CursorStyle::IBeam);
+ } else {
+ cx.set_cursor_style(CursorStyle::PointingHand);
+ }
+ }
let fold_corner_radius = 0.15 * layout.position_map.line_height;
cx.with_element_id(Some("folds"), |cx| {
@@ -1137,6 +1191,22 @@ impl EditorElement {
}
}
}
+
+ if let Some(mouse_context_menu) =
+ self.editor.read(cx).mouse_context_menu.as_ref()
+ {
+ let element = overlay()
+ .position(mouse_context_menu.position)
+ .child(mouse_context_menu.context_menu.clone())
+ .anchor(AnchorCorner::TopLeft)
+ .snap_to_window();
+ element.draw(
+ gpui::Point::default(),
+ size(AvailableSpace::MinContent, AvailableSpace::MinContent),
+ cx,
+ |_, _| {},
+ );
+ }
})
},
)
@@ -1661,11 +1731,6 @@ impl EditorElement {
cx: &mut WindowContext,
) -> LayoutState {
self.editor.update(cx, |editor, cx| {
- // let mut size = constraint.max;
- // if size.x.is_infinite() {
- // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
- // }
-
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
@@ -1701,6 +1766,7 @@ impl EditorElement {
};
editor.gutter_width = gutter_width;
+
let text_width = bounds.size.width - gutter_width;
let overscroll = size(em_width, px(0.));
let snapshot = {
@@ -1727,25 +1793,6 @@ impl EditorElement {
.collect::<SmallVec<[_; 2]>>();
let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
- // todo!("this should happen during layout")
- let editor_mode = snapshot.mode;
- if let EditorMode::AutoHeight { max_lines } = editor_mode {
- todo!()
- // size.set_y(
- // scroll_height
- // .min(constraint.max_along(Axis::Vertical))
- // .max(constraint.min_along(Axis::Vertical))
- // .max(line_height)
- // .min(line_height * max_lines as f32),
- // )
- } else if let EditorMode::SingleLine = editor_mode {
- bounds.size.height = line_height.min(bounds.size.height);
- }
- // todo!()
- // else if size.y.is_infinite() {
- // // size.set_y(scroll_height);
- // }
- //
let gutter_size = size(gutter_width, bounds.size.height);
let text_size = size(text_width, bounds.size.height);
@@ -2063,7 +2110,7 @@ impl EditorElement {
.unwrap();
LayoutState {
- mode: editor_mode,
+ mode: snapshot.mode,
position_map: Arc::new(PositionMap {
size: bounds.size,
scroll_position: point(
@@ -2307,10 +2354,10 @@ impl EditorElement {
return;
}
- let should_cancel = editor.update(cx, |editor, cx| {
+ let handled = editor.update(cx, |editor, cx| {
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
});
- if should_cancel {
+ if handled {
cx.stop_propagation();
}
}
@@ -2326,19 +2373,25 @@ impl EditorElement {
return;
}
- let should_cancel = editor.update(cx, |editor, cx| {
- Self::mouse_down(
- editor,
- event,
- &position_map,
- text_bounds,
- gutter_bounds,
- &stacking_order,
- cx,
- )
- });
+ let handled = match event.button {
+ MouseButton::Left => editor.update(cx, |editor, cx| {
+ Self::mouse_left_down(
+ editor,
+ event,
+ &position_map,
+ text_bounds,
+ gutter_bounds,
+ &stacking_order,
+ cx,
+ )
+ }),
+ MouseButton::Right => editor.update(cx, |editor, cx| {
+ Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
+ }),
+ _ => false,
+ };
- if should_cancel {
+ if handled {
cx.stop_propagation()
}
}
@@ -2350,7 +2403,7 @@ impl EditorElement {
let stacking_order = cx.stacking_order().clone();
move |event: &MouseUpEvent, phase, cx| {
- let should_cancel = editor.update(cx, |editor, cx| {
+ let handled = editor.update(cx, |editor, cx| {
Self::mouse_up(
editor,
event,
@@ -2361,26 +2414,11 @@ impl EditorElement {
)
});
- if should_cancel {
+ if handled {
cx.stop_propagation()
}
}
});
- //todo!()
- // on_down(MouseButton::Right, {
- // let position_map = layout.position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_right_down(
- // editor,
- // event.position,
- // position_map.as_ref(),
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event();
- // }
- // }
- // });
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
@@ -2616,19 +2654,44 @@ impl Element for EditorElement {
cx: &mut gpui::WindowContext,
) -> (gpui::LayoutId, Self::State) {
self.editor.update(cx, |editor, cx| {
- editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
+ editor.set_style(self.style.clone(), cx);
- let rem_size = cx.rem_size();
- let mut style = Style::default();
- style.size.width = relative(1.).into();
- style.size.height = match editor.mode {
+ let layout_id = match editor.mode {
EditorMode::SingleLine => {
- self.style.text.line_height_in_pixels(cx.rem_size()).into()
+ let rem_size = cx.rem_size();
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
+ cx.request_layout(&style, None)
+ }
+ EditorMode::AutoHeight { max_lines } => {
+ let editor_handle = cx.view().clone();
+ let max_line_number_width =
+ self.max_line_number_width(&editor.snapshot(cx), cx);
+ cx.request_measured_layout(
+ Style::default(),
+ move |known_dimensions, available_space, cx| {
+ editor_handle
+ .update(cx, |editor, cx| {
+ compute_auto_height_layout(
+ editor,
+ max_lines,
+ max_line_number_width,
+ known_dimensions,
+ cx,
+ )
+ })
+ .unwrap_or_default()
+ },
+ )
+ }
+ EditorMode::Full => {
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = relative(1.).into();
+ cx.request_layout(&style, None)
}
- EditorMode::AutoHeight { .. } => todo!(),
- EditorMode::Full => relative(1.).into(),
};
- let layout_id = cx.request_layout(&style, None);
(layout_id, ())
})
@@ -2656,6 +2719,7 @@ impl Element for EditorElement {
let dispatch_context = self.editor.read(cx).dispatch_context(cx);
cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
self.register_actions(cx);
+ self.register_key_listeners(cx);
// We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |cx| {
@@ -2697,604 +2761,6 @@ impl IntoElement for EditorElement {
}
}
-// impl EditorElement {
-// type LayoutState = LayoutState;
-// type PaintState = ();
-
-// fn layout(
-// &mut self,
-// constraint: SizeConstraint,
-// editor: &mut Editor,
-// cx: &mut ViewContext<Editor>,
-// ) -> (gpui::Point<Pixels>, Self::LayoutState) {
-// let mut size = constraint.max;
-// if size.x.is_infinite() {
-// unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-// }
-
-// let snapshot = editor.snapshot(cx);
-// let style = self.style.clone();
-
-// let line_height = (style.text.font_size * style.line_height_scalar).round();
-
-// let gutter_padding;
-// let gutter_width;
-// let gutter_margin;
-// if snapshot.show_gutter {
-// let em_width = style.text.em_width(cx.font_cache());
-// gutter_padding = (em_width * style.gutter_padding_factor).round();
-// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-// gutter_margin = -style.text.descent(cx.font_cache());
-// } else {
-// gutter_padding = 0.0;
-// gutter_width = 0.0;
-// gutter_margin = 0.0;
-// };
-
-// let text_width = size.x - gutter_width;
-// let em_width = style.text.em_width(cx.font_cache());
-// let em_advance = style.text.em_advance(cx.font_cache());
-// let overscroll = point(em_width, 0.);
-// let snapshot = {
-// editor.set_visible_line_count(size.y / line_height, cx);
-
-// let editor_width = text_width - gutter_margin - overscroll.x - em_width;
-// let wrap_width = match editor.soft_wrap_mode(cx) {
-// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
-// SoftWrap::EditorWidth => editor_width,
-// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
-// };
-
-// if editor.set_wrap_width(Some(wrap_width), cx) {
-// editor.snapshot(cx)
-// } else {
-// snapshot
-// }
-// };
-
-// let wrap_guides = editor
-// .wrap_guides(cx)
-// .iter()
-// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
-// .collect();
-
-// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
-// if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
-// size.set_y(
-// scroll_height
-// .min(constraint.max_along(Axis::Vertical))
-// .max(constraint.min_along(Axis::Vertical))
-// .max(line_height)
-// .min(line_height * max_lines as f32),
-// )
-// } else if let EditorMode::SingleLine = snapshot.mode {
-// size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
-// } else if size.y.is_infinite() {
-// size.set_y(scroll_height);
-// }
-// let gutter_size = point(gutter_width, size.y);
-// let text_size = point(text_width, size.y);
-
-// let autoscroll_horizontally = editor.autoscroll_vertically(size.y, line_height, cx);
-// let mut snapshot = editor.snapshot(cx);
-
-// let scroll_position = snapshot.scroll_position();
-// // The scroll position is a fractional point, the whole number of which represents
-// // the top of the window in terms of display rows.
-// let start_row = scroll_position.y as u32;
-// let height_in_lines = size.y / line_height;
-// let max_row = snapshot.max_point().row();
-
-// // Add 1 to ensure selections bleed off screen
-// let end_row = 1 + cmp::min(
-// (scroll_position.y + height_in_lines).ceil() as u32,
-// max_row,
-// );
-
-// let start_anchor = if start_row == 0 {
-// Anchor::min()
-// } else {
-// snapshot
-// .buffer_snapshot
-// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
-// };
-// let end_anchor = if end_row > max_row {
-// Anchor::max
-// } else {
-// snapshot
-// .buffer_snapshot
-// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
-// };
-
-// let mut selections: Vec<(SelectionStyle, Vec<SelectionLayout>)> = Vec::new();
-// let mut active_rows = BTreeMap::new();
-// let mut fold_ranges = Vec::new();
-// let is_singleton = editor.is_singleton(cx);
-
-// let highlighted_rows = editor.highlighted_rows();
-// let theme = theme::current(cx);
-// let highlighted_ranges = editor.background_highlights_in_range(
-// start_anchor..end_anchor,
-// &snapshot.display_snapshot,
-// theme.as_ref(),
-// );
-
-// fold_ranges.extend(
-// snapshot
-// .folds_in_range(start_anchor..end_anchor)
-// .map(|anchor| {
-// let start = anchor.start.to_point(&snapshot.buffer_snapshot);
-// (
-// start.row,
-// start.to_display_point(&snapshot.display_snapshot)
-// ..anchor.end.to_display_point(&snapshot),
-// )
-// }),
-// );
-
-// let mut newest_selection_head = None;
-
-// if editor.show_local_selections {
-// let mut local_selections: Vec<Selection<Point>> = editor
-// .selections
-// .disjoint_in_range(start_anchor..end_anchor, cx);
-// local_selections.extend(editor.selections.pending(cx));
-// let mut layouts = Vec::new();
-// let newest = editor.selections.newest(cx);
-// for selection in local_selections.drain(..) {
-// let is_empty = selection.start == selection.end;
-// let is_newest = selection == newest;
-
-// let layout = SelectionLayout::new(
-// selection,
-// editor.selections.line_mode,
-// editor.cursor_shape,
-// &snapshot.display_snapshot,
-// is_newest,
-// true,
-// );
-// if is_newest {
-// newest_selection_head = Some(layout.head);
-// }
-
-// for row in cmp::max(layout.active_rows.start, start_row)
-// ..=cmp::min(layout.active_rows.end, end_row)
-// {
-// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
-// *contains_non_empty_selection |= !is_empty;
-// }
-// layouts.push(layout);
-// }
-
-// selections.push((style.selection, layouts));
-// }
-
-// if let Some(collaboration_hub) = &editor.collaboration_hub {
-// // When following someone, render the local selections in their color.
-// if let Some(leader_id) = editor.leader_peer_id {
-// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-// if let Some(participant_index) = collaboration_hub
-// .user_participant_indices(cx)
-// .get(&collaborator.user_id)
-// {
-// if let Some((local_selection_style, _)) = selections.first_mut() {
-// *local_selection_style =
-// style.selection_style_for_room_participant(participant_index.0);
-// }
-// }
-// }
-// }
-
-// let mut remote_selections = HashMap::default();
-// for selection in snapshot.remote_selections_in_range(
-// &(start_anchor..end_anchor),
-// collaboration_hub.as_ref(),
-// cx,
-// ) {
-// let selection_style = if let Some(participant_index) = selection.participant_index {
-// style.selection_style_for_room_participant(participant_index.0)
-// } else {
-// style.absent_selection
-// };
-
-// // Don't re-render the leader's selections, since the local selections
-// // match theirs.
-// if Some(selection.peer_id) == editor.leader_peer_id {
-// continue;
-// }
-
-// remote_selections
-// .entry(selection.replica_id)
-// .or_insert((selection_style, Vec::new()))
-// .1
-// .push(SelectionLayout::new(
-// selection.selection,
-// selection.line_mode,
-// selection.cursor_shape,
-// &snapshot.display_snapshot,
-// false,
-// false,
-// ));
-// }
-
-// selections.extend(remote_selections.into_values());
-// }
-
-// let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
-// let show_scrollbars = match scrollbar_settings.show {
-// ShowScrollbar::Auto => {
-// // Git
-// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
-// ||
-// // Selections
-// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty)
-// // Scrollmanager
-// || editor.scroll_manager.scrollbars_visible()
-// }
-// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
-// ShowScrollbar::Always => true,
-// ShowScrollbar::Never => false,
-// };
-
-// let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
-// .into_iter()
-// .map(|(id, fold)| {
-// let color = self
-// .style
-// .folds
-// .ellipses
-// .background
-// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-// .color;
-
-// (id, fold, color)
-// })
-// .collect();
-
-// let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-// let newest = editor.selections.newest::<Point>(cx);
-// SelectionLayout::new(
-// newest,
-// editor.selections.line_mode,
-// editor.cursor_shape,
-// &snapshot.display_snapshot,
-// true,
-// true,
-// )
-// .head
-// });
-
-// let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
-// start_row..end_row,
-// &active_rows,
-// head_for_relative,
-// is_singleton,
-// &snapshot,
-// cx,
-// );
-
-// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
-// let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
-
-// let mut max_visible_line_width = 0.0;
-// let line_layouts =
-// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
-// for line_with_invisibles in &line_layouts {
-// if line_with_invisibles.line.width() > max_visible_line_width {
-// max_visible_line_width = line_with_invisibles.line.width();
-// }
-// }
-
-// let style = self.style.clone();
-// let longest_line_width = layout_line(
-// snapshot.longest_row(),
-// &snapshot,
-// &style,
-// cx.text_layout_cache(),
-// )
-// .width();
-// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x;
-// let em_width = style.text.em_width(cx.font_cache());
-// let (scroll_width, blocks) = self.layout_blocks(
-// start_row..end_row,
-// &snapshot,
-// size.x,
-// scroll_width,
-// gutter_padding,
-// gutter_width,
-// em_width,
-// gutter_width + gutter_margin,
-// line_height,
-// &style,
-// &line_layouts,
-// editor,
-// cx,
-// );
-
-// let scroll_max = point(
-// ((scroll_width - text_size.x) / em_width).max(0.0),
-// max_row as f32,
-// );
-
-// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
-// let autoscrolled = if autoscroll_horizontally {
-// editor.autoscroll_horizontally(
-// start_row,
-// text_size.x,
-// scroll_width,
-// em_width,
-// &line_layouts,
-// cx,
-// )
-// } else {
-// false
-// };
-
-// if clamped || autoscrolled {
-// snapshot = editor.snapshot(cx);
-// }
-
-// let style = editor.style(cx);
-
-// let mut context_menu = None;
-// let mut code_actions_indicator = None;
-// if let Some(newest_selection_head) = newest_selection_head {
-// if (start_row..end_row).contains(&newest_selection_head.row()) {
-// if editor.context_menu_visible() {
-// context_menu =
-// editor.render_context_menu(newest_selection_head, style.clone(), cx);
-// }
-
-// let active = matches!(
-// editor.context_menu.read().as_ref(),
-// Some(crate::ContextMenu::CodeActions(_))
-// );
-
-// code_actions_indicator = editor
-// .render_code_actions_indicator(&style, active, cx)
-// .map(|indicator| (newest_selection_head.row(), indicator));
-// }
-// }
-
-// let visible_rows = start_row..start_row + line_layouts.len() as u32;
-// let mut hover = editor.hover_state.render(
-// &snapshot,
-// &style,
-// visible_rows,
-// editor.workspace.as_ref().map(|(w, _)| w.clone()),
-// cx,
-// );
-// let mode = editor.mode;
-
-// let mut fold_indicators = editor.render_fold_indicators(
-// fold_statuses,
-// &style,
-// editor.gutter_hovered,
-// line_height,
-// gutter_margin,
-// cx,
-// );
-
-// if let Some((_, context_menu)) = context_menu.as_mut() {
-// context_menu.layout(
-// SizeConstraint {
-// min: gpui::Point::<Pixels>::zero(),
-// max: point(
-// cx.window_size().x * 0.7,
-// (12. * line_height).min((size.y - line_height) / 2.),
-// ),
-// },
-// editor,
-// cx,
-// );
-// }
-
-// if let Some((_, indicator)) = code_actions_indicator.as_mut() {
-// indicator.layout(
-// SizeConstraint::strict_along(
-// Axis::Vertical,
-// line_height * style.code_actions.vertical_scale,
-// ),
-// editor,
-// cx,
-// );
-// }
-
-// for fold_indicator in fold_indicators.iter_mut() {
-// if let Some(indicator) = fold_indicator.as_mut() {
-// indicator.layout(
-// SizeConstraint::strict_along(
-// Axis::Vertical,
-// line_height * style.code_actions.vertical_scale,
-// ),
-// editor,
-// cx,
-// );
-// }
-// }
-
-// if let Some((_, hover_popovers)) = hover.as_mut() {
-// for hover_popover in hover_popovers.iter_mut() {
-// hover_popover.layout(
-// SizeConstraint {
-// min: gpui::Point::<Pixels>::zero(),
-// max: point(
-// (120. * em_width) // Default size
-// .min(size.x / 2.) // Shrink to half of the editor width
-// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
-// (16. * line_height) // Default size
-// .min(size.y / 2.) // Shrink to half of the editor height
-// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-// ),
-// },
-// editor,
-// cx,
-// );
-// }
-// }
-
-// let invisible_symbol_font_size = self.style.text.font_size / 2.0;
-// let invisible_symbol_style = RunStyle {
-// color: self.style.whitespace,
-// font_id: self.style.text.font_id,
-// underline: Default::default(),
-// };
-
-// (
-// size,
-// LayoutState {
-// mode,
-// position_map: Arc::new(PositionMap {
-// size,
-// scroll_max,
-// line_layouts,
-// line_height,
-// em_width,
-// em_advance,
-// snapshot,
-// }),
-// visible_display_row_range: start_row..end_row,
-// wrap_guides,
-// gutter_size,
-// gutter_padding,
-// text_size,
-// scrollbar_row_range,
-// show_scrollbars,
-// is_singleton,
-// max_row,
-// gutter_margin,
-// active_rows,
-// highlighted_rows,
-// highlighted_ranges,
-// fold_ranges,
-// line_number_layouts,
-// display_hunks,
-// blocks,
-// selections,
-// context_menu,
-// code_actions_indicator,
-// fold_indicators,
-// tab_invisible: cx.text_layout_cache().layout_str(
-// "β",
-// invisible_symbol_font_size,
-// &[("β".len(), invisible_symbol_style)],
-// ),
-// space_invisible: cx.text_layout_cache().layout_str(
-// "β’",
-// invisible_symbol_font_size,
-// &[("β’".len(), invisible_symbol_style)],
-// ),
-// hover_popovers: hover,
-// },
-// )
-// }
-
-// fn paint(
-// &mut self,
-// bounds: Bounds<Pixels>,
-// visible_bounds: Bounds<Pixels>,
-// layout: &mut Self::LayoutState,
-// editor: &mut Editor,
-// cx: &mut ViewContext<Editor>,
-// ) -> Self::PaintState {
-// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-// cx.scene().push_layer(Some(visible_bounds));
-
-// let gutter_bounds = Bounds::<Pixels>::new(bounds.origin, layout.gutter_size);
-// let text_bounds = Bounds::<Pixels>::new(
-// bounds.origin + point(layout.gutter_size.x, 0.0),
-// layout.text_size,
-// );
-
-// Self::attach_mouse_handlers(
-// &layout.position_map,
-// layout.hover_popovers.is_some(),
-// visible_bounds,
-// text_bounds,
-// gutter_bounds,
-// bounds,
-// cx,
-// );
-
-// self.paint_background(gutter_bounds, text_bounds, layout, cx);
-// if layout.gutter_size.x > 0. {
-// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
-// }
-// self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
-
-// cx.scene().push_layer(Some(bounds));
-// if !layout.blocks.is_empty {
-// self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
-// }
-// self.paint_scrollbar(bounds, layout, &editor, cx);
-// cx.scene().pop_layer();
-// cx.scene().pop_layer();
-// }
-
-// fn rect_for_text_range(
-// &self,
-// range_utf16: Range<usize>,
-// bounds: Bounds<Pixels>,
-// _: Bounds<Pixels>,
-// layout: &Self::LayoutState,
-// _: &Self::PaintState,
-// _: &Editor,
-// _: &ViewContext<Editor>,
-// ) -> Option<Bounds<Pixels>> {
-// let text_bounds = Bounds::<Pixels>::new(
-// bounds.origin + point(layout.gutter_size.x, 0.0),
-// layout.text_size,
-// );
-// let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.);
-// let scroll_position = layout.position_map.snapshot.scroll_position();
-// let start_row = scroll_position.y as u32;
-// let scroll_top = scroll_position.y * layout.position_map.line_height;
-// let scroll_left = scroll_position.x * layout.position_map.em_width;
-
-// let range_start = OffsetUtf16(range_utf16.start)
-// .to_display_point(&layout.position_map.snapshot.display_snapshot);
-// if range_start.row() < start_row {
-// return None;
-// }
-
-// let line = &layout
-// .position_map
-// .line_layouts
-// .get((range_start.row() - start_row) as usize)?
-// .line;
-// let range_start_x = line.x_for_index(range_start.column() as usize);
-// let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
-// Some(Bounds::<Pixels>::new(
-// content_origin
-// + point(
-// range_start_x,
-// range_start_y + layout.position_map.line_height,
-// )
-// - point(scroll_left, scroll_top),
-// point(
-// layout.position_map.em_width,
-// layout.position_map.line_height,
-// ),
-// ))
-// }
-
-// fn debug(
-// &self,
-// bounds: Bounds<Pixels>,
-// _: &Self::LayoutState,
-// _: &Self::PaintState,
-// _: &Editor,
-// _: &ViewContext<Editor>,
-// ) -> json::Value {
-// json!({
-// "type": "BufferElement",
-// "bounds": bounds.to_json()
-// })
-// }
-// }
-
type BufferRow = u32;
pub struct LayoutState {
@@ -15,7 +15,7 @@ use lsp::DiagnosticSeverity;
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
use settings::Settings;
use std::{ops::Range, sync::Arc, time::Duration};
-use ui::Tooltip;
+use ui::{StyledExt, Tooltip};
use util::TryFutureExt;
use workspace::Workspace;
@@ -476,8 +476,10 @@ impl InfoPopover {
) -> AnyElement {
div()
.id("info_popover")
+ .elevation_2(cx)
+ .text_ui()
+ .p_2()
.overflow_y_scroll()
- .bg(gpui::red())
.max_w(max_size.width)
.max_h(max_size.height)
// Prevent a mouse move on the popover from being propagated to the editor,
@@ -4,13 +4,14 @@ use crate::{
EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
- div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
- FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
- ViewContext, VisualContext, WeakView, WindowContext,
+ div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Div, Entity,
+ EntityId, EventEmitter, FocusHandle, IntoElement, Model, ParentElement, Pixels, Render,
+ SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+ WindowContext,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -20,6 +21,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec;
+use std::fmt::Write;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@@ -31,8 +33,11 @@ use std::{
use text::Selection;
use theme::{ActiveTheme, Theme};
use ui::{Color, Label};
-use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+ item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+ StatusItemView,
+};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -71,110 +76,108 @@ impl FollowableItem for Editor {
workspace: View<Workspace>,
remote_id: ViewId,
state: &mut Option<proto::view::Variant>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Task<Result<View<Self>>>> {
- todo!()
+ let project = workspace.read(cx).project().to_owned();
+ let Some(proto::view::Variant::Editor(_)) = state else {
+ return None;
+ };
+ let Some(proto::view::Variant::Editor(state)) = state.take() else {
+ unreachable!()
+ };
+
+ let client = project.read(cx).client();
+ let replica_id = project.read(cx).replica_id();
+ let buffer_ids = state
+ .excerpts
+ .iter()
+ .map(|excerpt| excerpt.buffer_id)
+ .collect::<HashSet<_>>();
+ let buffers = project.update(cx, |project, cx| {
+ buffer_ids
+ .iter()
+ .map(|id| project.open_buffer_by_id(*id, cx))
+ .collect::<Vec<_>>()
+ });
+
+ let pane = pane.downgrade();
+ Some(cx.spawn(|mut cx| async move {
+ let mut buffers = futures::future::try_join_all(buffers).await?;
+ let editor = pane.update(&mut cx, |pane, cx| {
+ let mut editors = pane.items_of_type::<Self>();
+ editors.find(|editor| {
+ let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
+ let singleton_buffer_matches = state.singleton
+ && buffers.first()
+ == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
+ ids_match || singleton_buffer_matches
+ })
+ })?;
+
+ let editor = if let Some(editor) = editor {
+ editor
+ } else {
+ pane.update(&mut cx, |_, cx| {
+ let multibuffer = cx.build_model(|cx| {
+ let mut multibuffer;
+ if state.singleton && buffers.len() == 1 {
+ multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
+ } else {
+ multibuffer = MultiBuffer::new(replica_id);
+ let mut excerpts = state.excerpts.into_iter().peekable();
+ while let Some(excerpt) = excerpts.peek() {
+ let buffer_id = excerpt.buffer_id;
+ let buffer_excerpts = iter::from_fn(|| {
+ let excerpt = excerpts.peek()?;
+ (excerpt.buffer_id == buffer_id)
+ .then(|| excerpts.next().unwrap())
+ });
+ let buffer =
+ buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
+ if let Some(buffer) = buffer {
+ multibuffer.push_excerpts(
+ buffer.clone(),
+ buffer_excerpts.filter_map(deserialize_excerpt_range),
+ cx,
+ );
+ }
+ }
+ };
+
+ if let Some(title) = &state.title {
+ multibuffer = multibuffer.with_title(title.clone())
+ }
+
+ multibuffer
+ });
+
+ cx.build_view(|cx| {
+ let mut editor =
+ Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
+ editor.remote_id = Some(remote_id);
+ editor
+ })
+ })?
+ };
+
+ update_editor_from_message(
+ editor.downgrade(),
+ project,
+ proto::update_view::Editor {
+ selections: state.selections,
+ pending_selection: state.pending_selection,
+ scroll_top_anchor: state.scroll_top_anchor,
+ scroll_x: state.scroll_x,
+ scroll_y: state.scroll_y,
+ ..Default::default()
+ },
+ &mut cx,
+ )
+ .await?;
+
+ Ok(editor)
+ }))
}
- // let project = workspace.read(cx).project().to_owned();
- // let Some(proto::view::Variant::Editor(_)) = state else {
- // return None;
- // };
- // let Some(proto::view::Variant::Editor(state)) = state.take() else {
- // unreachable!()
- // };
-
- // let client = project.read(cx).client();
- // let replica_id = project.read(cx).replica_id();
- // let buffer_ids = state
- // .excerpts
- // .iter()
- // .map(|excerpt| excerpt.buffer_id)
- // .collect::<HashSet<_>>();
- // let buffers = project.update(cx, |project, cx| {
- // buffer_ids
- // .iter()
- // .map(|id| project.open_buffer_by_id(*id, cx))
- // .collect::<Vec<_>>()
- // });
-
- // let pane = pane.downgrade();
- // Some(cx.spawn(|mut cx| async move {
- // let mut buffers = futures::future::try_join_all(buffers).await?;
- // let editor = pane.read_with(&cx, |pane, cx| {
- // let mut editors = pane.items_of_type::<Self>();
- // editors.find(|editor| {
- // let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
- // let singleton_buffer_matches = state.singleton
- // && buffers.first()
- // == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
- // ids_match || singleton_buffer_matches
- // })
- // })?;
-
- // let editor = if let Some(editor) = editor {
- // editor
- // } else {
- // pane.update(&mut cx, |_, cx| {
- // let multibuffer = cx.add_model(|cx| {
- // let mut multibuffer;
- // if state.singleton && buffers.len() == 1 {
- // multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
- // } else {
- // multibuffer = MultiBuffer::new(replica_id);
- // let mut excerpts = state.excerpts.into_iter().peekable();
- // while let Some(excerpt) = excerpts.peek() {
- // let buffer_id = excerpt.buffer_id;
- // let buffer_excerpts = iter::from_fn(|| {
- // let excerpt = excerpts.peek()?;
- // (excerpt.buffer_id == buffer_id)
- // .then(|| excerpts.next().unwrap())
- // });
- // let buffer =
- // buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
- // if let Some(buffer) = buffer {
- // multibuffer.push_excerpts(
- // buffer.clone(),
- // buffer_excerpts.filter_map(deserialize_excerpt_range),
- // cx,
- // );
- // }
- // }
- // };
-
- // if let Some(title) = &state.title {
- // multibuffer = multibuffer.with_title(title.clone())
- // }
-
- // multibuffer
- // });
-
- // cx.add_view(|cx| {
- // let mut editor =
- // Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
- // editor.remote_id = Some(remote_id);
- // editor
- // })
- // })?
- // };
-
- // update_editor_from_message(
- // editor.downgrade(),
- // project,
- // proto::update_view::Editor {
- // selections: state.selections,
- // pending_selection: state.pending_selection,
- // scroll_top_anchor: state.scroll_top_anchor,
- // scroll_x: state.scroll_x,
- // scroll_y: state.scroll_y,
- // ..Default::default()
- // },
- // &mut cx,
- // )
- // .await?;
-
- // Ok(editor)
- // }))
- // }
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
self.leader_peer_id = leader_peer_id;
@@ -195,7 +198,7 @@ impl FollowableItem for Editor {
cx.notify();
}
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
let buffer = self.buffer.read(cx);
let scroll_anchor = self.scroll_manager.anchor();
let excerpts = buffer
@@ -242,7 +245,7 @@ impl FollowableItem for Editor {
&self,
event: &Self::FollowableEvent,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool {
let update =
update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
@@ -315,7 +318,7 @@ impl FollowableItem for Editor {
})
}
- fn is_project_item(&self, _cx: &AppContext) -> bool {
+ fn is_project_item(&self, _cx: &WindowContext) -> bool {
true
}
}
@@ -324,132 +327,129 @@ async fn update_editor_from_message(
this: WeakView<Editor>,
project: Model<Project>,
message: proto::update_view::Editor,
- cx: &mut AsyncAppContext,
+ cx: &mut AsyncWindowContext,
) -> Result<()> {
- todo!()
+ // Open all of the buffers of which excerpts were added to the editor.
+ let inserted_excerpt_buffer_ids = message
+ .inserted_excerpts
+ .iter()
+ .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
+ .collect::<HashSet<_>>();
+ let inserted_excerpt_buffers = project.update(cx, |project, cx| {
+ inserted_excerpt_buffer_ids
+ .into_iter()
+ .map(|id| project.open_buffer_by_id(id, cx))
+ .collect::<Vec<_>>()
+ })?;
+ let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
+
+ // Update the editor's excerpts.
+ this.update(cx, |editor, cx| {
+ editor.buffer.update(cx, |multibuffer, cx| {
+ let mut removed_excerpt_ids = message
+ .deleted_excerpts
+ .into_iter()
+ .map(ExcerptId::from_proto)
+ .collect::<Vec<_>>();
+ removed_excerpt_ids.sort_by({
+ let multibuffer = multibuffer.read(cx);
+ move |a, b| a.cmp(&b, &multibuffer)
+ });
+
+ let mut insertions = message.inserted_excerpts.into_iter().peekable();
+ while let Some(insertion) = insertions.next() {
+ let Some(excerpt) = insertion.excerpt else {
+ continue;
+ };
+ let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
+ continue;
+ };
+ let buffer_id = excerpt.buffer_id;
+ let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
+ continue;
+ };
+
+ let adjacent_excerpts = iter::from_fn(|| {
+ let insertion = insertions.peek()?;
+ if insertion.previous_excerpt_id.is_none()
+ && insertion.excerpt.as_ref()?.buffer_id == buffer_id
+ {
+ insertions.next()?.excerpt
+ } else {
+ None
+ }
+ });
+
+ multibuffer.insert_excerpts_with_ids_after(
+ ExcerptId::from_proto(previous_excerpt_id),
+ buffer,
+ [excerpt]
+ .into_iter()
+ .chain(adjacent_excerpts)
+ .filter_map(|excerpt| {
+ Some((
+ ExcerptId::from_proto(excerpt.id),
+ deserialize_excerpt_range(excerpt)?,
+ ))
+ }),
+ cx,
+ );
+ }
+
+ multibuffer.remove_excerpts(removed_excerpt_ids, cx);
+ });
+ })?;
+
+ // Deserialize the editor state.
+ let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
+ let buffer = editor.buffer.read(cx).read(cx);
+ let selections = message
+ .selections
+ .into_iter()
+ .filter_map(|selection| deserialize_selection(&buffer, selection))
+ .collect::<Vec<_>>();
+ let pending_selection = message
+ .pending_selection
+ .and_then(|selection| deserialize_selection(&buffer, selection));
+ let scroll_top_anchor = message
+ .scroll_top_anchor
+ .and_then(|anchor| deserialize_anchor(&buffer, anchor));
+ anyhow::Ok((selections, pending_selection, scroll_top_anchor))
+ })??;
+
+ // Wait until the buffer has received all of the operations referenced by
+ // the editor's new state.
+ this.update(cx, |editor, cx| {
+ editor.buffer.update(cx, |buffer, cx| {
+ buffer.wait_for_anchors(
+ selections
+ .iter()
+ .chain(pending_selection.as_ref())
+ .flat_map(|selection| [selection.start, selection.end])
+ .chain(scroll_top_anchor),
+ cx,
+ )
+ })
+ })?
+ .await?;
+
+ // Update the editor's state.
+ this.update(cx, |editor, cx| {
+ if !selections.is_empty() || pending_selection.is_some() {
+ editor.set_selections_from_remote(selections, pending_selection, cx);
+ editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
+ } else if let Some(scroll_top_anchor) = scroll_top_anchor {
+ editor.set_scroll_anchor_remote(
+ ScrollAnchor {
+ anchor: scroll_top_anchor,
+ offset: point(message.scroll_x, message.scroll_y),
+ },
+ cx,
+ );
+ }
+ })?;
+ Ok(())
}
-// Previous implementation of the above
-// // Open all of the buffers of which excerpts were added to the editor.
-// let inserted_excerpt_buffer_ids = message
-// .inserted_excerpts
-// .iter()
-// .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
-// .collect::<HashSet<_>>();
-// let inserted_excerpt_buffers = project.update(cx, |project, cx| {
-// inserted_excerpt_buffer_ids
-// .into_iter()
-// .map(|id| project.open_buffer_by_id(id, cx))
-// .collect::<Vec<_>>()
-// })?;
-// let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
-
-// // Update the editor's excerpts.
-// this.update(cx, |editor, cx| {
-// editor.buffer.update(cx, |multibuffer, cx| {
-// let mut removed_excerpt_ids = message
-// .deleted_excerpts
-// .into_iter()
-// .map(ExcerptId::from_proto)
-// .collect::<Vec<_>>();
-// removed_excerpt_ids.sort_by({
-// let multibuffer = multibuffer.read(cx);
-// move |a, b| a.cmp(&b, &multibuffer)
-// });
-
-// let mut insertions = message.inserted_excerpts.into_iter().peekable();
-// while let Some(insertion) = insertions.next() {
-// let Some(excerpt) = insertion.excerpt else {
-// continue;
-// };
-// let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
-// continue;
-// };
-// let buffer_id = excerpt.buffer_id;
-// let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
-// continue;
-// };
-
-// let adjacent_excerpts = iter::from_fn(|| {
-// let insertion = insertions.peek()?;
-// if insertion.previous_excerpt_id.is_none()
-// && insertion.excerpt.as_ref()?.buffer_id == buffer_id
-// {
-// insertions.next()?.excerpt
-// } else {
-// None
-// }
-// });
-
-// multibuffer.insert_excerpts_with_ids_after(
-// ExcerptId::from_proto(previous_excerpt_id),
-// buffer,
-// [excerpt]
-// .into_iter()
-// .chain(adjacent_excerpts)
-// .filter_map(|excerpt| {
-// Some((
-// ExcerptId::from_proto(excerpt.id),
-// deserialize_excerpt_range(excerpt)?,
-// ))
-// }),
-// cx,
-// );
-// }
-
-// multibuffer.remove_excerpts(removed_excerpt_ids, cx);
-// });
-// })?;
-
-// // Deserialize the editor state.
-// let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
-// let buffer = editor.buffer.read(cx).read(cx);
-// let selections = message
-// .selections
-// .into_iter()
-// .filter_map(|selection| deserialize_selection(&buffer, selection))
-// .collect::<Vec<_>>();
-// let pending_selection = message
-// .pending_selection
-// .and_then(|selection| deserialize_selection(&buffer, selection));
-// let scroll_top_anchor = message
-// .scroll_top_anchor
-// .and_then(|anchor| deserialize_anchor(&buffer, anchor));
-// anyhow::Ok((selections, pending_selection, scroll_top_anchor))
-// })??;
-
-// // Wait until the buffer has received all of the operations referenced by
-// // the editor's new state.
-// this.update(cx, |editor, cx| {
-// editor.buffer.update(cx, |buffer, cx| {
-// buffer.wait_for_anchors(
-// selections
-// .iter()
-// .chain(pending_selection.as_ref())
-// .flat_map(|selection| [selection.start, selection.end])
-// .chain(scroll_top_anchor),
-// cx,
-// )
-// })
-// })?
-// .await?;
-
-// // Update the editor's state.
-// this.update(cx, |editor, cx| {
-// if !selections.is_empty() || pending_selection.is_some() {
-// editor.set_selections_from_remote(selections, pending_selection, cx);
-// editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
-// } else if let Some(scroll_top_anchor) = scroll_top_anchor {
-// editor.set_scroll_anchor_remote(
-// ScrollAnchor {
-// anchor: scroll_top_anchor,
-// offset: point(message.scroll_x, message.scroll_y),
-// },
-// cx,
-// );
-// }
-// })?;
-// Ok(())
-// }
fn serialize_excerpt(
buffer_id: u64,
@@ -529,39 +529,38 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
impl Item for Editor {
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
- todo!();
- // if let Ok(data) = data.downcast::<NavigationData>() {
- // let newest_selection = self.selections.newest::<Point>(cx);
- // let buffer = self.buffer.read(cx).read(cx);
- // let offset = if buffer.can_resolve(&data.cursor_anchor) {
- // data.cursor_anchor.to_point(&buffer)
- // } else {
- // buffer.clip_point(data.cursor_position, Bias::Left)
- // };
-
- // let mut scroll_anchor = data.scroll_anchor;
- // if !buffer.can_resolve(&scroll_anchor.anchor) {
- // scroll_anchor.anchor = buffer.anchor_before(
- // buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
- // );
- // }
-
- // drop(buffer);
-
- // if newest_selection.head() == offset {
- // false
- // } else {
- // let nav_history = self.nav_history.take();
- // self.set_scroll_anchor(scroll_anchor, cx);
- // self.change_selections(Some(Autoscroll::fit()), cx, |s| {
- // s.select_ranges([offset..offset])
- // });
- // self.nav_history = nav_history;
- // true
- // }
- // } else {
- // false
- // }
+ if let Ok(data) = data.downcast::<NavigationData>() {
+ let newest_selection = self.selections.newest::<Point>(cx);
+ let buffer = self.buffer.read(cx).read(cx);
+ let offset = if buffer.can_resolve(&data.cursor_anchor) {
+ data.cursor_anchor.to_point(&buffer)
+ } else {
+ buffer.clip_point(data.cursor_position, Bias::Left)
+ };
+
+ let mut scroll_anchor = data.scroll_anchor;
+ if !buffer.can_resolve(&scroll_anchor.anchor) {
+ scroll_anchor.anchor = buffer.anchor_before(
+ buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
+ );
+ }
+
+ drop(buffer);
+
+ if newest_selection.head() == offset {
+ false
+ } else {
+ let nav_history = self.nav_history.take();
+ self.set_scroll_anchor(scroll_anchor, cx);
+ self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges([offset..offset])
+ });
+ self.nav_history = nav_history;
+ true
+ }
+ } else {
+ false
+ }
}
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
@@ -765,35 +764,34 @@ impl Item for Editor {
}
fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
- todo!();
- // let cursor = self.selections.newest_anchor().head();
- // let multibuffer = &self.buffer().read(cx);
- // let (buffer_id, symbols) =
- // multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
- // let buffer = multibuffer.buffer(buffer_id)?;
-
- // let buffer = buffer.read(cx);
- // let filename = buffer
- // .snapshot()
- // .resolve_file_path(
- // cx,
- // self.project
- // .as_ref()
- // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
- // .unwrap_or_default(),
- // )
- // .map(|path| path.to_string_lossy().to_string())
- // .unwrap_or_else(|| "untitled".to_string());
-
- // let mut breadcrumbs = vec![BreadcrumbText {
- // text: filename,
- // highlights: None,
- // }];
- // breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
- // text: symbol.text,
- // highlights: Some(symbol.highlight_ranges),
- // }));
- // Some(breadcrumbs)
+ let cursor = self.selections.newest_anchor().head();
+ let multibuffer = &self.buffer().read(cx);
+ let (buffer_id, symbols) =
+ multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
+ let buffer = multibuffer.buffer(buffer_id)?;
+
+ let buffer = buffer.read(cx);
+ let filename = buffer
+ .snapshot()
+ .resolve_file_path(
+ cx,
+ self.project
+ .as_ref()
+ .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
+ .unwrap_or_default(),
+ )
+ .map(|path| path.to_string_lossy().to_string())
+ .unwrap_or_else(|| "untitled".to_string());
+
+ let mut breadcrumbs = vec![BreadcrumbText {
+ text: filename,
+ highlights: None,
+ }];
+ breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+ text: symbol.text,
+ highlights: Some(symbol.highlight_ranges),
+ }));
+ Some(breadcrumbs)
}
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
@@ -1120,86 +1118,78 @@ pub struct CursorPosition {
_observe_active_editor: Option<Subscription>,
}
-// impl Default for CursorPosition {
-// fn default() -> Self {
-// Self::new()
-// }
-// }
-
-// impl CursorPosition {
-// pub fn new() -> Self {
-// Self {
-// position: None,
-// selected_count: 0,
-// _observe_active_editor: None,
-// }
-// }
-
-// fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-// let editor = editor.read(cx);
-// let buffer = editor.buffer().read(cx).snapshot(cx);
-
-// self.selected_count = 0;
-// let mut last_selection: Option<Selection<usize>> = None;
-// for selection in editor.selections.all::<usize>(cx) {
-// self.selected_count += selection.end - selection.start;
-// if last_selection
-// .as_ref()
-// .map_or(true, |last_selection| selection.id > last_selection.id)
-// {
-// last_selection = Some(selection);
-// }
-// }
-// self.position = last_selection.map(|s| s.head().to_point(&buffer));
-
-// cx.notify();
-// }
-// }
-
-// impl Entity for CursorPosition {
-// type Event = ();
-// }
-
-// impl View for CursorPosition {
-// fn ui_name() -> &'static str {
-// "CursorPosition"
-// }
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// if let Some(position) = self.position {
-// let theme = &theme::current(cx).workspace.status_bar;
-// let mut text = format!(
-// "{}{FILE_ROW_COLUMN_DELIMITER}{}",
-// position.row + 1,
-// position.column + 1
-// );
-// if self.selected_count > 0 {
-// write!(text, " ({} selected)", self.selected_count).unwrap();
-// }
-// Label::new(text, theme.cursor_position.clone()).into_any()
-// } else {
-// Empty::new().into_any()
-// }
-// }
-// }
-
-// impl StatusItemView for CursorPosition {
-// fn set_active_pane_item(
-// &mut self,
-// active_pane_item: Option<&dyn ItemHandle>,
-// cx: &mut ViewContext<Self>,
-// ) {
-// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-// self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
-// self.update_position(editor, cx);
-// } else {
-// self.position = None;
-// self._observe_active_editor = None;
-// }
-
-// cx.notify();
-// }
-// }
+impl Default for CursorPosition {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl CursorPosition {
+ pub fn new() -> Self {
+ Self {
+ position: None,
+ selected_count: 0,
+ _observe_active_editor: None,
+ }
+ }
+
+ fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+ let editor = editor.read(cx);
+ let buffer = editor.buffer().read(cx).snapshot(cx);
+
+ self.selected_count = 0;
+ let mut last_selection: Option<Selection<usize>> = None;
+ for selection in editor.selections.all::<usize>(cx) {
+ self.selected_count += selection.end - selection.start;
+ if last_selection
+ .as_ref()
+ .map_or(true, |last_selection| selection.id > last_selection.id)
+ {
+ last_selection = Some(selection);
+ }
+ }
+ self.position = last_selection.map(|s| s.head().to_point(&buffer));
+
+ cx.notify();
+ }
+}
+
+impl Render for CursorPosition {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ div().when_some(self.position, |el, position| {
+ let mut text = format!(
+ "{}{FILE_ROW_COLUMN_DELIMITER}{}",
+ position.row + 1,
+ position.column + 1
+ );
+ if self.selected_count > 0 {
+ write!(text, " ({} selected)", self.selected_count).unwrap();
+ }
+
+ el.child(Label::new(text))
+ })
+ }
+}
+
+impl StatusItemView for CursorPosition {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+ self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
+ self.update_position(editor, cx);
+ } else {
+ self.position = None;
+ self._observe_active_editor = None;
+ }
+
+ cx.notify();
+ }
+}
fn path_for_buffer<'a>(
buffer: &Model<MultiBuffer>,
@@ -5,7 +5,7 @@ use crate::{
Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId,
SelectPhase,
};
-use gpui::{Task, ViewContext};
+use gpui::{px, Task, ViewContext};
use language::{Bias, ToOffset};
use lsp::LanguageServerId;
use project::{
@@ -13,6 +13,7 @@ use project::{
ResolveState,
};
use std::ops::Range;
+use theme::ActiveTheme as _;
use util::TryFutureExt;
#[derive(Debug, Default)]
@@ -485,40 +486,45 @@ pub fn show_link_definition(
});
if any_definition_does_not_contain_current_location {
- // todo!()
- // // Highlight symbol using theme link definition highlight style
- // let style = theme::current(cx).editor.link_definition;
- // let highlight_range =
- // symbol_range.unwrap_or_else(|| match &trigger_point {
- // TriggerPoint::Text(trigger_anchor) => {
- // let snapshot = &snapshot.buffer_snapshot;
- // // If no symbol range returned from language server, use the surrounding word.
- // let (offset_range, _) =
- // snapshot.surrounding_word(*trigger_anchor);
- // RangeInEditor::Text(
- // snapshot.anchor_before(offset_range.start)
- // ..snapshot.anchor_after(offset_range.end),
- // )
- // }
- // TriggerPoint::InlayHint(highlight, _, _) => {
- // RangeInEditor::Inlay(highlight.clone())
- // }
- // });
-
- // match highlight_range {
- // RangeInEditor::Text(text_range) => this
- // .highlight_text::<LinkGoToDefinitionState>(
- // vec![text_range],
- // style,
- // cx,
- // ),
- // RangeInEditor::Inlay(highlight) => this
- // .highlight_inlays::<LinkGoToDefinitionState>(
- // vec![highlight],
- // style,
- // cx,
- // ),
- // }
+ let style = gpui::HighlightStyle {
+ underline: Some(gpui::UnderlineStyle {
+ thickness: px(1.),
+ ..Default::default()
+ }),
+ color: Some(gpui::red()),
+ ..Default::default()
+ };
+ let highlight_range =
+ symbol_range.unwrap_or_else(|| match &trigger_point {
+ TriggerPoint::Text(trigger_anchor) => {
+ let snapshot = &snapshot.buffer_snapshot;
+ // If no symbol range returned from language server, use the surrounding word.
+ let (offset_range, _) =
+ snapshot.surrounding_word(*trigger_anchor);
+ RangeInEditor::Text(
+ snapshot.anchor_before(offset_range.start)
+ ..snapshot.anchor_after(offset_range.end),
+ )
+ }
+ TriggerPoint::InlayHint(highlight, _, _) => {
+ RangeInEditor::Inlay(highlight.clone())
+ }
+ });
+
+ match highlight_range {
+ RangeInEditor::Text(text_range) => this
+ .highlight_text::<LinkGoToDefinitionState>(
+ vec![text_range],
+ style,
+ cx,
+ ),
+ RangeInEditor::Inlay(highlight) => this
+ .highlight_inlays::<LinkGoToDefinitionState>(
+ vec![highlight],
+ style,
+ cx,
+ ),
+ }
} else {
hide_link_definition(this, cx);
}
@@ -1,5 +1,14 @@
-use crate::{DisplayPoint, Editor, EditorMode, SelectMode};
-use gpui::{Pixels, Point, ViewContext};
+use crate::{
+ DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
+ Rename, RevealInFinder, SelectMode, ToggleCodeActions,
+};
+use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
+
+pub struct MouseContextMenu {
+ pub(crate) position: Point<Pixels>,
+ pub(crate) context_menu: View<ui::ContextMenu>,
+ _subscription: Subscription,
+}
pub fn deploy_context_menu(
editor: &mut Editor,
@@ -7,50 +16,57 @@ pub fn deploy_context_menu(
point: DisplayPoint,
cx: &mut ViewContext<Editor>,
) {
- todo!();
+ if !editor.is_focused(cx) {
+ editor.focus(cx);
+ }
+
+ // Don't show context menu for inline editors
+ if editor.mode() != EditorMode::Full {
+ return;
+ }
- // if !editor.focused {
- // cx.focus_self();
- // }
+ // Don't show the context menu if there isn't a project associated with this editor
+ if editor.project.is_none() {
+ return;
+ }
- // // Don't show context menu for inline editors
- // if editor.mode() != EditorMode::Full {
- // return;
- // }
+ // Move the cursor to the clicked location so that dispatched actions make sense
+ editor.change_selections(None, cx, |s| {
+ s.clear_disjoint();
+ s.set_pending_display_range(point..point, SelectMode::Character);
+ });
- // // Don't show the context menu if there isn't a project associated with this editor
- // if editor.project.is_none() {
- // return;
- // }
+ let context_menu = ui::ContextMenu::build(cx, |menu, cx| {
+ menu.action("Rename Symbol", Box::new(Rename), cx)
+ .action("Go to Definition", Box::new(GoToDefinition), cx)
+ .action("Go to Type Definition", Box::new(GoToTypeDefinition), cx)
+ .action("Find All References", Box::new(FindAllReferences), cx)
+ .action(
+ "Code Actions",
+ Box::new(ToggleCodeActions {
+ deployed_from_indicator: false,
+ }),
+ cx,
+ )
+ .separator()
+ .action("Reveal in Finder", Box::new(RevealInFinder), cx)
+ });
+ let context_menu_focus = context_menu.focus_handle(cx);
+ cx.focus(&context_menu_focus);
- // // Move the cursor to the clicked location so that dispatched actions make sense
- // editor.change_selections(None, cx, |s| {
- // s.clear_disjoint();
- // s.set_pending_display_range(point..point, SelectMode::Character);
- // });
+ let _subscription = cx.subscribe(&context_menu, move |this, _, event: &DismissEvent, cx| {
+ this.mouse_context_menu.take();
+ if context_menu_focus.contains_focused(cx) {
+ this.focus(cx);
+ }
+ });
- // editor.mouse_context_menu.update(cx, |menu, cx| {
- // menu.show(
- // position,
- // AnchorCorner::TopLeft,
- // vec![
- // ContextMenuItem::action("Rename Symbol", Rename),
- // ContextMenuItem::action("Go to Definition", GoToDefinition),
- // ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
- // ContextMenuItem::action("Find All References", FindAllReferences),
- // ContextMenuItem::action(
- // "Code Actions",
- // ToggleCodeActions {
- // deployed_from_indicator: false,
- // },
- // ),
- // ContextMenuItem::Separator,
- // ContextMenuItem::action("Reveal in Finder", RevealInFinder),
- // ],
- // cx,
- // );
- // });
- // cx.notify();
+ editor.mouse_context_menu = Some(MouseContextMenu {
+ position,
+ context_menu,
+ _subscription,
+ });
+ cx.notify();
}
// #[cfg(test)]
@@ -592,31 +592,32 @@ impl<'a> MutableSelectionsCollection<'a> {
self.select(selections)
}
- pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(&mut self, ranges: I) {
- todo!()
- // let buffer = self.buffer.read(self.cx).snapshot(self.cx);
- // let selections = ranges
- // .into_iter()
- // .map(|range| {
- // let mut start = range.start;
- // let mut end = range.end;
- // let reversed = if start.cmp(&end, &buffer).is_gt() {
- // mem::swap(&mut start, &mut end);
- // true
- // } else {
- // false
- // };
- // Selection {
- // id: post_inc(&mut self.collection.next_selection_id),
- // start,
- // end,
- // reversed,
- // goal: SelectionGoal::None,
- // }
- // })
- // .collect::<Vec<_>>();
-
- // self.select_anchors(selections)
+ pub fn select_anchor_ranges<I>(&mut self, ranges: I)
+ where
+ I: IntoIterator<Item = Range<Anchor>>,
+ {
+ let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+ let selections = ranges
+ .into_iter()
+ .map(|range| {
+ let mut start = range.start;
+ let mut end = range.end;
+ let reversed = if start.cmp(&end, &buffer).is_gt() {
+ mem::swap(&mut start, &mut end);
+ true
+ } else {
+ false
+ };
+ Selection {
+ id: post_inc(&mut self.collection.next_selection_id),
+ start,
+ end,
+ reversed,
+ goal: SelectionGoal::None,
+ }
+ })
+ .collect::<Vec<_>>();
+ self.select_anchors(selections)
}
pub fn new_selection_id(&mut self) -> usize {
@@ -27,7 +27,7 @@ pub fn marked_display_snapshot(
let (unmarked_text, markers) = marked_text_offsets(text);
let font = cx.text_style().font();
- let font_size: Pixels = 14.into();
+ let font_size: Pixels = 14usize.into();
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
@@ -518,6 +518,7 @@ impl PickerDelegate for FileFinderDelegate {
}
fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
+ let raw_query = raw_query.trim();
if raw_query.is_empty() {
let project = self.project.read(cx);
self.latest_search_id = post_inc(&mut self.search_count);
@@ -539,7 +540,6 @@ impl PickerDelegate for FileFinderDelegate {
cx.notify();
Task::ready(())
} else {
- let raw_query = &raw_query;
let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
Ok::<_, std::convert::Infallible>(FileSearchQuery {
raw_query: raw_query.to_owned(),
@@ -735,6 +735,7 @@ mod tests {
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
finder
.update(cx, |finder, cx| {
finder.delegate_mut().update_matches("bna".to_string(), cx)
@@ -743,7 +744,6 @@ mod tests {
finder.read_with(cx, |finder, _| {
assert_eq!(finder.delegate().matches.len(), 2);
});
-
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
@@ -762,6 +762,49 @@ mod tests {
"bandana"
);
});
+
+ for bandana_query in [
+ "bandana",
+ " bandana",
+ "bandana ",
+ " bandana ",
+ " ndan ",
+ " band ",
+ ] {
+ finder
+ .update(cx, |finder, cx| {
+ finder
+ .delegate_mut()
+ .update_matches(bandana_query.to_string(), cx)
+ })
+ .await;
+ finder.read_with(cx, |finder, _| {
+ assert_eq!(
+ finder.delegate().matches.len(),
+ 1,
+ "Wrong number of matches for bandana query '{bandana_query}'"
+ );
+ });
+ let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
+ active_pane
+ .condition(cx, |pane, _| pane.active_item().is_some())
+ .await;
+ cx.read(|cx| {
+ let active_item = active_pane.read(cx).active_item().unwrap();
+ assert_eq!(
+ active_item
+ .as_any()
+ .downcast_ref::<Editor>()
+ .unwrap()
+ .read(cx)
+ .title(cx),
+ "bandana",
+ "Wrong match for bandana query '{bandana_query}'"
+ );
+ });
+ }
}
#[gpui::test]
@@ -15,7 +15,7 @@ use std::{
},
};
use text::Point;
-use ui::{v_stack, HighlightedLabel, ListItem};
+use ui::{prelude::*, v_stack, HighlightedLabel, ListItem};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::Workspace;
@@ -552,6 +552,7 @@ impl PickerDelegate for FileFinderDelegate {
raw_query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<()> {
+ let raw_query = raw_query.trim();
if raw_query.is_empty() {
let project = self.project.read(cx);
self.latest_search_id = post_inc(&mut self.search_count);
@@ -573,7 +574,6 @@ impl PickerDelegate for FileFinderDelegate {
cx.notify();
Task::ready(())
} else {
- let raw_query = &raw_query;
let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
Ok::<_, std::convert::Infallible>(FileSearchQuery {
raw_query: raw_query.to_owned(),
@@ -766,18 +766,49 @@ mod tests {
let (picker, workspace, cx) = build_find_picker(project, cx);
cx.simulate_input("bna");
-
picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.matches.len(), 2);
});
-
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm);
-
cx.read(|cx| {
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
assert_eq!(active_editor.read(cx).title(cx), "bandana");
});
+
+ for bandana_query in [
+ "bandana",
+ " bandana",
+ "bandana ",
+ " bandana ",
+ " ndan ",
+ " band ",
+ ] {
+ picker
+ .update(cx, |picker, cx| {
+ picker
+ .delegate
+ .update_matches(bandana_query.to_string(), cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ assert_eq!(
+ picker.delegate.matches.len(),
+ 1,
+ "Wrong number of matches for bandana query '{bandana_query}'"
+ );
+ });
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
+ cx.read(|cx| {
+ let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+ assert_eq!(
+ active_editor.read(cx).title(cx),
+ "bandana",
+ "Wrong match for bandana query '{bandana_query}'"
+ );
+ });
+ }
}
#[gpui::test]
@@ -1225,7 +1256,7 @@ mod tests {
//
// TODO: without closing, the opened items do not propagate their history changes for some reason
// it does work in real app though, only tests do not propagate.
- workspace.update(cx, |_, cx| dbg!(cx.focused()));
+ workspace.update(cx, |_, cx| cx.focused());
let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
assert!(
@@ -65,6 +65,8 @@ fn generate_shader_bindings() -> PathBuf {
"MonochromeSprite".into(),
"PolychromeSprite".into(),
"PathSprite".into(),
+ "SurfaceInputIndex".into(),
+ "SurfaceBounds".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;
@@ -520,6 +520,10 @@ impl AppContext {
self.platform.should_auto_hide_scrollbars()
}
+ pub fn restart(&self) {
+ self.platform.restart()
+ }
+
pub(crate) fn push_effect(&mut self, effect: Effect) {
match &effect {
Effect::Notify { emitter } => {
@@ -2,8 +2,8 @@ use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
- TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
- WindowOptions,
+ TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
+ WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
@@ -502,6 +502,19 @@ impl<'a> VisualTestContext<'a> {
self.cx.dispatch_action(self.window, action)
}
+ pub fn window_title(&mut self) -> Option<String> {
+ self.cx
+ .update_window(self.window, |_, cx| {
+ cx.window
+ .platform_window
+ .as_test()
+ .unwrap()
+ .window_title
+ .clone()
+ })
+ .unwrap()
+ }
+
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
self.cx.simulate_keystrokes(self.window, keystrokes)
}
@@ -509,6 +522,39 @@ impl<'a> VisualTestContext<'a> {
pub fn simulate_input(&mut self, input: &str) {
self.cx.simulate_input(self.window, input)
}
+
+ pub fn simulate_activation(&mut self) {
+ self.simulate_window_events(&mut |handlers| {
+ handlers
+ .active_status_change
+ .iter_mut()
+ .for_each(|f| f(true));
+ })
+ }
+
+ pub fn simulate_deactivation(&mut self) {
+ self.simulate_window_events(&mut |handlers| {
+ handlers
+ .active_status_change
+ .iter_mut()
+ .for_each(|f| f(false));
+ })
+ }
+
+ fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
+ let handlers = self
+ .cx
+ .update_window(self.window, |_, cx| {
+ cx.window
+ .platform_window
+ .as_test()
+ .unwrap()
+ .handlers
+ .clone()
+ })
+ .unwrap();
+ f(&mut *handlers.lock());
+ }
}
impl<'a> Context for VisualTestContext<'a> {
@@ -12,6 +12,7 @@ use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
cell::RefCell,
+ cmp::Ordering,
fmt::Debug,
mem,
rc::Rc,
@@ -357,6 +358,11 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
+ self.interactivity().scroll_handle = Some(scroll_handle.clone());
+ self
+ }
+
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
@@ -626,6 +632,26 @@ impl Element for Div {
let mut child_max = Point::default();
let content_size = if element_state.child_layout_ids.is_empty() {
bounds.size
+ } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() {
+ let mut state = scroll_handle.0.borrow_mut();
+ state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
+ state.bounds = bounds;
+ let requested = state.requested_scroll_top.take();
+
+ for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
+ let child_bounds = cx.layout_bounds(*child_layout_id);
+ child_min = child_min.min(&child_bounds.origin);
+ child_max = child_max.max(&child_bounds.lower_right());
+ state.child_bounds.push(child_bounds);
+
+ if let Some(requested) = requested.as_ref() {
+ if requested.0 == ix {
+ *state.offset.borrow_mut() =
+ bounds.origin - (child_bounds.origin - point(px(0.), requested.1));
+ }
+ }
+ }
+ (child_max - child_min).into()
} else {
for child_layout_id in &element_state.child_layout_ids {
let child_bounds = cx.layout_bounds(*child_layout_id);
@@ -696,6 +722,7 @@ pub struct Interactivity {
pub key_context: KeyContext,
pub focusable: bool,
pub tracked_focus_handle: Option<FocusHandle>,
+ pub scroll_handle: Option<ScrollHandle>,
pub focus_listeners: FocusListeners,
pub group: Option<SharedString>,
pub base_style: StyleRefinement,
@@ -754,6 +781,10 @@ impl Interactivity {
});
}
+ if let Some(scroll_handle) = self.scroll_handle.as_ref() {
+ element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
+ }
+
let style = self.compute_style(None, &mut element_state, cx);
let layout_id = f(style, cx);
(layout_id, element_state)
@@ -824,7 +855,6 @@ impl Interactivity {
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
if let Some(group_bounds) = hover_group_bounds {
- // todo!() needs cx.was_top_layer
let hovered = group_bounds.contains_point(&cx.mouse_position());
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture {
@@ -836,13 +866,13 @@ impl Interactivity {
}
if self.hover_style.is_some()
- || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
+ || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
{
- let interactive_bounds = interactive_bounds.clone();
- let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
+ let bounds = bounds.intersect(&cx.content_mask().bounds);
+ let hovered = bounds.contains_point(&cx.mouse_position());
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture {
- if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
+ if bounds.contains_point(&event.position) != hovered {
cx.notify();
}
}
@@ -1143,7 +1173,9 @@ impl Interactivity {
let mouse_position = cx.mouse_position();
if let Some(group_hover) = self.group_hover_style.as_ref() {
if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
- if group_bounds.contains_point(&mouse_position) {
+ if group_bounds.contains_point(&mouse_position)
+ && cx.was_top_layer(&mouse_position, cx.stacking_order())
+ {
style.refine(&group_hover.style);
}
}
@@ -1162,7 +1194,6 @@ impl Interactivity {
for (state_type, group_drag_style) in &self.group_drag_over_styles {
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
if *state_type == drag.view.entity_type()
- // todo!() needs to handle cx.content_mask() and cx.is_top()
&& group_bounds.contains_point(&mouse_position)
{
style.refine(&group_drag_style.style);
@@ -1175,7 +1206,6 @@ impl Interactivity {
&& bounds
.intersect(&cx.content_mask().bounds)
.contains_point(&mouse_position)
- && cx.was_top_layer(&mouse_position, cx.stacking_order())
{
style.refine(drag_over_style);
}
@@ -1207,6 +1237,7 @@ impl Default for Interactivity {
key_context: KeyContext::default(),
focusable: false,
tracked_focus_handle: None,
+ scroll_handle: None,
focus_listeners: SmallVec::default(),
// scroll_offset: Point::default(),
group: None,
@@ -1430,3 +1461,83 @@ where
self.element.children_mut()
}
}
+
+#[derive(Default)]
+struct ScrollHandleState {
+ // not great to have the nested rc's...
+ offset: Rc<RefCell<Point<Pixels>>>,
+ bounds: Bounds<Pixels>,
+ child_bounds: Vec<Bounds<Pixels>>,
+ requested_scroll_top: Option<(usize, Pixels)>,
+}
+
+#[derive(Clone)]
+pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
+
+impl ScrollHandle {
+ pub fn new() -> Self {
+ Self(Rc::default())
+ }
+
+ pub fn offset(&self) -> Point<Pixels> {
+ self.0.borrow().offset.borrow().clone()
+ }
+
+ pub fn top_item(&self) -> usize {
+ let state = self.0.borrow();
+ let top = state.bounds.top() - state.offset.borrow().y;
+
+ match state.child_bounds.binary_search_by(|bounds| {
+ if top < bounds.top() {
+ Ordering::Greater
+ } else if top > bounds.bottom() {
+ Ordering::Less
+ } else {
+ Ordering::Equal
+ }
+ }) {
+ Ok(ix) => ix,
+ Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
+ }
+ }
+
+ pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
+ self.0.borrow().child_bounds.get(ix).cloned()
+ }
+
+ /// scroll_to_item scrolls the minimal amount to ensure that the item is
+ /// fully visible
+ pub fn scroll_to_item(&self, ix: usize) {
+ let state = self.0.borrow();
+
+ let Some(bounds) = state.child_bounds.get(ix) else {
+ return;
+ };
+
+ let scroll_offset = state.offset.borrow().y;
+
+ if bounds.top() + scroll_offset < state.bounds.top() {
+ state.offset.borrow_mut().y = state.bounds.top() - bounds.top();
+ } else if bounds.bottom() + scroll_offset > state.bounds.bottom() {
+ state.offset.borrow_mut().y = state.bounds.bottom() - bounds.bottom();
+ }
+ }
+
+ pub fn logical_scroll_top(&self) -> (usize, Pixels) {
+ let ix = self.top_item();
+ let state = self.0.borrow();
+
+ if let Some(child_bounds) = state.child_bounds.get(ix) {
+ (
+ ix,
+ child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
+ )
+ } else {
+ (ix, px(0.))
+ }
+ }
+
+ pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
+ self.0.borrow_mut().requested_scroll_top = Some((ix, px));
+ }
+}
@@ -1,10 +1,12 @@
use std::sync::Arc;
use crate::{
- Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
- IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
+ point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
+ InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
+ StyleRefinement, Styled, WindowContext,
};
use futures::FutureExt;
+use media::core_video::CVImageBuffer;
use util::ResultExt;
#[derive(Clone, Debug)]
@@ -12,6 +14,7 @@ pub enum ImageSource {
/// Image content will be loaded from provided URI at render time.
Uri(SharedString),
Data(Arc<ImageData>),
+ Surface(CVImageBuffer),
}
impl From<SharedString> for ImageSource {
@@ -20,40 +23,45 @@ impl From<SharedString> for ImageSource {
}
}
+impl From<&'static str> for ImageSource {
+ fn from(uri: &'static str) -> Self {
+ Self::Uri(uri.into())
+ }
+}
+
+impl From<String> for ImageSource {
+ fn from(uri: String) -> Self {
+ Self::Uri(uri.into())
+ }
+}
+
impl From<Arc<ImageData>> for ImageSource {
fn from(value: Arc<ImageData>) -> Self {
Self::Data(value)
}
}
+impl From<CVImageBuffer> for ImageSource {
+ fn from(value: CVImageBuffer) -> Self {
+ Self::Surface(value)
+ }
+}
+
pub struct Img {
interactivity: Interactivity,
- source: Option<ImageSource>,
+ source: ImageSource,
grayscale: bool,
}
-pub fn img() -> Img {
+pub fn img(source: impl Into<ImageSource>) -> Img {
Img {
interactivity: Interactivity::default(),
- source: None,
+ source: source.into(),
grayscale: false,
}
}
impl Img {
- pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
- self.source = Some(ImageSource::from(uri.into()));
- self
- }
- pub fn data(mut self, data: Arc<ImageData>) -> Self {
- self.source = Some(ImageSource::from(data));
- self
- }
-
- pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
- self.source = Some(source.into());
- self
- }
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.grayscale = grayscale;
self
@@ -68,9 +76,8 @@ impl Element for Img {
element_state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
- self.interactivity.layout(element_state, cx, |style, cx| {
- cx.request_layout(&style, None)
- })
+ self.interactivity
+ .layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
}
fn paint(
@@ -85,10 +92,9 @@ impl Element for Img {
element_state,
cx,
|style, _scroll_offset, cx| {
- let corner_radii = style.corner_radii;
-
- if let Some(source) = self.source {
- let image = match source {
+ let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
+ cx.with_z_index(1, |cx| {
+ match self.source {
ImageSource::Uri(uri) => {
let image_future = cx.image_cache.get(uri.clone());
if let Some(data) = image_future
@@ -96,7 +102,9 @@ impl Element for Img {
.now_or_never()
.and_then(|result| result.ok())
{
- data
+ let new_bounds = preserve_aspect_ratio(bounds, data.size());
+ cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+ .log_err();
} else {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
@@ -104,17 +112,23 @@ impl Element for Img {
}
})
.detach();
- return;
}
}
- ImageSource::Data(image) => image,
+
+ ImageSource::Data(data) => {
+ let new_bounds = preserve_aspect_ratio(bounds, data.size());
+ cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+ .log_err();
+ }
+
+ ImageSource::Surface(surface) => {
+ let size = size(surface.width().into(), surface.height().into());
+ let new_bounds = preserve_aspect_ratio(bounds, size);
+ // TODO: Add support for corner_radii and grayscale.
+ cx.paint_surface(new_bounds, surface);
+ }
};
- let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
- cx.with_z_index(1, |cx| {
- cx.paint_image(bounds, corner_radii, image, self.grayscale)
- .log_err()
- });
- }
+ });
},
)
}
@@ -143,3 +157,29 @@ impl InteractiveElement for Img {
&mut self.interactivity
}
}
+
+fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
+ let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
+ let image_ratio = image_size.width / image_size.height;
+ let bounds_ratio = bounds.size.width / bounds.size.height;
+
+ let new_size = if bounds_ratio > image_ratio {
+ size(
+ image_size.width * (bounds.size.height / image_size.height),
+ bounds.size.height,
+ )
+ } else {
+ size(
+ bounds.size.width,
+ image_size.height * (bounds.size.width / image_size.width),
+ )
+ };
+
+ Bounds {
+ origin: point(
+ bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
+ bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
+ ),
+ size: new_size,
+ }
+}
@@ -1,6 +1,7 @@
use crate::{
- Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
- Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
+ Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
+ MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
+ WhiteSpace, WindowContext, WrappedLine,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -87,7 +88,28 @@ impl StyledText {
}
}
- pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
+ pub fn with_highlights(
+ mut self,
+ default_style: &TextStyle,
+ highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
+ ) -> Self {
+ let mut runs = Vec::new();
+ let mut ix = 0;
+ for (range, highlight) in highlights {
+ if ix < range.start {
+ runs.push(default_style.clone().to_run(range.start - ix));
+ }
+ runs.push(
+ default_style
+ .clone()
+ .highlight(highlight)
+ .to_run(range.len()),
+ );
+ ix = range.end;
+ }
+ if ix < self.text.len() {
+ runs.push(default_style.to_run(self.text.len() - ix));
+ }
self.runs = Some(runs);
self
}
@@ -144,7 +166,6 @@ impl TextState {
runs: Option<Vec<TextRun>>,
cx: &mut WindowContext,
) -> LayoutId {
- let text_system = cx.text_system().clone();
let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let line_height = text_style
@@ -152,18 +173,16 @@ impl TextState {
.to_pixels(font_size.into(), cx.rem_size());
let text = SharedString::from(text);
- let rem_size = cx.rem_size();
-
let runs = if let Some(runs) = runs {
runs
} else {
vec![text_style.to_run(text.len())]
};
- let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
+ let layout_id = cx.request_measured_layout(Default::default(), {
let element_state = self.clone();
- move |known_dimensions, available_space| {
+ move |known_dimensions, available_space, cx| {
let wrap_width = if text_style.white_space == WhiteSpace::Normal {
known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
@@ -181,7 +200,8 @@ impl TextState {
}
}
- let Some(lines) = text_system
+ let Some(lines) = cx
+ .text_system()
.shape_text(
&text, font_size, &runs, wrap_width, // Wrap if we know the width.
)
@@ -265,7 +285,9 @@ impl TextState {
pub struct InteractiveText {
element_id: ElementId,
text: StyledText,
- click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+ click_listener:
+ Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+ clickable_ranges: Vec<Range<usize>>,
}
struct InteractiveTextClickEvent {
@@ -284,6 +306,7 @@ impl InteractiveText {
element_id: id.into(),
text,
click_listener: None,
+ clickable_ranges: Vec::new(),
}
}
@@ -292,7 +315,7 @@ impl InteractiveText {
ranges: Vec<Range<usize>>,
listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
) -> Self {
- self.click_listener = Some(Box::new(move |event, cx| {
+ self.click_listener = Some(Box::new(move |ranges, event, cx| {
for (range_ix, range) in ranges.iter().enumerate() {
if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
{
@@ -300,6 +323,7 @@ impl InteractiveText {
}
}
}));
+ self.clickable_ranges = ranges;
self
}
}
@@ -334,6 +358,19 @@ impl Element for InteractiveText {
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
if let Some(click_listener) = self.click_listener {
+ if let Some(ix) = state
+ .text_state
+ .index_for_position(bounds, cx.mouse_position())
+ {
+ if self
+ .clickable_ranges
+ .iter()
+ .any(|range| range.contains(&ix))
+ {
+ cx.set_cursor_style(crate::CursorStyle::PointingHand)
+ }
+ }
+
let text_state = state.text_state.clone();
let mouse_down = state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() {
@@ -343,6 +380,7 @@ impl Element for InteractiveText {
text_state.index_for_position(bounds, event.position)
{
click_listener(
+ &self.clickable_ranges,
InteractiveTextClickEvent {
mouse_down_index,
mouse_up_index,
@@ -109,7 +109,6 @@ impl Element for UniformList {
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let max_items = self.item_count;
- let rem_size = cx.rem_size();
let item_size = state
.as_ref()
.map(|s| s.item_size)
@@ -120,9 +119,7 @@ impl Element for UniformList {
.layout(state.map(|s| s.interactive), cx, |style, cx| {
cx.request_measured_layout(
style,
- rem_size,
- move |known_dimensions: Size<Option<Pixels>>,
- available_space: Size<AvailableSpace>| {
+ move |known_dimensions, available_space, _cx| {
let desired_height = item_size.height * max_items;
let width =
known_dimensions
@@ -655,6 +655,20 @@ pub struct Corners<T: Clone + Default + Debug> {
pub bottom_left: T,
}
+impl<T> Corners<T>
+where
+ T: Clone + Default + Debug,
+{
+ pub fn all(value: T) -> Self {
+ Self {
+ top_left: value.clone(),
+ top_right: value.clone(),
+ bottom_right: value.clone(),
+ bottom_left: value,
+ }
+ }
+}
+
impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = size.width.max(size.height) / 2.;
@@ -740,7 +754,7 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
Deserialize,
)]
#[repr(transparent)]
-pub struct Pixels(pub(crate) f32);
+pub struct Pixels(pub f32);
impl std::ops::Div for Pixels {
type Output = f32;
@@ -905,6 +919,12 @@ impl From<Pixels> for usize {
}
}
+impl From<usize> for Pixels {
+ fn from(pixels: usize) -> Self {
+ Pixels(pixels as f32)
+ }
+}
+
#[derive(
Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
)]
@@ -959,6 +979,18 @@ impl From<u64> for DevicePixels {
}
}
+impl From<DevicePixels> for usize {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0 as usize
+ }
+}
+
+impl From<usize> for DevicePixels {
+ fn from(device_pixels: usize) -> Self {
+ DevicePixels(device_pixels as i32)
+ }
+}
+
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct ScaledPixels(pub(crate) f32);
@@ -1034,7 +1066,7 @@ impl sqlez::bindable::Bind for GlobalPixels {
}
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
-pub struct Rems(f32);
+pub struct Rems(pub f32);
impl Mul<Pixels> for Rems {
type Output = Pixels;
@@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow {
fn draw(&self, scene: Scene);
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+ #[cfg(any(test, feature = "test-support"))]
+ fn as_test(&self) -> Option<&TestWindow> {
+ None
+ }
}
pub trait PlatformDispatcher: Send + Sync {
@@ -1,7 +1,7 @@
use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
- Quad, ScaledPixels, Scene, Shadow, Size, Underline,
+ Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
};
use cocoa::{
base::{NO, YES},
@@ -9,6 +9,9 @@ use cocoa::{
quartzcore::AutoresizingMask,
};
use collections::HashMap;
+use core_foundation::base::TCFType;
+use foreign_types::ForeignType;
+use media::core_video::CVMetalTextureCache;
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
use smallvec::SmallVec;
@@ -27,9 +30,11 @@ pub(crate) struct MetalRenderer {
underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
+ surfaces_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
sprite_atlas: Arc<MetalAtlas>,
+ core_video_texture_cache: CVMetalTextureCache,
}
impl MetalRenderer {
@@ -143,6 +148,14 @@ impl MetalRenderer {
"polychrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
);
+ let surfaces_pipeline_state = build_pipeline_state(
+ &device,
+ &library,
+ "surfaces",
+ "surface_vertex",
+ "surface_fragment",
+ MTLPixelFormat::BGRA8Unorm,
+ );
let command_queue = device.new_command_queue();
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
@@ -157,9 +170,11 @@ impl MetalRenderer {
underlines_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
+ surfaces_pipeline_state,
unit_vertices,
instances,
sprite_atlas,
+ core_video_texture_cache: CVMetalTextureCache::new(device.as_ptr()).unwrap(),
}
}
@@ -268,6 +283,14 @@ impl MetalRenderer {
command_encoder,
);
}
+ PrimitiveBatch::Surfaces(surfaces) => {
+ self.draw_surfaces(
+ surfaces,
+ &mut instance_offset,
+ viewport_size,
+ command_encoder,
+ );
+ }
}
}
@@ -793,6 +816,102 @@ impl MetalRenderer {
);
*offset = next_offset;
}
+
+ fn draw_surfaces(
+ &mut self,
+ surfaces: &[Surface],
+ offset: &mut usize,
+ viewport_size: Size<DevicePixels>,
+ command_encoder: &metal::RenderCommandEncoderRef,
+ ) {
+ command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ SurfaceInputIndex::Vertices as u64,
+ Some(&self.unit_vertices),
+ 0,
+ );
+ command_encoder.set_vertex_bytes(
+ SurfaceInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
+
+ for surface in surfaces {
+ let texture_size = size(
+ DevicePixels::from(surface.image_buffer.width() as i32),
+ DevicePixels::from(surface.image_buffer.height() as i32),
+ );
+
+ assert_eq!(
+ surface.image_buffer.pixel_format_type(),
+ media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
+ );
+
+ let y_texture = self
+ .core_video_texture_cache
+ .create_texture_from_image(
+ surface.image_buffer.as_concrete_TypeRef(),
+ ptr::null(),
+ MTLPixelFormat::R8Unorm,
+ surface.image_buffer.plane_width(0),
+ surface.image_buffer.plane_height(0),
+ 0,
+ )
+ .unwrap();
+ let cb_cr_texture = self
+ .core_video_texture_cache
+ .create_texture_from_image(
+ surface.image_buffer.as_concrete_TypeRef(),
+ ptr::null(),
+ MTLPixelFormat::RG8Unorm,
+ surface.image_buffer.plane_width(1),
+ surface.image_buffer.plane_height(1),
+ 1,
+ )
+ .unwrap();
+
+ align_offset(offset);
+ let next_offset = *offset + mem::size_of::<Surface>();
+ assert!(
+ next_offset <= INSTANCE_BUFFER_SIZE,
+ "instance buffer exhausted"
+ );
+
+ command_encoder.set_vertex_buffer(
+ SurfaceInputIndex::Surfaces as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ command_encoder.set_vertex_bytes(
+ SurfaceInputIndex::TextureSize as u64,
+ mem::size_of_val(&texture_size) as u64,
+ &texture_size as *const Size<DevicePixels> as *const _,
+ );
+ command_encoder.set_fragment_texture(
+ SurfaceInputIndex::YTexture as u64,
+ Some(y_texture.as_texture_ref()),
+ );
+ command_encoder.set_fragment_texture(
+ SurfaceInputIndex::CbCrTexture as u64,
+ Some(cb_cr_texture.as_texture_ref()),
+ );
+
+ unsafe {
+ let buffer_contents =
+ (self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
+ ptr::write(
+ buffer_contents,
+ SurfaceBounds {
+ bounds: surface.bounds,
+ content_mask: surface.content_mask.clone(),
+ },
+ );
+ }
+
+ command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
+ *offset = next_offset;
+ }
+ }
}
fn build_pipeline_state(
@@ -898,6 +1017,16 @@ enum SpriteInputIndex {
AtlasTexture = 4,
}
+#[repr(C)]
+enum SurfaceInputIndex {
+ Vertices = 0,
+ Surfaces = 1,
+ ViewportSize = 2,
+ TextureSize = 3,
+ YTexture = 4,
+ CbCrTexture = 5,
+}
+
#[repr(C)]
enum PathRasterizationInputIndex {
Vertices = 0,
@@ -911,3 +1040,10 @@ pub struct PathSprite {
pub color: Hsla,
pub tile: AtlasTile,
}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct SurfaceBounds {
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+}
@@ -469,6 +469,58 @@ fragment float4 path_sprite_fragment(
return color;
}
+struct SurfaceVertexOutput {
+ float4 position [[position]];
+ float2 texture_position;
+ float clip_distance [[clip_distance]][4];
+};
+
+struct SurfaceFragmentInput {
+ float4 position [[position]];
+ float2 texture_position;
+};
+
+vertex SurfaceVertexOutput surface_vertex(
+ uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
+ constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
+ constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
+ constant Size_DevicePixels *viewport_size
+ [[buffer(SurfaceInputIndex_ViewportSize)]],
+ constant Size_DevicePixels *texture_size
+ [[buffer(SurfaceInputIndex_TextureSize)]]) {
+ float2 unit_vertex = unit_vertices[unit_vertex_id];
+ SurfaceBounds surface = surfaces[surface_id];
+ float4 device_position =
+ to_device_position(unit_vertex, surface.bounds, viewport_size);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
+ surface.content_mask.bounds);
+ // We are going to copy the whole texture, so the texture position corresponds
+ // to the current vertex of the unit triangle.
+ float2 texture_position = unit_vertex;
+ return SurfaceVertexOutput{
+ device_position,
+ texture_position,
+ {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
+}
+
+fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
+ texture2d<float> y_texture
+ [[texture(SurfaceInputIndex_YTexture)]],
+ texture2d<float> cb_cr_texture
+ [[texture(SurfaceInputIndex_CbCrTexture)]]) {
+ constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
+ const float4x4 ycbcrToRGBTransform =
+ float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
+ float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
+ float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
+ float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
+ float4 ycbcr = float4(
+ y_texture.sample(texture_sampler, input.texture_position).r,
+ cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
+
+ return ycbcrToRGBTransform * ycbcr;
+}
+
float4 hsla_to_rgba(Hsla hsla) {
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
float s = hsla.s;
@@ -189,13 +189,9 @@ impl Platform for TestPlatform {
unimplemented!()
}
- fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_become_active(&self, _callback: Box<dyn FnMut()>) {}
- fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
@@ -11,19 +11,20 @@ use std::{
};
#[derive(Default)]
-struct Handlers {
- active_status_change: Vec<Box<dyn FnMut(bool)>>,
- input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
- moved: Vec<Box<dyn FnMut()>>,
- resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+pub(crate) struct TestWindowHandlers {
+ pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
+ pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+ pub(crate) moved: Vec<Box<dyn FnMut()>>,
+ pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
}
pub struct TestWindow {
bounds: WindowBounds,
current_scene: Mutex<Option<Scene>>,
display: Rc<dyn PlatformDisplay>,
+ pub(crate) window_title: Option<String>,
pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
- handlers: Mutex<Handlers>,
+ pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
}
@@ -42,6 +43,7 @@ impl TestWindow {
input_handler: None,
sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(),
+ window_title: Default::default(),
}
}
}
@@ -100,8 +102,8 @@ impl PlatformWindow for TestWindow {
todo!()
}
- fn set_title(&mut self, _title: &str) {
- todo!()
+ fn set_title(&mut self, title: &str) {
+ self.window_title = Some(title.to_owned());
}
fn set_edited(&mut self, _edited: bool) {
@@ -167,6 +169,10 @@ impl PlatformWindow for TestWindow {
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.sprite_atlas.clone()
}
+
+ fn as_test(&self) -> Option<&TestWindow> {
+ Some(self)
+ }
}
pub struct TestAtlasState {
@@ -25,6 +25,7 @@ pub(crate) struct SceneBuilder {
underlines: Vec<Underline>,
monochrome_sprites: Vec<MonochromeSprite>,
polychrome_sprites: Vec<PolychromeSprite>,
+ surfaces: Vec<Surface>,
}
impl Default for SceneBuilder {
@@ -38,6 +39,7 @@ impl Default for SceneBuilder {
underlines: Vec::new(),
monochrome_sprites: Vec::new(),
polychrome_sprites: Vec::new(),
+ surfaces: Vec::new(),
}
}
}
@@ -120,6 +122,7 @@ impl SceneBuilder {
(PrimitiveKind::PolychromeSprite, ix) => {
self.polychrome_sprites[ix].order = draw_order as DrawOrder
}
+ (PrimitiveKind::Surface, ix) => self.surfaces[ix].order = draw_order as DrawOrder,
}
}
@@ -129,6 +132,7 @@ impl SceneBuilder {
self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
+ self.surfaces.sort_unstable();
Scene {
shadows: mem::take(&mut self.shadows),
@@ -137,6 +141,7 @@ impl SceneBuilder {
underlines: mem::take(&mut self.underlines),
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+ surfaces: mem::take(&mut self.surfaces),
}
}
@@ -185,6 +190,10 @@ impl SceneBuilder {
sprite.order = layer_id;
self.polychrome_sprites.push(sprite);
}
+ Primitive::Surface(mut surface) => {
+ surface.order = layer_id;
+ self.surfaces.push(surface);
+ }
}
}
}
@@ -196,6 +205,7 @@ pub(crate) struct Scene {
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
+ pub surfaces: Vec<Surface>,
}
impl Scene {
@@ -224,6 +234,9 @@ impl Scene {
polychrome_sprites: &self.polychrome_sprites,
polychrome_sprites_start: 0,
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+ surfaces: &self.surfaces,
+ surfaces_start: 0,
+ surfaces_iter: self.surfaces.iter().peekable(),
}
}
}
@@ -247,6 +260,9 @@ struct BatchIterator<'a> {
polychrome_sprites: &'a [PolychromeSprite],
polychrome_sprites_start: usize,
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+ surfaces: &'a [Surface],
+ surfaces_start: usize,
+ surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
}
impl<'a> Iterator for BatchIterator<'a> {
@@ -272,6 +288,10 @@ impl<'a> Iterator for BatchIterator<'a> {
self.polychrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::PolychromeSprite,
),
+ (
+ self.surfaces_iter.peek().map(|s| s.order),
+ PrimitiveKind::Surface,
+ ),
];
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
@@ -378,6 +398,21 @@ impl<'a> Iterator for BatchIterator<'a> {
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
})
}
+ PrimitiveKind::Surface => {
+ let surfaces_start = self.surfaces_start;
+ let mut surfaces_end = surfaces_start;
+ while self
+ .surfaces_iter
+ .next_if(|surface| surface.order <= max_order)
+ .is_some()
+ {
+ surfaces_end += 1;
+ }
+ self.surfaces_start = surfaces_end;
+ Some(PrimitiveBatch::Surfaces(
+ &self.surfaces[surfaces_start..surfaces_end],
+ ))
+ }
}
}
}
@@ -391,6 +426,7 @@ pub enum PrimitiveKind {
Underline,
MonochromeSprite,
PolychromeSprite,
+ Surface,
}
pub enum Primitive {
@@ -400,6 +436,7 @@ pub enum Primitive {
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
+ Surface(Surface),
}
impl Primitive {
@@ -411,6 +448,7 @@ impl Primitive {
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+ Primitive::Surface(surface) => &surface.bounds,
}
}
@@ -422,6 +460,7 @@ impl Primitive {
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+ Primitive::Surface(surface) => &surface.content_mask,
}
}
}
@@ -440,6 +479,7 @@ pub(crate) enum PrimitiveBatch<'a> {
texture_id: AtlasTextureId,
sprites: &'a [PolychromeSprite],
},
+ Surfaces(&'a [Surface]),
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
@@ -593,6 +633,32 @@ impl From<PolychromeSprite> for Primitive {
}
}
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Surface {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub image_buffer: media::core_video::CVImageBuffer,
+}
+
+impl Ord for Surface {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
+ }
+}
+
+impl PartialOrd for Surface {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl From<Surface> for Primitive {
+ fn from(surface: Surface) -> Self {
+ Primitive::Surface(surface)
+ }
+}
+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
@@ -1,4 +1,7 @@
-use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
+use crate::{
+ AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style,
+ WindowContext,
+};
use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::fmt::Debug;
@@ -9,13 +12,21 @@ use taffy::{
Taffy,
};
-type Measureable = dyn Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync;
-
pub struct TaffyLayoutEngine {
- taffy: Taffy<Box<Measureable>>,
+ taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: HashSet<LayoutId>,
+ nodes_to_measure: HashMap<
+ LayoutId,
+ Box<
+ dyn FnMut(
+ Size<Option<Pixels>>,
+ Size<AvailableSpace>,
+ &mut WindowContext,
+ ) -> Size<Pixels>,
+ >,
+ >,
}
static EXPECT_MESSAGE: &'static str =
@@ -28,6 +39,7 @@ impl TaffyLayoutEngine {
children_to_parents: HashMap::default(),
absolute_layout_bounds: HashMap::default(),
computed_layouts: HashSet::default(),
+ nodes_to_measure: HashMap::default(),
}
}
@@ -36,6 +48,7 @@ impl TaffyLayoutEngine {
self.children_to_parents.clear();
self.absolute_layout_bounds.clear();
self.computed_layouts.clear();
+ self.nodes_to_measure.clear();
}
pub fn request_layout(
@@ -65,18 +78,18 @@ impl TaffyLayoutEngine {
&mut self,
style: Style,
rem_size: Pixels,
- measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
- + Send
- + Sync
+ measure: impl FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+ 'static,
) -> LayoutId {
let style = style.to_taffy(rem_size);
- let measurable = Box::new(measure);
- self.taffy
- .new_leaf_with_context(style, measurable)
+ let layout_id = self
+ .taffy
+ .new_leaf_with_context(style, ())
.expect(EXPECT_MESSAGE)
- .into()
+ .into();
+ self.nodes_to_measure.insert(layout_id, Box::new(measure));
+ layout_id
}
// Used to understand performance
@@ -126,7 +139,12 @@ impl TaffyLayoutEngine {
Ok(edges)
}
- pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
+ pub fn compute_layout(
+ &mut self,
+ id: LayoutId,
+ available_space: Size<AvailableSpace>,
+ cx: &mut WindowContext,
+ ) {
// Leaving this here until we have a better instrumentation approach.
// println!("Laying out {} children", self.count_all_children(id)?);
// println!("Max layout depth: {}", self.max_depth(0, id)?);
@@ -159,8 +177,8 @@ impl TaffyLayoutEngine {
.compute_layout_with_measure(
id.into(),
available_space.into(),
- |known_dimensions, available_space, _node_id, context| {
- let Some(measure) = context else {
+ |known_dimensions, available_space, node_id, _context| {
+ let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
return taffy::geometry::Size::default();
};
@@ -169,10 +187,11 @@ impl TaffyLayoutEngine {
height: known_dimensions.height.map(Pixels),
};
- measure(known_dimensions, available_space.into()).into()
+ measure(known_dimensions, available_space.into(), cx).into()
},
)
.expect(EXPECT_MESSAGE);
+
// println!("compute_layout took {:?}", started_at.elapsed());
}
@@ -209,9 +209,7 @@ impl AnyView {
) {
cx.with_absolute_element_offset(origin, |cx| {
let (layout_id, rendered_element) = (self.layout)(self, cx);
- cx.window
- .layout_engine
- .compute_layout(layout_id, available_space);
+ cx.compute_layout(layout_id, available_space);
(self.paint)(self, rendered_element, cx);
})
}
@@ -240,6 +238,10 @@ impl Element for AnyView {
}
fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ debug_assert!(
+ state.is_some(),
+ "state is None. Did you include an AnyView twice in the tree?"
+ );
(self.paint)(&self, state.take().unwrap(), cx)
}
}
@@ -8,8 +8,8 @@ use crate::{
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
- Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
- VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+ Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
+ UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
@@ -18,6 +18,7 @@ use futures::{
channel::{mpsc, oneshot},
StreamExt,
};
+use media::core_video::CVImageBuffer;
use parking_lot::RwLock;
use slotmap::SlotMap;
use smallvec::SmallVec;
@@ -208,7 +209,7 @@ pub struct Window {
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
viewport_size: Size<Pixels>,
- pub(crate) layout_engine: TaffyLayoutEngine,
+ layout_engine: Option<TaffyLayoutEngine>,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
pub(crate) previous_frame: Frame,
@@ -326,7 +327,7 @@ impl Window {
sprite_atlas,
rem_size: px(16.),
viewport_size: content_size,
- layout_engine: TaffyLayoutEngine::new(),
+ layout_engine: Some(TaffyLayoutEngine::new()),
root_view: None,
element_id_stack: GlobalElementId::default(),
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
@@ -605,9 +606,11 @@ impl<'a> WindowContext<'a> {
self.app.layout_id_buffer.extend(children.into_iter());
let rem_size = self.rem_size();
- self.window
- .layout_engine
- .request_layout(style, rem_size, &self.app.layout_id_buffer)
+ self.window.layout_engine.as_mut().unwrap().request_layout(
+ style,
+ rem_size,
+ &self.app.layout_id_buffer,
+ )
}
/// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
@@ -617,22 +620,25 @@ impl<'a> WindowContext<'a> {
/// The given closure is invoked at layout time with the known dimensions and available space and
/// returns a `Size`.
pub fn request_measured_layout<
- F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
+ F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+ + 'static,
>(
&mut self,
style: Style,
- rem_size: Pixels,
measure: F,
) -> LayoutId {
+ let rem_size = self.rem_size();
self.window
.layout_engine
+ .as_mut()
+ .unwrap()
.request_measured_layout(style, rem_size, measure)
}
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
- self.window
- .layout_engine
- .compute_layout(layout_id, available_space)
+ let mut layout_engine = self.window.layout_engine.take().unwrap();
+ layout_engine.compute_layout(layout_id, available_space, self);
+ self.window.layout_engine = Some(layout_engine);
}
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
@@ -642,6 +648,8 @@ impl<'a> WindowContext<'a> {
let mut bounds = self
.window
.layout_engine
+ .as_mut()
+ .unwrap()
.layout_bounds(layout_id)
.map(Into::into);
bounds.origin += self.element_offset();
@@ -677,6 +685,10 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.zoom();
}
+ pub fn set_window_title(&mut self, title: &str) {
+ self.window.platform_window.set_title(title);
+ }
+
pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.platform
.displays()
@@ -1116,6 +1128,23 @@ impl<'a> WindowContext<'a> {
Ok(())
}
+ /// Paint a surface into the scene for the current frame at the current z-index.
+ pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
+ let scale_factor = self.scale_factor();
+ let bounds = bounds.scale(scale_factor);
+ let content_mask = self.content_mask().scale(scale_factor);
+ let window = &mut *self.window;
+ window.current_frame.scene_builder.insert(
+ &window.current_frame.z_index_stack,
+ Surface {
+ order: 0,
+ bounds,
+ content_mask,
+ image_buffer,
+ },
+ );
+ }
+
/// Draw pixels to the display for this window based on the contents of its scene.
pub(crate) fn draw(&mut self) {
let root_view = self.window.root_view.take().unwrap();
@@ -1171,7 +1200,7 @@ impl<'a> WindowContext<'a> {
self.text_system().start_frame();
let window = &mut *self.window;
- window.layout_engine.clear();
+ window.layout_engine.as_mut().unwrap().clear();
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
@@ -2582,7 +2611,7 @@ impl<V: 'static + Render> WindowHandle<V> {
cx.read_window(self, |root_view, _cx| root_view.clone())
}
- pub fn is_active(&self, cx: &WindowContext) -> Option<bool> {
+ pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
cx.windows
.get(self.id)
.and_then(|window| window.as_ref().map(|window| window.active))
@@ -197,8 +197,12 @@ impl CachedLspAdapter {
self.adapter.code_action_kinds()
}
- pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
- self.adapter.workspace_configuration(cx)
+ pub fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
+ self.adapter.workspace_configuration(workspace_root, cx)
}
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -312,7 +316,7 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
futures::future::ready(serde_json::json!({})).boxed()
}
@@ -200,8 +200,12 @@ impl CachedLspAdapter {
self.adapter.code_action_kinds()
}
- pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
- self.adapter.workspace_configuration(cx)
+ pub fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
+ self.adapter.workspace_configuration(workspace_root, cx)
}
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -315,7 +319,7 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
futures::future::ready(serde_json::json!({})).boxed()
}
@@ -429,8 +429,8 @@ impl LanguageServer {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
let params = InitializeParams {
- process_id: Default::default(),
- root_path: Default::default(),
+ process_id: None,
+ root_path: None,
root_uri: Some(root_uri.clone()),
initialization_options: options,
capabilities: ClientCapabilities {
@@ -451,12 +451,15 @@ impl LanguageServer {
inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
+ diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+ refresh_support: None,
+ }),
..Default::default()
}),
text_document: Some(TextDocumentClientCapabilities {
definition: Some(GotoCapability {
link_support: Some(true),
- ..Default::default()
+ dynamic_registration: None,
}),
code_action: Some(CodeActionClientCapabilities {
code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -501,7 +504,7 @@ impl LanguageServer {
}),
hover: Some(HoverClientCapabilities {
content_format: Some(vec![MarkupKind::Markdown]),
- ..Default::default()
+ dynamic_registration: None,
}),
inlay_hint: Some(InlayHintClientCapabilities {
resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -515,6 +518,20 @@ impl LanguageServer {
}),
dynamic_registration: Some(false),
}),
+ publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+ related_information: Some(true),
+ ..Default::default()
+ }),
+ formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ diagnostic: Some(DiagnosticClientCapabilities {
+ related_document_support: Some(true),
+ dynamic_registration: None,
+ }),
..Default::default()
}),
experimental: Some(json!({
@@ -524,15 +541,15 @@ impl LanguageServer {
work_done_progress: Some(true),
..Default::default()
}),
- ..Default::default()
+ general: None,
},
- trace: Default::default(),
+ trace: None,
workspace_folders: Some(vec![WorkspaceFolder {
uri: root_uri,
name: Default::default(),
}]),
- client_info: Default::default(),
- locale: Default::default(),
+ client_info: None,
+ locale: None,
};
let response = self.request::<request::Initialize>(params).await?;
@@ -434,8 +434,8 @@ impl LanguageServer {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
let params = InitializeParams {
- process_id: Default::default(),
- root_path: Default::default(),
+ process_id: None,
+ root_path: None,
root_uri: Some(root_uri.clone()),
initialization_options: options,
capabilities: ClientCapabilities {
@@ -456,12 +456,15 @@ impl LanguageServer {
inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
+ diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+ refresh_support: None,
+ }),
..Default::default()
}),
text_document: Some(TextDocumentClientCapabilities {
definition: Some(GotoCapability {
link_support: Some(true),
- ..Default::default()
+ dynamic_registration: None,
}),
code_action: Some(CodeActionClientCapabilities {
code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -503,7 +506,7 @@ impl LanguageServer {
}),
hover: Some(HoverClientCapabilities {
content_format: Some(vec![MarkupKind::Markdown]),
- ..Default::default()
+ dynamic_registration: None,
}),
inlay_hint: Some(InlayHintClientCapabilities {
resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -517,6 +520,20 @@ impl LanguageServer {
}),
dynamic_registration: Some(false),
}),
+ publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+ related_information: Some(true),
+ ..Default::default()
+ }),
+ formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ diagnostic: Some(DiagnosticClientCapabilities {
+ related_document_support: Some(true),
+ dynamic_registration: None,
+ }),
..Default::default()
}),
experimental: Some(json!({
@@ -526,15 +543,15 @@ impl LanguageServer {
work_done_progress: Some(true),
..Default::default()
}),
- ..Default::default()
+ general: None,
},
- trace: Default::default(),
+ trace: None,
workspace_folders: Some(vec![WorkspaceFolder {
uri: root_uri,
name: Default::default(),
}]),
- client_info: Default::default(),
- locale: Default::default(),
+ client_info: None,
+ locale: None,
};
let response = self.request::<request::Initialize>(params).await?;
@@ -2641,8 +2641,9 @@ impl Project {
});
for (adapter, server) in servers {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(server.root_path(), cx))
+ .await;
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams {
@@ -2753,7 +2754,7 @@ impl Project {
stderr_capture.clone(),
language.clone(),
adapter.clone(),
- worktree_path,
+ Arc::clone(&worktree_path),
ProjectLspAdapterDelegate::new(self, cx),
cx,
) {
@@ -2776,6 +2777,7 @@ impl Project {
cx.spawn_weak(|this, mut cx| async move {
let result = Self::setup_and_insert_language_server(
this,
+ &worktree_path,
override_options,
pending_server,
adapter.clone(),
@@ -2891,6 +2893,7 @@ impl Project {
async fn setup_and_insert_language_server(
this: WeakModelHandle<Self>,
+ worktree_path: &Path,
override_initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
@@ -2903,6 +2906,7 @@ impl Project {
this,
override_initialization_options,
pending_server,
+ worktree_path,
adapter.clone(),
server_id,
cx,
@@ -2932,11 +2936,14 @@ impl Project {
this: WeakModelHandle<Self>,
override_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
+ worktree_path: &Path,
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Arc<LanguageServer>> {
- let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(worktree_path, cx))
+ .await;
let language_server = pending_server.task.await?;
language_server
@@ -2964,11 +2971,14 @@ impl Project {
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
let adapter = adapter.clone();
+ let worktree_path = worktree_path.to_path_buf();
move |params, mut cx| {
let adapter = adapter.clone();
+ let worktree_path = worktree_path.clone();
async move {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(&worktree_path, cx))
+ .await;
Ok(params
.items
.into_iter()
@@ -6523,9 +6533,15 @@ impl Project {
})
}
- pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+ pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
let mut summary = DiagnosticSummary::default();
- for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+ for (_, _, path_summary) in
+ self.diagnostic_summaries(include_ignored, cx)
+ .filter(|(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
+ {
summary.error_count += path_summary.error_count;
summary.warning_count += path_summary.warning_count;
}
@@ -6534,6 +6550,7 @@ impl Project {
pub fn diagnostic_summaries<'a>(
&'a self,
+ include_ignored: bool,
cx: &'a AppContext,
) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
self.visible_worktrees(cx).flat_map(move |worktree| {
@@ -6544,6 +6561,10 @@ impl Project {
.map(move |(path, server_id, summary)| {
(ProjectPath { worktree_id, path }, server_id, summary)
})
+ .filter(move |(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
})
}
@@ -806,7 +806,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background());
@@ -814,7 +814,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
"/root",
json!({
"dir": {
+ ".git": {
+ "HEAD": "ref: refs/heads/main",
+ },
+ ".gitignore": "b.rs",
"a.rs": "let a = 1;",
+ "b.rs": "let b = 2;",
},
"other.rs": "let b = c;"
}),
@@ -822,6 +827,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+ let (worktree, _) = project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root/dir", true, cx)
+ })
+ .await
+ .unwrap();
+ let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
let (worktree, _) = project
.update(cx, |project, cx| {
@@ -829,12 +841,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
})
.await
.unwrap();
- let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+ let other_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+ let server_id = LanguageServerId(0);
project.update(cx, |project, cx| {
project
.update_diagnostics(
- LanguageServerId(0),
+ server_id,
+ lsp::PublishDiagnosticsParams {
+ uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+ version: None,
+ diagnostics: vec![lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+ severity: Some(lsp::DiagnosticSeverity::ERROR),
+ message: "unused variable 'b'".to_string(),
+ ..Default::default()
+ }],
+ },
+ &[],
+ cx,
+ )
+ .unwrap();
+ project
+ .update_diagnostics(
+ server_id,
lsp::PublishDiagnosticsParams {
uri: Url::from_file_path("/root/other.rs").unwrap(),
version: None,
@@ -851,11 +881,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.unwrap();
});
- let buffer = project
- .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+ let main_ignored_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((main_worktree_id, "b.rs"), cx)
+ })
.await
.unwrap();
- buffer.read_with(cx, |buffer, _| {
+ main_ignored_buffer.read_with(cx, |buffer, _| {
+ let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+ assert_eq!(
+ chunks
+ .iter()
+ .map(|(s, d)| (s.as_str(), *d))
+ .collect::<Vec<_>>(),
+ &[
+ ("let ", None),
+ ("b", Some(DiagnosticSeverity::ERROR)),
+ (" = 2;", None),
+ ],
+ "Gigitnored buffers should still get in-buffer diagnostics",
+ );
+ });
+ let other_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((other_worktree_id, ""), cx)
+ })
+ .await
+ .unwrap();
+ other_buffer.read_with(cx, |buffer, _| {
let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
assert_eq!(
chunks
@@ -866,13 +919,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
("let b = ", None),
("c", Some(DiagnosticSeverity::ERROR)),
(";", None),
- ]
+ ],
+ "Buffers from hidden projects should still get in-buffer diagnostics"
);
});
project.read_with(cx, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).next(), None);
- assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+ assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+ assert_eq!(
+ project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+ vec![(
+ ProjectPath {
+ worktree_id: main_worktree_id,
+ path: Arc::from(Path::new("b.rs")),
+ },
+ server_id,
+ DiagnosticSummary {
+ error_count: 1,
+ warning_count: 0,
+ }
+ )]
+ );
+ assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+ assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
});
}
@@ -1145,7 +1214,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.read_with(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 1,
warning_count: 0,
@@ -1171,7 +1240,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.read_with(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 0,
warning_count: 0,
@@ -1763,7 +1832,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
.unwrap();
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 2,
warning_count: 0,
@@ -717,8 +717,9 @@ async fn location_links_from_lsp(
})?
.await?;
- buffer.update(&mut cx, |origin_buffer, cx| {
+ cx.update(|cx| {
let origin_location = origin_range.map(|origin_range| {
+ let origin_buffer = buffer.read(cx);
let origin_start =
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
let origin_end =
@@ -2677,8 +2677,9 @@ impl Project {
})?;
for (adapter, server) in servers {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(server.root_path(), cx))?
+ .await;
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams {
@@ -2790,7 +2791,7 @@ impl Project {
stderr_capture.clone(),
language.clone(),
adapter.clone(),
- worktree_path,
+ Arc::clone(&worktree_path),
ProjectLspAdapterDelegate::new(self, cx),
cx,
) {
@@ -2822,6 +2823,7 @@ impl Project {
cx.spawn(move |this, mut cx| async move {
let result = Self::setup_and_insert_language_server(
this.clone(),
+ &worktree_path,
initialization_options,
pending_server,
adapter.clone(),
@@ -2942,6 +2944,7 @@ impl Project {
async fn setup_and_insert_language_server(
this: WeakModel<Self>,
+ worktree_path: &Path,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
@@ -2954,6 +2957,7 @@ impl Project {
this.clone(),
initialization_options,
pending_server,
+ worktree_path,
adapter.clone(),
server_id,
cx,
@@ -2983,11 +2987,14 @@ impl Project {
this: WeakModel<Self>,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
+ worktree_path: &Path,
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Arc<LanguageServer>> {
- let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(worktree_path, cx))?
+ .await;
let language_server = pending_server.task.await?;
language_server
@@ -3016,11 +3023,14 @@ impl Project {
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
let adapter = adapter.clone();
+ let worktree_path = worktree_path.to_path_buf();
move |params, cx| {
let adapter = adapter.clone();
+ let worktree_path = worktree_path.clone();
async move {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(&worktree_path, cx))?
+ .await;
Ok(params
.items
.into_iter()
@@ -6596,9 +6606,15 @@ impl Project {
})
}
- pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+ pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
let mut summary = DiagnosticSummary::default();
- for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+ for (_, _, path_summary) in
+ self.diagnostic_summaries(include_ignored, cx)
+ .filter(|(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
+ {
summary.error_count += path_summary.error_count;
summary.warning_count += path_summary.warning_count;
}
@@ -6607,17 +6623,23 @@ impl Project {
pub fn diagnostic_summaries<'a>(
&'a self,
+ include_ignored: bool,
cx: &'a AppContext,
) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
- self.visible_worktrees(cx).flat_map(move |worktree| {
- let worktree = worktree.read(cx);
- let worktree_id = worktree.id();
- worktree
- .diagnostic_summaries()
- .map(move |(path, server_id, summary)| {
- (ProjectPath { worktree_id, path }, server_id, summary)
- })
- })
+ self.visible_worktrees(cx)
+ .flat_map(move |worktree| {
+ let worktree = worktree.read(cx);
+ let worktree_id = worktree.id();
+ worktree
+ .diagnostic_summaries()
+ .map(move |(path, server_id, summary)| {
+ (ProjectPath { worktree_id, path }, server_id, summary)
+ })
+ })
+ .filter(move |(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
}
pub fn disk_based_diagnostics_started(
@@ -823,7 +823,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
@@ -831,7 +831,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
"/root",
json!({
"dir": {
+ ".git": {
+ "HEAD": "ref: refs/heads/main",
+ },
+ ".gitignore": "b.rs",
"a.rs": "let a = 1;",
+ "b.rs": "let b = 2;",
},
"other.rs": "let b = c;"
}),
@@ -839,6 +844,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+ let (worktree, _) = project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root/dir", true, cx)
+ })
+ .await
+ .unwrap();
+ let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
let (worktree, _) = project
.update(cx, |project, cx| {
@@ -846,12 +858,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
})
.await
.unwrap();
- let worktree_id = worktree.update(cx, |tree, _| tree.id());
+ let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
+ let server_id = LanguageServerId(0);
project.update(cx, |project, cx| {
project
.update_diagnostics(
- LanguageServerId(0),
+ server_id,
+ lsp::PublishDiagnosticsParams {
+ uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+ version: None,
+ diagnostics: vec![lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+ severity: Some(lsp::DiagnosticSeverity::ERROR),
+ message: "unused variable 'b'".to_string(),
+ ..Default::default()
+ }],
+ },
+ &[],
+ cx,
+ )
+ .unwrap();
+ project
+ .update_diagnostics(
+ server_id,
lsp::PublishDiagnosticsParams {
uri: Url::from_file_path("/root/other.rs").unwrap(),
version: None,
@@ -868,11 +898,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.unwrap();
});
- let buffer = project
- .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+ let main_ignored_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((main_worktree_id, "b.rs"), cx)
+ })
.await
.unwrap();
- buffer.update(cx, |buffer, _| {
+ main_ignored_buffer.update(cx, |buffer, _| {
+ let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+ assert_eq!(
+ chunks
+ .iter()
+ .map(|(s, d)| (s.as_str(), *d))
+ .collect::<Vec<_>>(),
+ &[
+ ("let ", None),
+ ("b", Some(DiagnosticSeverity::ERROR)),
+ (" = 2;", None),
+ ],
+ "Gigitnored buffers should still get in-buffer diagnostics",
+ );
+ });
+ let other_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((other_worktree_id, ""), cx)
+ })
+ .await
+ .unwrap();
+ other_buffer.update(cx, |buffer, _| {
let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
assert_eq!(
chunks
@@ -883,13 +936,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
("let b = ", None),
("c", Some(DiagnosticSeverity::ERROR)),
(";", None),
- ]
+ ],
+ "Buffers from hidden projects should still get in-buffer diagnostics"
);
});
project.update(cx, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).next(), None);
- assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+ assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+ assert_eq!(
+ project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+ vec![(
+ ProjectPath {
+ worktree_id: main_worktree_id,
+ path: Arc::from(Path::new("b.rs")),
+ },
+ server_id,
+ DiagnosticSummary {
+ error_count: 1,
+ warning_count: 0,
+ }
+ )]
+ );
+ assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+ assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
});
}
@@ -1162,7 +1231,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.update(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 1,
warning_count: 0,
@@ -1188,7 +1257,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.update(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 0,
warning_count: 0,
@@ -1777,7 +1846,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
.unwrap();
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 2,
warning_count: 0,
@@ -1627,9 +1627,21 @@ impl View for ProjectPanel {
}
}
- fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+ fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
Self::reset_to_default_keymap_context(keymap);
keymap.add_identifier("menu");
+
+ if let Some(window) = cx.active_window() {
+ window.read_with(cx, |cx| {
+ let identifier = if self.filename_editor.is_focused(cx) {
+ "editing"
+ } else {
+ "not_editing"
+ };
+
+ keymap.add_identifier(identifier);
+ });
+ }
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
use gpui::{
actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
- InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
- PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
- ViewContext, VisualContext as _, WeakView, WindowContext,
+ InteractiveElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels,
+ Point, PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle,
+ View, ViewContext, VisualContext as _, WeakView, WindowContext,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{
@@ -29,8 +29,7 @@ use std::{
path::Path,
sync::Arc,
};
-use theme::ActiveTheme as _;
-use ui::{v_stack, ContextMenu, IconElement, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -1421,6 +1420,22 @@ impl ProjectPanel {
// );
// })
}
+
+ fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
+ let mut dispatch_context = KeyContext::default();
+ dispatch_context.add("ProjectPanel");
+ dispatch_context.add("menu");
+
+ let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) {
+ "editing"
+ } else {
+ "not_editing"
+ };
+
+ dispatch_context.add(identifier);
+
+ dispatch_context
+ }
}
impl Render for ProjectPanel {
@@ -1434,7 +1449,7 @@ impl Render for ProjectPanel {
.id("project-panel")
.size_full()
.relative()
- .key_context("ProjectPanel")
+ .key_context(self.dispatch_context(cx))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::expand_selected_entry))
@@ -1480,7 +1495,7 @@ impl Render for ProjectPanel {
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
overlay()
.position(*position)
- .anchor(gpui::AnchorCorner::BottomLeft)
+ .anchor(gpui::AnchorCorner::TopLeft)
.child(menu.clone())
}))
} else {
@@ -2845,7 +2860,7 @@ mod tests {
let worktree = worktree.read(cx);
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
- panel.selection = Some(Selection {
+ panel.selection = Some(crate::Selection {
worktree_id: worktree.id(),
entry_id,
});
@@ -9,4 +9,4 @@ pub use notification::*;
pub use peer::*;
mod macros;
-pub const PROTOCOL_VERSION: u32 = 64;
+pub const PROTOCOL_VERSION: u32 = 66;
@@ -18,7 +18,7 @@ use project::search::SearchQuery;
use serde::Deserialize;
use std::{any::Any, sync::Arc};
-use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement};
+use ui::{h_stack, Icon, IconButton, IconElement};
use util::ResultExt;
use workspace::{
item::ItemHandle,
@@ -214,10 +214,11 @@ impl Render for BufferSearchBar {
.child(
h_stack()
.flex_none()
- .child(ButtonGroup::new(vec![
- search_button_for_mode(SearchMode::Text),
- search_button_for_mode(SearchMode::Regex),
- ]))
+ .child(
+ h_stack()
+ .child(search_button_for_mode(SearchMode::Text))
+ .child(search_button_for_mode(SearchMode::Regex)),
+ )
.when(supported_options.replacement, |this| {
this.child(super::toggle_replace_button(self.replace_enabled))
}),
@@ -586,8 +587,7 @@ impl BufferSearchBar {
// let style = theme.search.action_button.clone();
- IconButton::new(0, ui::Icon::SelectAll)
- .on_click(|_, cx| cx.dispatch_action(Box::new(SelectAllMatches)))
+ IconButton::new(0, ui::Icon::SelectAll).action(Box::new(SelectAllMatches))
}
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
@@ -3,7 +3,8 @@ pub use buffer_search::BufferSearchBar;
use gpui::{actions, Action, AppContext, IntoElement};
pub use mode::SearchMode;
use project::search::SearchQuery;
-use ui::ButtonVariant;
+use ui::prelude::*;
+use ui::{ButtonStyle, Icon, IconButton};
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
// use theme::components::{
// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
@@ -83,35 +84,35 @@ impl SearchOptions {
}
pub fn as_button(&self, active: bool) -> impl IntoElement {
- ui::IconButton::new(0, self.icon())
+ IconButton::new(0, self.icon())
.on_click({
let action = self.to_toggle_action();
move |_, cx| {
cx.dispatch_action(action.boxed_clone());
}
})
- .variant(ui::ButtonVariant::Ghost)
- .when(active, |button| button.variant(ButtonVariant::Filled))
+ .style(ButtonStyle::Subtle)
+ .when(active, |button| button.style(ButtonStyle::Filled))
}
}
fn toggle_replace_button(active: bool) -> impl IntoElement {
// todo: add toggle_replace button
- ui::IconButton::new(0, ui::Icon::Replace)
+ IconButton::new(0, Icon::Replace)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(ToggleReplace));
cx.notify();
})
- .variant(ui::ButtonVariant::Ghost)
- .when(active, |button| button.variant(ButtonVariant::Filled))
+ .style(ButtonStyle::Subtle)
+ .when(active, |button| button.style(ButtonStyle::Filled))
}
fn render_replace_button(
action: impl Action + 'static + Send + Sync,
- icon: ui::Icon,
+ icon: Icon,
) -> impl IntoElement {
// todo: add tooltip
- ui::IconButton::new(0, icon).on_click(move |_, cx| {
+ IconButton::new(0, icon).on_click(move |_, cx| {
cx.dispatch_action(action.boxed_clone());
})
}
@@ -1,5 +1,6 @@
use gpui::{ClickEvent, IntoElement, WindowContext};
-use ui::{Button, ButtonVariant, IconButton};
+use ui::prelude::*;
+use ui::{Button, IconButton};
use crate::mode::SearchMode;
@@ -23,13 +24,7 @@ pub(crate) fn render_search_mode_button(
is_active: bool,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> Button {
- let button_variant = if is_active {
- ButtonVariant::Filled
- } else {
- ButtonVariant::Ghost
- };
-
- Button::new(mode.label())
+ Button::new(mode.label(), mode.label())
+ .selected(is_active)
.on_click(on_click)
- .variant(button_variant)
}
@@ -1,3 +1,4 @@
+mod auto_height_editor;
mod focus;
mod kitchen_sink;
mod picker;
@@ -5,6 +6,7 @@ mod scroll;
mod text;
mod z_index;
+pub use auto_height_editor::*;
pub use focus::*;
pub use kitchen_sink::*;
pub use picker::*;
@@ -0,0 +1,34 @@
+use editor::Editor;
+use gpui::{
+ div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+ WindowContext,
+};
+
+pub struct AutoHeightEditorStory {
+ editor: View<Editor>,
+}
+
+impl AutoHeightEditorStory {
+ pub fn new(cx: &mut WindowContext) -> View<Self> {
+ cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+ cx.build_view(|cx| Self {
+ editor: cx.build_view(|cx| {
+ let mut editor = Editor::auto_height(3, cx);
+ editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+ editor
+ }),
+ })
+ }
+}
+
+impl Render for AutoHeightEditorStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ .size_full()
+ .bg(white())
+ .text_sm()
+ .child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
+ }
+}
@@ -1,6 +1,6 @@
use gpui::{
- blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText,
- TextRun, View, VisualContext, WindowContext,
+ blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
+ Styled, StyledText, View, VisualContext, WindowContext,
};
use ui::v_stack;
@@ -59,13 +59,11 @@ impl Render for TextStory {
))).child(
InteractiveText::new(
"interactive",
- StyledText::new("Hello world, how is it going?").with_runs(vec![
- cx.text_style().to_run(6),
- TextRun {
+ StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
+ (6..11, HighlightStyle {
background_color: Some(green()),
- ..cx.text_style().to_run(5)
- },
- cx.text_style().to_run(18),
+ ..Default::default()
+ }),
]),
)
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
@@ -12,16 +12,19 @@ use ui::prelude::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
#[strum(serialize_all = "snake_case")]
pub enum ComponentStory {
+ AutoHeightEditor,
Avatar,
Button,
Checkbox,
ContextMenu,
+ Disclosure,
Focus,
Icon,
IconButton,
Keybinding,
Label,
List,
+ ListHeader,
ListItem,
Scroll,
Text,
@@ -32,16 +35,19 @@ pub enum ComponentStory {
impl ComponentStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
+ Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
+ Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
Self::Focus => FocusStory::view(cx).into(),
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
Self::List => cx.build_view(|_| ui::ListStory).into(),
+ Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
Self::Scroll => ScrollStory::view(cx).into(),
Self::Text => TextStory::view(cx).into(),
@@ -55,9 +55,8 @@ impl Render for PlayerStory {
.border_2()
.border_color(player.cursor)
.child(
- img()
+ img("https://avatars.githubusercontent.com/u/1714999?v=4")
.rounded_full()
- .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_6()
.bg(gpui::red()),
)
@@ -67,51 +66,62 @@ impl Render for PlayerStory {
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
- .my_1()
- .rounded_xl()
- .flex()
- .items_center()
- .h_8()
- .py_0p5()
- .px_1p5()
- .bg(player.background)
- .child(
- div().relative().neg_mx_1().rounded_full().z_index(3)
+ .my_1()
+ .rounded_xl()
+ .flex()
+ .items_center()
+ .h_8()
+ .py_0p5()
+ .px_1p5()
+ .bg(player.background)
+ .child(
+ div()
+ .relative()
+ .neg_mx_1()
+ .rounded_full()
+ .z_index(3)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
- img()
- .rounded_full()
- .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
- .size(px(24.))
- .bg(gpui::red()),
- ),
- ).child(
- div().relative().neg_mx_1().rounded_full().z_index(2)
- .border_2()
- .border_color(player.background)
- .size(px(28.))
- .child(
- img()
+ img("https://avatars.githubusercontent.com/u/1714999?v=4")
+ .rounded_full()
+ .size(px(24.))
+ .bg(gpui::red()),
+ ),
+ )
+ .child(
+ div()
+ .relative()
+ .neg_mx_1()
.rounded_full()
- .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
- .size(px(24.))
- .bg(gpui::red()),
- ),
- ).child(
- div().relative().neg_mx_1().rounded_full().z_index(1)
- .border_2()
- .border_color(player.background)
- .size(px(28.))
+ .z_index(2)
+ .border_2()
+ .border_color(player.background)
+ .size(px(28.))
+ .child(
+ img("https://avatars.githubusercontent.com/u/1714999?v=4")
+ .rounded_full()
+ .size(px(24.))
+ .bg(gpui::red()),
+ ),
+ )
.child(
- img()
- .rounded_full()
- .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
- .size(px(24.))
- .bg(gpui::red()),
- ),
- )
+ div()
+ .relative()
+ .neg_mx_1()
+ .rounded_full()
+ .z_index(1)
+ .border_2()
+ .border_color(player.background)
+ .size(px(28.))
+ .child(
+ img("https://avatars.githubusercontent.com/u/1714999?v=4")
+ .rounded_full()
+ .size(px(24.))
+ .bg(gpui::red()),
+ ),
+ )
}),
))
.child(Story::label("Player Selections"))
@@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
- actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
- SharedString, View, ViewContext, VisualContext, WeakView,
+ actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
+ ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use settings::{update_settings_file, SettingsStore};
use std::sync::Arc;
-use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
-use ui::ListItem;
+use theme::{Theme, ThemeRegistry, ThemeSettings};
+use ui::{prelude::*, ListItem};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace};
@@ -1,19 +1,17 @@
mod avatar;
mod button;
-mod button2;
mod checkbox;
mod context_menu;
mod disclosure;
mod divider;
mod icon;
-mod icon_button;
mod keybinding;
mod label;
mod list;
mod popover;
-mod slot;
+mod popover_menu;
+mod right_click_menu;
mod stack;
-mod toggle;
mod tooltip;
#[cfg(feature = "stories")]
@@ -21,20 +19,18 @@ mod stories;
pub use avatar::*;
pub use button::*;
-pub use button2::*;
pub use checkbox::*;
pub use context_menu::*;
pub use disclosure::*;
pub use divider::*;
pub use icon::*;
-pub use icon_button::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
pub use popover::*;
-pub use slot::*;
+pub use popover_menu::*;
+pub use right_click_menu::*;
pub use stack::*;
-pub use toggle::*;
pub use tooltip::*;
#[cfg(feature = "stories")]
@@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::prelude::*;
-use gpui::{img, ImageData, ImageSource, Img, IntoElement};
+use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
@@ -13,14 +13,15 @@ pub enum Shape {
#[derive(IntoElement)]
pub struct Avatar {
src: ImageSource,
+ is_available: Option<bool>,
shape: Shape,
}
impl RenderOnce for Avatar {
- type Rendered = Img;
+ type Rendered = Div;
- fn render(self, _: &mut WindowContext) -> Self::Rendered {
- let mut img = img();
+ fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+ let mut img = img(self.src);
if self.shape == Shape::Circle {
img = img.rounded_full();
@@ -28,10 +29,28 @@ impl RenderOnce for Avatar {
img = img.rounded_md();
}
- img.source(self.src.clone())
- .size_4()
- // todo!(Pull the avatar fallback background from the theme.)
- .bg(gpui::red())
+ let size = rems(1.0);
+
+ div()
+ .size(size)
+ .child(
+ img.size(size)
+ // todo!(Pull the avatar fallback background from the theme.)
+ .bg(gpui::red()),
+ )
+ .children(self.is_available.map(|is_free| {
+ // HACK: non-integer sizes result in oval indicators.
+ let indicator_size = (size.0 * cx.rem_size() * 0.4).round();
+
+ div()
+ .absolute()
+ .z_index(1)
+ .bg(if is_free { gpui::green() } else { gpui::red() })
+ .size(indicator_size)
+ .rounded(indicator_size)
+ .bottom_0()
+ .right_0()
+ }))
}
}
@@ -40,12 +59,14 @@ impl Avatar {
Self {
src: src.into().into(),
shape: Shape::Circle,
+ is_available: None,
}
}
pub fn data(src: Arc<ImageData>) -> Self {
Self {
src: src.into(),
shape: Shape::Circle,
+ is_available: None,
}
}
@@ -53,10 +74,15 @@ impl Avatar {
Self {
src,
shape: Shape::Circle,
+ is_available: None,
}
}
pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
}
+ pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
+ self.is_available = is_available.into();
+ self
+ }
}
@@ -1,228 +1,8 @@
-use gpui::{
- ClickEvent, DefiniteLength, Div, Hsla, IntoElement, StatefulInteractiveElement, WindowContext,
-};
-use std::rc::Rc;
-
-use crate::prelude::*;
-use crate::{h_stack, Color, Icon, IconButton, IconElement, Label, LineHeightStyle};
-
-/// Provides the flexibility to use either a standard
-/// button or an icon button in a given context.
-pub enum ButtonOrIconButton {
- Button(Button),
- IconButton(IconButton),
-}
-
-impl From<Button> for ButtonOrIconButton {
- fn from(value: Button) -> Self {
- Self::Button(value)
- }
-}
-
-impl From<IconButton> for ButtonOrIconButton {
- fn from(value: IconButton) -> Self {
- Self::IconButton(value)
- }
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum IconPosition {
- #[default]
- Left,
- Right,
-}
-
-#[derive(Default, Copy, Clone, PartialEq)]
-pub enum ButtonVariant {
- #[default]
- Ghost,
- Filled,
-}
-
-impl ButtonVariant {
- pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
- match self {
- ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
- ButtonVariant::Filled => cx.theme().colors().element_background,
- }
- }
-
- pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla {
- match self {
- ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover,
- ButtonVariant::Filled => cx.theme().colors().element_hover,
- }
- }
-
- pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla {
- match self {
- ButtonVariant::Ghost => cx.theme().colors().ghost_element_active,
- ButtonVariant::Filled => cx.theme().colors().element_active,
- }
- }
-}
-
-#[derive(IntoElement)]
-pub struct Button {
- disabled: bool,
- click_handler: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
- icon: Option<Icon>,
- icon_position: Option<IconPosition>,
- label: SharedString,
- variant: ButtonVariant,
- width: Option<DefiniteLength>,
- color: Option<Color>,
-}
-
-impl RenderOnce for Button {
- type Rendered = gpui::Stateful<Div>;
-
- fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let (icon_color, label_color) = match (self.disabled, self.color) {
- (true, _) => (Color::Disabled, Color::Disabled),
- (_, None) => (Color::Default, Color::Default),
- (_, Some(color)) => (Color::from(color), color),
- };
-
- let mut button = h_stack()
- .id(SharedString::from(format!("{}", self.label)))
- .relative()
- .p_1()
- .text_ui()
- .rounded_md()
- .bg(self.variant.bg_color(cx))
- .cursor_pointer()
- .hover(|style| style.bg(self.variant.bg_color_hover(cx)))
- .active(|style| style.bg(self.variant.bg_color_active(cx)));
-
- match (self.icon, self.icon_position) {
- (Some(_), Some(IconPosition::Left)) => {
- button = button
- .gap_1()
- .child(self.render_label(label_color))
- .children(self.render_icon(icon_color))
- }
- (Some(_), Some(IconPosition::Right)) => {
- button = button
- .gap_1()
- .children(self.render_icon(icon_color))
- .child(self.render_label(label_color))
- }
- (_, _) => button = button.child(self.render_label(label_color)),
- }
-
- if let Some(width) = self.width {
- button = button.w(width).justify_center();
- }
-
- if let Some(click_handler) = self.click_handler.clone() {
- button = button.on_click(move |event, cx| {
- click_handler(event, cx);
- });
- }
-
- button
- }
-}
-
-impl Button {
- pub fn new(label: impl Into<SharedString>) -> Self {
- Self {
- disabled: false,
- click_handler: None,
- icon: None,
- icon_position: None,
- label: label.into(),
- variant: Default::default(),
- width: Default::default(),
- color: None,
- }
- }
-
- pub fn ghost(label: impl Into<SharedString>) -> Self {
- Self::new(label).variant(ButtonVariant::Ghost)
- }
-
- pub fn variant(mut self, variant: ButtonVariant) -> Self {
- self.variant = variant;
- self
- }
-
- pub fn icon(mut self, icon: Icon) -> Self {
- self.icon = Some(icon);
- self
- }
-
- pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
- if self.icon.is_none() {
- panic!("An icon must be present if an icon_position is provided.");
- }
- self.icon_position = Some(icon_position);
- self
- }
-
- pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
- self.width = width;
- self
- }
-
- pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
- self.click_handler = Some(Rc::new(handler));
- self
- }
-
- pub fn disabled(mut self, disabled: bool) -> Self {
- self.disabled = disabled;
- self
- }
-
- pub fn color(mut self, color: Option<Color>) -> Self {
- self.color = color;
- self
- }
-
- pub fn label_color(&self, color: Option<Color>) -> Color {
- if self.disabled {
- Color::Disabled
- } else if let Some(color) = color {
- color
- } else {
- Default::default()
- }
- }
-
- fn render_label(&self, color: Color) -> Label {
- Label::new(self.label.clone())
- .color(color)
- .line_height_style(LineHeightStyle::UILabel)
- }
-
- fn render_icon(&self, icon_color: Color) -> Option<IconElement> {
- self.icon.map(|i| IconElement::new(i).color(icon_color))
- }
-}
-
-#[derive(IntoElement)]
-pub struct ButtonGroup {
- buttons: Vec<Button>,
-}
-
-impl RenderOnce for ButtonGroup {
- type Rendered = Div;
-
- fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let mut group = h_stack();
-
- for button in self.buttons.into_iter() {
- group = group.child(button.render(cx));
- }
-
- group
- }
-}
-
-impl ButtonGroup {
- pub fn new(buttons: Vec<Button>) -> Self {
- Self { buttons }
- }
-}
+mod button;
+pub(self) mod button_icon;
+mod button_like;
+mod icon_button;
+
+pub use button::*;
+pub use button_like::*;
+pub use icon_button::*;
@@ -0,0 +1,147 @@
+use gpui::AnyView;
+
+use crate::prelude::*;
+use crate::{
+ ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
+};
+
+use super::button_icon::ButtonIcon;
+
+#[derive(IntoElement)]
+pub struct Button {
+ base: ButtonLike,
+ label: SharedString,
+ label_color: Option<Color>,
+ selected_label: Option<SharedString>,
+ icon: Option<Icon>,
+ icon_size: Option<IconSize>,
+ icon_color: Option<Color>,
+ selected_icon: Option<Icon>,
+}
+
+impl Button {
+ pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
+ Self {
+ base: ButtonLike::new(id),
+ label: label.into(),
+ label_color: None,
+ selected_label: None,
+ icon: None,
+ icon_size: None,
+ icon_color: None,
+ selected_icon: None,
+ }
+ }
+
+ pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
+ self.label_color = label_color.into();
+ self
+ }
+
+ pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
+ self.selected_label = label.into().map(Into::into);
+ self
+ }
+
+ pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+ self.icon = icon.into();
+ self
+ }
+
+ pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
+ self.icon_size = icon_size.into();
+ self
+ }
+
+ pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
+ self.icon_color = icon_color.into();
+ self
+ }
+
+ pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+ self.selected_icon = icon.into();
+ self
+ }
+}
+
+impl Selectable for Button {
+ fn selected(mut self, selected: bool) -> Self {
+ self.base = self.base.selected(selected);
+ self
+ }
+}
+
+impl Disableable for Button {
+ fn disabled(mut self, disabled: bool) -> Self {
+ self.base = self.base.disabled(disabled);
+ self
+ }
+}
+
+impl Clickable for Button {
+ fn on_click(
+ mut self,
+ handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
+ ) -> Self {
+ self.base = self.base.on_click(handler);
+ self
+ }
+}
+
+impl ButtonCommon for Button {
+ fn id(&self) -> &ElementId {
+ self.base.id()
+ }
+
+ fn style(mut self, style: ButtonStyle) -> Self {
+ self.base = self.base.style(style);
+ self
+ }
+
+ fn size(mut self, size: ButtonSize) -> Self {
+ self.base = self.base.size(size);
+ self
+ }
+
+ fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+ self.base = self.base.tooltip(tooltip);
+ self
+ }
+}
+
+impl RenderOnce for Button {
+ type Rendered = ButtonLike;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ let is_disabled = self.base.disabled;
+ let is_selected = self.base.selected;
+
+ let label = self
+ .selected_label
+ .filter(|_| is_selected)
+ .unwrap_or(self.label);
+
+ let label_color = if is_disabled {
+ Color::Disabled
+ } else if is_selected {
+ Color::Selected
+ } else {
+ self.label_color.unwrap_or_default()
+ };
+
+ self.base
+ .children(self.icon.map(|icon| {
+ ButtonIcon::new(icon)
+ .disabled(is_disabled)
+ .selected(is_selected)
+ .selected_icon(self.selected_icon)
+ .size(self.icon_size)
+ .color(self.icon_color)
+ }))
+ .child(
+ Label::new(label)
+ .color(label_color)
+ .line_height_style(LineHeightStyle::UILabel),
+ )
+ }
+}
@@ -0,0 +1,84 @@
+use crate::{prelude::*, Icon, IconElement, IconSize};
+
+/// An icon that appears within a button.
+///
+/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button),
+/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
+#[derive(IntoElement)]
+pub(super) struct ButtonIcon {
+ icon: Icon,
+ size: IconSize,
+ color: Color,
+ disabled: bool,
+ selected: bool,
+ selected_icon: Option<Icon>,
+}
+
+impl ButtonIcon {
+ pub fn new(icon: Icon) -> Self {
+ Self {
+ icon,
+ size: IconSize::default(),
+ color: Color::default(),
+ disabled: false,
+ selected: false,
+ selected_icon: None,
+ }
+ }
+
+ pub fn size(mut self, size: impl Into<Option<IconSize>>) -> Self {
+ if let Some(size) = size.into() {
+ self.size = size;
+ }
+
+ self
+ }
+
+ pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
+ if let Some(color) = color.into() {
+ self.color = color;
+ }
+
+ self
+ }
+
+ pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+ self.selected_icon = icon.into();
+ self
+ }
+}
+
+impl Disableable for ButtonIcon {
+ fn disabled(mut self, disabled: bool) -> Self {
+ self.disabled = disabled;
+ self
+ }
+}
+
+impl Selectable for ButtonIcon {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
+ self
+ }
+}
+
+impl RenderOnce for ButtonIcon {
+ type Rendered = IconElement;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ let icon = self
+ .selected_icon
+ .filter(|_| self.selected)
+ .unwrap_or(self.icon);
+
+ let icon_color = if self.disabled {
+ Color::Disabled
+ } else if self.selected {
+ Color::Selected
+ } else {
+ self.color
+ };
+
+ IconElement::new(icon).size(self.size).color(icon_color)
+ }
+}
@@ -0,0 +1,350 @@
+use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
+use smallvec::SmallVec;
+
+use crate::h_stack;
+use crate::prelude::*;
+
+pub trait ButtonCommon: Clickable + Disableable {
+ /// A unique element ID to identify the button.
+ fn id(&self) -> &ElementId;
+
+ /// The visual style of the button.
+ ///
+ /// Mosty commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
+ /// for an emphasized button.
+ fn style(self, style: ButtonStyle) -> Self;
+
+ /// The size of the button.
+ ///
+ /// Most buttons will use the default size.
+ ///
+ /// [`ButtonSize`] can also be used to help build non-button elements
+ /// that are consistently sized with buttons.
+ fn size(self, size: ButtonSize) -> Self;
+
+ /// The tooltip that shows when a user hovers over the button.
+ ///
+ /// Nearly all interactable elements should have a tooltip. Some example
+ /// exceptions might a scroll bar, or a slider.
+ fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
+pub enum ButtonStyle {
+ /// A filled button with a solid background color. Provides emphasis versus
+ /// the more common subtle button.
+ Filled,
+
+ /// π§ Under construction π§
+ ///
+ /// Used to emphasize a button in some way, like a selected state, or a semantic
+ /// coloring like an error or success button.
+ Tinted,
+
+ /// The default button style, used for most buttons. Has a transparent background,
+ /// but has a background color to indicate states like hover and active.
+ #[default]
+ Subtle,
+
+ /// Used for buttons that only change forground color on hover and active states.
+ ///
+ /// TODO: Better docs for this.
+ Transparent,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct ButtonLikeStyles {
+ pub background: Hsla,
+ #[allow(unused)]
+ pub border_color: Hsla,
+ #[allow(unused)]
+ pub label_color: Hsla,
+ #[allow(unused)]
+ pub icon_color: Hsla,
+}
+
+impl ButtonStyle {
+ pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+ match self {
+ ButtonStyle::Filled => ButtonLikeStyles {
+ background: cx.theme().colors().element_background,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Tinted => ButtonLikeStyles {
+ background: gpui::red(),
+ border_color: gpui::red(),
+ label_color: gpui::red(),
+ icon_color: gpui::red(),
+ },
+ ButtonStyle::Subtle => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_background,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ }
+ }
+
+ pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+ match self {
+ ButtonStyle::Filled => ButtonLikeStyles {
+ background: cx.theme().colors().element_hover,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Tinted => ButtonLikeStyles {
+ background: gpui::red(),
+ border_color: gpui::red(),
+ label_color: gpui::red(),
+ icon_color: gpui::red(),
+ },
+ ButtonStyle::Subtle => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_hover,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
+ // TODO: These are not great
+ label_color: Color::Muted.color(cx),
+ // TODO: These are not great
+ icon_color: Color::Muted.color(cx),
+ },
+ }
+ }
+
+ pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+ match self {
+ ButtonStyle::Filled => ButtonLikeStyles {
+ background: cx.theme().colors().element_active,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Tinted => ButtonLikeStyles {
+ background: gpui::red(),
+ border_color: gpui::red(),
+ label_color: gpui::red(),
+ icon_color: gpui::red(),
+ },
+ ButtonStyle::Subtle => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_active,
+ border_color: transparent_black(),
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
+ // TODO: These are not great
+ label_color: Color::Muted.color(cx),
+ // TODO: These are not great
+ icon_color: Color::Muted.color(cx),
+ },
+ }
+ }
+
+ #[allow(unused)]
+ pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+ match self {
+ ButtonStyle::Filled => ButtonLikeStyles {
+ background: cx.theme().colors().element_background,
+ border_color: cx.theme().colors().border_focused,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Tinted => ButtonLikeStyles {
+ background: gpui::red(),
+ border_color: gpui::red(),
+ label_color: gpui::red(),
+ icon_color: gpui::red(),
+ },
+ ButtonStyle::Subtle => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_background,
+ border_color: cx.theme().colors().border_focused,
+ label_color: Color::Default.color(cx),
+ icon_color: Color::Default.color(cx),
+ },
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: cx.theme().colors().border_focused,
+ label_color: Color::Accent.color(cx),
+ icon_color: Color::Accent.color(cx),
+ },
+ }
+ }
+
+ pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+ match self {
+ ButtonStyle::Filled => ButtonLikeStyles {
+ background: cx.theme().colors().element_disabled,
+ border_color: cx.theme().colors().border_disabled,
+ label_color: Color::Disabled.color(cx),
+ icon_color: Color::Disabled.color(cx),
+ },
+ ButtonStyle::Tinted => ButtonLikeStyles {
+ background: gpui::red(),
+ border_color: gpui::red(),
+ label_color: gpui::red(),
+ icon_color: gpui::red(),
+ },
+ ButtonStyle::Subtle => ButtonLikeStyles {
+ background: cx.theme().colors().ghost_element_disabled,
+ border_color: cx.theme().colors().border_disabled,
+ label_color: Color::Disabled.color(cx),
+ icon_color: Color::Disabled.color(cx),
+ },
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
+ label_color: Color::Disabled.color(cx),
+ icon_color: Color::Disabled.color(cx),
+ },
+ }
+ }
+}
+
+/// ButtonSize can also be used to help build non-button elements
+/// that are consistently sized with buttons.
+#[derive(Default, PartialEq, Clone, Copy)]
+pub enum ButtonSize {
+ #[default]
+ Default,
+ Compact,
+ None,
+}
+
+impl ButtonSize {
+ fn height(self) -> Rems {
+ match self {
+ ButtonSize::Default => rems(22. / 16.),
+ ButtonSize::Compact => rems(18. / 16.),
+ ButtonSize::None => rems(16. / 16.),
+ }
+ }
+}
+
+/// A button-like element that can be used to create a custom button when
+/// prebuilt buttons are not sufficient. Use this sparingly, as it is
+/// unconstrained and may make the UI feel less consistent.
+///
+/// This is also used to build the prebuilt buttons.
+#[derive(IntoElement)]
+pub struct ButtonLike {
+ id: ElementId,
+ pub(super) style: ButtonStyle,
+ pub(super) disabled: bool,
+ pub(super) selected: bool,
+ size: ButtonSize,
+ tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
+ on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl ButtonLike {
+ pub fn new(id: impl Into<ElementId>) -> Self {
+ Self {
+ id: id.into(),
+ style: ButtonStyle::default(),
+ disabled: false,
+ selected: false,
+ size: ButtonSize::Default,
+ tooltip: None,
+ children: SmallVec::new(),
+ on_click: None,
+ }
+ }
+}
+
+impl Disableable for ButtonLike {
+ fn disabled(mut self, disabled: bool) -> Self {
+ self.disabled = disabled;
+ self
+ }
+}
+
+impl Selectable for ButtonLike {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
+ self
+ }
+}
+
+impl Clickable for ButtonLike {
+ fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
+ self.on_click = Some(Box::new(handler));
+ self
+ }
+}
+
+impl ButtonCommon for ButtonLike {
+ fn id(&self) -> &ElementId {
+ &self.id
+ }
+
+ fn style(mut self, style: ButtonStyle) -> Self {
+ self.style = style;
+ self
+ }
+
+ fn size(mut self, size: ButtonSize) -> Self {
+ self.size = size;
+ self
+ }
+
+ fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+ self.tooltip = Some(Box::new(tooltip));
+ self
+ }
+}
+
+impl ParentElement for ButtonLike {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+ &mut self.children
+ }
+}
+
+impl RenderOnce for ButtonLike {
+ type Rendered = Stateful<Div>;
+
+ fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+ h_stack()
+ .id(self.id.clone())
+ .h(self.size.height())
+ .rounded_md()
+ .when(!self.disabled, |el| el.cursor_pointer())
+ .gap_1()
+ .px_1()
+ .bg(self.style.enabled(cx).background)
+ .hover(|hover| hover.bg(self.style.hovered(cx).background))
+ .active(|active| active.bg(self.style.active(cx).background))
+ .when_some(
+ self.on_click.filter(|_| !self.disabled),
+ |this, on_click| {
+ this.on_click(move |event, cx| {
+ cx.stop_propagation();
+ (on_click)(event, cx)
+ })
+ },
+ )
+ .when_some(self.tooltip, |this, tooltip| {
+ if !self.selected {
+ this.tooltip(move |cx| tooltip(cx))
+ } else {
+ this
+ }
+ })
+ .children(self.children)
+ }
+}
@@ -0,0 +1,109 @@
+use gpui::{Action, AnyView};
+
+use crate::prelude::*;
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
+
+use super::button_icon::ButtonIcon;
+
+#[derive(IntoElement)]
+pub struct IconButton {
+ base: ButtonLike,
+ icon: Icon,
+ icon_size: IconSize,
+ icon_color: Color,
+ selected_icon: Option<Icon>,
+}
+
+impl IconButton {
+ pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
+ Self {
+ base: ButtonLike::new(id),
+ icon,
+ icon_size: IconSize::default(),
+ icon_color: Color::Default,
+ selected_icon: None,
+ }
+ }
+
+ pub fn icon_size(mut self, icon_size: IconSize) -> Self {
+ self.icon_size = icon_size;
+ self
+ }
+
+ pub fn icon_color(mut self, icon_color: Color) -> Self {
+ self.icon_color = icon_color;
+ self
+ }
+
+ pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+ self.selected_icon = icon.into();
+ self
+ }
+
+ pub fn action(self, action: Box<dyn Action>) -> Self {
+ self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
+ }
+}
+
+impl Disableable for IconButton {
+ fn disabled(mut self, disabled: bool) -> Self {
+ self.base = self.base.disabled(disabled);
+ self
+ }
+}
+
+impl Selectable for IconButton {
+ fn selected(mut self, selected: bool) -> Self {
+ self.base = self.base.selected(selected);
+ self
+ }
+}
+
+impl Clickable for IconButton {
+ fn on_click(
+ mut self,
+ handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
+ ) -> Self {
+ self.base = self.base.on_click(handler);
+ self
+ }
+}
+
+impl ButtonCommon for IconButton {
+ fn id(&self) -> &ElementId {
+ self.base.id()
+ }
+
+ fn style(mut self, style: ButtonStyle) -> Self {
+ self.base = self.base.style(style);
+ self
+ }
+
+ fn size(mut self, size: ButtonSize) -> Self {
+ self.base = self.base.size(size);
+ self
+ }
+
+ fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+ self.base = self.base.tooltip(tooltip);
+ self
+ }
+}
+
+impl RenderOnce for IconButton {
+ type Rendered = ButtonLike;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ let is_disabled = self.base.disabled;
+ let is_selected = self.base.selected;
+
+ self.base.child(
+ ButtonIcon::new(self.icon)
+ .disabled(is_disabled)
+ .selected(is_selected)
+ .selected_icon(self.selected_icon)
+ .size(self.icon_size)
+ .color(self.icon_color),
+ )
+ }
+}
@@ -1,405 +0,0 @@
-use gpui::{
- rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
- StatefulInteractiveElement, WindowContext,
-};
-use smallvec::SmallVec;
-
-use crate::{h_stack, prelude::*};
-
-// π§ Heavily WIP π§
-
-// #[derive(Default, PartialEq, Clone, Copy)]
-// pub enum ButtonType2 {
-// #[default]
-// DefaultButton,
-// IconButton,
-// ButtonLike,
-// SplitButton,
-// ToggleButton,
-// }
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum IconPosition2 {
- #[default]
- Before,
- After,
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonStyle2 {
- #[default]
- Filled,
- // Tinted,
- Subtle,
- Transparent,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct ButtonStyle {
- pub background: Hsla,
- pub border_color: Hsla,
- pub label_color: Hsla,
- pub icon_color: Hsla,
-}
-
-impl ButtonStyle2 {
- pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
- match self {
- ButtonStyle2::Filled => ButtonStyle {
- background: cx.theme().colors().element_background,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Subtle => ButtonStyle {
- background: cx.theme().colors().ghost_element_background,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- }
- }
-
- pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
- match self {
- ButtonStyle2::Filled => ButtonStyle {
- background: cx.theme().colors().element_hover,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Subtle => ButtonStyle {
- background: cx.theme().colors().ghost_element_hover,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
- // TODO: These are not great
- label_color: Color::Muted.color(cx),
- // TODO: These are not great
- icon_color: Color::Muted.color(cx),
- },
- }
- }
-
- pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
- match self {
- ButtonStyle2::Filled => ButtonStyle {
- background: cx.theme().colors().element_active,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Subtle => ButtonStyle {
- background: cx.theme().colors().ghost_element_active,
- border_color: gpui::transparent_black(),
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
- // TODO: These are not great
- label_color: Color::Muted.color(cx),
- // TODO: These are not great
- icon_color: Color::Muted.color(cx),
- },
- }
- }
-
- pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
- match self {
- ButtonStyle2::Filled => ButtonStyle {
- background: cx.theme().colors().element_background,
- border_color: cx.theme().colors().border_focused,
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Subtle => ButtonStyle {
- background: cx.theme().colors().ghost_element_background,
- border_color: cx.theme().colors().border_focused,
- label_color: Color::Default.color(cx),
- icon_color: Color::Default.color(cx),
- },
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: cx.theme().colors().border_focused,
- label_color: Color::Accent.color(cx),
- icon_color: Color::Accent.color(cx),
- },
- }
- }
-
- pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
- match self {
- ButtonStyle2::Filled => ButtonStyle {
- background: cx.theme().colors().element_disabled,
- border_color: cx.theme().colors().border_disabled,
- label_color: Color::Disabled.color(cx),
- icon_color: Color::Disabled.color(cx),
- },
- ButtonStyle2::Subtle => ButtonStyle {
- background: cx.theme().colors().ghost_element_disabled,
- border_color: cx.theme().colors().border_disabled,
- label_color: Color::Disabled.color(cx),
- icon_color: Color::Disabled.color(cx),
- },
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
- label_color: Color::Disabled.color(cx),
- icon_color: Color::Disabled.color(cx),
- },
- }
- }
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonSize2 {
- #[default]
- Default,
- Compact,
- None,
-}
-
-impl ButtonSize2 {
- fn height(self) -> Rems {
- match self {
- ButtonSize2::Default => rems(22. / 16.),
- ButtonSize2::Compact => rems(18. / 16.),
- ButtonSize2::None => rems(16. / 16.),
- }
- }
-}
-
-// pub struct Button {
-// id: ElementId,
-// icon: Option<Icon>,
-// icon_color: Option<Color>,
-// icon_position: Option<IconPosition2>,
-// label: Option<Label>,
-// label_color: Option<Color>,
-// appearance: ButtonAppearance2,
-// state: InteractionState,
-// selected: bool,
-// disabled: bool,
-// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
-// width: Option<DefiniteLength>,
-// action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
-// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
-// /// Used to pass down some content to the button
-// /// to enable creating custom buttons.
-// children: SmallVec<[AnyElement; 2]>,
-// }
-
-pub trait ButtonCommon: Clickable + Disableable {
- fn id(&self) -> &ElementId;
- fn style(self, style: ButtonStyle2) -> Self;
- fn size(self, size: ButtonSize2) -> Self;
- fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
-}
-
-// pub struct LabelButton {
-// // Base properties...
-// id: ElementId,
-// appearance: ButtonAppearance,
-// state: InteractionState,
-// disabled: bool,
-// size: ButtonSize,
-// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
-// width: Option<DefiniteLength>,
-// // Button-specific properties...
-// label: Option<SharedString>,
-// label_color: Option<Color>,
-// icon: Option<Icon>,
-// icon_color: Option<Color>,
-// icon_position: Option<IconPosition>,
-// // Define more fields for additional properties as needed
-// }
-
-// impl ButtonCommon for LabelButton {
-// fn id(&self) -> &ElementId {
-// &self.id
-// }
-
-// fn appearance(&mut self, appearance: ButtonAppearance) -> &mut Self {
-// self.style= style;
-// self
-// }
-// // implement methods from ButtonCommon trait...
-// }
-
-// impl LabelButton {
-// pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
-// Self {
-// id: id.into(),
-// label: Some(label.into()),
-// // initialize other fields with default values...
-// }
-// }
-
-// // ... Define other builder methods specific to Button type...
-// }
-
-// TODO: Icon Button
-
-#[derive(IntoElement)]
-pub struct ButtonLike {
- id: ElementId,
- style: ButtonStyle2,
- disabled: bool,
- size: ButtonSize2,
- tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
- on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
- children: SmallVec<[AnyElement; 2]>,
-}
-
-impl ButtonLike {
- pub fn new(id: impl Into<ElementId>) -> Self {
- Self {
- id: id.into(),
- style: ButtonStyle2::default(),
- disabled: false,
- size: ButtonSize2::Default,
- tooltip: None,
- children: SmallVec::new(),
- on_click: None,
- }
- }
-}
-
-impl Disableable for ButtonLike {
- fn disabled(mut self, disabled: bool) -> Self {
- self.disabled = disabled;
- self
- }
-}
-
-impl Clickable for ButtonLike {
- fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
- self.on_click = Some(Box::new(handler));
- self
- }
-}
-
-// impl Selectable for ButtonLike {
-// fn selected(&mut self, selected: bool) -> &mut Self {
-// todo!()
-// }
-
-// fn selected_tooltip(
-// &mut self,
-// tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
-// ) -> &mut Self {
-// todo!()
-// }
-// }
-
-impl ButtonCommon for ButtonLike {
- fn id(&self) -> &ElementId {
- &self.id
- }
-
- fn style(mut self, style: ButtonStyle2) -> Self {
- self.style = style;
- self
- }
-
- fn size(mut self, size: ButtonSize2) -> Self {
- self.size = size;
- self
- }
-
- fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
- self.tooltip = Some(Box::new(tooltip));
- self
- }
-}
-
-impl RenderOnce for ButtonLike {
- type Rendered = Stateful<Div>;
-
- fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- h_stack()
- .id(self.id.clone())
- .h(self.size.height())
- .rounded_md()
- .cursor_pointer()
- .gap_1()
- .px_1()
- .bg(self.style.enabled(cx).background)
- .hover(|hover| hover.bg(self.style.hovered(cx).background))
- .active(|active| active.bg(self.style.active(cx).background))
- .when_some(
- self.on_click.filter(|_| !self.disabled),
- |this, on_click| this.on_click(move |event, cx| (on_click)(event, cx)),
- )
- .when_some(self.tooltip, |this, tooltip| {
- this.tooltip(move |cx| tooltip(cx))
- })
- .children(self.children)
- }
-}
-
-impl ParentElement for ButtonLike {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
- }
-}
-
-// pub struct ToggleButton {
-// // based on either IconButton2 or Button, with additional 'selected: bool' property
-// }
-
-// impl ButtonCommon for ToggleButton {
-// fn id(&self) -> &ElementId {
-// &self.id
-// }
-// // ... Implement other methods from ButtonCommon trait with builder patterns...
-// }
-
-// impl ToggleButton {
-// pub fn new() -> Self {
-// // Initialize with default values
-// Self {
-// // ... initialize fields, possibly with defaults or required parameters...
-// }
-// }
-
-// // ... Define other builder methods specific to ToggleButton type...
-// }
-
-// pub struct SplitButton {
-// // Base properties...
-// id: ElementId,
-// // Button-specific properties, possibly including a DefaultButton
-// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
-// // More fields as necessary...
-// }
-
-// impl ButtonCommon for SplitButton {
-// fn id(&self) -> &ElementId {
-// &self.id
-// }
-// // ... Implement other methods from ButtonCommon trait with builder patterns...
-// }
-
-// impl SplitButton {
-// pub fn new(id: impl Into<ElementId>) -> Self {
-// Self {
-// id: id.into(),
-// // ... initialize other fields with default values...
-// }
-// }
-
-// // ... Define other builder methods specific to SplitButton type...
-// }
@@ -2,12 +2,11 @@ use crate::{
h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader,
};
use gpui::{
- overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase,
- Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton,
- MouseDownEvent, Pixels, Point, Render, View, VisualContext,
+ px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
+ IntoElement, Render, View, VisualContext,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
-use std::{cell::RefCell, rc::Rc};
+use std::rc::Rc;
pub enum ContextMenuItem {
Separator,
@@ -208,174 +207,3 @@ impl Render for ContextMenu {
)
}
}
-
-pub struct MenuHandle<M: ManagedView> {
- id: ElementId,
- child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement + 'static>>,
- menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
- anchor: Option<AnchorCorner>,
- attach: Option<AnchorCorner>,
-}
-
-impl<M: ManagedView> MenuHandle<M> {
- pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View<M> + 'static) -> Self {
- self.menu_builder = Some(Rc::new(f));
- self
- }
-
- pub fn child<R: IntoElement>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
- self.child_builder = Some(Box::new(|b| f(b).into_element().into_any()));
- self
- }
-
- /// anchor defines which corner of the menu to anchor to the attachment point
- /// (by default the cursor position, but see attach)
- pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
- self.anchor = Some(anchor);
- self
- }
-
- /// attach defines which corner of the handle to attach the menu's anchor to
- pub fn attach(mut self, attach: AnchorCorner) -> Self {
- self.attach = Some(attach);
- self
- }
-}
-
-pub fn menu_handle<M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<M> {
- MenuHandle {
- id: id.into(),
- child_builder: None,
- menu_builder: None,
- anchor: None,
- attach: None,
- }
-}
-
-pub struct MenuHandleState<M> {
- menu: Rc<RefCell<Option<View<M>>>>,
- position: Rc<RefCell<Point<Pixels>>>,
- child_layout_id: Option<LayoutId>,
- child_element: Option<AnyElement>,
- menu_element: Option<AnyElement>,
-}
-
-impl<M: ManagedView> Element for MenuHandle<M> {
- type State = MenuHandleState<M>;
-
- fn layout(
- &mut self,
- element_state: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (gpui::LayoutId, Self::State) {
- let (menu, position) = if let Some(element_state) = element_state {
- (element_state.menu, element_state.position)
- } else {
- (Rc::default(), Rc::default())
- };
-
- let mut menu_layout_id = None;
-
- let menu_element = menu.borrow_mut().as_mut().map(|menu| {
- let mut overlay = overlay().snap_to_window();
- if let Some(anchor) = self.anchor {
- overlay = overlay.anchor(anchor);
- }
- overlay = overlay.position(*position.borrow());
-
- let mut element = overlay.child(menu.clone()).into_any();
- menu_layout_id = Some(element.layout(cx));
- element
- });
-
- let mut child_element = self
- .child_builder
- .take()
- .map(|child_builder| (child_builder)(menu.borrow().is_some()));
-
- let child_layout_id = child_element
- .as_mut()
- .map(|child_element| child_element.layout(cx));
-
- let layout_id = cx.request_layout(
- &gpui::Style::default(),
- menu_layout_id.into_iter().chain(child_layout_id),
- );
-
- (
- layout_id,
- MenuHandleState {
- menu,
- position,
- child_element,
- child_layout_id,
- menu_element,
- },
- )
- }
-
- fn paint(
- self,
- bounds: Bounds<gpui::Pixels>,
- element_state: &mut Self::State,
- cx: &mut WindowContext,
- ) {
- if let Some(child) = element_state.child_element.take() {
- child.paint(cx);
- }
-
- if let Some(menu) = element_state.menu_element.take() {
- menu.paint(cx);
- return;
- }
-
- let Some(builder) = self.menu_builder else {
- return;
- };
- let menu = element_state.menu.clone();
- let position = element_state.position.clone();
- let attach = self.attach.clone();
- let child_layout_id = element_state.child_layout_id.clone();
-
- cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && event.button == MouseButton::Right
- && bounds.contains_point(&event.position)
- {
- cx.stop_propagation();
- cx.prevent_default();
-
- let new_menu = (builder)(cx);
- let menu2 = menu.clone();
- cx.subscribe(&new_menu, move |_modal, _: &DismissEvent, cx| {
- *menu2.borrow_mut() = None;
- cx.notify();
- })
- .detach();
- cx.focus_view(&new_menu);
- *menu.borrow_mut() = Some(new_menu);
-
- *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
- attach
- .unwrap()
- .corner(cx.layout_bounds(child_layout_id.unwrap()))
- } else {
- cx.mouse_position()
- };
- cx.notify();
- }
- });
- }
-}
-
-impl<M: ManagedView> IntoElement for MenuHandle<M> {
- type Element = Self;
-
- fn element_id(&self) -> Option<gpui::ElementId> {
- Some(self.id.clone())
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
@@ -1,30 +1,48 @@
use std::rc::Rc;
-use gpui::{div, ClickEvent, Element, IntoElement, ParentElement, WindowContext};
+use gpui::ClickEvent;
-use crate::{Color, Icon, IconButton, IconSize, Toggle};
+use crate::prelude::*;
+use crate::{Color, Icon, IconButton, IconSize};
-pub fn disclosure_control(
- toggle: Toggle,
+#[derive(IntoElement)]
+pub struct Disclosure {
+ is_open: bool,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-) -> impl Element {
- match (toggle.is_toggleable(), toggle.is_toggled()) {
- (false, _) => div(),
- (_, true) => div().child(
- IconButton::new("toggle", Icon::ChevronDown)
- .color(Color::Muted)
- .size(IconSize::Small)
- .when_some(on_toggle, move |el, on_toggle| {
- el.on_click(move |e, cx| on_toggle(e, cx))
- }),
- ),
- (_, false) => div().child(
- IconButton::new("toggle", Icon::ChevronRight)
- .color(Color::Muted)
- .size(IconSize::Small)
- .when_some(on_toggle, move |el, on_toggle| {
- el.on_click(move |e, cx| on_toggle(e, cx))
- }),
- ),
+}
+
+impl Disclosure {
+ pub fn new(is_open: bool) -> Self {
+ Self {
+ is_open,
+ on_toggle: None,
+ }
+ }
+
+ pub fn on_toggle(
+ mut self,
+ handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
+ ) -> Self {
+ self.on_toggle = handler.into();
+ self
+ }
+}
+
+impl RenderOnce for Disclosure {
+ type Rendered = IconButton;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ IconButton::new(
+ "toggle",
+ match self.is_open {
+ true => Icon::ChevronDown,
+ false => Icon::ChevronRight,
+ },
+ )
+ .icon_color(Color::Muted)
+ .icon_size(IconSize::Small)
+ .when_some(self.on_toggle, move |this, on_toggle| {
+ this.on_click(move |event, cx| on_toggle(event, cx))
+ })
}
}
@@ -1,135 +0,0 @@
-use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
-use gpui::{prelude::*, Action, AnyView, ClickEvent, Div, Stateful};
-
-#[derive(IntoElement)]
-pub struct IconButton {
- id: ElementId,
- icon: Icon,
- color: Color,
- size: IconSize,
- variant: ButtonVariant,
- disabled: bool,
- selected: bool,
- tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
- on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-}
-
-impl RenderOnce for IconButton {
- type Rendered = Stateful<Div>;
-
- fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let icon_color = match (self.disabled, self.selected, self.color) {
- (true, _, _) => Color::Disabled,
- (false, true, _) => Color::Selected,
- _ => self.color,
- };
-
- let (mut bg_color, bg_active_color) = match self.variant {
- ButtonVariant::Filled => (
- cx.theme().colors().element_background,
- cx.theme().colors().element_active,
- ),
- ButtonVariant::Ghost => (
- cx.theme().colors().ghost_element_background,
- cx.theme().colors().ghost_element_active,
- ),
- };
-
- if self.selected {
- bg_color = cx.theme().colors().element_selected;
- }
-
- let mut button = h_stack()
- .id(self.id.clone())
- .justify_center()
- .rounded_md()
- .p_1()
- .bg(bg_color)
- .cursor_pointer()
- // Nate: Trying to figure out the right places we want to show a
- // hover state here. I think it is a bit heavy to have it on every
- // place we use an icon button.
- // .hover(|style| style.bg(bg_hover_color))
- .active(|style| style.bg(bg_active_color))
- .child(
- IconElement::new(self.icon)
- .size(self.size)
- .color(icon_color),
- );
-
- if let Some(click_handler) = self.on_click {
- button = button.on_click(move |event, cx| {
- cx.stop_propagation();
- click_handler(event, cx);
- })
- }
-
- if let Some(tooltip) = self.tooltip {
- if !self.selected {
- button = button.tooltip(move |cx| tooltip(cx))
- }
- }
-
- button
- }
-}
-
-impl IconButton {
- pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
- Self {
- id: id.into(),
- icon,
- color: Color::default(),
- size: Default::default(),
- variant: ButtonVariant::default(),
- selected: false,
- disabled: false,
- tooltip: None,
- on_click: None,
- }
- }
-
- pub fn icon(mut self, icon: Icon) -> Self {
- self.icon = icon;
- self
- }
-
- pub fn color(mut self, color: Color) -> Self {
- self.color = color;
- self
- }
-
- pub fn size(mut self, size: IconSize) -> Self {
- self.size = size;
- self
- }
-
- pub fn variant(mut self, variant: ButtonVariant) -> Self {
- self.variant = variant;
- self
- }
-
- pub fn selected(mut self, selected: bool) -> Self {
- self.selected = selected;
- self
- }
-
- pub fn disabled(mut self, disabled: bool) -> Self {
- self.disabled = disabled;
- self
- }
-
- pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
- self.tooltip = Some(Box::new(tooltip));
- self
- }
-
- pub fn on_click(mut self, handler: impl 'static + Fn(&ClickEvent, &mut WindowContext)) -> Self {
- self.on_click = Some(Box::new(handler));
- self
- }
-
- pub fn action(self, action: Box<dyn Action>) -> Self {
- self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
- }
-}
@@ -1,6 +1,8 @@
+use std::ops::Range;
+
use crate::prelude::*;
use crate::styled_ext::StyledExt;
-use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
+use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize {
@@ -99,38 +101,32 @@ impl RenderOnce for HighlightedLabel {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let highlight_color = cx.theme().colors().text_accent;
- let mut text_style = cx.text_style().clone();
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
-
- let mut runs: Vec<TextRun> = Vec::new();
-
- for (char_ix, char) in self.label.char_indices() {
- let mut color = self.color.color(cx);
-
- if let Some(highlight_ix) = highlight_indices.peek() {
- if char_ix == *highlight_ix {
- color = highlight_color;
- highlight_indices.next();
+ let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
+
+ while let Some(start_ix) = highlight_indices.next() {
+ let mut end_ix = start_ix;
+
+ loop {
+ end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
+ if let Some(&next_ix) = highlight_indices.peek() {
+ if next_ix == end_ix {
+ end_ix = next_ix;
+ highlight_indices.next();
+ continue;
+ }
}
+ break;
}
- let last_run = runs.last_mut();
- let start_new_run = if let Some(last_run) = last_run {
- if color == last_run.color {
- last_run.len += char.len_utf8();
- false
- } else {
- true
- }
- } else {
- true
- };
-
- if start_new_run {
- text_style.color = color;
- runs.push(text_style.to_run(char.len_utf8()))
- }
+ highlights.push((
+ start_ix..end_ix,
+ HighlightStyle {
+ color: Some(highlight_color),
+ ..Default::default()
+ },
+ ));
}
div()
@@ -150,7 +146,7 @@ impl RenderOnce for HighlightedLabel {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
- .child(StyledText::new(self.label).with_runs(runs))
+ .child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights))
}
}
@@ -1,73 +1,11 @@
+mod list;
mod list_header;
mod list_item;
mod list_separator;
mod list_sub_header;
-use gpui::{AnyElement, Div};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::{v_stack, Label, Toggle};
-
+pub use list::*;
pub use list_header::*;
pub use list_item::*;
pub use list_separator::*;
pub use list_sub_header::*;
-
-#[derive(IntoElement)]
-pub struct List {
- /// Message to display when the list is empty
- /// Defaults to "No items"
- empty_message: SharedString,
- header: Option<ListHeader>,
- toggle: Toggle,
- children: SmallVec<[AnyElement; 2]>,
-}
-
-impl List {
- pub fn new() -> Self {
- Self {
- empty_message: "No items".into(),
- header: None,
- toggle: Toggle::NotToggleable,
- children: SmallVec::new(),
- }
- }
-
- pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
- self.empty_message = empty_message.into();
- self
- }
-
- pub fn header(mut self, header: ListHeader) -> Self {
- self.header = Some(header);
- self
- }
-
- pub fn toggle(mut self, toggle: Toggle) -> Self {
- self.toggle = toggle;
- self
- }
-}
-
-impl ParentElement for List {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
- }
-}
-
-impl RenderOnce for List {
- type Rendered = Div;
-
- fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
- v_stack()
- .w_full()
- .py_1()
- .children(self.header.map(|header| header))
- .map(|this| match (self.children.is_empty(), self.toggle) {
- (false, _) => this.children(self.children),
- (true, Toggle::Toggled(false)) => this,
- (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
- })
- }
-}
@@ -0,0 +1,60 @@
+use gpui::{AnyElement, Div};
+use smallvec::SmallVec;
+
+use crate::{prelude::*, v_stack, Label, ListHeader};
+
+#[derive(IntoElement)]
+pub struct List {
+ /// Message to display when the list is empty
+ /// Defaults to "No items"
+ empty_message: SharedString,
+ header: Option<ListHeader>,
+ toggle: Option<bool>,
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl List {
+ pub fn new() -> Self {
+ Self {
+ empty_message: "No items".into(),
+ header: None,
+ toggle: None,
+ children: SmallVec::new(),
+ }
+ }
+
+ pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
+ self.empty_message = empty_message.into();
+ self
+ }
+
+ pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
+ self.header = header.into();
+ self
+ }
+
+ pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+ self.toggle = toggle.into();
+ self
+ }
+}
+
+impl ParentElement for List {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+ &mut self.children
+ }
+}
+
+impl RenderOnce for List {
+ type Rendered = Div;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ v_stack().w_full().py_1().children(self.header).map(|this| {
+ match (self.children.is_empty(), self.toggle) {
+ (false, _) => this.children(self.children),
+ (true, Some(false)) => this,
+ (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
+ }
+ })
+ }
+}
@@ -1,23 +1,17 @@
use std::rc::Rc;
-use gpui::{ClickEvent, Div};
+use gpui::{AnyElement, ClickEvent, Div};
+use smallvec::SmallVec;
use crate::prelude::*;
-use crate::{disclosure_control, h_stack, Icon, IconButton, IconElement, IconSize, Label, Toggle};
-
-pub enum ListHeaderMeta {
- Tools(Vec<IconButton>),
- // TODO: This should be a button
- Button(Label),
- Text(Label),
-}
+use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
#[derive(IntoElement)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
- meta: Option<ListHeaderMeta>,
- toggle: Toggle,
+ meta: SmallVec<[AnyElement; 2]>,
+ toggle: Option<bool>,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
inset: bool,
selected: bool,
@@ -28,16 +22,16 @@ impl ListHeader {
Self {
label: label.into(),
left_icon: None,
- meta: None,
+ meta: SmallVec::new(),
inset: false,
- toggle: Toggle::NotToggleable,
+ toggle: None,
on_toggle: None,
selected: false,
}
}
- pub fn toggle(mut self, toggle: Toggle) -> Self {
- self.toggle = toggle;
+ pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+ self.toggle = toggle.into();
self
}
@@ -49,21 +43,19 @@ impl ListHeader {
self
}
- pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
- self.left_icon = left_icon;
+ pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
+ self.left_icon = left_icon.into();
self
}
- pub fn right_button(self, button: IconButton) -> Self {
- self.meta(Some(ListHeaderMeta::Tools(vec![button])))
- }
-
- pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
- self.meta = meta;
+ pub fn meta(mut self, meta: impl IntoElement) -> Self {
+ self.meta.push(meta.into_any_element());
self
}
+}
- pub fn selected(mut self, selected: bool) -> Self {
+impl Selectable for ListHeader {
+ fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
@@ -73,20 +65,6 @@ impl RenderOnce for ListHeader {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let disclosure_control = disclosure_control(self.toggle, self.on_toggle);
-
- let meta = match self.meta {
- Some(ListHeaderMeta::Tools(icons)) => div().child(
- h_stack()
- .gap_2()
- .items_center()
- .children(icons.into_iter().map(|i| i.color(Color::Muted))),
- ),
- Some(ListHeaderMeta::Button(label)) => div().child(label),
- Some(ListHeaderMeta::Text(label)) => div().child(label),
- None => div(),
- };
-
h_stack().w_full().relative().child(
div()
.h_5()
@@ -115,9 +93,12 @@ impl RenderOnce for ListHeader {
}))
.child(Label::new(self.label.clone()).color(Color::Muted)),
)
- .child(disclosure_control),
+ .children(
+ self.toggle
+ .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
+ ),
)
- .child(meta),
+ .child(h_stack().gap_2().items_center().children(self.meta)),
)
}
}
@@ -6,7 +6,7 @@ use gpui::{
use smallvec::SmallVec;
use crate::prelude::*;
-use crate::{disclosure_control, Avatar, GraphicSlot, Icon, IconElement, IconSize, Toggle};
+use crate::{Avatar, Disclosure, Icon, IconElement, IconSize};
#[derive(IntoElement)]
pub struct ListItem {
@@ -16,8 +16,8 @@ pub struct ListItem {
// disclosure_control_style: DisclosureControlVisibility,
indent_level: usize,
indent_step_size: Pixels,
- left_slot: Option<GraphicSlot>,
- toggle: Toggle,
+ left_slot: Option<AnyElement>,
+ toggle: Option<bool>,
inset: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
@@ -33,7 +33,7 @@ impl ListItem {
indent_level: 0,
indent_step_size: px(12.),
left_slot: None,
- toggle: Toggle::NotToggleable,
+ toggle: None,
inset: false,
on_click: None,
on_secondary_mouse_down: None,
@@ -70,8 +70,8 @@ impl ListItem {
self
}
- pub fn toggle(mut self, toggle: Toggle) -> Self {
- self.toggle = toggle;
+ pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+ self.toggle = toggle.into();
self
}
@@ -83,23 +83,30 @@ impl ListItem {
self
}
- pub fn selected(mut self, selected: bool) -> Self {
- self.selected = selected;
+ pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
+ self.left_slot = Some(left_content.into_any_element());
self
}
- pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
- self.left_slot = Some(left_content);
+ pub fn left_icon(mut self, left_icon: Icon) -> Self {
+ self.left_slot = Some(
+ IconElement::new(left_icon)
+ .size(IconSize::Small)
+ .color(Color::Muted)
+ .into_any_element(),
+ );
self
}
- pub fn left_icon(mut self, left_icon: Icon) -> Self {
- self.left_slot = Some(GraphicSlot::Icon(left_icon));
+ pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
+ self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element());
self
}
+}
- pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
- self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
+impl Selectable for ListItem {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
self
}
}
@@ -150,17 +157,11 @@ impl RenderOnce for ListItem {
.gap_1()
.items_center()
.relative()
- .child(disclosure_control(self.toggle, self.on_toggle))
- .map(|this| match self.left_slot {
- Some(GraphicSlot::Icon(i)) => this.child(
- IconElement::new(i)
- .size(IconSize::Small)
- .color(Color::Muted),
- ),
- Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
- Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
- None => this,
- })
+ .children(
+ self.toggle
+ .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
+ )
+ .children(self.left_slot)
.children(self.children),
)
}
@@ -0,0 +1,231 @@
+use std::{cell::RefCell, rc::Rc};
+
+use gpui::{
+ overlay, point, px, rems, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
+ Element, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseDownEvent,
+ ParentElement, Pixels, Point, View, VisualContext, WindowContext,
+};
+
+use crate::{Clickable, Selectable};
+
+pub trait PopoverTrigger: IntoElement + Clickable + Selectable + 'static {}
+
+impl<T: IntoElement + Clickable + Selectable + 'static> PopoverTrigger for T {}
+
+pub struct PopoverMenu<M: ManagedView> {
+ id: ElementId,
+ child_builder: Option<
+ Box<
+ dyn FnOnce(
+ Rc<RefCell<Option<View<M>>>>,
+ Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
+ ) -> AnyElement
+ + 'static,
+ >,
+ >,
+ menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
+ anchor: AnchorCorner,
+ attach: Option<AnchorCorner>,
+ offset: Option<Point<Pixels>>,
+}
+
+impl<M: ManagedView> PopoverMenu<M> {
+ pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View<M> + 'static) -> Self {
+ self.menu_builder = Some(Rc::new(f));
+ self
+ }
+
+ pub fn trigger<T: PopoverTrigger>(mut self, t: T) -> Self {
+ self.child_builder = Some(Box::new(|menu, builder| {
+ let open = menu.borrow().is_some();
+ t.selected(open)
+ .when_some(builder, |el, builder| {
+ el.on_click({
+ move |_, cx| {
+ let new_menu = (builder)(cx);
+ let menu2 = menu.clone();
+ let previous_focus_handle = cx.focused();
+
+ cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
+ if modal.focus_handle(cx).contains_focused(cx) {
+ if previous_focus_handle.is_some() {
+ cx.focus(&previous_focus_handle.as_ref().unwrap())
+ }
+ }
+ *menu2.borrow_mut() = None;
+ cx.notify();
+ })
+ .detach();
+ cx.focus_view(&new_menu);
+ *menu.borrow_mut() = Some(new_menu);
+ }
+ })
+ })
+ .into_any_element()
+ }));
+ self
+ }
+
+ /// anchor defines which corner of the menu to anchor to the attachment point
+ /// (by default the cursor position, but see attach)
+ pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+ self.anchor = anchor;
+ self
+ }
+
+ /// attach defines which corner of the handle to attach the menu's anchor to
+ pub fn attach(mut self, attach: AnchorCorner) -> Self {
+ self.attach = Some(attach);
+ self
+ }
+
+ /// offset offsets the position of the content by that many pixels.
+ pub fn offset(mut self, offset: Point<Pixels>) -> Self {
+ self.offset = Some(offset);
+ self
+ }
+
+ fn resolved_attach(&self) -> AnchorCorner {
+ self.attach.unwrap_or_else(|| match self.anchor {
+ AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
+ AnchorCorner::TopRight => AnchorCorner::BottomRight,
+ AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
+ AnchorCorner::BottomRight => AnchorCorner::TopRight,
+ })
+ }
+
+ fn resolved_offset(&self, cx: &WindowContext) -> Point<Pixels> {
+ self.offset.unwrap_or_else(|| {
+ // Default offset = 4px padding + 1px border
+ let offset = rems(5. / 16.) * cx.rem_size();
+ match self.anchor {
+ AnchorCorner::TopRight | AnchorCorner::BottomRight => point(offset, px(0.)),
+ AnchorCorner::TopLeft | AnchorCorner::BottomLeft => point(-offset, px(0.)),
+ }
+ })
+ }
+}
+
+pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
+ PopoverMenu {
+ id: id.into(),
+ child_builder: None,
+ menu_builder: None,
+ anchor: AnchorCorner::TopLeft,
+ attach: None,
+ offset: None,
+ }
+}
+
+pub struct PopoverMenuState<M> {
+ child_layout_id: Option<LayoutId>,
+ child_element: Option<AnyElement>,
+ child_bounds: Option<Bounds<Pixels>>,
+ menu_element: Option<AnyElement>,
+ menu: Rc<RefCell<Option<View<M>>>>,
+}
+
+impl<M: ManagedView> Element for PopoverMenu<M> {
+ type State = PopoverMenuState<M>;
+
+ fn layout(
+ &mut self,
+ element_state: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (gpui::LayoutId, Self::State) {
+ let mut menu_layout_id = None;
+
+ let (menu, child_bounds) = if let Some(element_state) = element_state {
+ (element_state.menu, element_state.child_bounds)
+ } else {
+ (Rc::default(), None)
+ };
+
+ let menu_element = menu.borrow_mut().as_mut().map(|menu| {
+ let mut overlay = overlay().snap_to_window().anchor(self.anchor);
+
+ if let Some(child_bounds) = child_bounds {
+ overlay = overlay.position(
+ self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
+ );
+ }
+
+ let mut element = overlay.child(menu.clone()).into_any();
+ menu_layout_id = Some(element.layout(cx));
+ element
+ });
+
+ let mut child_element = self
+ .child_builder
+ .take()
+ .map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone()));
+
+ let child_layout_id = child_element
+ .as_mut()
+ .map(|child_element| child_element.layout(cx));
+
+ let layout_id = cx.request_layout(
+ &gpui::Style::default(),
+ menu_layout_id.into_iter().chain(child_layout_id),
+ );
+
+ (
+ layout_id,
+ PopoverMenuState {
+ menu,
+ child_element,
+ child_layout_id,
+ menu_element,
+ child_bounds,
+ },
+ )
+ }
+
+ fn paint(
+ self,
+ _: Bounds<gpui::Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
+ ) {
+ if let Some(child) = element_state.child_element.take() {
+ child.paint(cx);
+ }
+
+ if let Some(child_layout_id) = element_state.child_layout_id.take() {
+ element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
+ }
+
+ if let Some(menu) = element_state.menu_element.take() {
+ menu.paint(cx);
+
+ if let Some(child_bounds) = element_state.child_bounds {
+ let interactive_bounds = InteractiveBounds {
+ bounds: child_bounds,
+ stacking_order: cx.stacking_order().clone(),
+ };
+
+ // Mouse-downing outside the menu dismisses it, so we don't
+ // want a click on the toggle to re-open it.
+ cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && interactive_bounds.visibly_contains(&e.position, cx)
+ {
+ cx.stop_propagation()
+ }
+ })
+ }
+ }
+ }
+}
+
+impl<M: ManagedView> IntoElement for PopoverMenu<M> {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<gpui::ElementId> {
+ Some(self.id.clone())
+ }
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
@@ -0,0 +1,185 @@
+use std::{cell::RefCell, rc::Rc};
+
+use gpui::{
+ overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, ElementId,
+ IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
+ View, VisualContext, WindowContext,
+};
+
+pub struct RightClickMenu<M: ManagedView> {
+ id: ElementId,
+ child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement + 'static>>,
+ menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
+ anchor: Option<AnchorCorner>,
+ attach: Option<AnchorCorner>,
+}
+
+impl<M: ManagedView> RightClickMenu<M> {
+ pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View<M> + 'static) -> Self {
+ self.menu_builder = Some(Rc::new(f));
+ self
+ }
+
+ pub fn trigger<E: IntoElement + 'static>(mut self, e: E) -> Self {
+ self.child_builder = Some(Box::new(move |_| e.into_any_element()));
+ self
+ }
+
+ /// anchor defines which corner of the menu to anchor to the attachment point
+ /// (by default the cursor position, but see attach)
+ pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+ self.anchor = Some(anchor);
+ self
+ }
+
+ /// attach defines which corner of the handle to attach the menu's anchor to
+ pub fn attach(mut self, attach: AnchorCorner) -> Self {
+ self.attach = Some(attach);
+ self
+ }
+}
+
+pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
+ RightClickMenu {
+ id: id.into(),
+ child_builder: None,
+ menu_builder: None,
+ anchor: None,
+ attach: None,
+ }
+}
+
+pub struct MenuHandleState<M> {
+ menu: Rc<RefCell<Option<View<M>>>>,
+ position: Rc<RefCell<Point<Pixels>>>,
+ child_layout_id: Option<LayoutId>,
+ child_element: Option<AnyElement>,
+ menu_element: Option<AnyElement>,
+}
+
+impl<M: ManagedView> Element for RightClickMenu<M> {
+ type State = MenuHandleState<M>;
+
+ fn layout(
+ &mut self,
+ element_state: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (gpui::LayoutId, Self::State) {
+ let (menu, position) = if let Some(element_state) = element_state {
+ (element_state.menu, element_state.position)
+ } else {
+ (Rc::default(), Rc::default())
+ };
+
+ let mut menu_layout_id = None;
+
+ let menu_element = menu.borrow_mut().as_mut().map(|menu| {
+ let mut overlay = overlay().snap_to_window();
+ if let Some(anchor) = self.anchor {
+ overlay = overlay.anchor(anchor);
+ }
+ overlay = overlay.position(*position.borrow());
+
+ let mut element = overlay.child(menu.clone()).into_any();
+ menu_layout_id = Some(element.layout(cx));
+ element
+ });
+
+ let mut child_element = self
+ .child_builder
+ .take()
+ .map(|child_builder| (child_builder)(menu.borrow().is_some()));
+
+ let child_layout_id = child_element
+ .as_mut()
+ .map(|child_element| child_element.layout(cx));
+
+ let layout_id = cx.request_layout(
+ &gpui::Style::default(),
+ menu_layout_id.into_iter().chain(child_layout_id),
+ );
+
+ (
+ layout_id,
+ MenuHandleState {
+ menu,
+ position,
+ child_element,
+ child_layout_id,
+ menu_element,
+ },
+ )
+ }
+
+ fn paint(
+ self,
+ bounds: Bounds<gpui::Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
+ ) {
+ if let Some(child) = element_state.child_element.take() {
+ child.paint(cx);
+ }
+
+ if let Some(menu) = element_state.menu_element.take() {
+ menu.paint(cx);
+ return;
+ }
+
+ let Some(builder) = self.menu_builder else {
+ return;
+ };
+ let menu = element_state.menu.clone();
+ let position = element_state.position.clone();
+ let attach = self.attach.clone();
+ let child_layout_id = element_state.child_layout_id.clone();
+
+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && event.button == MouseButton::Right
+ && bounds.contains_point(&event.position)
+ {
+ cx.stop_propagation();
+ cx.prevent_default();
+
+ let new_menu = (builder)(cx);
+ let menu2 = menu.clone();
+ let previous_focus_handle = cx.focused();
+
+ cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
+ if modal.focus_handle(cx).contains_focused(cx) {
+ if previous_focus_handle.is_some() {
+ cx.focus(&previous_focus_handle.as_ref().unwrap())
+ }
+ }
+ *menu2.borrow_mut() = None;
+ cx.notify();
+ })
+ .detach();
+ cx.focus_view(&new_menu);
+ *menu.borrow_mut() = Some(new_menu);
+
+ *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
+ attach
+ .unwrap()
+ .corner(cx.layout_bounds(child_layout_id.unwrap()))
+ } else {
+ cx.mouse_position()
+ };
+ cx.notify();
+ }
+ });
+ }
+}
+
+impl<M: ManagedView> IntoElement for RightClickMenu<M> {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<gpui::ElementId> {
+ Some(self.id.clone())
+ }
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
@@ -1,14 +0,0 @@
-use gpui::{ImageSource, SharedString};
-
-use crate::Icon;
-
-#[derive(Debug, Clone)]
-/// A slot utility that provides a way to to pass either
-/// an icon or an image to a component.
-///
-/// Can be filled with a []
-pub enum GraphicSlot {
- Icon(Icon),
- Avatar(ImageSource),
- PublicActor(SharedString),
-}
@@ -2,20 +2,24 @@ mod avatar;
mod button;
mod checkbox;
mod context_menu;
+mod disclosure;
mod icon;
mod icon_button;
mod keybinding;
mod label;
mod list;
+mod list_header;
mod list_item;
pub use avatar::*;
pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
+pub use disclosure::*;
pub use icon::*;
pub use icon_button::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
+pub use list_header::*;
pub use list_item::*;
@@ -19,5 +19,13 @@ impl Render for AvatarStory {
.child(Avatar::uri(
"https://avatars.githubusercontent.com/u/326587?v=4",
))
+ .child(
+ Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
+ .availability_indicator(true),
+ )
+ .child(
+ Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
+ .availability_indicator(false),
+ )
}
}
@@ -1,8 +1,8 @@
use gpui::{Div, Render};
use story::Story;
-use crate::prelude::*;
-use crate::{h_stack, Button, Icon, IconPosition};
+use crate::{prelude::*, Icon};
+use crate::{Button, ButtonStyle};
pub struct ButtonStory;
@@ -12,66 +12,29 @@ impl Render for ButtonStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container()
.child(Story::title_for::<Button>())
+ .child(Story::label("Default"))
+ .child(Button::new("default_filled", "Click me"))
+ .child(Story::label("Selected"))
+ .child(Button::new("selected_filled", "Click me").selected(true))
+ .child(Story::label("Selected with `selected_label`"))
.child(
- div()
- .flex()
- .gap_8()
- .child(
- div().child(Story::label("Ghost (Default)")).child(
- h_stack()
- .gap_2()
- .child(Button::new("Label").variant(ButtonVariant::Ghost)),
- ),
- )
- .child(Story::label("Ghost β Left Icon"))
- .child(
- h_stack().gap_2().child(
- Button::new("Label")
- .variant(ButtonVariant::Ghost)
- .icon(Icon::Plus)
- .icon_position(IconPosition::Left),
- ),
- ),
+ Button::new("selected_label_filled", "Click me")
+ .selected(true)
+ .selected_label("I have been selected"),
)
- .child(Story::label("Ghost β Right Icon"))
+ .child(Story::label("With `label_color`"))
+ .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
+ .child(Story::label("With `icon`"))
+ .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
+ .child(Story::label("Selected with `icon`"))
.child(
- h_stack().gap_2().child(
- Button::new("Label")
- .variant(ButtonVariant::Ghost)
- .icon(Icon::Plus)
- .icon_position(IconPosition::Right),
- ),
- )
- .child(
- div().child(Story::label("Filled")).child(
- h_stack()
- .gap_2()
- .child(Button::new("Label").variant(ButtonVariant::Filled)),
- ),
- )
- .child(Story::label("Filled β Left Button"))
- .child(
- h_stack().gap_2().child(
- Button::new("Label")
- .variant(ButtonVariant::Filled)
- .icon(Icon::Plus)
- .icon_position(IconPosition::Left),
- ),
- )
- .child(Story::label("Filled β Right Button"))
- .child(
- h_stack().gap_2().child(
- Button::new("Label")
- .variant(ButtonVariant::Filled)
- .icon(Icon::Plus)
- .icon_position(IconPosition::Right),
- ),
- )
- .child(Story::label("Button with `on_click`"))
- .child(
- Button::new("Label")
- .variant(ButtonVariant::Ghost)
- .on_click(|_, _cx| println!("Button clicked.")),
+ Button::new("filled_and_selected_with_icon", "Click me")
+ .selected(true)
+ .icon(Icon::FileGit),
)
+ .child(Story::label("Default (Subtle)"))
+ .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
+ .child(Story::label("Default (Transparent)"))
+ .child(Button::new("default_transparent", "Click me").style(ButtonStyle::Transparent))
}
}
@@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View};
use story::Story;
use crate::prelude::*;
-use crate::{menu_handle, ContextMenu, Label};
+use crate::{right_click_menu, ContextMenu, Label};
actions!(PrintCurrentDate, PrintBestFood);
@@ -45,25 +45,13 @@ impl Render for ContextMenuStory {
.flex_col()
.justify_between()
.child(
- menu_handle("test2")
- .child(|is_open| {
- Label::new(if is_open {
- "TOP LEFT"
- } else {
- "RIGHT CLICK ME"
- })
- })
+ right_click_menu("test2")
+ .trigger(Label::new("TOP LEFT"))
.menu(move |cx| build_menu(cx, "top left")),
)
.child(
- menu_handle("test1")
- .child(|is_open| {
- Label::new(if is_open {
- "BOTTOM LEFT"
- } else {
- "RIGHT CLICK ME"
- })
- })
+ right_click_menu("test1")
+ .trigger(Label::new("BOTTOM LEFT"))
.anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft)
.menu(move |cx| build_menu(cx, "bottom left")),
@@ -75,26 +63,14 @@ impl Render for ContextMenuStory {
.flex_col()
.justify_between()
.child(
- menu_handle("test3")
- .child(|is_open| {
- Label::new(if is_open {
- "TOP RIGHT"
- } else {
- "RIGHT CLICK ME"
- })
- })
+ right_click_menu("test3")
+ .trigger(Label::new("TOP RIGHT"))
.anchor(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "top right")),
)
.child(
- menu_handle("test4")
- .child(|is_open| {
- Label::new(if is_open {
- "BOTTOM RIGHT"
- } else {
- "RIGHT CLICK ME"
- })
- })
+ right_click_menu("test4")
+ .trigger(Label::new("BOTTOM RIGHT"))
.anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "bottom right")),
@@ -0,0 +1,20 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::Disclosure;
+
+pub struct DisclosureStory;
+
+impl Render for DisclosureStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ Story::container()
+ .child(Story::title_for::<Disclosure>())
+ .child(Story::label("Toggled"))
+ .child(Disclosure::new(true))
+ .child(Story::label("Not Toggled"))
+ .child(Disclosure::new(false))
+ }
+}
@@ -14,6 +14,26 @@ impl Render for IconButtonStory {
.child(Story::title_for::<IconButton>())
.child(Story::label("Default"))
.child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
+ .child(Story::label("Selected"))
+ .child(
+ div()
+ .w_8()
+ .child(IconButton::new("icon_a", Icon::Hash).selected(true)),
+ )
+ .child(Story::label("Selected with `selected_icon`"))
+ .child(
+ div().w_8().child(
+ IconButton::new("icon_a", Icon::AudioOn)
+ .selected(true)
+ .selected_icon(Icon::AudioOff),
+ ),
+ )
+ .child(Story::label("Disabled"))
+ .child(
+ div()
+ .w_8()
+ .child(IconButton::new("icon_a", Icon::Hash).disabled(true)),
+ )
.child(Story::label("With `on_click`"))
.child(
div()
@@ -22,12 +22,12 @@ impl Render for ListStory {
.child(Story::label("With sections"))
.child(
List::new()
- .child(ListHeader::new("Fruits"))
+ .header(ListHeader::new("Produce"))
+ .child(ListSubHeader::new("Fruits"))
.child(ListItem::new("apple").child("Apple"))
.child(ListItem::new("banana").child("Banana"))
.child(ListItem::new("cherry").child("Cherry"))
.child(ListSeparator)
- .child(ListHeader::new("Vegetables"))
.child(ListSubHeader::new("Root Vegetables"))
.child(ListItem::new("carrot").child("Carrot"))
.child(ListItem::new("potato").child("Potato"))
@@ -0,0 +1,33 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::{prelude::*, IconButton};
+use crate::{Icon, ListHeader};
+
+pub struct ListHeaderStory;
+
+impl Render for ListHeaderStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ Story::container()
+ .child(Story::title_for::<ListHeader>())
+ .child(Story::label("Default"))
+ .child(ListHeader::new("Section 1"))
+ .child(Story::label("With left icon"))
+ .child(ListHeader::new("Section 2").left_icon(Icon::Bell))
+ .child(Story::label("With left icon and meta"))
+ .child(
+ ListHeader::new("Section 3")
+ .left_icon(Icon::BellOff)
+ .meta(IconButton::new("action_1", Icon::Bolt)),
+ )
+ .child(Story::label("With multiple meta"))
+ .child(
+ ListHeader::new("Section 4")
+ .meta(IconButton::new("action_1", Icon::Bolt))
+ .meta(IconButton::new("action_2", Icon::ExclamationTriangle))
+ .meta(IconButton::new("action_3", Icon::Plus)),
+ )
+ }
+}
@@ -1,41 +0,0 @@
-/// Whether the entry is toggleable, and if so, whether it is currently toggled.
-///
-/// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases.
-///
-/// You can check if an element is toggleable with `.is_toggleable()`
-///
-/// Possible values:
-/// - `Toggle::NotToggleable` - The entry is not toggleable
-/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled
-/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum Toggle {
- NotToggleable,
- Toggled(bool),
-}
-
-impl Toggle {
- /// Returns true if the entry is toggled (or is not toggleable.)
- ///
- /// As element that isn't toggleable is always "expanded" or "enabled"
- /// returning true in that case makes sense.
- pub fn is_toggled(&self) -> bool {
- match self {
- Self::Toggled(false) => false,
- _ => true,
- }
- }
-
- pub fn is_toggleable(&self) -> bool {
- match self {
- Self::Toggled(_) => true,
- _ => false,
- }
- }
-}
-
-impl From<bool> for Toggle {
- fn from(toggled: bool) -> Self {
- Toggle::Toggled(toggled)
- }
-}
@@ -8,6 +8,5 @@ pub use crate::clickable::*;
pub use crate::disableable::*;
pub use crate::fixed::*;
pub use crate::selectable::*;
-pub use crate::StyledExt;
-pub use crate::{ButtonVariant, Color};
+pub use crate::{ButtonCommon, Color, StyledExt};
pub use theme::ActiveTheme;
@@ -1,15 +1,7 @@
-use gpui::{AnyView, WindowContext};
-
/// A trait for elements that can be selected.
pub trait Selectable {
/// Sets whether the element is selected.
fn selected(self, selected: bool) -> Self;
-
- /// Sets the tooltip that should be shown when the element is selected.
- fn selected_tooltip(
- self,
- tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
- ) -> Self;
}
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
@@ -1,14 +1,14 @@
use super::base_keymap_setting::BaseKeymap;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
- actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
- View, ViewContext, VisualContext, WeakView,
+ actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
+ ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use project::Fs;
use settings::{update_settings_file, Settings};
use std::sync::Arc;
-use ui::ListItem;
+use ui::{prelude::*, ListItem};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace};
@@ -10,29 +10,29 @@ doctest = false
[features]
test-support = [
- "call2/test-support",
- "client2/test-support",
- "project2/test-support",
- "settings2/test-support",
+ "call/test-support",
+ "client/test-support",
+ "project/test-support",
+ "settings/test-support",
"gpui/test-support",
- "fs2/test-support"
+ "fs/test-support"
]
[dependencies]
-db2 = { path = "../db2" }
-client2 = { path = "../client2" }
+db = { path = "../db2", package = "db2" }
+client = { path = "../client2", package = "client2" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
-fs2 = { path = "../fs2" }
+fs = { path = "../fs2", package = "fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
-install_cli2 = { path = "../install_cli2" }
-language2 = { path = "../language2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
#menu = { path = "../menu" }
node_runtime = { path = "../node_runtime" }
-project2 = { path = "../project2" }
-settings2 = { path = "../settings2" }
-terminal2 = { path = "../terminal2" }
-theme2 = { path = "../theme2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+terminal = { path = "../terminal2", package = "terminal2" }
+theme = { path = "../theme2", package = "theme2" }
util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" }
@@ -54,13 +54,13 @@ smallvec.workspace = true
uuid.workspace = true
[dev-dependencies]
-call2 = { path = "../call2", features = ["test-support"] }
-client2 = { path = "../client2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-project2 = { path = "../project2", features = ["test-support"] }
-settings2 = { path = "../settings2", features = ["test-support"] }
-fs2 = { path = "../fs2", features = ["test-support"] }
-db2 = { path = "../db2", features = ["test-support"] }
+call = { path = "../call2", package = "call2", features = ["test-support"] }
+client = { path = "../client2", package = "client2", features = ["test-support"] }
+gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+project = { path = "../project2", package = "project2", features = ["test-support"] }
+settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
+fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
+db = { path = "../db2", package = "db2", features = ["test-support"] }
indoc.workspace = true
env_logger.workspace = true
@@ -7,8 +7,8 @@ use gpui::{
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
-use ui::prelude::*;
-use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip};
+use ui::{h_stack, ContextMenu, IconButton, Tooltip};
+use ui::{prelude::*, right_click_menu};
pub enum PanelEvent {
ChangePosition,
@@ -701,13 +701,8 @@ impl Render for PanelButtons {
(action, name.into())
};
- let button = IconButton::new(name, icon)
- .selected(is_active_button)
- .action(action.boxed_clone())
- .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx));
-
Some(
- menu_handle(name)
+ right_click_menu(name)
.menu(move |cx| {
const POSITIONS: [DockPosition; 3] = [
DockPosition::Left,
@@ -731,7 +726,14 @@ impl Render for PanelButtons {
})
.anchor(menu_anchor)
.attach(menu_attach)
- .child(|is_open| button.selected(is_open)),
+ .trigger(
+ IconButton::new(name, icon)
+ .selected(is_active_button)
+ .action(action.boxed_clone())
+ .tooltip(move |cx| {
+ Tooltip::for_action(tooltip.clone(), &*action, cx)
+ }),
+ ),
)
});
@@ -7,7 +7,7 @@ use crate::{
ViewId, Workspace, WorkspaceId,
};
use anyhow::Result;
-use client2::{
+use client::{
proto::{self, PeerId},
Client,
};
@@ -16,10 +16,10 @@ use gpui::{
HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
WindowContext,
};
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -33,7 +33,7 @@ use std::{
},
time::Duration,
};
-use theme2::Theme;
+use theme::Theme;
#[derive(Deserialize)]
pub struct ItemSettings {
@@ -110,7 +110,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
fn for_each_project_item(
&self,
_: &AppContext,
- _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ _: &mut dyn FnMut(EntityId, &dyn project::Item),
) {
}
fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -222,7 +222,7 @@ pub trait ItemHandle: 'static + Send {
fn for_each_project_item(
&self,
_: &AppContext,
- _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ _: &mut dyn FnMut(EntityId, &dyn project::Item),
);
fn is_singleton(&self, cx: &AppContext) -> bool;
fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@@ -347,7 +347,7 @@ impl<T: Item> ItemHandle for View<T> {
fn for_each_project_item(
&self,
cx: &AppContext,
- f: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ f: &mut dyn FnMut(EntityId, &dyn project::Item),
) {
self.read(cx).for_each_project_item(cx, f)
}
@@ -375,6 +375,7 @@ impl<T: Item> ItemHandle for View<T> {
pane: View<Pane>,
cx: &mut ViewContext<Workspace>,
) {
+ let weak_item = self.downgrade();
let history = pane.read(cx).nav_history_for_item(self);
self.update(cx, |this, cx| {
this.set_nav_history(history, cx);
@@ -491,16 +492,15 @@ impl<T: Item> ItemHandle for View<T> {
}
}));
- // todo!()
- // cx.observe_focus(self, move |workspace, item, focused, cx| {
- // if !focused
- // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
- // {
- // Pane::autosave_item(&item, workspace.project.clone(), cx)
- // .detach_and_log_err(cx);
- // }
- // })
- // .detach();
+ cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
+ if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
+ if let Some(item) = weak_item.upgrade() {
+ Pane::autosave_item(&item, workspace.project.clone(), cx)
+ .detach_and_log_err(cx);
+ }
+ }
+ })
+ .detach();
let item_id = self.item_id();
cx.observe_release(self, move |workspace, _, _| {
@@ -640,7 +640,7 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
}
pub trait ProjectItem: Item {
- type Item: project2::Item;
+ type Item: project::Item;
fn for_project_item(
project: Model<Project>,
@@ -662,19 +662,19 @@ pub trait FollowableEvents {
pub trait FollowableItem: Item {
type FollowableEvent: FollowableEvents;
fn remote_id(&self) -> Option<ViewId>;
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
fn from_state_proto(
pane: View<Pane>,
project: View<Workspace>,
id: ViewId,
state: &mut Option<proto::view::Variant>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Task<Result<View<Self>>>>;
fn add_event_to_update_proto(
&self,
event: &Self::FollowableEvent,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool;
fn apply_update_proto(
&mut self,
@@ -682,20 +682,20 @@ pub trait FollowableItem: Item {
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>>;
- fn is_project_item(&self, cx: &AppContext) -> bool;
+ fn is_project_item(&self, cx: &WindowContext) -> bool;
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
}
pub trait FollowableItemHandle: ItemHandle {
- fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+ fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
fn add_event_to_update_proto(
&self,
event: &dyn Any,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool;
fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
fn apply_update_proto(
@@ -704,11 +704,11 @@ pub trait FollowableItemHandle: ItemHandle {
message: proto::update_view::Variant,
cx: &mut WindowContext,
) -> Task<Result<()>>;
- fn is_project_item(&self, cx: &AppContext) -> bool;
+ fn is_project_item(&self, cx: &WindowContext) -> bool;
}
impl<T: FollowableItem> FollowableItemHandle for View<T> {
- fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+ fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
self.read(cx).remote_id().or_else(|| {
client.peer_id().map(|creator| ViewId {
creator,
@@ -721,7 +721,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
}
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
self.read(cx).to_state_proto(cx)
}
@@ -729,7 +729,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
&self,
event: &dyn Any,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool {
if let Some(event) = event.downcast_ref() {
self.read(cx).add_event_to_update_proto(event, update, cx)
@@ -754,305 +754,315 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
}
- fn is_project_item(&self, cx: &AppContext) -> bool {
+ fn is_project_item(&self, cx: &WindowContext) -> bool {
self.read(cx).is_project_item(cx)
}
}
-// #[cfg(any(test, feature = "test-support"))]
-// pub mod test {
-// use super::{Item, ItemEvent};
-// use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
-// use gpui::{
-// elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
-// ViewContext, View, WeakViewHandle,
-// };
-// use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-// use smallvec::SmallVec;
-// use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
-
-// pub struct TestProjectItem {
-// pub entry_id: Option<ProjectEntryId>,
-// pub project_path: Option<ProjectPath>,
-// }
-
-// pub struct TestItem {
-// pub workspace_id: WorkspaceId,
-// pub state: String,
-// pub label: String,
-// pub save_count: usize,
-// pub save_as_count: usize,
-// pub reload_count: usize,
-// pub is_dirty: bool,
-// pub is_singleton: bool,
-// pub has_conflict: bool,
-// pub project_items: Vec<Model<TestProjectItem>>,
-// pub nav_history: Option<ItemNavHistory>,
-// pub tab_descriptions: Option<Vec<&'static str>>,
-// pub tab_detail: Cell<Option<usize>>,
-// }
-
-// impl Entity for TestProjectItem {
-// type Event = ();
-// }
-
-// impl project2::Item for TestProjectItem {
-// fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-// self.entry_id
-// }
-
-// fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-// self.project_path.clone()
-// }
-// }
-
-// pub enum TestItemEvent {
-// Edit,
-// }
-
-// impl Clone for TestItem {
-// fn clone(&self) -> Self {
-// Self {
-// state: self.state.clone(),
-// label: self.label.clone(),
-// save_count: self.save_count,
-// save_as_count: self.save_as_count,
-// reload_count: self.reload_count,
-// is_dirty: self.is_dirty,
-// is_singleton: self.is_singleton,
-// has_conflict: self.has_conflict,
-// project_items: self.project_items.clone(),
-// nav_history: None,
-// tab_descriptions: None,
-// tab_detail: Default::default(),
-// workspace_id: self.workspace_id,
-// }
-// }
-// }
-
-// impl TestProjectItem {
-// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
-// let entry_id = Some(ProjectEntryId::from_proto(id));
-// let project_path = Some(ProjectPath {
-// worktree_id: WorktreeId::from_usize(0),
-// path: Path::new(path).into(),
-// });
-// cx.add_model(|_| Self {
-// entry_id,
-// project_path,
-// })
-// }
-
-// pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-// cx.add_model(|_| Self {
-// project_path: None,
-// entry_id: None,
-// })
-// }
-// }
-
-// impl TestItem {
-// pub fn new() -> Self {
-// Self {
-// state: String::new(),
-// label: String::new(),
-// save_count: 0,
-// save_as_count: 0,
-// reload_count: 0,
-// is_dirty: false,
-// has_conflict: false,
-// project_items: Vec::new(),
-// is_singleton: true,
-// nav_history: None,
-// tab_descriptions: None,
-// tab_detail: Default::default(),
-// workspace_id: 0,
-// }
-// }
-
-// pub fn new_deserialized(id: WorkspaceId) -> Self {
-// let mut this = Self::new();
-// this.workspace_id = id;
-// this
-// }
-
-// pub fn with_label(mut self, state: &str) -> Self {
-// self.label = state.to_string();
-// self
-// }
-
-// pub fn with_singleton(mut self, singleton: bool) -> Self {
-// self.is_singleton = singleton;
-// self
-// }
-
-// pub fn with_dirty(mut self, dirty: bool) -> Self {
-// self.is_dirty = dirty;
-// self
-// }
-
-// pub fn with_conflict(mut self, has_conflict: bool) -> Self {
-// self.has_conflict = has_conflict;
-// self
-// }
-
-// pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
-// self.project_items.clear();
-// self.project_items.extend(items.iter().cloned());
-// self
-// }
-
-// pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
-// self.push_to_nav_history(cx);
-// self.state = state;
-// }
-
-// fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
-// if let Some(history) = &mut self.nav_history {
-// history.push(Some(Box::new(self.state.clone())), cx);
-// }
-// }
-// }
-
-// impl Entity for TestItem {
-// type Event = TestItemEvent;
-// }
-
-// impl View for TestItem {
-// fn ui_name() -> &'static str {
-// "TestItem"
-// }
-
-// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-// Empty::new().into_any()
-// }
-// }
-
-// impl Item for TestItem {
-// fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
-// self.tab_descriptions.as_ref().and_then(|descriptions| {
-// let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
-// Some(description.into())
-// })
-// }
-
-// fn tab_content<V: 'static>(
-// &self,
-// detail: Option<usize>,
-// _: &theme2::Tab,
-// _: &AppContext,
-// ) -> AnyElement<V> {
-// self.tab_detail.set(detail);
-// Empty::new().into_any()
-// }
-
-// fn for_each_project_item(
-// &self,
-// cx: &AppContext,
-// f: &mut dyn FnMut(usize, &dyn project2::Item),
-// ) {
-// self.project_items
-// .iter()
-// .for_each(|item| f(item.id(), item.read(cx)))
-// }
-
-// fn is_singleton(&self, _: &AppContext) -> bool {
-// self.is_singleton
-// }
-
-// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-// self.nav_history = Some(history);
-// }
-
-// fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-// let state = *state.downcast::<String>().unwrap_or_default();
-// if state != self.state {
-// self.state = state;
-// true
-// } else {
-// false
-// }
-// }
-
-// fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-// self.push_to_nav_history(cx);
-// }
-
-// fn clone_on_split(
-// &self,
-// _workspace_id: WorkspaceId,
-// _: &mut ViewContext<Self>,
-// ) -> Option<Self>
-// where
-// Self: Sized,
-// {
-// Some(self.clone())
-// }
-
-// fn is_dirty(&self, _: &AppContext) -> bool {
-// self.is_dirty
-// }
-
-// fn has_conflict(&self, _: &AppContext) -> bool {
-// self.has_conflict
-// }
-
-// fn can_save(&self, cx: &AppContext) -> bool {
-// !self.project_items.is_empty()
-// && self
-// .project_items
-// .iter()
-// .all(|item| item.read(cx).entry_id.is_some())
-// }
-
-// fn save(
-// &mut self,
-// _: Model<Project>,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.save_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn save_as(
-// &mut self,
-// _: Model<Project>,
-// _: std::path::PathBuf,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.save_as_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn reload(
-// &mut self,
-// _: Model<Project>,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.reload_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-// [ItemEvent::UpdateTab, ItemEvent::Edit].into()
-// }
-
-// fn serialized_item_kind() -> Option<&'static str> {
-// Some("TestItem")
-// }
-
-// fn deserialize(
-// _project: Model<Project>,
-// _workspace: WeakViewHandle<Workspace>,
-// workspace_id: WorkspaceId,
-// _item_id: ItemId,
-// cx: &mut ViewContext<Pane>,
-// ) -> Task<anyhow::Result<View<Self>>> {
-// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
-// Task::Ready(Some(anyhow::Ok(view)))
-// }
-// }
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+ use super::{Item, ItemEvent};
+ use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+ use gpui::{
+ AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
+ IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+ };
+ use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+ use std::{any::Any, cell::Cell, path::Path};
+
+ pub struct TestProjectItem {
+ pub entry_id: Option<ProjectEntryId>,
+ pub project_path: Option<ProjectPath>,
+ }
+
+ pub struct TestItem {
+ pub workspace_id: WorkspaceId,
+ pub state: String,
+ pub label: String,
+ pub save_count: usize,
+ pub save_as_count: usize,
+ pub reload_count: usize,
+ pub is_dirty: bool,
+ pub is_singleton: bool,
+ pub has_conflict: bool,
+ pub project_items: Vec<Model<TestProjectItem>>,
+ pub nav_history: Option<ItemNavHistory>,
+ pub tab_descriptions: Option<Vec<&'static str>>,
+ pub tab_detail: Cell<Option<usize>>,
+ focus_handle: gpui::FocusHandle,
+ }
+
+ impl project::Item for TestProjectItem {
+ fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+ self.entry_id
+ }
+
+ fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+ self.project_path.clone()
+ }
+ }
+
+ pub enum TestItemEvent {
+ Edit,
+ }
+
+ // impl Clone for TestItem {
+ // fn clone(&self) -> Self {
+ // Self {
+ // state: self.state.clone(),
+ // label: self.label.clone(),
+ // save_count: self.save_count,
+ // save_as_count: self.save_as_count,
+ // reload_count: self.reload_count,
+ // is_dirty: self.is_dirty,
+ // is_singleton: self.is_singleton,
+ // has_conflict: self.has_conflict,
+ // project_items: self.project_items.clone(),
+ // nav_history: None,
+ // tab_descriptions: None,
+ // tab_detail: Default::default(),
+ // workspace_id: self.workspace_id,
+ // focus_handle: self.focus_handle.clone(),
+ // }
+ // }
+ // }
+
+ impl TestProjectItem {
+ pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
+ let entry_id = Some(ProjectEntryId::from_proto(id));
+ let project_path = Some(ProjectPath {
+ worktree_id: WorktreeId::from_usize(0),
+ path: Path::new(path).into(),
+ });
+ cx.build_model(|_| Self {
+ entry_id,
+ project_path,
+ })
+ }
+
+ pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+ cx.build_model(|_| Self {
+ project_path: None,
+ entry_id: None,
+ })
+ }
+ }
+
+ impl TestItem {
+ pub fn new(cx: &mut ViewContext<Self>) -> Self {
+ Self {
+ state: String::new(),
+ label: String::new(),
+ save_count: 0,
+ save_as_count: 0,
+ reload_count: 0,
+ is_dirty: false,
+ has_conflict: false,
+ project_items: Vec::new(),
+ is_singleton: true,
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ workspace_id: 0,
+ focus_handle: cx.focus_handle(),
+ }
+ }
+
+ pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+ let mut this = Self::new(cx);
+ this.workspace_id = id;
+ this
+ }
+
+ pub fn with_label(mut self, state: &str) -> Self {
+ self.label = state.to_string();
+ self
+ }
+
+ pub fn with_singleton(mut self, singleton: bool) -> Self {
+ self.is_singleton = singleton;
+ self
+ }
+
+ pub fn with_dirty(mut self, dirty: bool) -> Self {
+ self.is_dirty = dirty;
+ self
+ }
+
+ pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+ self.has_conflict = has_conflict;
+ self
+ }
+
+ pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
+ self.project_items.clear();
+ self.project_items.extend(items.iter().cloned());
+ self
+ }
+
+ pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ self.state = state;
+ }
+
+ fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(history) = &mut self.nav_history {
+ history.push(Some(Box::new(self.state.clone())), cx);
+ }
+ }
+ }
+
+ impl Render for TestItem {
+ type Element = Div;
+
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+ gpui::div()
+ }
+ }
+
+ impl EventEmitter<ItemEvent> for TestItem {}
+
+ impl FocusableView for TestItem {
+ fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+ self.focus_handle.clone()
+ }
+ }
+
+ impl Item for TestItem {
+ fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
+ self.tab_descriptions.as_ref().and_then(|descriptions| {
+ let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+ Some(description.into())
+ })
+ }
+
+ fn tab_content(
+ &self,
+ detail: Option<usize>,
+ cx: &ui::prelude::WindowContext,
+ ) -> AnyElement {
+ self.tab_detail.set(detail);
+ gpui::div().into_any_element()
+ }
+
+ fn for_each_project_item(
+ &self,
+ cx: &AppContext,
+ f: &mut dyn FnMut(EntityId, &dyn project::Item),
+ ) {
+ self.project_items
+ .iter()
+ .for_each(|item| f(item.entity_id(), item.read(cx)))
+ }
+
+ fn is_singleton(&self, _: &AppContext) -> bool {
+ self.is_singleton
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ let state = *state.downcast::<String>().unwrap_or_default();
+ if state != self.state {
+ self.state = state;
+ true
+ } else {
+ false
+ }
+ }
+
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ }
+
+ fn clone_on_split(
+ &self,
+ _workspace_id: WorkspaceId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<View<Self>>
+ where
+ Self: Sized,
+ {
+ Some(cx.build_view(|cx| Self {
+ state: self.state.clone(),
+ label: self.label.clone(),
+ save_count: self.save_count,
+ save_as_count: self.save_as_count,
+ reload_count: self.reload_count,
+ is_dirty: self.is_dirty,
+ is_singleton: self.is_singleton,
+ has_conflict: self.has_conflict,
+ project_items: self.project_items.clone(),
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ workspace_id: self.workspace_id,
+ focus_handle: cx.focus_handle(),
+ }))
+ }
+
+ fn is_dirty(&self, _: &AppContext) -> bool {
+ self.is_dirty
+ }
+
+ fn has_conflict(&self, _: &AppContext) -> bool {
+ self.has_conflict
+ }
+
+ fn can_save(&self, cx: &AppContext) -> bool {
+ !self.project_items.is_empty()
+ && self
+ .project_items
+ .iter()
+ .all(|item| item.read(cx).entry_id.is_some())
+ }
+
+ fn save(
+ &mut self,
+ _: Model<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn save_as(
+ &mut self,
+ _: Model<Project>,
+ _: std::path::PathBuf,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_as_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn reload(
+ &mut self,
+ _: Model<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.reload_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ Some("TestItem")
+ }
+
+ fn deserialize(
+ _project: Model<Project>,
+ _workspace: WeakView<Workspace>,
+ workspace_id: WorkspaceId,
+ _item_id: ItemId,
+ cx: &mut ViewContext<Pane>,
+ ) -> Task<anyhow::Result<View<Self>>> {
+ let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx));
+ Task::Ready(Some(anyhow::Ok(view)))
+ }
+ }
+}
@@ -181,6 +181,7 @@ pub mod simple_message_notification {
};
use serde::Deserialize;
use std::{borrow::Cow, sync::Arc};
+ use ui::prelude::*;
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
#[derive(Clone, Default, Deserialize, PartialEq)]
@@ -287,12 +288,14 @@ pub mod simple_message_notification {
),
)
.children(self.click_message.iter().map(|message| {
- Button::new(message.clone()).on_click(cx.listener(|this, _, cx| {
- if let Some(on_click) = this.on_click.as_ref() {
- (on_click)(cx)
- };
- this.dismiss(cx)
- }))
+ Button::new(message.clone(), message.clone()).on_click(cx.listener(
+ |this, _, cx| {
+ if let Some(on_click) = this.on_click.as_ref() {
+ (on_click)(cx)
+ };
+ this.dismiss(cx)
+ },
+ ))
}))
}
}
@@ -2,19 +2,20 @@ use crate::{
item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
toolbar::Toolbar,
workspace_settings::{AutosaveSetting, WorkspaceSettings},
- SplitDirection, Workspace,
+ NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
use gpui::{
- actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter,
- FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View,
- ViewContext, VisualContext, WeakView, WindowContext,
+ actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext,
+ AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
+ FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext,
+ VisualContext, WeakView, WindowContext,
};
use parking_lot::Mutex;
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
use std::{
any::Any,
cmp, fmt, mem,
@@ -25,8 +26,8 @@ use std::{
},
};
-use ui::v_stack;
-use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip};
+use ui::{prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Tooltip};
+use ui::{v_stack, ContextMenu};
use util::truncate_and_remove_front;
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@@ -50,7 +51,7 @@ pub enum SaveIntent {
//todo!("Do we need the default bound on actions? Decide soon")
// #[register_action]
-#[derive(Clone, Deserialize, PartialEq, Debug)]
+#[derive(Action, Clone, Deserialize, PartialEq, Debug)]
pub struct ActivateItem(pub usize);
// #[derive(Clone, PartialEq)]
@@ -143,17 +144,24 @@ impl fmt::Debug for Event {
}
}
+struct FocusedView {
+ view: AnyWeakView,
+ focus_handle: FocusHandle,
+}
+
pub struct Pane {
focus_handle: FocusHandle,
items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>,
zoomed: bool,
active_item_index: usize,
- // last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
+ last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
autoscroll: bool,
nav_history: NavHistory,
toolbar: View<Toolbar>,
- // tab_bar_context_menu: TabBarContextMenu,
+ tab_bar_focus_handle: FocusHandle,
+ new_item_menu: Option<View<ContextMenu>>,
+ split_item_menu: Option<View<ContextMenu>>,
// tab_context_menu: ViewHandle<ContextMenu>,
workspace: WeakView<Workspace>,
project: Model<Project>,
@@ -306,7 +314,7 @@ impl Pane {
activation_history: Vec::new(),
zoomed: false,
active_item_index: 0,
- // last_focused_view_by_item: Default::default(),
+ last_focused_view_by_item: Default::default(),
autoscroll: false,
nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
mode: NavigationMode::Normal,
@@ -318,6 +326,9 @@ impl Pane {
next_timestamp,
}))),
toolbar: cx.build_view(|_| Toolbar::new()),
+ tab_bar_focus_handle: cx.focus_handle(),
+ new_item_menu: None,
+ split_item_menu: None,
// tab_bar_context_menu: TabBarContextMenu {
// kind: TabBarContextMenuKind::New,
// handle: context_menu,
@@ -392,9 +403,48 @@ impl Pane {
}
pub fn has_focus(&self, cx: &WindowContext) -> bool {
+ // todo!(); // inline this manually
self.focus_handle.contains_focused(cx)
}
+ fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+ if !self.has_focus(cx) {
+ cx.emit(Event::Focus);
+ cx.notify();
+ }
+
+ self.toolbar.update(cx, |toolbar, cx| {
+ toolbar.focus_changed(true, cx);
+ });
+
+ if let Some(active_item) = self.active_item() {
+ if self.focus_handle.is_focused(cx) {
+ // Pane was focused directly. We need to either focus a view inside the active item,
+ // or focus the active item itself
+ if let Some(weak_last_focused_view) =
+ self.last_focused_view_by_item.get(&active_item.item_id())
+ {
+ weak_last_focused_view.focus(cx);
+ return;
+ }
+
+ active_item.focus_handle(cx).focus(cx);
+ } else if !self.tab_bar_focus_handle.contains_focused(cx) {
+ if let Some(focused) = cx.focused() {
+ self.last_focused_view_by_item
+ .insert(active_item.item_id(), focused);
+ }
+ }
+ }
+ }
+
+ fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+ self.toolbar.update(cx, |toolbar, cx| {
+ toolbar.focus_changed(false, cx);
+ });
+ cx.notify();
+ }
+
pub fn active_item_index(&self) -> usize {
self.active_item_index
}
@@ -457,6 +507,28 @@ impl Pane {
!self.nav_history.0.lock().forward_stack.is_empty()
}
+ fn navigate_backward(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(workspace) = self.workspace.upgrade() {
+ let pane = cx.view().downgrade();
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.go_back(pane, cx).detach_and_log_err(cx)
+ })
+ })
+ }
+ }
+
+ fn navigate_forward(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(workspace) = self.workspace.upgrade() {
+ let pane = cx.view().downgrade();
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.go_forward(pane, cx).detach_and_log_err(cx)
+ })
+ })
+ }
+ }
+
fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
self.toolbar.update(cx, |_, cx| cx.notify());
}
@@ -630,21 +702,16 @@ impl Pane {
.position(|i| i.item_id() == item.item_id())
}
- // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
- // // Potentially warn the user of the new keybinding
- // let workspace_handle = self.workspace().clone();
- // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
- // .detach();
-
- // if self.zoomed {
- // cx.emit(Event::ZoomOut);
- // } else if !self.items.is_empty() {
- // if !self.has_focus {
- // cx.focus_self();
- // }
- // cx.emit(Event::ZoomIn);
- // }
- // }
+ pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
+ if self.zoomed {
+ cx.emit(Event::ZoomOut);
+ } else if !self.items.is_empty() {
+ if !self.focus_handle.contains_focused(cx) {
+ cx.focus_self();
+ }
+ cx.emit(Event::ZoomIn);
+ }
+ }
pub fn activate_item(
&mut self,
@@ -1381,7 +1448,7 @@ impl Pane {
let close_right = ItemSettings::get_global(cx).close_position.right();
let is_active = ix == self.active_item_index;
- div()
+ let tab = div()
.group("")
.id(ix)
.cursor_pointer()
@@ -1455,13 +1522,41 @@ impl Pane {
.children((!close_right).then(|| close_icon()))
.child(label)
.children(close_right.then(|| close_icon())),
- )
+ );
+
+ right_click_menu(ix).trigger(tab).menu(|cx| {
+ ContextMenu::build(cx, |menu, cx| {
+ menu.action(
+ "Close Active Item",
+ CloseActiveItem { save_intent: None }.boxed_clone(),
+ cx,
+ )
+ .action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx)
+ .action("Close Clean Items", CloseCleanItems.boxed_clone(), cx)
+ .action(
+ "Close Items To The Left",
+ CloseItemsToTheLeft.boxed_clone(),
+ cx,
+ )
+ .action(
+ "Close Items To The Right",
+ CloseItemsToTheRight.boxed_clone(),
+ cx,
+ )
+ .action(
+ "Close All Items",
+ CloseAllItems { save_intent: None }.boxed_clone(),
+ cx,
+ )
+ })
+ })
}
fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
div()
.group("tab_bar")
.id("tab_bar")
+ .track_focus(&self.tab_bar_focus_handle)
.w_full()
.flex()
.bg(cx.theme().colors().tab_bar_background)
@@ -1483,12 +1578,20 @@ impl Pane {
.child(
div().border().border_color(gpui::red()).child(
IconButton::new("navigate_backward", Icon::ArrowLeft)
+ .on_click({
+ let view = cx.view().clone();
+ move |_, cx| view.update(cx, Self::navigate_backward)
+ })
.disabled(!self.can_navigate_backward()),
),
)
.child(
div().border().border_color(gpui::red()).child(
IconButton::new("navigate_forward", Icon::ArrowRight)
+ .on_click({
+ let view = cx.view().clone();
+ move |_, cx| view.update(cx, Self::navigate_backward)
+ })
.disabled(!self.can_navigate_forward()),
),
),
@@ -1520,20 +1623,87 @@ impl Pane {
.gap_px()
.child(
div()
+ .bg(gpui::blue())
.border()
.border_color(gpui::red())
- .child(IconButton::new("plus", Icon::Plus)),
+ .child(IconButton::new("plus", Icon::Plus).on_click(
+ cx.listener(|this, _, cx| {
+ let menu = ContextMenu::build(cx, |menu, cx| {
+ menu.action("New File", NewFile.boxed_clone(), cx)
+ .action(
+ "New Terminal",
+ NewCenterTerminal.boxed_clone(),
+ cx,
+ )
+ .action(
+ "New Search",
+ NewSearch.boxed_clone(),
+ cx,
+ )
+ });
+ cx.subscribe(
+ &menu,
+ |this, _, event: &DismissEvent, cx| {
+ this.focus(cx);
+ this.new_item_menu = None;
+ },
+ )
+ .detach();
+ this.new_item_menu = Some(menu);
+ }),
+ ))
+ .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
+ el.child(Self::render_menu_overlay(new_item_menu))
+ }),
)
.child(
div()
.border()
.border_color(gpui::red())
- .child(IconButton::new("split", Icon::Split)),
+ .child(IconButton::new("split", Icon::Split).on_click(
+ cx.listener(|this, _, cx| {
+ let menu = ContextMenu::build(cx, |menu, cx| {
+ menu.action(
+ "Split Right",
+ SplitRight.boxed_clone(),
+ cx,
+ )
+ .action("Split Left", SplitLeft.boxed_clone(), cx)
+ .action("Split Up", SplitUp.boxed_clone(), cx)
+ .action("Split Down", SplitDown.boxed_clone(), cx)
+ });
+ cx.subscribe(
+ &menu,
+ |this, _, event: &DismissEvent, cx| {
+ this.focus(cx);
+ this.split_item_menu = None;
+ },
+ )
+ .detach();
+ this.split_item_menu = Some(menu);
+ }),
+ ))
+ .when_some(
+ self.split_item_menu.as_ref(),
+ |el, split_item_menu| {
+ el.child(Self::render_menu_overlay(split_item_menu))
+ },
+ ),
),
),
)
}
+ fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
+ div()
+ .absolute()
+ .z_index(1)
+ .bottom_0()
+ .right_0()
+ .size_0()
+ .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
+ }
+
// fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
// let theme = theme::current(cx).clone();
@@ -1932,40 +2102,78 @@ impl Render for Pane {
type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ let this = cx.view().downgrade();
+
v_stack()
.key_context("Pane")
.track_focus(&self.focus_handle)
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitLeft, cx| {
- pane.split(SplitDirection::Left, cx)
- }))
+ .on_focus_in({
+ let this = this.clone();
+ move |event, cx| {
+ this.update(cx, |this, cx| this.focus_in(cx)).ok();
+ }
+ })
+ .on_focus_out({
+ let this = this.clone();
+ move |event, cx| {
+ this.update(cx, |this, cx| this.focus_out(cx)).ok();
+ }
+ })
+ .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
+ .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
.on_action(
- cx.listener(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)),
+ cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
)
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitRight, cx| {
- pane.split(SplitDirection::Right, cx)
+ .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
+ .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
+ .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
+ .on_action(cx.listener(Pane::toggle_zoom))
+ .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
+ pane.activate_item(action.0, true, true, cx);
+ }))
+ .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+ pane.activate_item(pane.items.len() - 1, true, true, cx);
+ }))
+ .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+ pane.activate_prev_item(true, cx);
}))
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| {
- pane.split(SplitDirection::Down, cx)
+ .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+ pane.activate_next_item(true, cx);
+ }))
+ .on_action(
+ cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
+ pane.close_active_item(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ )
+ .on_action(
+ cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
+ pane.close_inactive_items(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ )
+ .on_action(
+ cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
+ pane.close_clean_items(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ )
+ .on_action(
+ cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
+ pane.close_items_to_the_left(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ )
+ .on_action(
+ cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
+ pane.close_items_to_the_right(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
+ }),
+ )
+ .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
+ pane.close_all_items(action, cx)
+ .map(|task| task.detach_and_log_err(cx));
}))
- // cx.add_action(Pane::toggle_zoom);
- // cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
- // pane.activate_item(action.0, true, true, cx);
- // });
- // cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
- // pane.activate_item(pane.items.len() - 1, true, true, cx);
- // });
- // cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
- // pane.activate_prev_item(true, cx);
- // });
- // cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
- // pane.activate_next_item(true, cx);
- // });
- // cx.add_async_action(Pane::close_active_item);
- // cx.add_async_action(Pane::close_inactive_items);
- // cx.add_async_action(Pane::close_clean_items);
- // cx.add_async_action(Pane::close_items_to_the_left);
- // cx.add_async_action(Pane::close_items_to_the_right);
- // cx.add_async_action(Pane::close_all_items);
.size_full()
.on_action(
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
@@ -1,7 +1,7 @@
use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result};
use collections::HashMap;
-use db2::sqlez::{
+use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
@@ -9,7 +9,7 @@ use gpui::{
point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
};
use parking_lot::Mutex;
-use project2::Project;
+use project::Project;
use serde::Deserialize;
use std::sync::Arc;
use ui::prelude::*;
@@ -5,7 +5,7 @@ pub mod model;
use std::path::Path;
use anyhow::{anyhow, bail, Context, Result};
-use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::WindowBounds;
use util::{unzip_option, ResultExt};
@@ -552,7 +552,7 @@ impl WorkspaceDb {
#[cfg(test)]
mod tests {
use super::*;
- use db2::open_test_db;
+ use db::open_test_db;
use gpui;
#[gpui::test]
@@ -3,12 +3,12 @@ use crate::{
};
use anyhow::{Context, Result};
use async_recursion::async_recursion;
-use db2::sqlez::{
+use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
-use project2::Project;
+use project::Project;
use std::{
path::{Path, PathBuf},
sync::Arc,
@@ -4,7 +4,7 @@ use gpui::{
AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
WindowContext,
};
-use project2::search::SearchQuery;
+use project::search::SearchQuery;
use crate::{
item::{Item, WeakItemHandle},
@@ -47,39 +47,18 @@ impl Render for StatusBar {
.w_full()
.h_8()
.bg(cx.theme().colors().status_bar_background)
- // Nate: I know this isn't how we render status bar tools
- // We can move these to the correct place once we port their tools
- .child(
- h_stack().gap_1().child(self.render_left_tools(cx)).child(
- h_stack().gap_4().child(
- // TODO: Language Server status
- div()
- .border()
- .border_color(gpui::red())
- .child("Checking..."),
- ),
- ),
- )
+ .child(h_stack().gap_1().child(self.render_left_tools(cx)))
.child(
h_stack()
.gap_4()
.child(
- h_stack()
- .gap_1()
- .child(
- // TODO: Line / column numbers
- div()
- .border()
- .border_color(gpui::red())
- .child(Button::new("15:22")),
- )
- .child(
- // TODO: Language picker
- div()
- .border()
- .border_color(gpui::red())
- .child(Button::new("Rust")),
- ),
+ h_stack().gap_1().child(
+ // TODO: Language picker
+ div()
+ .border()
+ .border_color(gpui::red())
+ .child(Button::new("status_buffer_language", "Rust")),
+ ),
)
.child(
h_stack()
@@ -145,7 +124,7 @@ impl StatusBar {
h_stack()
.items_center()
.gap_2()
- .children(self.right_items.iter().map(|item| item.to_any()))
+ .children(self.right_items.iter().rev().map(|item| item.to_any()))
}
}
@@ -4,7 +4,7 @@ use gpui::{
ViewContext, WindowContext,
};
use ui::prelude::*;
-use ui::{h_stack, v_stack, Button, Color, Icon, IconButton, Label};
+use ui::{h_stack, v_stack, Icon, IconButton};
pub enum ToolbarItemEvent {
ChangeLocation(ToolbarItemLocation),
@@ -80,7 +80,6 @@ impl Render for Toolbar {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- //dbg!(&self.items.len());
v_stack()
.border_b()
.border_color(cx.theme().colors().border_variant)
@@ -88,16 +87,8 @@ impl Render for Toolbar {
.child(
h_stack()
.justify_between()
- .child(
- // Toolbar left side
- h_stack()
- .border()
- .border_color(gpui::red())
- .p_1()
- .child(Button::new("crates"))
- .child(Label::new("/").color(Color::Muted))
- .child(Button::new("workspace2")),
- )
+ // Toolbar left side
+ .children(self.items.iter().map(|(child, _)| child.to_any()))
// Toolbar right side
.child(
h_stack()
@@ -116,7 +107,6 @@ impl Render for Toolbar {
),
),
)
- .children(self.items.iter().map(|(child, _)| child.to_any()))
}
}
@@ -16,7 +16,7 @@ mod workspace_settings;
use anyhow::{anyhow, Context as _, Result};
use async_trait::async_trait;
-use client2::{
+use client::{
proto::{self, PeerId},
Client, TypedEnvelope, User, UserStore,
};
@@ -37,7 +37,7 @@ use gpui::{
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
-use language2::{LanguageRegistry, Rope};
+use language::{LanguageRegistry, Rope};
use lazy_static::lazy_static;
pub use modal_layer::*;
use node_runtime::NodeRuntime;
@@ -49,9 +49,9 @@ pub use persistence::{
WorkspaceDb, DB,
};
use postage::stream::Stream;
-use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
use status_bar::StatusBar;
pub use status_bar::StatusItemView;
use std::{
@@ -62,7 +62,7 @@ use std::{
sync::{atomic::AtomicUsize, Arc},
time::Duration,
};
-use theme2::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use util::ResultExt;
@@ -247,7 +247,7 @@ type FollowableItemBuilder = fn(
View<Workspace>,
ViewId,
&mut Option<proto::view::Variant>,
- &mut AppContext,
+ &mut WindowContext,
) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
type FollowableItemBuilders = HashMap<
TypeId,
@@ -301,7 +301,7 @@ pub struct AppState {
pub client: Arc<Client>,
pub user_store: Model<UserStore>,
pub workspace_store: Model<WorkspaceStore>,
- pub fs: Arc<dyn fs2::Fs>,
+ pub fs: Arc<dyn fs::Fs>,
pub call_factory: CallFactory,
pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
@@ -312,7 +312,7 @@ pub struct WorkspaceStore {
workspaces: HashSet<WindowHandle<Workspace>>,
followers: Vec<Follower>,
client: Arc<Client>,
- _subscriptions: Vec<client2::Subscription>,
+ _subscriptions: Vec<client::Subscription>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -326,7 +326,12 @@ pub struct TestCallHandler;
#[cfg(any(test, feature = "test-support"))]
impl CallHandler for TestCallHandler {
- fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
+ fn peer_state(
+ &mut self,
+ id: PeerId,
+ project: &Model<Project>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<(bool, bool)> {
None
}
@@ -383,22 +388,22 @@ impl AppState {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> {
use node_runtime::FakeNodeRuntime;
- use settings2::SettingsStore;
+ use settings::SettingsStore;
if !cx.has_global::<SettingsStore>() {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
}
- let fs = fs2::FakeFs::new(cx.background_executor().clone());
+ let fs = fs::FakeFs::new(cx.background_executor().clone());
let languages = Arc::new(LanguageRegistry::test());
let http_client = util::http::FakeHttpClient::with_404_response();
let client = Client::new(http_client.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
- theme2::init(theme2::LoadThemes::JustBase, cx);
- client2::init(&client, cx);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ client::init(&client, cx);
crate::init_settings(cx);
Arc::new(Self {
@@ -409,7 +414,7 @@ impl AppState {
workspace_store,
node_runtime: FakeNodeRuntime::new(),
build_window_options: |_, _, _| Default::default(),
- call_factory: |_, _| Box::new(TestCallHandler),
+ call_factory: |_| Box::new(TestCallHandler),
})
}
}
@@ -468,7 +473,12 @@ pub enum Event {
#[async_trait(?Send)]
pub trait CallHandler {
- fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
+ fn peer_state(
+ &mut self,
+ id: PeerId,
+ project: &Model<Project>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<(bool, bool)>;
fn shared_screen_for_peer(
&self,
peer_id: PeerId,
@@ -546,7 +556,7 @@ struct FollowerState {
enum WorkspaceBounds {}
-type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
+type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
impl Workspace {
pub fn new(
workspace_id: WorkspaceId,
@@ -557,29 +567,29 @@ impl Workspace {
cx.observe(&project, |_, _, cx| cx.notify()).detach();
cx.subscribe(&project, move |this, _, event, cx| {
match event {
- project2::Event::RemoteIdChanged(_) => {
+ project::Event::RemoteIdChanged(_) => {
this.update_window_title(cx);
}
- project2::Event::CollaboratorLeft(peer_id) => {
+ project::Event::CollaboratorLeft(peer_id) => {
this.collaborator_left(*peer_id, cx);
}
- project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+ project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
this.update_window_title(cx);
this.serialize_workspace(cx);
}
- project2::Event::DisconnectedFromHost => {
+ project::Event::DisconnectedFromHost => {
this.update_window_edited(cx);
cx.blur();
}
- project2::Event::Closed => {
+ project::Event::Closed => {
cx.remove_window();
}
- project2::Event::DeletedEntry(entry_id) => {
+ project::Event::DeletedEntry(entry_id) => {
for pane in this.panes.iter() {
pane.update(cx, |pane, cx| {
pane.handle_deleted_project_item(*entry_id, cx)
@@ -587,7 +597,7 @@ impl Workspace {
}
}
- project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+ project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
cx.build_view(|_| MessageNotification::new(message.clone()))
}),
@@ -760,7 +770,7 @@ impl Workspace {
last_leaders_by_pane: Default::default(),
window_edited: false,
- call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
+ call_handler: (app_state.call_factory)(cx),
database_id: workspace_id,
app_state,
_observe_current_user,
@@ -1440,7 +1450,7 @@ impl Workspace {
.map(|entry| entry.id);
if let Some(entry_id) = entry_id {
workspace.project.update(cx, |_, cx| {
- cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
+ cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
})
}
})
@@ -1802,8 +1812,7 @@ impl Workspace {
});
cx.subscribe(&pane, Self::handle_pane_event).detach();
self.panes.push(pane.clone());
- // todo!()
- // cx.focus(&pane);
+ cx.focus_view(&pane);
cx.emit(Event::PaneAdded(pane.clone()));
pane
}
@@ -1978,7 +1987,7 @@ impl Workspace {
where
T: ProjectItem,
{
- use project2::Item as _;
+ use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
@@ -2003,7 +2012,7 @@ impl Workspace {
where
T: ProjectItem,
{
- use project2::Item as _;
+ use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
@@ -2582,8 +2591,7 @@ impl Workspace {
title.push_str(" β");
}
- // todo!()
- // cx.set_window_title(&title);
+ cx.set_window_title(&title);
}
fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
@@ -2884,7 +2892,7 @@ impl Workspace {
cx.notify();
let (leader_in_this_project, leader_in_this_app) =
- self.call_handler.peer_state(leader_id, cx)?;
+ self.call_handler.peer_state(leader_id, &self.project, cx)?;
let mut items_to_activate = Vec::new();
for (pane, state) in &self.follower_states {
if state.leader_id != leader_id {
@@ -3385,7 +3393,7 @@ impl Workspace {
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(),
- call_factory: |_, _| Box::new(TestCallHandler),
+ call_factory: |_| Box::new(TestCallHandler),
});
let workspace = Self::new(0, project, app_state, cx);
workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@@ -3577,93 +3585,12 @@ fn open_items(
})
}
-// todo!()
-// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
-// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
-// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
-// const MESSAGE_ID: usize = 2;
-
-// if workspace
-// .read_with(cx, |workspace, cx| {
-// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-// })
-// .unwrap_or(false)
-// {
-// return;
-// }
-
-// if db::kvp::KEY_VALUE_STORE
-// .read_kvp(NEW_DOCK_HINT_KEY)
-// .ok()
-// .flatten()
-// .is_some()
-// {
-// if !workspace
-// .read_with(cx, |workspace, cx| {
-// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-// })
-// .unwrap_or(false)
-// {
-// cx.update(|cx| {
-// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-// let entry = tracker
-// .entry(TypeId::of::<MessageNotification>())
-// .or_default();
-// if !entry.contains(&MESSAGE_ID) {
-// entry.push(MESSAGE_ID);
-// }
-// });
-// });
-// }
-
-// return;
-// }
-
-// cx.spawn(|_| async move {
-// db::kvp::KEY_VALUE_STORE
-// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
-// .await
-// .ok();
-// })
-// .detach();
-
-// workspace
-// .update(cx, |workspace, cx| {
-// workspace.show_notification_once(2, cx, |cx| {
-// cx.build_view(|_| {
-// MessageNotification::new_element(|text, _| {
-// Text::new(
-// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
-// text,
-// )
-// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
-// let code_span_background_color = settings::get::<ThemeSettings>(cx)
-// .theme
-// .editor
-// .document_highlight_read_background;
-
-// cx.scene().push_quad(gpui::Quad {
-// bounds,
-// background: Some(code_span_background_color),
-// border: Default::default(),
-// corner_radii: (2.0).into(),
-// })
-// })
-// .into_any()
-// })
-// .with_click_message("Read more about the new panel system")
-// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
-// })
-// })
-// })
-// .ok();
-
fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
workspace
.update(cx, |workspace, cx| {
- if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+ if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
workspace.show_notification_once(0, cx, |cx| {
cx.build_view(|_| {
MessageNotification::new("Failed to load the database file.")
@@ -3711,6 +3638,8 @@ impl Render for Workspace {
.items_start()
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().background)
+ .border()
+ .border_color(cx.theme().colors().border)
.children(self.titlebar_item.clone())
.child(
div()
@@ -4481,50 +4410,54 @@ pub fn create_and_open_local_file(
// })
// }
-// pub fn restart(_: &Restart, cx: &mut AppContext) {
-// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
-// cx.spawn(|mut cx| async move {
-// let mut workspace_windows = cx
-// .windows()
-// .into_iter()
-// .filter_map(|window| window.downcast::<Workspace>())
-// .collect::<Vec<_>>();
-
-// // If multiple windows have unsaved changes, and need a save prompt,
-// // prompt in the active window before switching to a different window.
-// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
-
-// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
-// let answer = window.prompt(
-// PromptLevel::Info,
-// "Are you sure you want to restart?",
-// &["Restart", "Cancel"],
-// &mut cx,
-// );
+pub fn restart(_: &Restart, cx: &mut AppContext) {
+ let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
+ let mut workspace_windows = cx
+ .windows()
+ .into_iter()
+ .filter_map(|window| window.downcast::<Workspace>())
+ .collect::<Vec<_>>();
+
+ // If multiple windows have unsaved changes, and need a save prompt,
+ // prompt in the active window before switching to a different window.
+ workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
+
+ let mut prompt = None;
+ if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
+ prompt = window
+ .update(cx, |_, cx| {
+ cx.prompt(
+ PromptLevel::Info,
+ "Are you sure you want to restart?",
+ &["Restart", "Cancel"],
+ )
+ })
+ .ok();
+ }
-// if let Some(mut answer) = answer {
-// let answer = answer.next().await;
-// if answer != Some(0) {
-// return Ok(());
-// }
-// }
-// }
+ cx.spawn(|mut cx| async move {
+ if let Some(mut prompt) = prompt {
+ let answer = prompt.await?;
+ if answer != 0 {
+ return Ok(());
+ }
+ }
-// // If the user cancels any save prompt, then keep the app open.
-// for window in workspace_windows {
-// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
-// workspace.prepare_to_close(true, cx)
-// }) {
-// if !should_close.await? {
-// return Ok(());
-// }
-// }
-// }
-// cx.platform().restart();
-// anyhow::Ok(())
-// })
-// .detach_and_log_err(cx);
-// }
+ // If the user cancels any save prompt, then keep the app open.
+ for window in workspace_windows {
+ if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
+ workspace.prepare_to_close(true, cx)
+ }) {
+ if !should_close.await? {
+ return Ok(());
+ }
+ }
+ }
+
+ cx.update(|cx| cx.restart())
+ })
+ .detach_and_log_err(cx);
+}
fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
let mut parts = value.split(',');
@@ -4540,960 +4473,950 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
Some(size((width as f64).into(), (height as f64).into()))
}
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-// use crate::{
-// dock::test::TestPanel,
-// item::test::{TestItem, TestItemEvent, TestProjectItem},
-// };
-// use fs::FakeFs;
-// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
-// use project::{Project, ProjectEntryId};
-// use serde_json::json;
-// use settings::SettingsStore;
-// use std::{cell::RefCell, rc::Rc};
-
-// #[gpui::test]
-// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-
-// // Adding an item with no ambiguity renders the tab without detail.
-// let item1 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item1.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
-
-// // Adding an item that creates ambiguity increases the level of detail on
-// // both tabs.
-// let item2 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item2.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-
-// // Adding an item that creates ambiguity increases the level of detail only
-// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
-// // we stop at the highest detail available.
-// let item3 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item3.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-// }
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::item::{
+ test::{TestItem, TestProjectItem},
+ ItemEvent,
+ };
+ use fs::FakeFs;
+ use gpui::TestAppContext;
+ use project::{Project, ProjectEntryId};
+ use serde_json::json;
+ use settings::SettingsStore;
+ use std::{cell::RefCell, rc::Rc};
+
+ #[gpui::test]
+ async fn test_tab_disambiguation(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+ // Adding an item with no ambiguity renders the tab without detail.
+ let item1 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item1.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
+
+ // Adding an item that creates ambiguity increases the level of detail on
+ // both tabs.
+ let item2 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item2.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+ item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+ // Adding an item that creates ambiguity increases the level of detail only
+ // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+ // we stop at the highest detail available.
+ let item3 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item3.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+ item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+ item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+ }
-// #[gpui::test]
-// async fn test_tracking_active_path(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// fs.insert_tree(
-// "/root1",
-// json!({
-// "one.txt": "",
-// "two.txt": "",
-// }),
-// )
-// .await;
-// fs.insert_tree(
-// "/root2",
-// json!({
-// "three.txt": "",
-// }),
-// )
-// .await;
+ #[gpui::test]
+ async fn test_tracking_active_path(cx: &mut TestAppContext) {
+ init_test(cx);
-// let project = Project::test(fs, ["root1".as_ref()], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// let worktree_id = project.read_with(cx, |project, cx| {
-// project.worktrees().next().unwrap().read(cx).id()
-// });
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/root1",
+ json!({
+ "one.txt": "",
+ "two.txt": "",
+ }),
+ )
+ .await;
+ fs.insert_tree(
+ "/root2",
+ json!({
+ "three.txt": "",
+ }),
+ )
+ .await;
-// let item1 = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-// });
-// let item2 = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-// });
+ let project = Project::test(fs, ["root1".as_ref()], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let worktree_id = project.read_with(cx, |project, cx| {
+ project.worktrees().next().unwrap().read(cx).id()
+ });
-// // Add an item to an empty pane
-// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
-
-// // Add a second item to a non-empty pane
-// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
+ let item1 = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+ });
+ let item2 = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+ });
-// // Close the active item
-// pane.update(cx, |pane, cx| {
-// pane.close_active_item(&Default::default(), cx).unwrap()
-// })
-// .await
-// .unwrap();
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
+ // Add an item to an empty pane
+ workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
+
+ // Add a second item to a non-empty pane
+ workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+ assert_eq!(cx.window_title().as_deref(), Some("two.txt β root1"));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
-// // Add a project folder
-// project
-// .update(cx, |project, cx| {
-// project.find_or_create_local_worktree("/root2", true, cx)
-// })
-// .await
-// .unwrap();
-// assert_eq!(
-// window.current_title(cx).as_deref(),
-// Some("one.txt β root1, root2")
-// );
-
-// // Remove a project folder
-// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
-// }
+ // Close the active item
+ pane.update(cx, |pane, cx| {
+ pane.close_active_item(&Default::default(), cx).unwrap()
+ })
+ .await
+ .unwrap();
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
-// #[gpui::test]
-// async fn test_close_window(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// fs.insert_tree("/root", json!({ "one": "" })).await;
-
-// let project = Project::test(fs, ["root".as_ref()], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-
-// // When there are no dirty items, there's nothing to do.
-// let item1 = window.build_view(cx, |_| TestItem::new());
-// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
-// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-// assert!(task.await.unwrap());
-
-// // When there are dirty untitled items, prompt to save each one. If the user
-// // cancels any prompt, then abort.
-// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
-// let item3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// workspace.update(cx, |w, cx| {
-// w.add_item(Box::new(item2.clone()), cx);
-// w.add_item(Box::new(item3.clone()), cx);
-// });
-// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-// cx.foreground().run_until_parked();
-// window.simulate_prompt_answer(2, cx); // cancel save all
-// cx.foreground().run_until_parked();
-// window.simulate_prompt_answer(2, cx); // cancel save all
-// cx.foreground().run_until_parked();
-// assert!(!window.has_pending_prompt(cx));
-// assert!(!task.await.unwrap());
-// }
+ // Add a project folder
+ project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root2", true, cx)
+ })
+ .await
+ .unwrap();
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1, root2"));
-// #[gpui::test]
-// async fn test_close_pane_items(cx: &mut TestAppContext) {
-// init_test(cx);
+ // Remove a project folder
+ project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root2"));
+ }
-// let fs = FakeFs::new(cx.background());
+ #[gpui::test]
+ async fn test_close_window(cx: &mut TestAppContext) {
+ init_test(cx);
-// let project = Project::test(fs, None, cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree("/root", json!({ "one": "" })).await;
-// let item1 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let item2 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_conflict(true)
-// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-// });
-// let item3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_conflict(true)
-// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-// });
-// let item4 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new_untitled(cx)])
-// });
-// let pane = workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item1.clone()), cx);
-// workspace.add_item(Box::new(item2.clone()), cx);
-// workspace.add_item(Box::new(item3.clone()), cx);
-// workspace.add_item(Box::new(item4.clone()), cx);
-// workspace.active_pane().clone()
-// });
+ let project = Project::test(fs, ["root".as_ref()], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-// let close_items = pane.update(cx, |pane, cx| {
-// pane.activate_item(1, true, true, cx);
-// assert_eq!(pane.active_item().unwrap().id(), item2.id());
-// let item1_id = item1.id();
-// let item3_id = item3.id();
-// let item4_id = item4.id();
-// pane.close_items(cx, SaveIntent::Close, move |id| {
-// [item1_id, item3_id, item4_id].contains(&id)
-// })
-// });
-// cx.foreground().run_until_parked();
-
-// assert!(window.has_pending_prompt(cx));
-// // Ignore "Save all" prompt
-// window.simulate_prompt_answer(2, cx);
-// cx.foreground().run_until_parked();
-// // There's a prompt to save item 1.
-// pane.read_with(cx, |pane, _| {
-// assert_eq!(pane.items_len(), 4);
-// assert_eq!(pane.active_item().unwrap().id(), item1.id());
-// });
-// // Confirm saving item 1.
-// window.simulate_prompt_answer(0, cx);
-// cx.foreground().run_until_parked();
-
-// // Item 1 is saved. There's a prompt to save item 3.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item1.read(cx).save_count, 1);
-// assert_eq!(item1.read(cx).save_as_count, 0);
-// assert_eq!(item1.read(cx).reload_count, 0);
-// assert_eq!(pane.items_len(), 3);
-// assert_eq!(pane.active_item().unwrap().id(), item3.id());
-// });
-// assert!(window.has_pending_prompt(cx));
-
-// // Cancel saving item 3.
-// window.simulate_prompt_answer(1, cx);
-// cx.foreground().run_until_parked();
-
-// // Item 3 is reloaded. There's a prompt to save item 4.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item3.read(cx).save_count, 0);
-// assert_eq!(item3.read(cx).save_as_count, 0);
-// assert_eq!(item3.read(cx).reload_count, 1);
-// assert_eq!(pane.items_len(), 2);
-// assert_eq!(pane.active_item().unwrap().id(), item4.id());
-// });
-// assert!(window.has_pending_prompt(cx));
-
-// // Confirm saving item 4.
-// window.simulate_prompt_answer(0, cx);
-// cx.foreground().run_until_parked();
-
-// // There's a prompt for a path for item 4.
-// cx.simulate_new_path_selection(|_| Some(Default::default()));
-// close_items.await.unwrap();
-
-// // The requested items are closed.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item4.read(cx).save_count, 0);
-// assert_eq!(item4.read(cx).save_as_count, 1);
-// assert_eq!(item4.read(cx).reload_count, 0);
-// assert_eq!(pane.items_len(), 1);
-// assert_eq!(pane.active_item().unwrap().id(), item2.id());
-// });
-// }
+ // When there are no dirty items, there's nothing to do.
+ let item1 = cx.build_view(|cx| TestItem::new(cx));
+ workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+ let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+ assert!(task.await.unwrap());
-// #[gpui::test]
-// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-
-// // Create several workspace items with single project entries, and two
-// // workspace items with multiple project entries.
-// let single_entry_items = (0..=4)
-// .map(|project_entry_id| {
-// window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(
-// project_entry_id,
-// &format!("{project_entry_id}.txt"),
-// cx,
-// )])
-// })
-// })
-// .collect::<Vec<_>>();
-// let item_2_3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_singleton(false)
-// .with_project_items(&[
-// single_entry_items[2].read(cx).project_items[0].clone(),
-// single_entry_items[3].read(cx).project_items[0].clone(),
-// ])
-// });
-// let item_3_4 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_singleton(false)
-// .with_project_items(&[
-// single_entry_items[3].read(cx).project_items[0].clone(),
-// single_entry_items[4].read(cx).project_items[0].clone(),
-// ])
-// });
+ // When there are dirty untitled items, prompt to save each one. If the user
+ // cancels any prompt, then abort.
+ let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
+ let item3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ workspace.update(cx, |w, cx| {
+ w.add_item(Box::new(item2.clone()), cx);
+ w.add_item(Box::new(item3.clone()), cx);
+ });
+ let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+ cx.executor().run_until_parked();
+ cx.simulate_prompt_answer(2); // cancel save all
+ cx.executor().run_until_parked();
+ cx.simulate_prompt_answer(2); // cancel save all
+ cx.executor().run_until_parked();
+ assert!(!cx.has_pending_prompt());
+ assert!(!task.await.unwrap());
+ }
-// // Create two panes that contain the following project entries:
-// // left pane:
-// // multi-entry items: (2, 3)
-// // single-entry items: 0, 1, 2, 3, 4
-// // right pane:
-// // single-entry items: 1
-// // multi-entry items: (3, 4)
-// let left_pane = workspace.update(cx, |workspace, cx| {
-// let left_pane = workspace.active_pane().clone();
-// workspace.add_item(Box::new(item_2_3.clone()), cx);
-// for item in single_entry_items {
-// workspace.add_item(Box::new(item), cx);
-// }
-// left_pane.update(cx, |pane, cx| {
-// pane.activate_item(2, true, true, cx);
-// });
+ #[gpui::test]
+ async fn test_close_pane_items(cx: &mut TestAppContext) {
+ init_test(cx);
-// workspace
-// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-// .unwrap();
+ let fs = FakeFs::new(cx.executor());
-// left_pane
-// });
+ let project = Project::test(fs, None, cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-// //Need to cause an effect flush in order to respect new focus
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item_3_4.clone()), cx);
-// cx.focus(&left_pane);
-// });
+ let item1 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let item2 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_conflict(true)
+ .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+ });
+ let item3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_conflict(true)
+ .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+ });
+ let item4 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new_untitled(cx)])
+ });
+ let pane = workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item1.clone()), cx);
+ workspace.add_item(Box::new(item2.clone()), cx);
+ workspace.add_item(Box::new(item3.clone()), cx);
+ workspace.add_item(Box::new(item4.clone()), cx);
+ workspace.active_pane().clone()
+ });
-// // When closing all of the items in the left pane, we should be prompted twice:
-// // once for project entry 0, and once for project entry 2. After those two
-// // prompts, the task should complete.
+ let close_items = pane.update(cx, |pane, cx| {
+ pane.activate_item(1, true, true, cx);
+ assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+ let item1_id = item1.item_id();
+ let item3_id = item3.item_id();
+ let item4_id = item4.item_id();
+ pane.close_items(cx, SaveIntent::Close, move |id| {
+ [item1_id, item3_id, item4_id].contains(&id)
+ })
+ });
+ cx.executor().run_until_parked();
+
+ assert!(cx.has_pending_prompt());
+ // Ignore "Save all" prompt
+ cx.simulate_prompt_answer(2);
+ cx.executor().run_until_parked();
+ // There's a prompt to save item 1.
+ pane.update(cx, |pane, _| {
+ assert_eq!(pane.items_len(), 4);
+ assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
+ });
+ // Confirm saving item 1.
+ cx.simulate_prompt_answer(0);
+ cx.executor().run_until_parked();
+
+ // Item 1 is saved. There's a prompt to save item 3.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item1.read(cx).save_count, 1);
+ assert_eq!(item1.read(cx).save_as_count, 0);
+ assert_eq!(item1.read(cx).reload_count, 0);
+ assert_eq!(pane.items_len(), 3);
+ assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
+ });
+ assert!(cx.has_pending_prompt());
+
+ // Cancel saving item 3.
+ cx.simulate_prompt_answer(1);
+ cx.executor().run_until_parked();
+
+ // Item 3 is reloaded. There's a prompt to save item 4.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item3.read(cx).save_count, 0);
+ assert_eq!(item3.read(cx).save_as_count, 0);
+ assert_eq!(item3.read(cx).reload_count, 1);
+ assert_eq!(pane.items_len(), 2);
+ assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
+ });
+ assert!(cx.has_pending_prompt());
+
+ // Confirm saving item 4.
+ cx.simulate_prompt_answer(0);
+ cx.executor().run_until_parked();
+
+ // There's a prompt for a path for item 4.
+ cx.simulate_new_path_selection(|_| Some(Default::default()));
+ close_items.await.unwrap();
+
+ // The requested items are closed.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item4.read(cx).save_count, 0);
+ assert_eq!(item4.read(cx).save_as_count, 1);
+ assert_eq!(item4.read(cx).reload_count, 0);
+ assert_eq!(pane.items_len(), 1);
+ assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+ });
+ }
-// let close = left_pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |_| true)
-// });
-// cx.foreground().run_until_parked();
-// // Discard "Save all" prompt
-// window.simulate_prompt_answer(2, cx);
-
-// cx.foreground().run_until_parked();
-// left_pane.read_with(cx, |pane, cx| {
-// assert_eq!(
-// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-// &[ProjectEntryId::from_proto(0)]
-// );
-// });
-// window.simulate_prompt_answer(0, cx);
+ #[gpui::test]
+ async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+ // Create several workspace items with single project entries, and two
+ // workspace items with multiple project entries.
+ let single_entry_items = (0..=4)
+ .map(|project_entry_id| {
+ cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(
+ project_entry_id,
+ &format!("{project_entry_id}.txt"),
+ cx,
+ )])
+ })
+ })
+ .collect::<Vec<_>>();
+ let item_2_3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_singleton(false)
+ .with_project_items(&[
+ single_entry_items[2].read(cx).project_items[0].clone(),
+ single_entry_items[3].read(cx).project_items[0].clone(),
+ ])
+ });
+ let item_3_4 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_singleton(false)
+ .with_project_items(&[
+ single_entry_items[3].read(cx).project_items[0].clone(),
+ single_entry_items[4].read(cx).project_items[0].clone(),
+ ])
+ });
-// cx.foreground().run_until_parked();
-// left_pane.read_with(cx, |pane, cx| {
-// assert_eq!(
-// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-// &[ProjectEntryId::from_proto(2)]
-// );
-// });
-// window.simulate_prompt_answer(0, cx);
+ // Create two panes that contain the following project entries:
+ // left pane:
+ // multi-entry items: (2, 3)
+ // single-entry items: 0, 1, 2, 3, 4
+ // right pane:
+ // single-entry items: 1
+ // multi-entry items: (3, 4)
+ let left_pane = workspace.update(cx, |workspace, cx| {
+ let left_pane = workspace.active_pane().clone();
+ workspace.add_item(Box::new(item_2_3.clone()), cx);
+ for item in single_entry_items {
+ workspace.add_item(Box::new(item), cx);
+ }
+ left_pane.update(cx, |pane, cx| {
+ pane.activate_item(2, true, true, cx);
+ });
-// cx.foreground().run_until_parked();
-// close.await.unwrap();
-// left_pane.read_with(cx, |pane, _| {
-// assert_eq!(pane.items_len(), 0);
-// });
-// }
+ let right_pane = workspace
+ .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+ .unwrap();
-// #[gpui::test]
-// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-// init_test(cx);
+ right_pane.update(cx, |pane, cx| {
+ pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
+ });
-// let fs = FakeFs::new(cx.background());
+ left_pane
+ });
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+ cx.focus_view(&left_pane);
-// let item = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let item_id = item.id();
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// });
+ // When closing all of the items in the left pane, we should be prompted twice:
+ // once for project entry 0, and once for project entry 2. Project entries 1,
+ // 3, and 4 are all still open in the other paten. After those two
+ // prompts, the task should complete.
-// // Autosave on window change.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnWindowChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ let close = left_pane.update(cx, |pane, cx| {
+ pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
+ });
+ cx.executor().run_until_parked();
-// // Deactivating the window saves the file.
-// window.simulate_deactivation(cx);
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
-
-// // Autosave on focus change.
-// item.update(cx, |item, cx| {
-// cx.focus_self();
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnFocusChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ // Discard "Save all" prompt
+ cx.simulate_prompt_answer(2);
-// // Blurring the item saves the file.
-// item.update(cx, |_, cx| cx.blur());
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+ cx.executor().run_until_parked();
+ left_pane.update(cx, |pane, cx| {
+ assert_eq!(
+ pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+ &[ProjectEntryId::from_proto(0)]
+ );
+ });
+ cx.simulate_prompt_answer(0);
-// // Deactivating the window still saves the file.
-// window.simulate_activation(cx);
-// item.update(cx, |item, cx| {
-// cx.focus_self();
-// item.is_dirty = true;
-// });
-// window.simulate_deactivation(cx);
+ cx.executor().run_until_parked();
+ left_pane.update(cx, |pane, cx| {
+ assert_eq!(
+ pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+ &[ProjectEntryId::from_proto(2)]
+ );
+ });
+ cx.simulate_prompt_answer(0);
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+ cx.executor().run_until_parked();
+ close.await.unwrap();
+ left_pane.update(cx, |pane, _| {
+ assert_eq!(pane.items_len(), 0);
+ });
+ }
-// // Autosave after delay.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
-// })
-// });
-// item.is_dirty = true;
-// cx.emit(TestItemEvent::Edit);
-// });
+ #[gpui::test]
+ async fn test_autosave(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
-// // Delay hasn't fully expired, so the file is still dirty and unsaved.
-// deterministic.advance_clock(Duration::from_millis(250));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-// // After delay expires, the file is saved.
-// deterministic.advance_clock(Duration::from_millis(250));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+ let item = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let item_id = item.entity_id();
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ });
-// // Autosave on focus change, ensuring closing the tab counts as such.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnFocusChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ // Autosave on window change.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnWindowChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-// })
-// .await
-// .unwrap();
-// assert!(!window.has_pending_prompt(cx));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-
-// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// });
-// item.update(cx, |item, cx| {
-// item.project_items[0].update(cx, |item, _| {
-// item.entry_id = None;
-// });
-// item.is_dirty = true;
-// cx.blur();
-// });
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+ // Deactivating the window saves the file.
+ cx.simulate_deactivation();
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 1));
+
+ // Autosave on focus change.
+ item.update(cx, |item, cx| {
+ cx.focus_self();
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnFocusChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// // Ensure autosave is prevented for deleted files also when closing the buffer.
-// let _close_items = pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-// });
-// deterministic.run_until_parked();
-// assert!(window.has_pending_prompt(cx));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-// }
+ // Blurring the item saves the file.
+ item.update(cx, |_, cx| cx.blur());
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 2));
-// #[gpui::test]
-// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
+ // Deactivating the window still saves the file.
+ cx.simulate_activation();
+ item.update(cx, |item, cx| {
+ cx.focus_self();
+ item.is_dirty = true;
+ });
+ cx.simulate_deactivation();
-// let fs = FakeFs::new(cx.background());
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 3));
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ // Autosave after delay.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+ })
+ });
+ item.is_dirty = true;
+ cx.emit(ItemEvent::Edit);
+ });
-// let item = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
-// let toolbar_notify_count = Rc::new(RefCell::new(0));
-
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// let toolbar_notification_count = toolbar_notify_count.clone();
-// cx.observe(&toolbar, move |_, _, _| {
-// *toolbar_notification_count.borrow_mut() += 1
-// })
-// .detach();
-// });
+ // Delay hasn't fully expired, so the file is still dirty and unsaved.
+ cx.executor().advance_clock(Duration::from_millis(250));
+ item.update(cx, |item, _| assert_eq!(item.save_count, 3));
-// pane.read_with(cx, |pane, _| {
-// assert!(!pane.can_navigate_backward());
-// assert!(!pane.can_navigate_forward());
-// });
+ // After delay expires, the file is saved.
+ cx.executor().advance_clock(Duration::from_millis(250));
+ item.update(cx, |item, _| assert_eq!(item.save_count, 4));
-// item.update(cx, |item, cx| {
-// item.set_state("one".to_string(), cx);
-// });
+ // Autosave on focus change, ensuring closing the tab counts as such.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnFocusChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// // Toolbar must be notified to re-render the navigation buttons
-// assert_eq!(*toolbar_notify_count.borrow(), 1);
+ pane.update(cx, |pane, cx| {
+ pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+ })
+ .await
+ .unwrap();
+ assert!(!cx.has_pending_prompt());
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+
+ // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ });
+ item.update(cx, |item, cx| {
+ item.project_items[0].update(cx, |item, _| {
+ item.entry_id = None;
+ });
+ item.is_dirty = true;
+ cx.blur();
+ });
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
-// pane.read_with(cx, |pane, _| {
-// assert!(pane.can_navigate_backward());
-// assert!(!pane.can_navigate_forward());
-// });
+ // Ensure autosave is prevented for deleted files also when closing the buffer.
+ let _close_items = pane.update(cx, |pane, cx| {
+ pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+ });
+ cx.executor().run_until_parked();
+ assert!(cx.has_pending_prompt());
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+ }
-// workspace
-// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-// .await
-// .unwrap();
+ #[gpui::test]
+ async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
-// assert_eq!(*toolbar_notify_count.borrow(), 3);
-// pane.read_with(cx, |pane, _| {
-// assert!(!pane.can_navigate_backward());
-// assert!(pane.can_navigate_forward());
-// });
-// }
+ let fs = FakeFs::new(cx.executor());
-// #[gpui::test]
-// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
-// let fs = FakeFs::new(cx.background());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ let item = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
+ let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ let toolbar_notification_count = toolbar_notify_count.clone();
+ cx.observe(&toolbar, move |_, _, _| {
+ *toolbar_notification_count.borrow_mut() += 1
+ })
+ .detach();
+ });
-// let panel = workspace.update(cx, |workspace, cx| {
-// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-// workspace.add_panel(panel.clone(), cx);
+ pane.update(cx, |pane, _| {
+ assert!(!pane.can_navigate_backward());
+ assert!(!pane.can_navigate_forward());
+ });
-// workspace
-// .right_dock()
-// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+ item.update(cx, |item, cx| {
+ item.set_state("one".to_string(), cx);
+ });
-// panel
-// });
+ // Toolbar must be notified to re-render the navigation buttons
+ assert_eq!(*toolbar_notify_count.borrow(), 1);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// pane.update(cx, |pane, cx| {
-// let item = cx.build_view(|_| TestItem::new());
-// pane.add_item(Box::new(item), true, true, None, cx);
-// });
+ pane.update(cx, |pane, _| {
+ assert!(pane.can_navigate_backward());
+ assert!(!pane.can_navigate_forward());
+ });
-// // Transfer focus from center to panel
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ workspace
+ .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+ .await
+ .unwrap();
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ assert_eq!(*toolbar_notify_count.borrow(), 2);
+ pane.update(cx, |pane, _| {
+ assert!(!pane.can_navigate_backward());
+ assert!(pane.can_navigate_forward());
+ });
+ }
-// // Transfer focus from panel to center
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // #[gpui::test]
+ // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+ // init_test(cx);
+ // let fs = FakeFs::new(cx.executor());
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // let project = Project::test(fs, [], cx).await;
+ // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ // let workspace = window.root(cx);
-// // Close the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx);
-// });
+ // let panel = workspace.update(cx, |workspace, cx| {
+ // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+ // workspace.add_panel(panel.clone(), cx);
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace
+ // .right_dock()
+ // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-// // Open the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx);
-// });
+ // panel
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ // pane.update(cx, |pane, cx| {
+ // let item = cx.build_view(|_| TestItem::new(cx));
+ // pane.add_item(Box::new(item), true, true, None, cx);
+ // });
-// // Focus and zoom panel
-// panel.update(cx, |panel, cx| {
-// cx.focus_self();
-// panel.set_zoomed(true, cx)
-// });
+ // // Transfer focus from center to panel
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Transfer focus to the center closes the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // // Transfer focus from panel to center
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// // Transferring focus back to the panel keeps it zoomed
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // // Close the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// // Close the dock while it is zoomed
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
+ // // Open the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(workspace.zoomed.is_none());
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Opening the dock, when it's zoomed, retains focus
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
+ // // Focus and zoom panel
+ // panel.update(cx, |panel, cx| {
+ // cx.focus_self();
+ // panel.set_zoomed(true, cx)
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(workspace.zoomed.is_some());
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Unzoom and close the panel, zoom the active pane.
-// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
-// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+ // // Transfer focus to the center closes the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// // Opening a dock unzooms the pane.
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
-// workspace.read_with(cx, |workspace, cx| {
-// let pane = pane.read(cx);
-// assert!(!pane.is_zoomed());
-// assert!(!pane.has_focus());
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(workspace.zoomed.is_none());
-// });
-// }
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// #[gpui::test]
-// async fn test_panels(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
-// let fs = FakeFs::new(cx.background());
-
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-
-// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-// // Add panel_1 on the left, panel_2 on the right.
-// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-// workspace.add_panel(panel_1.clone(), cx);
-// workspace
-// .left_dock()
-// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-// workspace.add_panel(panel_2.clone(), cx);
-// workspace
-// .right_dock()
-// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-// let left_dock = workspace.left_dock();
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(
-// left_dock.read(cx).active_panel_size(cx).unwrap(),
-// panel_1.size(cx)
-// );
+ // // Transferring focus back to the panel keeps it zoomed
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// left_dock.update(cx, |left_dock, cx| {
-// left_dock.resize_active_panel(Some(1337.), cx)
-// });
-// assert_eq!(
-// workspace
-// .right_dock()
-// .read(cx)
-// .visible_panel()
-// .unwrap()
-// .id(),
-// panel_2.id()
-// );
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// (panel_1, panel_2)
-// });
+ // // Close the dock while it is zoomed
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
-// // Move panel_1 to the right
-// panel_1.update(cx, |panel_1, cx| {
-// panel_1.set_position(DockPosition::Right, cx)
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(workspace.zoomed.is_none());
+ // assert!(!panel.has_focus(cx));
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-// // Since it was the only panel on the left, the left dock should now be closed.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-// let right_dock = workspace.right_dock();
-// assert_eq!(
-// right_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+ // // Opening the dock, when it's zoomed, retains focus
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
-// // Now we move panel_2Β to the left
-// panel_2.set_position(DockPosition::Left, cx);
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(workspace.zoomed.is_some());
+ // assert!(panel.has_focus(cx));
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_2 was not visible on the right, we don't open the left dock.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// // And the right dock is unaffected in it's displaying of panel_1
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert_eq!(
-// workspace
-// .right_dock()
-// .read(cx)
-// .visible_panel()
-// .unwrap()
-// .id(),
-// panel_1.id()
-// );
-// });
+ // // Unzoom and close the panel, zoom the active pane.
+ // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
+ // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
-// // Move panel_1 back to the left
-// panel_1.update(cx, |panel_1, cx| {
-// panel_1.set_position(DockPosition::Left, cx)
-// });
+ // // Opening a dock unzooms the pane.
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
+ // workspace.update(cx, |workspace, cx| {
+ // let pane = pane.read(cx);
+ // assert!(!pane.is_zoomed());
+ // assert!(!pane.has_focus());
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(workspace.zoomed.is_none());
+ // });
+ // }
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-// // And right the dock should be closed as it no longer has any panels.
-// assert!(!workspace.right_dock().read(cx).is_open());
+ // #[gpui::test]
+ // async fn test_panels(cx: &mut gpui::TestAppContext) {
+ // init_test(cx);
+ // let fs = FakeFs::new(cx.executor());
+
+ // let project = Project::test(fs, [], cx).await;
+ // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ // let workspace = window.root(cx);
+
+ // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+ // // Add panel_1 on the left, panel_2 on the right.
+ // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
+ // workspace.add_panel(panel_1.clone(), cx);
+ // workspace
+ // .left_dock()
+ // .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+ // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+ // workspace.add_panel(panel_2.clone(), cx);
+ // workspace
+ // .right_dock()
+ // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+ // let left_dock = workspace.left_dock();
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(
+ // left_dock.read(cx).active_panel_size(cx).unwrap(),
+ // panel_1.size(cx)
+ // );
+
+ // left_dock.update(cx, |left_dock, cx| {
+ // left_dock.resize_active_panel(Some(1337.), cx)
+ // });
+ // assert_eq!(
+ // workspace
+ // .right_dock()
+ // .read(cx)
+ // .visible_panel()
+ // .unwrap()
+ // .id(),
+ // panel_2.id()
+ // );
+
+ // (panel_1, panel_2)
+ // });
-// // Now we move panel_1 to the bottom
-// panel_1.set_position(DockPosition::Bottom, cx);
-// });
+ // // Move panel_1 to the right
+ // panel_1.update(cx, |panel_1, cx| {
+ // panel_1.set_position(DockPosition::Right, cx)
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the left, we close the left dock.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// // The bottom dock is sized based on the panel's default size,
-// // since the panel orientation changed from vertical to horizontal.
-// let bottom_dock = workspace.bottom_dock();
-// assert_eq!(
-// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-// panel_1.size(cx),
-// );
-// // Close bottom dock and move panel_1 back to the left.
-// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-// panel_1.set_position(DockPosition::Left, cx);
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+ // // Since it was the only panel on the left, the left dock should now be closed.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+ // let right_dock = workspace.right_dock();
+ // assert_eq!(
+ // right_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+ // // Now we move panel_2Β to the left
+ // panel_2.set_position(DockPosition::Left, cx);
+ // });
-// // Emit activated event on panel 1
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_2 was not visible on the right, we don't open the left dock.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // // And the right dock is unaffected in it's displaying of panel_1
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert_eq!(
+ // workspace
+ // .right_dock()
+ // .read(cx)
+ // .visible_panel()
+ // .unwrap()
+ // .id(),
+ // panel_1.id()
+ // );
+ // });
-// // Now the left dock is open and panel_1 is active and focused.
-// workspace.read_with(cx, |workspace, cx| {
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert!(panel_1.is_focused(cx));
-// });
+ // // Move panel_1 back to the left
+ // panel_1.update(cx, |panel_1, cx| {
+ // panel_1.set_position(DockPosition::Left, cx)
+ // });
-// // Emit closed event on panel 2, which is not active
-// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+ // // And right the dock should be closed as it no longer has any panels.
+ // assert!(!workspace.right_dock().read(cx).is_open());
+
+ // // Now we move panel_1 to the bottom
+ // panel_1.set_position(DockPosition::Bottom, cx);
+ // });
-// // Wo don't close the left dock, because panel_2 wasn't the active panel
-// workspace.read_with(cx, |workspace, cx| {
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the left, we close the left dock.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // // The bottom dock is sized based on the panel's default size,
+ // // since the panel orientation changed from vertical to horizontal.
+ // let bottom_dock = workspace.bottom_dock();
+ // assert_eq!(
+ // bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+ // panel_1.size(cx),
+ // );
+ // // Close bottom dock and move panel_1 back to the left.
+ // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+ // panel_1.set_position(DockPosition::Left, cx);
+ // });
-// // Emitting a ZoomIn event shows the panel as zoomed.
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-// });
+ // // Emit activated event on panel 1
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+
+ // // Now the left dock is open and panel_1 is active and focused.
+ // workspace.update(cx, |workspace, cx| {
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert!(panel_1.is_focused(cx));
+ // });
-// // Move panel to another dock while it is zoomed
-// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // Emit closed event on panel 2, which is not active
+ // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+ // // Wo don't close the left dock, because panel_2 wasn't the active panel
+ // workspace.update(cx, |workspace, cx| {
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // });
-// // If focus is transferred to another view that's not a panel or another pane, we still show
-// // the panel as zoomed.
-// let focus_receiver = window.build_view(cx, |_| EmptyView);
-// focus_receiver.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // Emitting a ZoomIn event shows the panel as zoomed.
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+ // });
-// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-// workspace.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // Move panel to another dock while it is zoomed
+ // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // If focus is transferred again to another view that's not a panel or a pane, we won't
-// // show the panel as zoomed because it wasn't zoomed before.
-// focus_receiver.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // If focus is transferred to another view that's not a panel or another pane, we still show
+ // // the panel as zoomed.
+ // let focus_receiver = cx.build_view(|_| EmptyView);
+ // focus_receiver.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // When focus is transferred back to the panel, it is zoomed again.
-// panel_1.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+ // workspace.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// // Emitting a ZoomOut event unzooms the panel.
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // If focus is transferred again to another view that's not a panel or a pane, we won't
+ // // show the panel as zoomed because it wasn't zoomed before.
+ // focus_receiver.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// // Emit closed event on panel 1, which is active
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+ // // When focus is transferred back to the panel, it is zoomed again.
+ // panel_1.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // Now the left dock is closed, because panel_1 was the active panel
-// workspace.read_with(cx, |workspace, cx| {
-// let right_dock = workspace.right_dock();
-// assert!(!right_dock.read(cx).is_open());
-// });
-// }
+ // // Emitting a ZoomOut event unzooms the panel.
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// pub fn init_test(cx: &mut TestAppContext) {
-// cx.foreground().forbid_parking();
-// cx.update(|cx| {
-// cx.set_global(SettingsStore::test(cx));
-// theme::init((), cx);
-// language::init(cx);
-// crate::init_settings(cx);
-// Project::init_settings(cx);
-// });
-// }
-// }
+ // // Emit closed event on panel 1, which is active
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+ // // Now the left dock is closed, because panel_1 was the active panel
+ // workspace.update(cx, |workspace, cx| {
+ // let right_dock = workspace.right_dock();
+ // assert!(!right_dock.read(cx).is_open());
+ // });
+ // }
+
+ pub fn init_test(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ language::init(cx);
+ crate::init_settings(cx);
+ Project::init_settings(cx);
+ });
+ }
+}
@@ -1,6 +1,6 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
#[derive(Deserialize)]
pub struct WorkspaceSettings {
@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
fn workspace_configuration(
&self,
+ _workspace_root: &Path,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names().collect::<Vec<_>>();
@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
Self { node }
}
@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
}))
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"tailwindCSS": {
"emmetCompletions": true,
@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
EsLintLspAdapter { node }
}
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"": {
"validate": "on",
"rulesCustomizations": [],
"run": "onType",
"nodePath": null,
+ "workingDirectory": {"mode": "auto"},
+ "workspaceFolder": {
+ "uri": workspace_root,
+ "name": workspace_root.file_name()
+ .unwrap_or_else(|| workspace_root.as_os_str()),
+ },
}
}))
.boxed()
@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
- fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
let tab_size = all_language_settings(None, cx)
.language(Some("YAML"))
.tab_size;
@@ -17,9 +17,9 @@ path = "src/main.rs"
[dependencies]
ai = { package = "ai2", path = "../ai2"}
audio = { package = "audio2", path = "../audio2" }
-# activity_indicator = { path = "../activity_indicator" }
+activity_indicator = { package = "activity_indicator2", path = "../activity_indicator2"}
auto_update = { package = "auto_update2", path = "../auto_update2" }
-# breadcrumbs = { path = "../breadcrumbs" }
+breadcrumbs = { package = "breadcrumbs2", path = "../breadcrumbs2" }
call = { package = "call2", path = "../call2" }
channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" }
@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
fn workspace_configuration(
&self,
+ _workspace_root: &Path,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names();
@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
Self { node }
}
@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
}))
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"tailwindCSS": {
"emmetCompletions": true,
@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
EsLintLspAdapter { node }
}
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"": {
"validate": "on",
"rulesCustomizations": [],
"run": "onType",
"nodePath": null,
+ "workingDirectory": {"mode": "auto"},
+ "workspaceFolder": {
+ "uri": workspace_root,
+ "name": workspace_root.file_name()
+ .unwrap_or_else(|| workspace_root.as_os_str()),
+ },
}
}))
.boxed()
@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
- fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
let tab_size = all_language_settings(None, cx)
.language(Some("YAML"))
.tab_size;
@@ -746,9 +746,9 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
}
#[cfg(not(debug_assertions))]
-async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
+async fn watch_languages(_: Arc<dyn fs::Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
None
}
#[cfg(not(debug_assertions))]
-fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
+fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
@@ -7,6 +7,7 @@ mod only_instance;
mod open_listener;
pub use assets::*;
+use breadcrumbs::Breadcrumbs;
use collections::VecDeque;
use editor::{Editor, MultiBuffer};
use gpui::{
@@ -95,11 +96,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
if let workspace::Event::PaneAdded(pane) = event {
pane.update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
- // todo!()
- // let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
- // toolbar.add_item(breadcrumbs, cx);
+ let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
+ toolbar.add_item(breadcrumbs, cx);
let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
toolbar.add_item(buffer_search_bar.clone(), cx);
+ // todo!()
// let quick_action_bar = cx.add_view(|_| {
// QuickActionBar::new(buffer_search_bar, workspace)
// });
@@ -139,27 +140,24 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
// cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
let diagnostic_summary =
cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
- // let activity_indicator = activity_indicator::ActivityIndicator::new(
- // workspace,
- // app_state.languages.clone(),
- // cx,
- // );
+ let activity_indicator =
+ activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
// let active_buffer_language =
// cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
// let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
// let feedback_button = cx.add_view(|_| {
// feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
// });
- // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+ let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx);
- // status_bar.add_left_item(activity_indicator, cx);
+ status_bar.add_left_item(activity_indicator, cx);
// status_bar.add_right_item(feedback_button, cx);
// status_bar.add_right_item(copilot, cx);
// status_bar.add_right_item(active_buffer_language, cx);
// status_bar.add_right_item(vim_mode_indicator, cx);
- // status_bar.add_right_item(cursor_position, cx);
+ status_bar.add_right_item(cursor_position, cx);
});
auto_update::notify_of_any_new_update(cx);
@@ -1,5618 +0,0 @@
-#![feature(prelude_import)]
-#![allow(dead_code, unused_variables)]
-#[prelude_import]
-use std::prelude::rust_2021::*;
-#[macro_use]
-extern crate std;
-use color::black;
-use components::button;
-use element::Element;
-use frame::frame;
-use gpui::{
- geometry::{rect::RectF, vector::vec2f},
- platform::WindowOptions,aa
-};
-use log::LevelFilter;a
-use simplelog::SimpleLogger;
-use themes::{rose_pine, ThemeColors};
-use view::view;a
-mod adapter {
- use crate::element::AnyElement;
- use crate::element::{LayoutContext, PaintContext};
- use gpui::{geometry::rect::RectF, LayoutEngine};aaaa
- use util::ResultExt;
- pub struct Adapter<V>(pub(crate) AnyElement<V>);
- impl<V: 'static> gpui::Element<V> for Adapter<V> {aa
- type LayoutState = Option<LayaoutEngine>;
- type PaintState = ();
- fn layout(
- &mut self,
- constraint: gpui::SizeConstraint,
- view: &mut V,
- cx: &mut LayoutContext<V>,aa
- ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
- cx.push_layout_engine(LayoutEngine::new());
- let node = self.0.layout(view, cx).log_err();a
- if let Some(node) = node {
- let layout_engine = cx.layout_engine().unwrap();
- layout_engine.compute_layout(node, constraint.max).log_err();
- }
- let layout_engine = cx.pop_layout_engine();
- if true {a
- if !layout_engine.is_some() {
- ::core::panicking::panic("assertion failed: layout_engine.is_some()")
- }
- }
- (constraint.max, layout_engine)a
- }
- fn paint(a
- &mut self,
- scene: &mut gpui::SceneBuilder,
- bounds: RectF,
- visible_bounds: RectF,
- layout_engine: &mut Option<LayoutEngine>,
- view: &mut V,
- legacy_cx: &mut gpui::PaintContext<V>,aaa
- ) -> Self::PaintState {
- legacy_cx.push_layout_engine(layout_engine.take().unwrap());
- let mut cx = PaintContext::new(legacy_cx, scene);
- self.0.paint(view, &mut cx).log_err();
- *layout_engine = legacy_cx.pop_layout_engine();
- if true {
- if !layout_engine.is_some() {
- ::core::panicking::panic("assertion failed: layout_engine.is_some()")
- }
- }
- }
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- bounds: RectF,
- visible_bounds: RectF,
- layout: &Self::LayoutState,
- paint: &Self::PaintState,
- view: &V,
- cx: &gpui::ViewContext<V>,
- ) -> Option<RectF> {
- ::core::panicking::panic("not yet implemented")
- }
- fn debug(
- &self,
- bounds: RectF,
- layout: &Self::LayoutState,
- paint: &Self::PaintState,
- view: &V,
- cx: &gpui::ViewContext<V>,
- ) -> gpui::serde_json::Value {
- ::core::panicking::panic("not yet implemented")
- }
- }
-}
-mod color {
- #![allow(dead_code)]
- use smallvec::SmallVec;
- use std::{num::ParseIntError, ops::Range};
- pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
- let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
- let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
- let b = (hex & 0xFF) as f32 / 255.0;
- Rgba { r, g, b, a: 1.0 }.into()
- }
- pub struct Rgba {
- pub r: f32,
- pub g: f32,
- pub b: f32,
- pub a: f32,
- }
- #[automatically_derived]
- impl ::core::clone::Clone for Rgba {
- #[inline]
- fn clone(&self) -> Rgba {
- let _: ::core::clone::AssertParamIsClone<f32>;
- *self
- }
- }
- #[automatically_derived]
- impl ::core::marker::Copy for Rgba {}
- #[automatically_derived]
- impl ::core::default::Default for Rgba {
- #[inline]
- fn default() -> Rgba {
- Rgba {
- r: ::core::default::Default::default(),
- g: ::core::default::Default::default(),
- b: ::core::default::Default::default(),
- a: ::core::default::Default::default(),
- }
- }
- }
- #[automatically_derived]
- impl ::core::fmt::Debug for Rgba {
- fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
- ::core::fmt::Formatter::debug_struct_field4_finish(
- f, "Rgba", "r", &self.r, "g", &self.g, "b", &self.b, "a", &&self.a,
- )
- }
- }
- pub trait Lerp {
- fn lerp(&self, level: f32) -> Hsla;
- }
- impl Lerp for Range<Hsla> {
- fn lerp(&self, level: f32) -> Hsla {
- let level = level.clamp(0., 1.);
- Hsla {
- h: self.start.h + (level * (self.end.h - self.start.h)),
- s: self.start.s + (level * (self.end.s - self.start.s)),
- l: self.start.l + (level * (self.end.l - self.start.l)),
- a: self.start.a + (level * (self.end.a - self.start.a)),
- }
- }
- }
- impl From<gpui::color::Color> for Rgba {
- fn from(value: gpui::color::Color) -> Self {
- Self {
- r: value.0.r as f32 / 255.0,
- g: value.0.g as f32 / 255.0,
- b: value.0.b as f32 / 255.0,
- a: value.0.a as f32 / 255.0,
- }
- }
- }
- impl From<Hsla> for Rgba {
- fn from(color: Hsla) -> Self {
- let h = color.h;
- let s = color.s;
- let l = color.l;
- let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
- let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
- let m = l - c / 2.0;
- let cm = c + m;
- let xm = x + m;
- let (r, g, b) = match (h * 6.0).floor() as i32 {
- 0 | 6 => (cm, xm, m),
- 1 => (xm, cm, m),
- 2 => (m, cm, xm),
- 3 => (m, xm, cm),
- 4 => (xm, m, cm),
- _ => (cm, m, xm),
- };
- Rgba {
- r,
- g,
- b,
- a: color.a,
- }
- }
- }
- impl TryFrom<&'_ str> for Rgba {
- type Error = ParseIntError;
- fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
- let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
- let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
- let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
- let a = if value.len() > 7 {
- u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
- } else {
- 1.0
- };
- Ok(Rgba { r, g, b, a })
- }
- }
- impl Into<gpui::color::Color> for Rgba {
- fn into(self) -> gpui::color::Color {
- gpui::color::rgba(self.r, self.g, self.b, self.a)
- }
- }
- pub struct Hsla {
- pub h: f32,
- pub s: f32,
- pub l: f32,
- pub a: f32,
- }
- #[automatically_derived]
- impl ::core::default::Default for Hsla {
- #[inline]
- fn default() -> Hsla {
- Hsla {
- h: ::core::default::Default::default(),
- s: ::core::default::Default::default(),
- l: ::core::default::Default::default(),
- a: ::core::default::Default::default(),
- }
- }
- }
- #[automatically_derived]
- impl ::core::marker::Copy for Hsla {}
- #[automatically_derived]
- impl ::core::clone::Clone for Hsla {
- #[inline]
- fn clone(&self) -> Hsla {
- let _: ::core::clone::AssertParamIsClone<f32>;
- *self
- }
- }
- #[automatically_derived]
- impl ::core::fmt::Debug for Hsla {
- fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
- ::core::fmt::Formatter::debug_struct_field4_finish(
- f, "Hsla", "h", &self.h, "s", &self.s, "l", &self.l, "a", &&self.a,
- )
- }
- }
- #[automatically_derived]
- impl ::core::marker::StructuralPartialEq for Hsla {}
- #[automatically_derived]
- impl ::core::cmp::PartialEq for Hsla {
- #[inline]
- fn eq(&self, other: &Hsla) -> bool {
- self.h == other.h && self.s == other.s && self.l == other.l && self.a == other.a
- }
- }
- pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
- Hsla {
- h: h.clamp(0., 1.),
- s: s.clamp(0., 1.),
- l: l.clamp(0., 1.),
- a: a.clamp(0., 1.),
- }
- }
- pub fn black() -> Hsla {
- Hsla {
- h: 0.,
- s: 0.,
- l: 0.,
- a: 1.,
- }
- }
- impl From<Rgba> for Hsla {
- fn from(color: Rgba) -> Self {
- let r = color.r;
- let g = color.g;
- let b = color.b;
- let max = r.max(g.max(b));
- let min = r.min(g.min(b));
- let delta = max - min;
- let l = (max + min) / 2.0;
- let s = if l == 0.0 || l == 1.0 {
- 0.0
- } else if l < 0.5 {
- delta / (2.0 * l)
- } else {
- delta / (2.0 - 2.0 * l)
- };
- let h = if delta == 0.0 {
- 0.0
- } else if max == r {
- ((g - b) / delta).rem_euclid(6.0) / 6.0
- } else if max == g {
- ((b - r) / delta + 2.0) / 6.0
- } else {
- ((r - g) / delta + 4.0) / 6.0
- };
- Hsla {
- h,
- s,
- l,
- a: color.a,
- }
- }
- }
- impl Hsla {
- /// Scales the saturation and lightness by the given values, clamping at 1.0.
- pub fn scale_sl(mut self, s: f32, l: f32) -> Self {
- self.s = (self.s * s).clamp(0., 1.);
- self.l = (self.l * l).clamp(0., 1.);
- self
- }
- /// Increases the saturation of the color by a certain amount, with a max
- /// value of 1.0.
- pub fn saturate(mut self, amount: f32) -> Self {
- self.s += amount;
- self.s = self.s.clamp(0.0, 1.0);
- self
- }
- /// Decreases the saturation of the color by a certain amount, with a min
- /// value of 0.0.
- pub fn desaturate(mut self, amount: f32) -> Self {
- self.s -= amount;
- self.s = self.s.max(0.0);
- if self.s < 0.0 {
- self.s = 0.0;
- }
- self
- }
- /// Brightens the color by increasing the lightness by a certain amount,
- /// with a max value of 1.0.
- pub fn brighten(mut self, amount: f32) -> Self {
- self.l += amount;
- self.l = self.l.clamp(0.0, 1.0);
- self
- }
- /// Darkens the color by decreasing the lightness by a certain amount,
- /// with a max value of 0.0.
- pub fn darken(mut self, amount: f32) -> Self {
- self.l -= amount;
- self.l = self.l.clamp(0.0, 1.0);
- self
- }
- }
- impl From<gpui::color::Color> for Hsla {
- fn from(value: gpui::color::Color) -> Self {
- Rgba::from(value).into()
- }
- }
- impl Into<gpui::color::Color> for Hsla {
- fn into(self) -> gpui::color::Color {
- Rgba::from(self).into()
- }
- }
- pub struct ColorScale {
- colors: SmallVec<[Hsla; 2]>,
- positions: SmallVec<[f32; 2]>,
- }
- pub fn scale<I, C>(colors: I) -> ColorScale
- where
- I: IntoIterator<Item = C>,
- C: Into<Hsla>,
- {
- let mut scale = ColorScale {
- colors: colors.into_iter().map(Into::into).collect(),
- positions: SmallVec::new(),
- };
- let num_colors: f32 = scale.colors.len() as f32 - 1.0;
- scale.positions = (0..scale.colors.len())
- .map(|i| i as f32 / num_colors)
- .collect();
- scale
- }
- impl ColorScale {
- fn at(&self, t: f32) -> Hsla {
- if true {
- if !(0.0 <= t && t <= 1.0) {
- {
- ::core::panicking::panic_fmt(format_args!(
- "t value {0} is out of range. Expected value in range 0.0 to 1.0",
- t,
- ));
- }
- }
- }
- let position = match self
- .positions
- .binary_search_by(|a| a.partial_cmp(&t).unwrap())
- {
- Ok(index) | Err(index) => index,
- };
- let lower_bound = position.saturating_sub(1);
- let upper_bound = position.min(self.colors.len() - 1);
- let lower_color = &self.colors[lower_bound];
- let upper_color = &self.colors[upper_bound];
- match upper_bound.checked_sub(lower_bound) {
- Some(0) | None => *lower_color,
- Some(_) => {
- let interval_t = (t - self.positions[lower_bound])
- / (self.positions[upper_bound] - self.positions[lower_bound]);
- let h = lower_color.h + interval_t * (upper_color.h - lower_color.h);
- let s = lower_color.s + interval_t * (upper_color.s - lower_color.s);
- let l = lower_color.l + interval_t * (upper_color.l - lower_color.l);
- let a = lower_color.a + interval_t * (upper_color.a - lower_color.a);
- Hsla { h, s, l, a }
- }
- }
- }
- }
-}
-mod components {
- use crate::{
- element::{Element, ElementMetadata},
- frame,
- text::ArcCow,
- themes::rose_pine,
- };
- use gpui::{platform::MouseButton, ViewContext};
- use gpui_macros::Element;
- use std::{marker::PhantomData, rc::Rc};
- struct ButtonHandlers<V, D> {
- click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
- }
- impl<V, D> Default for ButtonHandlers<V, D> {
- fn default() -> Self {
- Self { click: None }
- }
- }
- #[element_crate = "crate"]
- pub struct Button<V: 'static, D: 'static> {
- metadata: ElementMetadata<V>,
- handlers: ButtonHandlers<V, D>,
- label: Option<ArcCow<'static, str>>,
- icon: Option<ArcCow<'static, str>>,
- data: Rc<D>,
- view_type: PhantomData<V>,
- }
- impl<V: 'static, D: 'static> crate::element::Element<V> for Button<V, D> {
- type Layout = crate::element::AnyElement<V>;
- fn declared_style(&mut self) -> &mut crate::style::OptionalStyle {
- &mut self.metadata.style
- }
- fn handlers_mut(&mut self) -> &mut Vec<crate::element::EventHandler<V>> {
- &mut self.metadata.handlers
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut crate::element::LayoutContext<V>,
- ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
- let mut element = self.render(view, cx).into_any();
- let node_id = element.layout(view, cx)?;
- Ok((node_id, element))
- }
- fn paint<'a>(
- &mut self,
- layout: crate::element::Layout<'a, Self::Layout>,
- view: &mut V,
- cx: &mut crate::element::PaintContext<V>,
- ) -> anyhow::Result<()> {
- layout.from_element.paint(view, cx)?;
- Ok(())
- }
- }
- impl<V: 'static, D: 'static> crate::element::IntoElement<V> for Button<V, D> {
- type Element = Self;
- fn into_element(self) -> Self {
- self
- }
- }
- impl<V: 'static> Button<V, ()> {
- fn new() -> Self {
- Self {
- metadata: Default::default(),
- handlers: ButtonHandlers::default(),
- label: None,
- icon: None,
- data: Rc::new(()),
- view_type: PhantomData,
- }
- }
- pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
- Button {
- metadata: Default::default(),
- handlers: ButtonHandlers::default(),
- label: self.label,
- icon: self.icon,
- data: Rc::new(data),
- view_type: PhantomData,
- }
- }
- }
- impl<V: 'static, D: 'static> Button<V, D> {
- pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
- self.label = Some(label.into());
- self
- }
- pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
- self.icon = Some(icon.into());
- self
- }
- pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
- let data = self.data.clone();
- Element::click(self, MouseButton::Left, move |view, _, cx| {
- handler(view, data.as_ref(), cx);
- })
- }
- }
- pub fn button<V>() -> Button<V, ()> {
- Button::new()
- }
- impl<V: 'static, D: 'static> Button<V, D> {
- fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
- let button = frame()
- .fill(rose_pine::dawn().error(0.5))
- .h_4()
- .children(self.label.clone());
- if let Some(handler) = self.handlers.click.clone() {
- let data = self.data.clone();
- button.mouse_down(MouseButton::Left, move |view, event, cx| {
- handler(view, data.as_ref(), cx)
- })
- } else {
- button
- }
- }
- }
-}
-mod element {
- pub use crate::paint_context::PaintContext;
- use crate::{
- adapter::Adapter,
- color::Hsla,
- hoverable::Hoverable,
- style::{Display, Fill, OptionalStyle, Overflow, Position},
- };
- use anyhow::Result;
- pub use gpui::LayoutContext;
- use gpui::{
- geometry::{DefinedLength, Length, OptionalPoint},
- platform::{MouseButton, MouseButtonEvent},
- EngineLayout, EventContext, RenderContext, ViewContext,
- };
- use gpui_macros::tailwind_lengths;
- use std::{
- any::{Any, TypeId},
- cell::Cell,
- rc::Rc,
- };
- pub use taffy::tree::NodeId;
- pub struct Layout<'a, E: ?Sized> {
- pub from_engine: EngineLayout,
- pub from_element: &'a mut E,
- }
- pub struct ElementMetadata<V> {
- pub style: OptionalStyle,
- pub handlers: Vec<EventHandler<V>>,
- }
- pub struct EventHandler<V> {
- handler: Rc<dyn Fn(&mut V, &dyn Any, &mut EventContext<V>)>,
- event_type: TypeId,
- outside_bounds: bool,
- }
- impl<V> Clone for EventHandler<V> {
- fn clone(&self) -> Self {
- Self {
- handler: self.handler.clone(),
- event_type: self.event_type,
- outside_bounds: self.outside_bounds,
- }
- }
- }
- impl<V> Default for ElementMetadata<V> {
- fn default() -> Self {
- Self {
- style: OptionalStyle::default(),
- handlers: Vec::new(),
- }
- }
- }
- pub trait Element<V: 'static>: 'static {
- type Layout: 'static;
- fn declared_style(&mut self) -> &mut OptionalStyle;
- fn computed_style(&mut self) -> &OptionalStyle {
- self.declared_style()
- }
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<(NodeId, Self::Layout)>;
- fn paint<'a>(
- &mut self,
- layout: Layout<Self::Layout>,
- view: &mut V,
- cx: &mut PaintContext<V>,
- ) -> Result<()>;
- /// Convert to a dynamically-typed element suitable for layout and paint.
- fn into_any(self) -> AnyElement<V>
- where
- Self: 'static + Sized,
- {
- AnyElement {
- element: Box::new(self) as Box<dyn ElementObject<V>>,
- layout: None,
- }
- }
- fn adapt(self) -> Adapter<V>
- where
- Self: Sized,
- Self: Element<V>,
- {
- Adapter(self.into_any())
- }
- fn click(
- self,
- button: MouseButton,
- handler: impl Fn(&mut V, &MouseButtonEvent, &mut ViewContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- let pressed: Rc<Cell<bool>> = Default::default();
- self.mouse_down(button, {
- let pressed = pressed.clone();
- move |_, _, _| {
- pressed.set(true);
- }
- })
- .mouse_up_outside(button, {
- let pressed = pressed.clone();
- move |_, _, _| {
- pressed.set(false);
- }
- })
- .mouse_up(button, move |view, event, event_cx| {
- if pressed.get() {
- pressed.set(false);
- handler(view, event, event_cx);
- }
- })
- }
- fn mouse_down(
- mut self,
- button: MouseButton,
- handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.handlers_mut().push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: false,
- });
- self
- }
- fn mouse_down_outside(
- mut self,
- button: MouseButton,
- handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.handlers_mut().push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: true,
- });
- self
- }
- fn mouse_up(
- mut self,
- button: MouseButton,
- handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.handlers_mut().push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && !event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: false,
- });
- self
- }
- fn mouse_up_outside(
- mut self,
- button: MouseButton,
- handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
- ) -> Self
- where
- Self: Sized,
- {
- self.handlers_mut().push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && !event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: true,
- });
- self
- }
- fn block(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().display = Some(Display::Block);
- self
- }
- fn flex(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().display = Some(Display::Flex);
- self
- }
- fn grid(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().display = Some(Display::Grid);
- self
- }
- fn overflow_visible(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow = OptionalPoint {
- x: Some(Overflow::Visible),
- y: Some(Overflow::Visible),
- };
- self
- }
- fn overflow_hidden(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow = OptionalPoint {
- x: Some(Overflow::Hidden),
- y: Some(Overflow::Hidden),
- };
- self
- }
- fn overflow_scroll(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow = OptionalPoint {
- x: Some(Overflow::Scroll),
- y: Some(Overflow::Scroll),
- };
- self
- }
- fn overflow_x_visible(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.x = Some(Overflow::Visible);
- self
- }
- fn overflow_x_hidden(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.x = Some(Overflow::Hidden);
- self
- }
- fn overflow_x_scroll(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.x = Some(Overflow::Scroll);
- self
- }
- fn overflow_y_visible(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.y = Some(Overflow::Visible);
- self
- }
- fn overflow_y_hidden(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.y = Some(Overflow::Hidden);
- self
- }
- fn overflow_y_scroll(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().overflow.y = Some(Overflow::Scroll);
- self
- }
- fn relative(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().position = Some(Position::Relative);
- self
- }
- fn absolute(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().position = Some(Position::Absolute);
- self
- }
- fn inset_0(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(0.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_px(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(1.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_0_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.125).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.25).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.375).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.5).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.625).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.75).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.875).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_4(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.25).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_6(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.5).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_7(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.75).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_8(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_9(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.25).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_10(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.5).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_11(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.75).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_12(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_14(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.5).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_16(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(4.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_20(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(5.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_24(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(6.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_28(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(7.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_32(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(8.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_36(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(9.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_40(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(10.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_44(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(11.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_48(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(12.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_52(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(13.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_56(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(14.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_60(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(15.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_64(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(16.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_72(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(18.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_80(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(20.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_96(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(24.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_half(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(20.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(40.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(60.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_4_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(80.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_4_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_5_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_1_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(8.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_2_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_3_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_4_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_5_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(41.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_6_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_7_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(58.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_8_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_9_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_10_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_11_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(91.666667).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn inset_full(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(100.).into();
- {
- let inset = self
- .computed_style()
- .inset
- .get_or_insert_with(Default::default);
- inset.top = length;
- inset.right = length;
- inset.bottom = length;
- inset.left = length;
- self
- }
- }
- fn w(mut self, width: impl Into<Length>) -> Self
- where
- Self: Sized,
- {
- self.declared_style().size.width = Some(width.into());
- self
- }
- fn w_auto(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().size.width = Some(Length::Auto);
- self
- }
- fn w_0(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(0.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_px(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(1.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_0_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.125).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.25).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.375).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.5).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.625).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.75).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.875).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_4(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.25).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_6(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.5).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_7(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.75).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_8(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_9(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.25).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_10(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.5).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_11(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.75).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_12(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_14(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.5).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_16(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(4.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_20(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(5.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_24(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(6.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_28(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(7.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_32(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(8.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_36(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(9.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_40(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(10.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_44(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(11.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_48(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(12.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_52(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(13.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_56(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(14.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_60(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(15.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_64(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(16.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_72(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(18.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_80(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(20.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_96(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(24.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_half(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(20.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(40.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(60.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_4_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(80.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_4_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_5_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_1_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(8.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_2_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_3_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_4_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_5_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(41.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_6_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_7_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(58.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_8_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_9_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_10_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_11_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(91.666667).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn w_full(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(100.).into();
- {
- self.declared_style().size.width = Some(length);
- self
- }
- }
- fn min_w_0(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(0.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_px(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(1.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_0_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.125).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.25).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.375).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.5).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.625).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.75).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.875).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_4(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.25).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_6(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.5).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_7(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.75).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_8(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_9(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.25).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_10(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.5).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_11(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.75).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_12(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_14(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.5).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_16(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(4.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_20(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(5.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_24(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(6.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_28(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(7.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_32(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(8.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_36(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(9.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_40(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(10.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_44(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(11.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_48(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(12.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_52(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(13.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_56(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(14.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_60(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(15.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_64(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(16.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_72(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(18.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_80(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(20.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_96(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(24.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_half(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(20.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(40.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(60.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_4_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(80.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_4_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_5_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_1_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(8.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_2_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_3_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_4_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_5_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(41.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_6_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_7_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(58.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_8_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_9_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_10_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_11_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(91.666667).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn min_w_full(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(100.).into();
- {
- self.declared_style().min_size.width = Some(length);
- self
- }
- }
- fn h(mut self, height: impl Into<Length>) -> Self
- where
- Self: Sized,
- {
- self.declared_style().size.height = Some(height.into());
- self
- }
- fn h_auto(mut self) -> Self
- where
- Self: Sized,
- {
- self.declared_style().size.height = Some(Length::Auto);
- self
- }
- fn h_0(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Pixels(0.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_px(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Pixels(1.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_0_5(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.125).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.25).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_5(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.375).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.5).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_5(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.625).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.75).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3_5(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(0.875).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_4(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(1.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_5(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(1.25).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_6(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(1.5).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_7(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(1.75).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_8(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(2.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_9(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(2.25).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_10(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(2.5).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_11(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(2.75).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_12(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(3.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_14(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(3.5).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_16(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(4.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_20(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(5.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_24(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(6.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_28(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(7.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_32(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(8.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_36(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(9.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_40(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(10.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_44(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(11.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_48(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(12.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_52(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(13.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_56(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(14.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_60(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(15.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_64(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(16.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_72(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(18.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_80(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(20.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_96(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Rems(24.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_half(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(25.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(75.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(20.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(40.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(60.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_4_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(80.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_4_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_5_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_1_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(8.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_2_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_3_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(25.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_4_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_5_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(41.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_6_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(50.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_7_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(58.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_8_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_9_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(75.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_10_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_11_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(91.666667).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn h_full(mut self) -> Self
- where
- Self: Sized,
- {
- let height = DefinedLength::Percent(100.).into();
- {
- self.declared_style().size.height = Some(height);
- self
- }
- }
- fn min_h_0(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(0.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_px(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Pixels(1.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_0_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.125).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.25).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.375).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.5).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.625).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.75).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(0.875).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_4(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_5(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.25).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_6(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.5).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_7(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(1.75).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_8(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_9(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.25).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_10(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.5).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_11(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(2.75).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_12(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_14(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(3.5).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_16(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(4.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_20(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(5.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_24(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(6.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_28(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(7.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_32(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(8.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_36(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(9.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_40(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(10.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_44(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(11.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_48(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(12.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_52(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(13.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_56(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(14.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_60(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(15.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_64(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(16.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_72(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(18.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_80(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(20.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_96(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Rems(24.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_half(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_3rd(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3_4th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(20.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(40.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(60.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_4_5th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(80.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_4_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_5_6th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_1_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(8.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_2_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(16.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_3_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(25.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_4_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(33.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_5_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(41.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_6_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(50.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_7_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(58.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_8_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(66.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_9_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(75.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_10_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(83.333333).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_11_12th(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(91.666667).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn min_h_full(mut self) -> Self
- where
- Self: Sized,
- {
- let length = DefinedLength::Percent(100.).into();
- {
- self.declared_style().min_size.height = Some(length);
- self
- }
- }
- fn hoverable(self) -> Hoverable<V, Self>
- where
- Self: Sized,
- {
- Hoverable::new(self)
- }
- fn fill(mut self, fill: impl Into<Fill>) -> Self
- where
- Self: Sized,
- {
- self.declared_style().fill = Some(Some(fill.into()));
- self
- }
- fn text_color(mut self, color: impl Into<Hsla>) -> Self
- where
- Self: Sized,
- {
- self.declared_style().text_color = Some(Some(color.into()));
- self
- }
- }
- trait ElementObject<V> {
- fn style(&mut self) -> &mut OptionalStyle;
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<(NodeId, Box<dyn Any>)>;
- fn paint(
- &mut self,
- layout: Layout<dyn Any>,
- view: &mut V,
- cx: &mut PaintContext<V>,
- ) -> Result<()>;
- }
- impl<V: 'static, E: Element<V>> ElementObject<V> for E {
- fn style(&mut self) -> &mut OptionalStyle {
- Element::declared_style(self)
- }
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
- Element::handlers_mut(self)
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<(NodeId, Box<dyn Any>)> {
- let (node_id, layout) = self.layout(view, cx)?;
- let layout = Box::new(layout) as Box<dyn Any>;
- Ok((node_id, layout))
- }
- fn paint(
- &mut self,
- layout: Layout<dyn Any>,
- view: &mut V,
- cx: &mut PaintContext<V>,
- ) -> Result<()> {
- let layout = Layout {
- from_engine: layout.from_engine,
- from_element: layout.from_element.downcast_mut::<E::Layout>().unwrap(),
- };
- self.paint(layout, view, cx)
- }
- }
- /// A dynamically typed element.
- pub struct AnyElement<V> {
- element: Box<dyn ElementObject<V>>,
- layout: Option<(NodeId, Box<dyn Any>)>,
- }
- impl<V: 'static> AnyElement<V> {
- pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
- let pushed_text_style = self.push_text_style(cx);
- let (node_id, layout) = self.element.layout(view, cx)?;
- self.layout = Some((node_id, layout));
- if pushed_text_style {
- cx.pop_text_style();
- }
- Ok(node_id)
- }
- pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool {
- let text_style = self.element.style().text_style();
- if let Some(text_style) = text_style {
- let mut current_text_style = cx.text_style();
- text_style.apply(&mut current_text_style);
- cx.push_text_style(current_text_style);
- true
- } else {
- false
- }
- }
- pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
- let pushed_text_style = self.push_text_style(cx);
- let (layout_node_id, element_layout) =
- self.layout.as_mut().expect("paint called before layout");
- let layout = Layout {
- from_engine: cx
- .layout_engine()
- .unwrap()
- .computed_layout(*layout_node_id)
- .expect("make sure you're using this within a gpui2 adapter element"),
- from_element: element_layout.as_mut(),
- };
- let style = self.element.style();
- let fill_color = style.fill.flatten().and_then(|fill| fill.color());
- if let Some(fill_color) = fill_color {
- cx.scene.push_quad(gpui::scene::Quad {
- bounds: layout.from_engine.bounds,
- background: Some(fill_color.into()),
- border: Default::default(),
- corner_radii: Default::default(),
- });
- }
- for event_handler in self.element.handlers_mut().iter().cloned() {
- let EngineLayout { order, bounds } = layout.from_engine;
- let view_id = cx.view_id();
- let view_event_handler = event_handler.handler.clone();
- cx.scene
- .interactive_regions
- .push(gpui::scene::InteractiveRegion {
- order,
- bounds,
- outside_bounds: event_handler.outside_bounds,
- event_handler: Rc::new(move |view, event, window_cx, view_id| {
- let mut view_context = ViewContext::mutable(window_cx, view_id);
- let mut event_context = EventContext::new(&mut view_context);
- view_event_handler(
- view.downcast_mut().unwrap(),
- event,
- &mut event_context,
- );
- }),
- event_type: event_handler.event_type,
- view_id,
- });
- }
- self.element.paint(layout, view, cx)?;
- if pushed_text_style {
- cx.pop_text_style();
- }
- Ok(())
- }
- }
- impl<V: 'static> Element<V> for AnyElement<V> {
- type Layout = ();
- fn declared_style(&mut self) -> &mut OptionalStyle {
- self.element.style()
- }
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
- self.element.handlers_mut()
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<(NodeId, Self::Layout)> {
- Ok((self.layout(view, cx)?, ()))
- }
- fn paint(
- &mut self,
- layout: Layout<()>,
- view: &mut V,
- cx: &mut PaintContext<V>,
- ) -> Result<()> {
- self.paint(view, cx)
- }
- }
- pub trait IntoElement<V: 'static> {
- type Element: Element<V>;
- fn into_element(self) -> Self::Element;
- fn into_any_element(self) -> AnyElement<V>
- where
- Self: Sized,
- {
- self.into_element().into_any()
- }
- }
-}
-mod frame {
- use crate::{
- element::{
- AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId,
- PaintContext,
- },
- style::{OptionalStyle, Style},
- };
- use anyhow::{anyhow, Result};
- use gpui::LayoutNodeId;
- use gpui_macros::IntoElement;
- #[element_crate = "crate"]
- pub struct Frame<V: 'static> {
- style: OptionalStyle,
- handlers: Vec<EventHandler<V>>,
- children: Vec<AnyElement<V>>,
- }
- impl<V: 'static> crate::element::IntoElement<V> for Frame<V> {
- type Element = Self;
- fn into_element(self) -> Self {
- self
- }
- }
- pub fn frame<V>() -> Frame<V> {
- Frame {
- style: OptionalStyle::default(),
- handlers: Vec::new(),
- children: Vec::new(),
- }
- }
- impl<V: 'static> Element<V> for Frame<V> {
- type Layout = ();
- fn declared_style(&mut self) -> &mut OptionalStyle {
- &mut self.style
- }
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
- &mut self.handlers
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<(NodeId, Self::Layout)> {
- let child_layout_node_ids = self
- .children
- .iter_mut()
- .map(|child| child.layout(view, cx))
- .collect::<Result<Vec<LayoutNodeId>>>()?;
- let rem_size = cx.rem_pixels();
- let style: Style = self.style.into();
- let node_id = cx
- .layout_engine()
- .ok_or_else(|| {
- ::anyhow::__private::must_use({
- let error =
- ::anyhow::__private::format_err(format_args!("no layout engine"));
- error
- })
- })?
- .add_node(style.to_taffy(rem_size), child_layout_node_ids)?;
- Ok((node_id, ()))
- }
- fn paint(
- &mut self,
- layout: Layout<()>,
- view: &mut V,
- cx: &mut PaintContext<V>,
- ) -> Result<()> {
- for child in &mut self.children {
- child.paint(view, cx)?;
- }
- Ok(())
- }
- }
- impl<V: 'static> Frame<V> {
- pub fn child(mut self, child: impl IntoElement<V>) -> Self {
- self.children.push(child.into_any_element());
- self
- }
- pub fn children<I, E>(mut self, children: I) -> Self
- where
- I: IntoIterator<Item = E>,
- E: IntoElement<V>,
- {
- self.children
- .extend(children.into_iter().map(|e| e.into_any_element()));
- self
- }
- }
-}
-mod hoverable {
- use crate::{
- element::Element,
- style::{OptionalStyle, Style},
- };
- use gpui::{
- geometry::{rect::RectF, vector::Vector2F},
- scene::MouseMove,
- EngineLayout,
- };
- use std::{cell::Cell, marker::PhantomData, rc::Rc};
- pub struct Hoverable<V, E> {
- hover_style: OptionalStyle,
- computed_style: Option<Style>,
- view_type: PhantomData<V>,
- child: E,
- }
- impl<V, E> Hoverable<V, E> {
- pub fn new(child: E) -> Self {
- Self {
- hover_style: OptionalStyle::default(),
- computed_style: None,
- view_type: PhantomData,
- child,
- }
- }
- }
- impl<V: 'static, E: Element<V>> Element<V> for Hoverable<V, E> {
- type Layout = E::Layout;
- fn declared_style(&mut self) -> &mut OptionalStyle {
- &mut self.hover_style
- }
- fn computed_style(&mut self) -> &OptionalStyle {
- ::core::panicking::panic("not yet implemented")
- }
- fn handlers_mut(&mut self) -> &mut Vec<crate::element::EventHandler<V>> {
- self.child.handlers_mut()
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut gpui::LayoutContext<V>,
- ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
- self.child.layout(view, cx)
- }
- fn paint<'a>(
- &mut self,
- layout: crate::element::Layout<Self::Layout>,
- view: &mut V,
- cx: &mut crate::element::PaintContext<V>,
- ) -> anyhow::Result<()> {
- let EngineLayout { bounds, order } = layout.from_engine;
- let window_bounds = RectF::new(Vector2F::zero(), cx.window_size());
- let was_hovered = Rc::new(Cell::new(false));
- self.child.paint(layout, view, cx)?;
- cx.draw_interactive_region(
- order,
- window_bounds,
- false,
- move |view, event: &MouseMove, cx| {
- let is_hovered = bounds.contains_point(cx.mouse_position());
- if is_hovered != was_hovered.get() {
- was_hovered.set(is_hovered);
- cx.repaint();
- }
- },
- );
- Ok(())
- }
- }
-}
-mod paint_context {
- use derive_more::{Deref, DerefMut};
- use gpui::{geometry::rect::RectF, EventContext, RenderContext, ViewContext};
- pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
- use std::{any::TypeId, rc::Rc};
- pub use taffy::tree::NodeId;
- pub struct PaintContext<'a, 'b, 'c, 'd, V> {
- #[deref]
- #[deref_mut]
- pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
- pub(crate) scene: &'d mut gpui::SceneBuilder,
- }
- impl<'a, 'b, 'c, 'd, V> ::core::ops::Deref for PaintContext<'a, 'b, 'c, 'd, V> {
- type Target = &'d mut LegacyPaintContext<'a, 'b, 'c, V>;
- #[inline]
- fn deref(&self) -> &Self::Target {
- &self.legacy_cx
- }
- }
- impl<'a, 'b, 'c, 'd, V> ::core::ops::DerefMut for PaintContext<'a, 'b, 'c, 'd, V> {
- #[inline]
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.legacy_cx
- }
- }
- impl<V> RenderContext for PaintContext<'_, '_, '_, '_, V> {
- fn text_style(&self) -> gpui::fonts::TextStyle {
- self.legacy_cx.text_style()
- }
- fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
- self.legacy_cx.push_text_style(style)
- }
- fn pop_text_style(&mut self) {
- self.legacy_cx.pop_text_style()
- }
- }
- impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
- pub fn new(
- legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
- scene: &'d mut gpui::SceneBuilder,
- ) -> Self {
- Self { legacy_cx, scene }
- }
- pub fn draw_interactive_region<E: 'static>(
- &mut self,
- order: u32,
- bounds: RectF,
- outside_bounds: bool,
- event_handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
- ) {
- self.scene
- .interactive_regions
- .push(gpui::scene::InteractiveRegion {
- order,
- bounds,
- outside_bounds,
- event_handler: Rc::new(move |view, event, window_cx, view_id| {
- let mut view_context = ViewContext::mutable(window_cx, view_id);
- let mut event_context = EventContext::new(&mut view_context);
- event_handler(
- view.downcast_mut().unwrap(),
- event.downcast_ref().unwrap(),
- &mut event_context,
- );
- }),
- event_type: TypeId::of::<E>(),
- view_id: self.view_id(),
- });
- }
- }
-}
-mod style {
- use crate::color::Hsla;
- use gpui::geometry::{
- DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point, Size,
- };
- use optional::Optional;
- pub use taffy::style::{
- AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
- Overflow, Position,
- };
- pub struct Style {
- /// What layout strategy should be used?
- pub display: Display,
- /// How children overflowing their container should affect layout
- #[optional]
- pub overflow: Point<Overflow>,
- /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
- pub scrollbar_width: f32,
- /// What should the `position` value of this struct use as a base offset?
- pub position: Position,
- /// How should the position of this element be tweaked relative to the layout defined?
- pub inset: Edges<Length>,
- /// Sets the initial size of the item
- #[optional]
- pub size: Size<Length>,
- /// Controls the minimum size of the item
- #[optional]
- pub min_size: Size<Length>,
- /// Controls the maximum size of the item
- #[optional]
- pub max_size: Size<Length>,
- /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
- pub aspect_ratio: Option<f32>,
- /// How large should the margin be on each side?
- #[optional]
- pub margin: Edges<Length>,
- /// How large should the padding be on each side?
- pub padding: Edges<DefinedLength>,
- /// How large should the border be on each side?
- pub border: Edges<DefinedLength>,
- /// How this node's children aligned in the cross/block axis?
- pub align_items: Option<AlignItems>,
- /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
- pub align_self: Option<AlignSelf>,
- /// How should content contained within this item be aligned in the cross/block axis
- pub align_content: Option<AlignContent>,
- /// How should contained within this item be aligned in the main/inline axis
- pub justify_content: Option<JustifyContent>,
- /// How large should the gaps between items in a flex container be?
- pub gap: Size<DefinedLength>,
- /// Which direction does the main axis flow in?
- pub flex_direction: FlexDirection,
- /// Should elements wrap, or stay in a single line?
- pub flex_wrap: FlexWrap,
- /// Sets the initial main axis size of the item
- pub flex_basis: Length,
- /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
- pub flex_grow: f32,
- /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
- pub flex_shrink: f32,
- /// The fill color of this element
- pub fill: Option<Fill>,
- /// The color of text within this element. Cascades to children unless overridden.
- pub text_color: Option<Hsla>,
- }
- #[automatically_derived]
- impl ::core::clone::Clone for Style {
- #[inline]
- fn clone(&self) -> Style {
- Style {
- display: ::core::clone::Clone::clone(&self.display),
- overflow: ::core::clone::Clone::clone(&self.overflow),
- scrollbar_width: ::core::clone::Clone::clone(&self.scrollbar_width),
- position: ::core::clone::Clone::clone(&self.position),
- inset: ::core::clone::Clone::clone(&self.inset),
- size: ::core::clone::Clone::clone(&self.size),
- min_size: ::core::clone::Clone::clone(&self.min_size),
- max_size: ::core::clone::Clone::clone(&self.max_size),
- aspect_ratio: ::core::clone::Clone::clone(&self.aspect_ratio),
- margin: ::core::clone::Clone::clone(&self.margin),
- padding: ::core::clone::Clone::clone(&self.padding),
- border: ::core::clone::Clone::clone(&self.border),
- align_items: ::core::clone::Clone::clone(&self.align_items),
- align_self: ::core::clone::Clone::clone(&self.align_self),
- align_content: ::core::clone::Clone::clone(&self.align_content),
- justify_content: ::core::clone::Clone::clone(&self.justify_content),
- gap: ::core::clone::Clone::clone(&self.gap),
- flex_direction: ::core::clone::Clone::clone(&self.flex_direction),
- flex_wrap: ::core::clone::Clone::clone(&self.flex_wrap),
- flex_basis: ::core::clone::Clone::clone(&self.flex_basis),
- flex_grow: ::core::clone::Clone::clone(&self.flex_grow),
- flex_shrink: ::core::clone::Clone::clone(&self.flex_shrink),
- fill: ::core::clone::Clone::clone(&self.fill),
- text_color: ::core::clone::Clone::clone(&self.text_color),
- }
- }
- }
- pub struct OptionalStyle {
- pub display: Option<Display>,
- pub overflow: OptionalPoint<Overflow>,
- pub scrollbar_width: Option<f32>,
- pub position: Option<Position>,
- pub inset: Option<Edges<Length>>,
- pub size: OptionalSize<Length>,
- pub min_size: OptionalSize<Length>,
- pub max_size: OptionalSize<Length>,
- pub aspect_ratio: Option<Option<f32>>,
- pub margin: OptionalEdges<Length>,
- pub padding: Option<Edges<DefinedLength>>,
- pub border: Option<Edges<DefinedLength>>,
- pub align_items: Option<Option<AlignItems>>,
- pub align_self: Option<Option<AlignSelf>>,
- pub align_content: Option<Option<AlignContent>>,
- pub justify_content: Option<Option<JustifyContent>>,
- pub gap: Option<Size<DefinedLength>>,
- pub flex_direction: Option<FlexDirection>,
- pub flex_wrap: Option<FlexWrap>,
- pub flex_basis: Option<Length>,
- pub flex_grow: Option<f32>,
- pub flex_shrink: Option<f32>,
- pub fill: Option<Option<Fill>>,
- pub text_color: Option<Option<Hsla>>,
- }
- #[automatically_derived]
- impl ::core::default::Default for OptionalStyle {
- #[inline]
- fn default() -> OptionalStyle {
- OptionalStyle {
- display: ::core::default::Default::default(),
- overflow: ::core::default::Default::default(),
- scrollbar_width: ::core::default::Default::default(),
- position: ::core::default::Default::default(),
- inset: ::core::default::Default::default(),
- size: ::core::default::Default::default(),
- min_size: ::core::default::Default::default(),
- max_size: ::core::default::Default::default(),
- aspect_ratio: ::core::default::Default::default(),
- margin: ::core::default::Default::default(),
- padding: ::core::default::Default::default(),
- border: ::core::default::Default::default(),
- align_items: ::core::default::Default::default(),
- align_self: ::core::default::Default::default(),
- align_content: ::core::default::Default::default(),
- justify_content: ::core::default::Default::default(),
- gap: ::core::default::Default::default(),
- flex_direction: ::core::default::Default::default(),
- flex_wrap: ::core::default::Default::default(),
- flex_basis: ::core::default::Default::default(),
- flex_grow: ::core::default::Default::default(),
- flex_shrink: ::core::default::Default::default(),
- fill: ::core::default::Default::default(),
- text_color: ::core::default::Default::default(),
- }
- }
- }
- #[automatically_derived]
- impl ::core::clone::Clone for OptionalStyle {
- #[inline]
- fn clone(&self) -> OptionalStyle {
- OptionalStyle {
- display: ::core::clone::Clone::clone(&self.display),
- overflow: ::core::clone::Clone::clone(&self.overflow),
- scrollbar_width: ::core::clone::Clone::clone(&self.scrollbar_width),
- position: ::core::clone::Clone::clone(&self.position),
- inset: ::core::clone::Clone::clone(&self.inset),
- size: ::core::clone::Clone::clone(&self.size),
- min_size: ::core::clone::Clone::clone(&self.min_size),
- max_size: ::core::clone::Clone::clone(&self.max_size),
- aspect_ratio: ::core::clone::Clone::clone(&self.aspect_ratio),
- margin: ::core::clone::Clone::clone(&self.margin),
- padding: ::core::clone::Clone::clone(&self.padding),
- border: ::core::clone::Clone::clone(&self.border),
- align_items: ::core::clone::Clone::clone(&self.align_items),
- align_self: ::core::clone::Clone::clone(&self.align_self),
- align_content: ::core::clone::Clone::clone(&self.align_content),
- justify_content: ::core::clone::Clone::clone(&self.justify_content),
- gap: ::core::clone::Clone::clone(&self.gap),
- flex_direction: ::core::clone::Clone::clone(&self.flex_direction),
- flex_wrap: ::core::clone::Clone::clone(&self.flex_wrap),
- flex_basis: ::core::clone::Clone::clone(&self.flex_basis),
- flex_grow: ::core::clone::Clone::clone(&self.flex_grow),
- flex_shrink: ::core::clone::Clone::clone(&self.flex_shrink),
- fill: ::core::clone::Clone::clone(&self.fill),
- text_color: ::core::clone::Clone::clone(&self.text_color),
- }
- }
- }
- impl Optional for OptionalStyle {
- type Base = Style;
- fn assign(&self, base: &mut Self::Base) {
- if let Some(value) = self.display.clone() {
- base.display = value;
- }
- if let Some(value) = self.overflow.clone() {
- base.overflow = value;
- }
- if let Some(value) = self.scrollbar_width.clone() {
- base.scrollbar_width = value;
- }
- if let Some(value) = self.position.clone() {
- base.position = value;
- }
- if let Some(value) = self.inset.clone() {
- base.inset = value;
- }
- if let Some(value) = self.size.clone() {
- base.size = value;
- }
- if let Some(value) = self.min_size.clone() {
- base.min_size = value;
- }
- if let Some(value) = self.max_size.clone() {
- base.max_size = value;
- }
- if let Some(value) = self.aspect_ratio.clone() {
- base.aspect_ratio = value;
- }
- if let Some(value) = self.margin.clone() {
- base.margin = value;
- }
- if let Some(value) = self.padding.clone() {
- base.padding = value;
- }
- if let Some(value) = self.border.clone() {
- base.border = value;
- }
- if let Some(value) = self.align_items.clone() {
- base.align_items = value;
- }
- if let Some(value) = self.align_self.clone() {
- base.align_self = value;
- }
- if let Some(value) = self.align_content.clone() {
- base.align_content = value;
- }
- if let Some(value) = self.justify_content.clone() {
- base.justify_content = value;
- }
- if let Some(value) = self.gap.clone() {
- base.gap = value;
- }
- if let Some(value) = self.flex_direction.clone() {
- base.flex_direction = value;
- }
- if let Some(value) = self.flex_wrap.clone() {
- base.flex_wrap = value;
- }
- if let Some(value) = self.flex_basis.clone() {
- base.flex_basis = value;
- }
- if let Some(value) = self.flex_grow.clone() {
- base.flex_grow = value;
- }
- if let Some(value) = self.flex_shrink.clone() {
- base.flex_shrink = value;
- }
- if let Some(value) = self.fill.clone() {
- base.fill = value;
- }
- if let Some(value) = self.text_color.clone() {
- base.text_color = value;
- }
- }
- }
- impl From<OptionalStyle> for Style
- where
- Style: Default,
- {
- fn from(wrapper: OptionalStyle) -> Self {
- let mut base = Self::default();
- wrapper.assign(&mut base);
- base
- }
- }
- impl Style {
- pub const DEFAULT: Style = Style {
- display: Display::DEFAULT,
- overflow: Point {
- x: Overflow::Visible,
- y: Overflow::Visible,
- },
- scrollbar_width: 0.0,
- position: Position::Relative,
- inset: Edges::auto(),
- margin: Edges::<Length>::zero(),
- padding: Edges::<DefinedLength>::zero(),
- border: Edges::<DefinedLength>::zero(),
- size: Size::auto(),
- min_size: Size::auto(),
- max_size: Size::auto(),
- aspect_ratio: None,
- gap: Size::zero(),
- align_items: None,
- align_self: None,
- align_content: None,
- justify_content: None,
- flex_direction: FlexDirection::Row,
- flex_wrap: FlexWrap::NoWrap,
- flex_grow: 0.0,
- flex_shrink: 1.0,
- flex_basis: Length::Auto,
- fill: None,
- text_color: None,
- };
- pub fn new() -> Self {
- Self::DEFAULT.clone()
- }
- pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
- taffy::style::Style {
- display: self.display,
- overflow: self.overflow.clone().into(),
- scrollbar_width: self.scrollbar_width,
- position: self.position,
- inset: self.inset.to_taffy(rem_size),
- size: self.size.to_taffy(rem_size),
- min_size: self.min_size.to_taffy(rem_size),
- max_size: self.max_size.to_taffy(rem_size),
- aspect_ratio: self.aspect_ratio,
- margin: self.margin.to_taffy(rem_size),
- padding: self.padding.to_taffy(rem_size),
- border: self.border.to_taffy(rem_size),
- align_items: self.align_items,
- align_self: self.align_self,
- align_content: self.align_content,
- justify_content: self.justify_content,
- gap: self.gap.to_taffy(rem_size),
- flex_direction: self.flex_direction,
- flex_wrap: self.flex_wrap,
- flex_basis: self.flex_basis.to_taffy(rem_size).into(),
- flex_grow: self.flex_grow,
- flex_shrink: self.flex_shrink,
- ..Default::default()
- }
- }
- }
- impl Default for Style {
- fn default() -> Self {
- Self::DEFAULT.clone()
- }
- }
- impl OptionalStyle {
- pub fn text_style(&self) -> Option<OptionalTextStyle> {
- self.text_color.map(|color| OptionalTextStyle { color })
- }
- }
- pub struct OptionalTextStyle {
- color: Option<Hsla>,
- }
- impl OptionalTextStyle {
- pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
- if let Some(color) = self.color {
- style.color = color.into();
- }
- }
- }
- pub enum Fill {
- Color(Hsla),
- }
- #[automatically_derived]
- impl ::core::clone::Clone for Fill {
- #[inline]
- fn clone(&self) -> Fill {
- match self {
- Fill::Color(__self_0) => Fill::Color(::core::clone::Clone::clone(__self_0)),
- }
- }
- }
- impl Fill {
- pub fn color(&self) -> Option<Hsla> {
- match self {
- Fill::Color(color) => Some(*color),
- }
- }
- }
- impl Default for Fill {
- fn default() -> Self {
- Self::Color(Hsla::default())
- }
- }
- impl From<Hsla> for Fill {
- fn from(color: Hsla) -> Self {
- Self::Color(color)
- }
- }
-}
-mod text {
- use crate::{
- element::{Element, ElementMetadata, EventHandler, IntoElement},
- style::Style,
- };
- use gpui::{geometry::Size, text_layout::LineLayout, RenderContext};
- use parking_lot::Mutex;
- use std::sync::Arc;
- impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
- type Element = Text<V>;
- fn into_element(self) -> Self::Element {
- Text {
- text: self.into(),
- metadata: Default::default(),
- }
- }
- }
- pub struct Text<V> {
- text: ArcCow<'static, str>,
- metadata: ElementMetadata<V>,
- }
- impl<V: 'static> Element<V> for Text<V> {
- type Layout = Arc<Mutex<Option<TextLayout>>>;
- fn declared_style(&mut self) -> &mut crate::style::OptionalStyle {
- &mut self.metadata.style
- }
- fn layout(
- &mut self,
- view: &mut V,
- cx: &mut gpui::LayoutContext<V>,
- ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
- let rem_size = cx.rem_pixels();
- let fonts = cx.platform().fonts();
- let text_style = cx.text_style();
- let line_height = cx.font_cache().line_height(text_style.font_size);
- let layout_engine = cx.layout_engine().expect("no layout engine present");
- let text = self.text.clone();
- let layout = Arc::new(Mutex::new(None));
- let style: Style = self.metadata.style.into();
- let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
- let layout = layout.clone();
- move |params| {
- let line_layout = fonts.layout_line(
- text.as_ref(),
- text_style.font_size,
- &[(text.len(), text_style.to_run())],
- );
- let size = Size {
- width: line_layout.width,
- height: line_height,
- };
- layout.lock().replace(TextLayout {
- line_layout: Arc::new(line_layout),
- line_height,
- });
- size
- }
- })?;
- Ok((node_id, layout))
- }
- fn paint<'a>(
- &mut self,
- layout: crate::element::Layout<Arc<Mutex<Option<TextLayout>>>>,
- view: &mut V,
- cx: &mut crate::element::PaintContext<V>,
- ) -> anyhow::Result<()> {
- let element_layout_lock = layout.from_element.lock();
- let element_layout = element_layout_lock
- .as_ref()
- .expect("layout has not been performed");
- let line_layout = element_layout.line_layout.clone();
- let line_height = element_layout.line_height;
- drop(element_layout_lock);
- let text_style = cx.text_style();
- let line = gpui::text_layout::Line::new(
- line_layout,
- &[(self.text.len(), text_style.to_run())],
- );
- line.paint(
- cx.scene,
- layout.from_engine.bounds.origin(),
- layout.from_engine.bounds,
- line_height,
- cx.legacy_cx,
- );
- Ok(())
- }
- fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
- &mut self.metadata.handlers
- }
- }
- pub struct TextLayout {
- line_layout: Arc<LineLayout>,
- line_height: f32,
- }
- pub enum ArcCow<'a, T: ?Sized> {
- Borrowed(&'a T),
- Owned(Arc<T>),
- }
- impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
- fn clone(&self) -> Self {
- match self {
- Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
- Self::Owned(owned) => Self::Owned(owned.clone()),
- }
- }
- }
- impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
- fn from(s: &'a T) -> Self {
- Self::Borrowed(s)
- }
- }
- impl<T> From<Arc<T>> for ArcCow<'_, T> {
- fn from(s: Arc<T>) -> Self {
- Self::Owned(s)
- }
- }
- impl From<String> for ArcCow<'_, str> {
- fn from(value: String) -> Self {
- Self::Owned(value.into())
- }
- }
- impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
- type Target = T;
- fn deref(&self) -> &Self::Target {
- match self {
- ArcCow::Borrowed(s) => s,
- ArcCow::Owned(s) => s.as_ref(),
- }
- }
- }
- impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
- fn as_ref(&self) -> &T {
- match self {
- ArcCow::Borrowed(borrowed) => borrowed,
- ArcCow::Owned(owned) => owned.as_ref(),
- }
- }
- }
-}
-mod themes {
- use crate::color::{Hsla, Lerp};
- use std::ops::Range;
- pub mod rose_pine {
- use crate::{
- color::{hsla, rgb, Hsla},
- ThemeColors,
- };
- use std::ops::Range;
- pub struct RosePineThemes {
- pub default: RosePinePalette,
- pub dawn: RosePinePalette,
- pub moon: RosePinePalette,
- }
- pub struct RosePinePalette {
- pub base: Hsla,
- pub surface: Hsla,
- pub overlay: Hsla,
- pub muted: Hsla,
- pub subtle: Hsla,
- pub text: Hsla,
- pub love: Hsla,
- pub gold: Hsla,
- pub rose: Hsla,
- pub pine: Hsla,
- pub foam: Hsla,
- pub iris: Hsla,
- pub highlight_low: Hsla,
- pub highlight_med: Hsla,
- pub highlight_high: Hsla,
- }
- #[automatically_derived]
- impl ::core::clone::Clone for RosePinePalette {
- #[inline]
- fn clone(&self) -> RosePinePalette {
- let _: ::core::clone::AssertParamIsClone<Hsla>;
- *self
- }
- }
- #[automatically_derived]
- impl ::core::marker::Copy for RosePinePalette {}
- #[automatically_derived]
- impl ::core::fmt::Debug for RosePinePalette {
- fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
- let names: &'static _ = &[
- "base",
- "surface",
- "overlay",
- "muted",
- "subtle",
- "text",
- "love",
- "gold",
- "rose",
- "pine",
- "foam",
- "iris",
- "highlight_low",
- "highlight_med",
- "highlight_high",
- ];
- let values: &[&dyn::core::fmt::Debug] = &[
- &self.base,
- &self.surface,
- &self.overlay,
- &self.muted,
- &self.subtle,
- &self.text,
- &self.love,
- &self.gold,
- &self.rose,
- &self.pine,
- &self.foam,
- &self.iris,
- &self.highlight_low,
- &self.highlight_med,
- &&self.highlight_high,
- ];
- ::core::fmt::Formatter::debug_struct_fields_finish(
- f,
- "RosePinePalette",
- names,
- values,
- )
- }
- }
- impl RosePinePalette {
- pub fn default() -> RosePinePalette {
- RosePinePalette {
- base: rgb(0x191724),
- surface: rgb(0x1f1d2e),
- overlay: rgb(0x26233a),
- muted: rgb(0x6e6a86),
- subtle: rgb(0x908caa),
- text: rgb(0xe0def4),
- love: rgb(0xeb6f92),
- gold: rgb(0xf6c177),
- rose: rgb(0xebbcba),
- pine: rgb(0x31748f),
- foam: rgb(0x9ccfd8),
- iris: rgb(0xc4a7e7),
- highlight_low: rgb(0x21202e),
- highlight_med: rgb(0x403d52),
- highlight_high: rgb(0x524f67),
- }
- }
- pub fn moon() -> RosePinePalette {
- RosePinePalette {
- base: rgb(0x232136),
- surface: rgb(0x2a273f),
- overlay: rgb(0x393552),
- muted: rgb(0x6e6a86),
- subtle: rgb(0x908caa),
- text: rgb(0xe0def4),
- love: rgb(0xeb6f92),
- gold: rgb(0xf6c177),
- rose: rgb(0xea9a97),
- pine: rgb(0x3e8fb0),
- foam: rgb(0x9ccfd8),
- iris: rgb(0xc4a7e7),
- highlight_low: rgb(0x2a283e),
- highlight_med: rgb(0x44415a),
- highlight_high: rgb(0x56526e),
- }
- }
- pub fn dawn() -> RosePinePalette {
- RosePinePalette {
- base: rgb(0xfaf4ed),
- surface: rgb(0xfffaf3),
- overlay: rgb(0xf2e9e1),
- muted: rgb(0x9893a5),
- subtle: rgb(0x797593),
- text: rgb(0x575279),
- love: rgb(0xb4637a),
- gold: rgb(0xea9d34),
- rose: rgb(0xd7827e),
- pine: rgb(0x286983),
- foam: rgb(0x56949f),
- iris: rgb(0x907aa9),
- highlight_low: rgb(0xf4ede8),
- highlight_med: rgb(0xdfdad9),
- highlight_high: rgb(0xcecacd),
- }
- }
- }
- pub fn default() -> ThemeColors {
- theme_colors(&RosePinePalette::default())
- }
- pub fn moon() -> ThemeColors {
- theme_colors(&RosePinePalette::moon())
- }
- pub fn dawn() -> ThemeColors {
- theme_colors(&RosePinePalette::dawn())
- }
- fn theme_colors(p: &RosePinePalette) -> ThemeColors {
- ThemeColors {
- base: scale_sl(p.base, (0.8, 0.8), (1.2, 1.2)),
- surface: scale_sl(p.surface, (0.8, 0.8), (1.2, 1.2)),
- overlay: scale_sl(p.overlay, (0.8, 0.8), (1.2, 1.2)),
- muted: scale_sl(p.muted, (0.8, 0.8), (1.2, 1.2)),
- subtle: scale_sl(p.subtle, (0.8, 0.8), (1.2, 1.2)),
- text: scale_sl(p.text, (0.8, 0.8), (1.2, 1.2)),
- highlight_low: scale_sl(p.highlight_low, (0.8, 0.8), (1.2, 1.2)),
- highlight_med: scale_sl(p.highlight_med, (0.8, 0.8), (1.2, 1.2)),
- highlight_high: scale_sl(p.highlight_high, (0.8, 0.8), (1.2, 1.2)),
- success: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
- warning: scale_sl(p.gold, (0.8, 0.8), (1.2, 1.2)),
- error: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
- inserted: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
- deleted: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
- modified: scale_sl(p.rose, (0.8, 0.8), (1.2, 1.2)),
- }
- }
- /// Produces a range by multiplying the saturation and lightness of the base color by the given
- /// start and end factors.
- fn scale_sl(
- base: Hsla,
- (start_s, start_l): (f32, f32),
- (end_s, end_l): (f32, f32),
- ) -> Range<Hsla> {
- let start = hsla(base.h, base.s * start_s, base.l * start_l, base.a);
- let end = hsla(base.h, base.s * end_s, base.l * end_l, base.a);
- Range { start, end }
- }
- }
- pub struct ThemeColors {
- pub base: Range<Hsla>,
- pub surface: Range<Hsla>,
- pub overlay: Range<Hsla>,
- pub muted: Range<Hsla>,
- pub subtle: Range<Hsla>,
- pub text: Range<Hsla>,
- pub highlight_low: Range<Hsla>,
- pub highlight_med: Range<Hsla>,
- pub highlight_high: Range<Hsla>,
- pub success: Range<Hsla>,
- pub warning: Range<Hsla>,
- pub error: Range<Hsla>,
- pub inserted: Range<Hsla>,
- pub deleted: Range<Hsla>,
- pub modified: Range<Hsla>,
- }
- impl ThemeColors {
- pub fn base(&self, level: f32) -> Hsla {
- self.base.lerp(level)
- }
- pub fn surface(&self, level: f32) -> Hsla {
- self.surface.lerp(level)
- }
- pub fn overlay(&self, level: f32) -> Hsla {
- self.overlay.lerp(level)
- }
- pub fn muted(&self, level: f32) -> Hsla {
- self.muted.lerp(level)
- }
- pub fn subtle(&self, level: f32) -> Hsla {
- self.subtle.lerp(level)
- }
- pub fn text(&self, level: f32) -> Hsla {
- self.text.lerp(level)
- }
- pub fn highlight_low(&self, level: f32) -> Hsla {
- self.highlight_low.lerp(level)
- }
- pub fn highlight_med(&self, level: f32) -> Hsla {
- self.highlight_med.lerp(level)
- }
- pub fn highlight_high(&self, level: f32) -> Hsla {
- self.highlight_high.lerp(level)
- }
- pub fn success(&self, level: f32) -> Hsla {
- self.success.lerp(level)
- }
- pub fn warning(&self, level: f32) -> Hsla {
- self.warning.lerp(level)
- }
- pub fn error(&self, level: f32) -> Hsla {
- self.error.lerp(level)
- }
- pub fn inserted(&self, level: f32) -> Hsla {
- self.inserted.lerp(level)
- }
- pub fn deleted(&self, level: f32) -> Hsla {
- self.deleted.lerp(level)
- }
- pub fn modified(&self, level: f32) -> Hsla {
- self.modified.lerp(level)
- }
- }
-}
-mod view {
- use crate::element::{AnyElement, Element};
- use gpui::{Element as _, ViewContext};
- pub fn view<F, E>(mut render: F) -> ViewFn
- where
- F: 'static + FnMut(&mut ViewContext<ViewFn>) -> E,
- E: Element<ViewFn>,
- {
- ViewFn(Box::new(move |cx| (render)(cx).into_any()))
- }
- pub struct ViewFn(Box<dyn FnMut(&mut ViewContext<ViewFn>) -> AnyElement<ViewFn>>);
- impl gpui::Entity for ViewFn {
- type Event = ();
- }
- impl gpui::View for ViewFn {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
- (self.0)(cx).adapt().into_any()
- }
- }
-}
-fn main() {
- SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
- gpui::App::new(()).unwrap().run(|cx| {
- cx.add_window(
- WindowOptions {
- bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
- vec2f(0., 0.),
- vec2f(400., 300.),
- )),
- center: true,
- ..Default::default()
- },
- |_| view(|_| storybook(&rose_pine::moon())),
- );
- cx.platform().activate(true);
- });
-}
-fn storybook<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
- frame()
- .text_color(black())
- .h_full()
- .w_half()
- .fill(theme.success(0.5))
- .child(button().label("Hello").click(|_, _, _| {
- ::std::io::_print(format_args!("click!\n"));
- }))
-}