Show active buffer's language on the right in the status bar

Antonio Scandurra created

Change summary

crates/language_selector/src/active_buffer_language.rs | 86 ++++++++++++
crates/language_selector/src/language_selector.rs      |  4 
crates/theme/src/theme.rs                              |  1 
crates/zed/src/zed.rs                                  |  2 
styles/src/styleTree/statusBar.ts                      |  9 +
5 files changed, 100 insertions(+), 2 deletions(-)

Detailed changes

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -0,0 +1,86 @@
+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<Arc<str>>,
+    _observe_active_editor: Option<Subscription>,
+}
+
+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<Editor>, cx: &mut ViewContext<Self>) {
+        let editor = editor.read(cx);
+        self.active_language.take();
+        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<Self>) -> ElementBox {
+        if let Some(active_language) = self.active_language.as_ref() {
+            MouseEventHandler::<Self>::new(0, cx, |state, cx| {
+                let theme = &cx.global::<Settings>().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<Self>,
+    ) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+            self._observe_active_editor = Some(cx.observe(&editor, Self::update_language));
+            self.update_language(editor, cx);
+        } else {
+            self.active_language = None;
+            self._observe_active_editor = None;
+        }
+
+        cx.notify();
+    }
+}

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]);

crates/theme/src/theme.rs 🔗

@@ -277,6 +277,7 @@ pub struct StatusBar {
     pub height: f32,
     pub item_spacing: f32,
     pub cursor_position: TextStyle,
+    pub active_language: Interactive<TextStyle>,
     pub auto_update_progress_message: TextStyle,
     pub auto_update_done_message: TextStyle,
     pub lsp_status: Interactive<StatusBarLspStatus>,

crates/zed/src/zed.rs 🔗

@@ -338,6 +338,7 @@ 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 active_buffer_language = cx.add_view(|_| language_selector::ActiveBufferLanguage::new());
     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
     let feedback_button =
         cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton {});
@@ -346,6 +347,7 @@ pub fn initialize_workspace(
         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(active_buffer_language, cx);
     });
 
     auto_update::notify_of_any_new_update(cx.weak_handle(), cx);

styles/src/styleTree/statusBar.ts 🔗

@@ -25,6 +25,12 @@ export default function statusBar(colorScheme: ColorScheme) {
         },
         border: border(layer, { top: true, overlay: true }),
         cursorPosition: text(layer, "sans", "variant"),
+        activeLanguage: {
+            ...text(layer, "sans", "variant"),
+            hover: {
+                ...text(layer, "sans", "on")
+            }
+        },
         autoUpdateProgressMessage: text(layer, "sans", "variant"),
         autoUpdateDoneMessage: text(layer, "sans", "variant"),
         lspStatus: {
@@ -45,9 +51,10 @@ export default function statusBar(colorScheme: ColorScheme) {
             hover: text(layer, "sans", "hovered"),
         },
         feedback: {
+            margin: { left: 2 },
             color: foreground(layer, "variant"),
             iconWidth: 14,
-            buttonWidth: 20,
+            buttonWidth: 14,
             hover: {
               color: foreground(layer, "on"),
             },