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