diff --git a/Cargo.lock b/Cargo.lock index f58eba8635..e30e39e723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1259,6 +1259,7 @@ dependencies = [ "collections", "context_menu", "editor", + "feedback", "futures 0.3.25", "fuzzy", "gpui", diff --git a/assets/icons/speech_bubble_12.svg b/assets/icons/speech_bubble_12.svg new file mode 100644 index 0000000000..f5f330056a --- /dev/null +++ b/assets/icons/speech_bubble_12.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 899f8cc8b4..2afeb8ad8a 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -29,6 +29,7 @@ clock = { path = "../clock" } collections = { path = "../collections" } context_menu = { path = "../context_menu" } editor = { path = "../editor" } +feedback = { path = "../feedback" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } menu = { path = "../menu" } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index f9f5738ad2..6e87a76c37 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -304,12 +304,22 @@ impl CollabTitlebarItem { label: "Sign out".into(), action: Box::new(SignOut), }, + ContextMenuItem::Item { + label: "Give Feedback".into(), + action: Box::new(feedback::feedback_editor::GiveFeedback), + }, ] } else { - vec![ContextMenuItem::Item { - label: "Sign in".into(), - action: Box::new(Authenticate), - }] + vec![ + ContextMenuItem::Item { + label: "Sign in".into(), + action: Box::new(Authenticate), + }, + ContextMenuItem::Item { + label: "Give Feedback".into(), + action: Box::new(feedback::feedback_editor::GiveFeedback), + }, + ] }; user_menu.show( diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index da3c6bc4bd..e1e8285931 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1082,18 +1082,21 @@ impl MultiBuffer { let mut cursor = snapshot.excerpts.cursor::(); cursor.seek(&position, Bias::Right, &()); - cursor.item().map(|excerpt| { - ( - excerpt.id.clone(), - self.buffers - .borrow() - .get(&excerpt.buffer_id) - .unwrap() - .buffer - .clone(), - excerpt.range.context.clone(), - ) - }) + cursor + .item() + .or_else(|| snapshot.excerpts.last()) + .map(|excerpt| { + ( + excerpt.id.clone(), + self.buffers + .borrow() + .get(&excerpt.buffer_id) + .unwrap() + .buffer + .clone(), + excerpt.range.context.clone(), + ) + }) } // If point is at the end of the buffer, the last excerpt is returned diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 8fcafdfede..222c542eed 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,34 +1,59 @@ -use gpui::{ - elements::{MouseEventHandler, ParentElement, Stack, Text}, - CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext, -}; +use gpui::{elements::*, CursorStyle, Entity, MouseButton, RenderContext, View, ViewContext}; use settings::Settings; use workspace::{item::ItemHandle, StatusItemView}; -use crate::feedback_editor::GiveFeedback; +use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; -pub struct DeployFeedbackButton; +pub struct DeployFeedbackButton { + active: bool, +} impl Entity for DeployFeedbackButton { type Event = (); } +impl DeployFeedbackButton { + pub fn new() -> Self { + DeployFeedbackButton { active: false } + } +} + impl View for DeployFeedbackButton { fn ui_name() -> &'static str { "DeployFeedbackButton" } fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + let active = self.active; Stack::new() .with_child( MouseEventHandler::::new(0, cx, |state, cx| { let theme = &cx.global::().theme; - let theme = &theme.workspace.status_bar.feedback; + let style = &theme + .workspace + .status_bar + .sidebar_buttons + .item + .style_for(state, active); - Text::new("Give Feedback", theme.style_for(state, true).clone()).boxed() + Svg::new("icons/speech_bubble_12.svg") + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned() + .constrained() + .with_width(style.icon_size) + .with_height(style.icon_size) + .contained() + .with_style(style.container) + .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(GiveFeedback)) + .on_click(MouseButton::Left, move |_, cx| { + if !active { + cx.dispatch_action(GiveFeedback) + } + }) .boxed(), ) .boxed() @@ -36,5 +61,15 @@ impl View for DeployFeedbackButton { } impl StatusItemView for DeployFeedbackButton { - fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} + fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { + if let Some(item) = item { + if let Some(_) = item.downcast::() { + self.active = true; + cx.notify(); + return; + } + } + self.active = false; + cx.notify(); + } } diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs new file mode 100644 index 0000000000..1da0b4323c --- /dev/null +++ b/crates/language_selector/src/active_buffer_language.rs @@ -0,0 +1,87 @@ +use editor::Editor; +use gpui::{ + elements::*, CursorStyle, Entity, MouseButton, RenderContext, Subscription, View, ViewContext, + ViewHandle, +}; +use settings::Settings; +use std::sync::Arc; +use workspace::{item::ItemHandle, StatusItemView}; + +pub struct ActiveBufferLanguage { + active_language: Option>, + _observe_active_editor: Option, +} + +impl Default for ActiveBufferLanguage { + fn default() -> Self { + Self::new() + } +} + +impl ActiveBufferLanguage { + pub fn new() -> Self { + Self { + active_language: None, + _observe_active_editor: None, + } + } + + fn update_language(&mut self, editor: ViewHandle, cx: &mut ViewContext) { + self.active_language.take(); + + let editor = editor.read(cx); + if let Some((_, buffer, _)) = editor.active_excerpt(cx) { + if let Some(language) = buffer.read(cx).language() { + self.active_language = Some(language.name()); + } + } + + cx.notify(); + } +} + +impl Entity for ActiveBufferLanguage { + type Event = (); +} + +impl View for ActiveBufferLanguage { + fn ui_name() -> &'static str { + "ActiveBufferLanguage" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + if let Some(active_language) = self.active_language.as_ref() { + MouseEventHandler::::new(0, cx, |state, cx| { + let theme = &cx.global::().theme.workspace.status_bar; + let style = theme.active_language.style_for(state, false); + Label::new(active_language.to_string(), style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Toggle)) + .boxed() + } else { + Empty::new().boxed() + } + } +} + +impl StatusItemView for ActiveBufferLanguage { + 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_language)); + self.update_language(editor, cx); + } else { + self.active_language = None; + self._observe_active_editor = None; + } + + cx.notify(); + } +} diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 786dd5abe0..711e36f9c4 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -1,5 +1,6 @@ -use std::sync::Arc; +mod active_buffer_language; +pub use active_buffer_language::ActiveBufferLanguage; use editor::Editor; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ @@ -10,6 +11,7 @@ use language::{Buffer, LanguageRegistry}; use picker::{Picker, PickerDelegate}; use project::Project; use settings::Settings; +use std::sync::Arc; use workspace::{AppState, Workspace}; actions!(language_selector, [Toggle]); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 484c542ede..70ee22d37d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -277,10 +277,10 @@ pub struct StatusBar { pub height: f32, pub item_spacing: f32, pub cursor_position: TextStyle, + pub active_language: Interactive, pub auto_update_progress_message: TextStyle, pub auto_update_done_message: TextStyle, pub lsp_status: Interactive, - pub feedback: Interactive, pub sidebar_buttons: StatusBarSidebarButtons, pub diagnostic_summary: Interactive, pub diagnostic_message: Interactive, diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index bb519c7a95..2c16d4ba8b 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -140,6 +140,7 @@ pub fn menus() -> Vec> { MenuItem::action("View Telemetry Log", crate::OpenTelemetryLog), MenuItem::action("View Dependency Licenses", crate::OpenLicenses), MenuItem::separator(), + MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), MenuItem::action( "Copy System Specs Into Clipboard", feedback::CopySystemSpecsIntoClipboard, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 59e9336e17..23f159d845 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -338,14 +338,16 @@ pub fn initialize_workspace( cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx)); let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + let active_buffer_language = cx.add_view(|_| language_selector::ActiveBufferLanguage::new()); let feedback_button = - cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton {}); + cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new()); + let cursor_position = cx.add_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_right_item(cursor_position, cx); status_bar.add_right_item(feedback_button, cx); + status_bar.add_right_item(active_buffer_language, cx); + status_bar.add_right_item(cursor_position, cx); }); auto_update::notify_of_any_new_update(cx.weak_handle(), cx); diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index a60a55df1e..9fa427d302 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -25,6 +25,13 @@ export default function statusBar(colorScheme: ColorScheme) { }, border: border(layer, { top: true, overlay: true }), cursorPosition: text(layer, "sans", "variant"), + activeLanguage: { + padding: { left: 6, right: 6 }, + ...text(layer, "sans", "variant"), + hover: { + ...text(layer, "sans", "on") + } + }, autoUpdateProgressMessage: text(layer, "sans", "variant"), autoUpdateDoneMessage: text(layer, "sans", "variant"), lspStatus: { @@ -44,10 +51,6 @@ export default function statusBar(colorScheme: ColorScheme) { ...text(layer, "sans"), hover: text(layer, "sans", "hovered"), }, - feedback: { - ...text(layer, "sans", "variant"), - hover: text(layer, "sans", "hovered"), - }, diagnosticSummary: { height: 20, iconWidth: 16,