diff --git a/Cargo.lock b/Cargo.lock index 5ec386837baa43369f46bd685d8ff3fc181b3f2e..3110a9ff43e75bc698b782d3ec4d79383851fca3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9970,7 +9970,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", diff --git a/Cargo.toml b/Cargo.toml index 674bfbd6065b244903e46b8641cad5b7fbbde0de..5dc30ca40b1687200ce69dded3fdf8e54ca03b5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,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 diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ef6a655bdcead3cd64f29e9aa25b90d0d4e4d626..2a8d19f8829039d759c92e79b6acebe79e55b143 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -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": { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index cac8bf6c54f8b335df756c13b08fb056b6378dd5..e472e8c8dfc7f3f7f2dc072825efea4fd89b41de 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -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() diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index fa7c4fe67df4fed4645e8c6552e242b3d7662276..c5820b539526c94879edfd2a06412c7271ab3252 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -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::>(), + project.diagnostic_summaries(false, cx).collect::>(), &[( 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::>() + project.diagnostic_summaries(false, cx).collect::>() }))); 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::>(), + project.diagnostic_summaries(false, cx).collect::>(), [( 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::>(), + project.diagnostic_summaries(false, cx).collect::>(), [( 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::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); project_b.read_with(cx_b, |project, cx| { - assert_eq!(project.diagnostic_summaries(cx).collect::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); project_c.read_with(cx_c, |project, cx| { - assert_eq!(project.diagnostic_summaries(cx).collect::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); } diff --git a/crates/collab2/src/tests/integration_tests.rs b/crates/collab2/src/tests/integration_tests.rs index e579c384e36a0e20beccbc812c7866bcbe6a6991..2268a51f2ba5f1671a02707101ecad8f65501d1c 100644 --- a/crates/collab2/src/tests/integration_tests.rs +++ b/crates/collab2/src/tests/integration_tests.rs @@ -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::>(), + project.diagnostic_summaries(false, cx).collect::>(), &[( 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::>() + project.diagnostic_summaries(false, cx).collect::>() }))); 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::>(), + project.diagnostic_summaries(false, cx).collect::>(), [( 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::>(), + project.diagnostic_summaries(false, cx).collect::>(), [( 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::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); project_b.read_with(cx_b, |project, cx| { - assert_eq!(project.diagnostic_summaries(cx).collect::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); project_c.read_with(cx_c, |project, cx| { - assert_eq!(project.diagnostic_summaries(cx).collect::>(), []) + assert_eq!( + project.diagnostic_summaries(false, cx).collect::>(), + [] + ) }); } diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index b62056a3be92ba178e62b44d4b1557c0d848f2a8..b90df68c2a8ac08ed3e7a2fdf0ff31fe0f920998 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2511,7 +2511,7 @@ impl CollabPanel { } else { el.child( ListHeader::new(text) - .when_some(button, |el, button| el.right_button(button)) + .when_some(button, |el, button| el.meta(button)) .selected(is_selected), ) } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index d76242afa32e766ce5b885870e0fd20006ff6517..f18e4cb2db4358f810499598ce96a55af4dc1064 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,7 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle2, IconButton, KeyBinding, Tooltip}; +use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle, IconButton, KeyBinding, Tooltip}; use util::ResultExt; use workspace::{notifications::NotifyResultExt, Workspace}; @@ -154,7 +154,7 @@ impl Render for CollabTitlebarItem { .id("project_owner_indicator") .child( Button::new("player", "player") - .style(ButtonStyle2::Subtle) + .style(ButtonStyle::Subtle) .color(Some(Color::Player(0))), ) .tooltip(move |cx| Tooltip::text("Toggle following", cx)), @@ -167,7 +167,7 @@ impl Render for CollabTitlebarItem { .id("titlebar_project_menu_button") .child( Button::new("project_name", "project_name") - .style(ButtonStyle2::Subtle), + .style(ButtonStyle::Subtle), ) .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), ) @@ -179,7 +179,7 @@ impl Render for CollabTitlebarItem { .id("titlebar_git_menu_button") .child( Button::new("branch_name", "branch_name") - .style(ButtonStyle2::Subtle) + .style(ButtonStyle::Subtle) .color(Some(Color::Muted)), ) .tooltip(move |cx| { diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index f9b58b1d56139326192edf7e5fa008d90351283d..04688b05492c8c298c33d32423cdb5e7ce1fe393 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -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, diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4748f63e5d37e29174777ea326ce8158787acd09..9ff3c04de85d9afd12f2da0a2020a0f26d05fc2a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -126,7 +126,7 @@ impl View for ProjectDiagnosticsEditor { json!({ "project": json!({ "language_servers": project.language_server_statuses().collect::>(), - "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> = 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(()) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 8d3c2fedd6d9d65d7d28e0a88f05f1e85161d117..86d8d01db1e837ab56f8a221768b38cc3096ad6d 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -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() diff --git a/crates/diagnostics2/src/diagnostics.rs b/crates/diagnostics2/src/diagnostics.rs index dc7f0a1f3f58dfe87784dcd3debbb3035471fb99..0a0f4da8932a50b1ddac155e27d3fb8ff22cd53a 100644 --- a/crates/diagnostics2/src/diagnostics.rs +++ b/crates/diagnostics2/src/diagnostics.rs @@ -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> = 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); })?; diff --git a/crates/diagnostics2/src/items.rs b/crates/diagnostics2/src/items.rs index ac24b7ad508613c6d41e02238d8c030e29abdb22..92b0641deae4d049fda3d968cf88b28568b50d41 100644 --- a/crates/diagnostics2/src/items.rs +++ b/crates/diagnostics2/src/items.rs @@ -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() diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index e41632c6c9522b1f787b9792b65027adf62f993e..93bb37c6222932f395d738e4a8bac9ec20d7076c 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -8,9 +8,10 @@ use anyhow::{anyhow, Context as _, Result}; use collections::HashSet; use futures::future::try_join_all; use gpui::{ - div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, 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}, @@ -1113,86 +1118,78 @@ pub struct CursorPosition { _observe_active_editor: Option, } -// 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, cx: &mut ViewContext) { -// let editor = editor.read(cx); -// let buffer = editor.buffer().read(cx).snapshot(cx); - -// self.selected_count = 0; -// let mut last_selection: Option> = None; -// for selection in editor.selections.all::(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) -> AnyElement { -// 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, -// ) { -// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(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, cx: &mut ViewContext) { + let editor = editor.read(cx); + let buffer = editor.buffer().read(cx).snapshot(cx); + + self.selected_count = 0; + let mut last_selection: Option> = None; + for selection in editor.selections.all::(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::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, + ) { + if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(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, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index b7a4a387ab63a8592992c538e606c6e0d915290f..156b062df85f2c1fed4aa30fda7e41aa507297a4 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -518,6 +518,7 @@ impl PickerDelegate for FileFinderDelegate { } fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext) -> 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::().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::() + .unwrap() + .read(cx) + .title(cx), + "bandana", + "Wrong match for bandana query '{bandana_query}'" + ); + }); + } } #[gpui::test] diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 980ad9cf84c7eaf701b9aad06d305fb13f237910..9938b94edb2892d15f547e95df6ee3192f0f992b 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -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>, ) -> 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::(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::(cx).unwrap(); + assert_eq!( + active_editor.read(cx).title(cx), + "bandana", + "Wrong match for bandana query '{bandana_query}'" + ); + }); + } } #[gpui::test] diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9637720a67e78ba6f735534f924b4028d8d8c75f..c915753749f4f85bb75cf1440df0775b232d7fa4 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -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 { + 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> { diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 3027c05fbd84c27c6f51fe02d5da84f38815d067..7375f47939899d8d9902e400b337da723f96dc34 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow { fn draw(&self, scene: Scene); fn sprite_atlas(&self) -> Arc; + + #[cfg(any(test, feature = "test-support"))] + fn as_test(&self) -> Option<&TestWindow> { + None + } } pub trait PlatformDispatcher: Send + Sync { diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index df1ed9b2a6dee715fb707b821e0cdba533ac90b2..4532b33f5037bcf5ab139a7acb3708ade901dd88 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -189,13 +189,9 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_become_active(&self, _callback: Box) { - unimplemented!() - } + fn on_become_active(&self, _callback: Box) {} - fn on_resign_active(&self, _callback: Box) { - unimplemented!() - } + fn on_resign_active(&self, _callback: Box) {} fn on_quit(&self, _callback: Box) {} diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index e355c3aa4b5ec0681faad153f24c1f3ca5a4fceb..2ad54eff0d0f8d33f97685f5032729291f720fb6 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -11,19 +11,20 @@ use std::{ }; #[derive(Default)] -struct Handlers { - active_status_change: Vec>, - input: Vec bool>>, - moved: Vec>, - resize: Vec, f32)>>, +pub(crate) struct TestWindowHandlers { + pub(crate) active_status_change: Vec>, + pub(crate) input: Vec bool>>, + pub(crate) moved: Vec>, + pub(crate) resize: Vec, f32)>>, } pub struct TestWindow { bounds: WindowBounds, current_scene: Mutex>, display: Rc, + pub(crate) window_title: Option, pub(crate) input_handler: Option>>>, - handlers: Mutex, + pub(crate) handlers: Arc>, platform: Weak, sprite_atlas: Arc, } @@ -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 { self.sprite_atlas.clone() } + + fn as_test(&self) -> Option<&TestWindow> { + Some(self) + } } pub struct TestAtlasState { diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 6e1d59fc5ab17871c3c58de5c62d0b16ec9fe15d..280c52df2afad19af029a75e336222eae82aa74e 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -238,6 +238,10 @@ impl Element for AnyView { } fn paint(self, _: Bounds, 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) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index d706787da8d8c50449d0c2279ad62bd4d67a8365..5724f1e0701a2b960afb478fad0186649c29debd 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -685,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> { self.platform .displays() diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index af7504529cefbee215fb96e53798242da340a4d6..811e54940672ee075993d8cd981c945199409675 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -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() } diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 5c17592f0ca3ff2a1a352954b79e2ed0a937079e..8fdf524f69e16a219bb264504051bb96f7e565ad 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -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() } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 98fd81f012f163402af47a840ae765e8760b062b..dc5b63d222d0f1545dfce7eab0748f5fd9c23e2d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -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::(params).await?; diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 356d029c587b57aaaf182bb090d82d42f14123d9..788c424373deca7c1490dd954fa005e0943d8a99 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -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::(params).await?; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a2ad82585eadb9d4e010fe953ae17319ad23728f..21d64fe91f8509496c7641225863a86dcd2945ce 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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::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, + worktree_path: &Path, override_initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, @@ -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, override_options: Option, pending_server: PendingLanguageServer, + worktree_path: &Path, adapter: Arc, server_id: LanguageServerId, cx: &mut AsyncAppContext, ) -> Result> { - 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::({ 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 + '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) + }) }) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 264c1ff7b54fa52dbffd87545603736704a5f932..5d061b868fb37dd730d09ea184fd3f7a91d447be 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -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::>(), + &[ + ("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![( + 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, diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index d2cc4fe406a0eb67de102e624ab9025338ad8689..12940dd2c427f8872fd95c9a1c66a5535738463e 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -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::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, + worktree_path: &Path, initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, @@ -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, initialization_options: Option, pending_server: PendingLanguageServer, + worktree_path: &Path, adapter: Arc, server_id: LanguageServerId, cx: &mut AsyncAppContext, ) -> Result> { - 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::({ 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 + '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( diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 53b2f6ba1fc7cc04489bce5256a0954b9e2bf7ff..4dfb8004e3e644309b89ee31d99f5ac07e05f4b3 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -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::>(), + &[ + ("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![( + 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, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index eb124bfca28840f4b99a3b022abbbee33611fc0e..875d4d4f83736aa6c768b6dda21e915e889afc06 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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) { diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index dc584d52ff520a810b27ecc2a803738491643950..0a5a63f14a289f5f8aeb44a20a454a35f8bea20b 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -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) -> 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)) @@ -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, }); diff --git a/crates/search2/src/search.rs b/crates/search2/src/search.rs index 65a4ddfd422abc5700a11a90fd15391e82d8ea78..13def6b4a7ea4d6918353f117e37d5ee8fd3a96c 100644 --- a/crates/search2/src/search.rs +++ b/crates/search2/src/search.rs @@ -4,7 +4,7 @@ use gpui::{actions, Action, AppContext, IntoElement}; pub use mode::SearchMode; use project::search::SearchQuery; use ui::prelude::*; -use ui::{ButtonStyle2, Icon, IconButton}; +use ui::{ButtonStyle, Icon, IconButton}; //pub use project_search::{ProjectSearchBar, ProjectSearchView}; // use theme::components::{ // action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle, @@ -91,8 +91,8 @@ impl SearchOptions { cx.dispatch_action(action.boxed_clone()); } }) - .style(ButtonStyle2::Subtle) - .when(active, |button| button.style(ButtonStyle2::Filled)) + .style(ButtonStyle::Subtle) + .when(active, |button| button.style(ButtonStyle::Filled)) } } @@ -103,8 +103,8 @@ fn toggle_replace_button(active: bool) -> impl IntoElement { cx.dispatch_action(Box::new(ToggleReplace)); cx.notify(); }) - .style(ButtonStyle2::Subtle) - .when(active, |button| button.style(ButtonStyle2::Filled)) + .style(ButtonStyle::Subtle) + .when(active, |button| button.style(ButtonStyle::Filled)) } fn render_replace_button( diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 35d8ea49b431bcc5a27a6a669d89468d5fa94f2f..4fe76ce878d7e9488013f7484581f0134f0e1c40 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -24,6 +24,7 @@ pub enum ComponentStory { Keybinding, Label, List, + ListHeader, ListItem, Scroll, Text, @@ -46,6 +47,7 @@ impl ComponentStory { 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(), diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs index 7b0a0c3d3ae72c646bf5590a29a272528f312279..be55194e76ebdaedc0e72aa4a9f9d4d6314fb3eb 100644 --- a/crates/theme_selector2/src/theme_selector.rs +++ b/crates/theme_selector2/src/theme_selector.rs @@ -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}; diff --git a/crates/ui2/src/components/button/mod.rs b/crates/ui2/src/components/button.rs similarity index 100% rename from crates/ui2/src/components/button/mod.rs rename to crates/ui2/src/components/button.rs diff --git a/crates/ui2/src/components/button/button.rs b/crates/ui2/src/components/button/button.rs index b4e666e9adba796d2cdea6df55b591fd021f3519..ce26ee76a5df38c29e17c024bb839d0229b332d4 100644 --- a/crates/ui2/src/components/button/button.rs +++ b/crates/ui2/src/components/button/button.rs @@ -1,7 +1,7 @@ use gpui::AnyView; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle}; #[derive(IntoElement)] pub struct Button { @@ -54,12 +54,12 @@ impl ButtonCommon for Button { self.base.id() } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } @@ -79,7 +79,7 @@ impl RenderOnce for Button { } else if self.base.selected { Color::Selected } else { - Color::Default + self.label_color.unwrap_or_default() }; self.base.child( diff --git a/crates/ui2/src/components/button/button_like.rs b/crates/ui2/src/components/button/button_like.rs index 74cb506e70dad090288e6ab3544b14a793fbbec2..207d59ecf15704025d77b1dd26e18e340f4f0c47 100644 --- a/crates/ui2/src/components/button/button_like.rs +++ b/crates/ui2/src/components/button/button_like.rs @@ -1,4 +1,4 @@ -use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful}; +use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful}; use smallvec::SmallVec; use crate::h_stack; @@ -6,13 +6,13 @@ use crate::prelude::*; pub trait ButtonCommon: Clickable + Disableable { fn id(&self) -> &ElementId; - fn style(self, style: ButtonStyle2) -> Self; - fn size(self, size: ButtonSize2) -> Self; + fn style(self, style: ButtonStyle) -> Self; + fn size(self, size: ButtonSize) -> Self; fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self; } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] -pub enum ButtonStyle2 { +pub enum ButtonStyle { #[default] Filled, // Tinted, @@ -21,54 +21,57 @@ pub enum ButtonStyle2 { } #[derive(Debug, Clone)] -pub struct ButtonStyle { +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 ButtonStyle2 { - pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle { +impl ButtonStyle { + pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_background, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, - border_color: gpui::transparent_black(), + border_color: 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(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, } } - pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_hover, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_hover, - border_color: gpui::transparent_black(), + border_color: 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(), + 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 @@ -77,23 +80,23 @@ impl ButtonStyle2 { } } - pub fn active(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_active, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_active, - border_color: gpui::transparent_black(), + border_color: 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(), + 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 @@ -102,22 +105,23 @@ impl ButtonStyle2 { } } - pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle { + #[allow(unused)] + pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + 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), }, - ButtonStyle2::Subtle => ButtonStyle { + 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), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), + 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), @@ -125,23 +129,23 @@ impl ButtonStyle2 { } } - pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + 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), }, - ButtonStyle2::Subtle => ButtonStyle { + 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), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), - border_color: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, @@ -150,19 +154,19 @@ impl ButtonStyle2 { } #[derive(Default, PartialEq, Clone, Copy)] -pub enum ButtonSize2 { +pub enum ButtonSize { #[default] Default, Compact, None, } -impl ButtonSize2 { +impl ButtonSize { fn height(self) -> Rems { match self { - ButtonSize2::Default => rems(22. / 16.), - ButtonSize2::Compact => rems(18. / 16.), - ButtonSize2::None => rems(16. / 16.), + ButtonSize::Default => rems(22. / 16.), + ButtonSize::Compact => rems(18. / 16.), + ButtonSize::None => rems(16. / 16.), } } } @@ -170,10 +174,10 @@ impl ButtonSize2 { #[derive(IntoElement)] pub struct ButtonLike { id: ElementId, - pub(super) style: ButtonStyle2, + pub(super) style: ButtonStyle, pub(super) disabled: bool, pub(super) selected: bool, - size: ButtonSize2, + size: ButtonSize, tooltip: Option AnyView>>, on_click: Option>, children: SmallVec<[AnyElement; 2]>, @@ -183,10 +187,10 @@ impl ButtonLike { pub fn new(id: impl Into) -> Self { Self { id: id.into(), - style: ButtonStyle2::default(), + style: ButtonStyle::default(), disabled: false, selected: false, - size: ButtonSize2::Default, + size: ButtonSize::Default, tooltip: None, children: SmallVec::new(), on_click: None, @@ -220,12 +224,12 @@ impl ButtonCommon for ButtonLike { &self.id } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.style = style; self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.size = size; self } diff --git a/crates/ui2/src/components/button/icon_button.rs b/crates/ui2/src/components/button/icon_button.rs index 7746c3f8bee621e07ab73b870ecc14333483b798..a62832059d148c0d5ed9a1ec34aa6352695217f0 100644 --- a/crates/ui2/src/components/button/icon_button.rs +++ b/crates/ui2/src/components/button/icon_button.rs @@ -1,7 +1,7 @@ use gpui::{Action, AnyView}; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize}; #[derive(IntoElement)] pub struct IconButton { @@ -65,12 +65,12 @@ impl ButtonCommon for IconButton { self.base.id() } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index aafd04539149a703ab48e35fe8401f3b7db81f18..88650b6ae8d2aa8d60cf0dbe682474f417919a9d 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -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}; - +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, - toggle: Option, - 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) -> 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: impl Into>) -> 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(|header| 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)), - }) - } -} diff --git a/crates/ui2/src/components/list/list.rs b/crates/ui2/src/components/list/list.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdfe256bd6b40b3c3926e74d517c28dc1f2f9216 --- /dev/null +++ b/crates/ui2/src/components/list/list.rs @@ -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, + toggle: Option, + 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) -> Self { + self.empty_message = empty_message.into(); + self + } + + pub fn header(mut self, header: impl Into>) -> Self { + self.header = header.into(); + self + } + + pub fn toggle(mut self, toggle: impl Into>) -> 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)), + } + }) + } +} diff --git a/crates/ui2/src/components/list/list_header.rs b/crates/ui2/src/components/list/list_header.rs index 8761acd60869e4ed91897644c8bcb98b898ae5e5..799b1c5dae886aba2e323def0cf9270b132ba2cb 100644 --- a/crates/ui2/src/components/list/list_header.rs +++ b/crates/ui2/src/components/list/list_header.rs @@ -1,22 +1,16 @@ use std::rc::Rc; -use gpui::{ClickEvent, Div}; +use gpui::{AnyElement, ClickEvent, Div}; +use smallvec::SmallVec; use crate::prelude::*; -use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label}; - -pub enum ListHeaderMeta { - Tools(Vec), - // 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, - meta: Option, + meta: SmallVec<[AnyElement; 2]>, toggle: Option, on_toggle: Option>, inset: bool, @@ -28,7 +22,7 @@ impl ListHeader { Self { label: label.into(), left_icon: None, - meta: None, + meta: SmallVec::new(), inset: false, toggle: None, on_toggle: None, @@ -49,21 +43,19 @@ impl ListHeader { self } - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; + pub fn left_icon(mut self, left_icon: impl Into>) -> 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) -> 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,18 +65,6 @@ impl RenderOnce for ListHeader { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - let meta = match self.meta { - Some(ListHeaderMeta::Tools(icons)) => div().child( - h_stack() - .gap_2() - .items_center() - .children(icons.into_iter().map(|i| i.icon_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() @@ -118,7 +98,7 @@ impl RenderOnce for ListHeader { .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)), ), ) - .child(meta), + .child(h_stack().gap_2().items_center().children(self.meta)), ) } } diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index 7ad1d5fb72d5c739f4514ed8a6e0a250d3d399ef..85198416cd94e7b498fa2c9283a175d390b77a82 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -83,11 +83,6 @@ impl ListItem { self } - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - pub fn left_child(mut self, left_content: impl IntoElement) -> Self { self.left_slot = Some(left_content.into_any_element()); self @@ -109,6 +104,13 @@ impl ListItem { } } +impl Selectable for ListItem { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + impl ParentElement for ListItem { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index e870515caf170b3477c0c7a3ff75abf3d67c7780..113c2679b7f2b9cfec58d92d51060f7712b8a384 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -8,6 +8,7 @@ mod icon_button; mod keybinding; mod label; mod list; +mod list_header; mod list_item; pub use avatar::*; @@ -20,4 +21,5 @@ pub use icon_button::*; pub use keybinding::*; pub use label::*; pub use list::*; +pub use list_header::*; pub use list_item::*; diff --git a/crates/ui2/src/components/stories/button.rs b/crates/ui2/src/components/stories/button.rs index 918c321c25de39bcc90ceaeec2202abe54d7e2a8..17bcd8b26850333db4dabfbae2fc0e3570f1e7cc 100644 --- a/crates/ui2/src/components/stories/button.rs +++ b/crates/ui2/src/components/stories/button.rs @@ -2,7 +2,7 @@ use gpui::{Div, Render}; use story::Story; use crate::prelude::*; -use crate::{Button, ButtonStyle2}; +use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -14,9 +14,13 @@ impl Render for ButtonStory { .child(Story::title_for::