Language Selector 2 working!

Conrad Irwin created

Change summary

Cargo.lock                                              |   1 
crates/editor2/src/editor.rs                            |  16 
crates/language_selector2/Cargo.toml                    |   3 
crates/language_selector2/src/active_buffer_language.rs |  58 +--
crates/language_selector2/src/language_selector.rs      | 138 +++++++---
crates/workspace2/src/workspace2.rs                     |   5 
crates/zed2/src/main.rs                                 |   2 
crates/zed2/src/zed2.rs                                 |   6 
8 files changed, 131 insertions(+), 98 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4804,6 +4804,7 @@ dependencies = [
  "project2",
  "settings2",
  "theme2",
+ "ui2",
  "util",
  "workspace2",
 ]

crates/editor2/src/editor.rs 🔗

@@ -1920,14 +1920,14 @@ impl Editor {
     //         self.buffer.read(cx).read(cx).file_at(point).cloned()
     //     }
 
-    //     pub fn active_excerpt(
-    //         &self,
-    //         cx: &AppContext,
-    //     ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
-    //         self.buffer
-    //             .read(cx)
-    //             .excerpt_containing(self.selections.newest_anchor().head(), cx)
-    //     }
+    pub fn active_excerpt(
+        &self,
+        cx: &AppContext,
+    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
+        self.buffer
+            .read(cx)
+            .excerpt_containing(self.selections.newest_anchor().head(), cx)
+    }
 
     //     pub fn style(&self, cx: &AppContext) -> EditorStyle {
     //         build_style(

crates/language_selector2/Cargo.toml 🔗

@@ -9,13 +9,14 @@ path = "src/language_selector.rs"
 doctest = false
 
 [dependencies]
-editor2 = { package = "editor2", path = "../editor2" }
+editor = { package = "editor2", path = "../editor2" }
 fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 language = { package = "language2", path = "../language2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 picker = { package = "picker2", path = "../picker2" }
 project = { package = "project2", path = "../project2" }
 theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
 workspace = { package = "workspace2", path = "../workspace2" }

crates/language_selector2/src/active_buffer_language.rs 🔗

@@ -1,15 +1,16 @@
 use editor::Editor;
 use gpui::{
-    elements::*,
-    platform::{CursorStyle, MouseButton},
-    Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+    div, Div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView,
 };
 use std::sync::Arc;
+use ui::{Button, ButtonCommon, Clickable, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
+use crate::LanguageSelector;
+
 pub struct ActiveBufferLanguage {
     active_language: Option<Option<Arc<str>>>,
-    workspace: WeakViewHandle<Workspace>,
+    workspace: WeakView<Workspace>,
     _observe_active_editor: Option<Subscription>,
 }
 
@@ -22,7 +23,7 @@ impl ActiveBufferLanguage {
         }
     }
 
-    fn update_language(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+    fn update_language(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
         self.active_language = Some(None);
 
         let editor = editor.read(cx);
@@ -36,44 +37,29 @@ impl ActiveBufferLanguage {
     }
 }
 
-impl Entity for ActiveBufferLanguage {
-    type Event = ();
-}
-
-impl View for ActiveBufferLanguage {
-    fn ui_name() -> &'static str {
-        "ActiveBufferLanguage"
-    }
+impl Render for ActiveBufferLanguage {
+    type Element = Div;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if let Some(active_language) = self.active_language.as_ref() {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div {
+        div().when_some(self.active_language.as_ref(), |el, active_language| {
             let active_language_text = if let Some(active_language_text) = active_language {
                 active_language_text.to_string()
             } else {
                 "Unknown".to_string()
             };
-            let theme = theme::current(cx).clone();
 
-            MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
-                let theme = &theme::current(cx).workspace.status_bar;
-                let style = theme.active_language.style_for(state);
-                Label::new(active_language_text, style.text.clone())
-                    .contained()
-                    .with_style(style.container)
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, this, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        crate::toggle(workspace, &Default::default(), cx)
-                    });
-                }
-            })
-            .with_tooltip::<Self>(0, "Select Language", None, theme.tooltip.clone(), cx)
-            .into_any()
-        } else {
-            Empty::new().into_any()
-        }
+            el.child(
+                Button::new("change-language", active_language_text)
+                    .on_click(cx.listener(|this, _, cx| {
+                        if let Some(workspace) = this.workspace.upgrade() {
+                            workspace.update(cx, |workspace, cx| {
+                                LanguageSelector::toggle(workspace, cx)
+                            });
+                        }
+                    }))
+                    .tooltip(|cx| Tooltip::text("Select Language", cx)),
+            )
+        })
     }
 }
 

crates/language_selector2/src/language_selector.rs 🔗

@@ -4,50 +4,88 @@ pub use active_buffer_language::ActiveBufferLanguage;
 use anyhow::anyhow;
 use editor::Editor;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
+use gpui::{
+    actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
+    Model, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
+};
 use language::{Buffer, LanguageRegistry};
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
 use project::Project;
 use std::sync::Arc;
+use ui::{v_stack, HighlightedLabel, ListItem, Selectable};
 use util::ResultExt;
 use workspace::Workspace;
 
 actions!(Toggle);
 
 pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(LanguagePicker::register).detach();
+    cx.observe_new_views(LanguageSelector::register).detach();
 }
 
-pub fn init(cx: &mut AppContext) {
-    Picker::<LanguageSelectorDelegate>::init(cx);
-    cx.add_action(toggle);
+pub struct LanguageSelector {
+    picker: View<Picker<LanguageSelectorDelegate>>,
 }
 
-pub fn toggle(
-    workspace: &mut Workspace,
-    _: &Toggle,
-    cx: &mut ViewContext<Workspace>,
-) -> Option<()> {
-    let (_, buffer, _) = workspace
-        .active_item(cx)?
-        .act_as::<Editor>(cx)?
-        .read(cx)
-        .active_excerpt(cx)?;
-    workspace.toggle_modal(cx, |workspace, cx| {
+impl LanguageSelector {
+    fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+        dbg!("regsiter");
+        workspace.register_action(move |workspace, _: &Toggle, cx| {
+            Self::toggle(workspace, cx);
+        });
+    }
+
+    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
         let registry = workspace.app_state().languages.clone();
-        cx.add_view(|cx| {
-            Picker::new(
-                LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
-                cx,
-            )
-        })
-    });
-    Some(())
+        let (_, buffer, _) = workspace
+            .active_item(cx)?
+            .act_as::<Editor>(cx)?
+            .read(cx)
+            .active_excerpt(cx)?;
+        let project = workspace.project().clone();
+
+        workspace.toggle_modal(cx, move |cx| {
+            LanguageSelector::new(buffer, project, registry, cx)
+        });
+        Some(())
+    }
+
+    fn new(
+        buffer: Model<Buffer>,
+        project: Model<Project>,
+        language_registry: Arc<LanguageRegistry>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let delegate = LanguageSelectorDelegate::new(
+            cx.view().downgrade(),
+            buffer,
+            project,
+            language_registry,
+        );
+
+        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
+        Self { picker }
+    }
 }
 
+impl Render for LanguageSelector {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        v_stack().min_w_96().child(self.picker.clone())
+    }
+}
+
+impl FocusableView for LanguageSelector {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+impl EventEmitter<DismissEvent> for LanguageSelector {}
+
 pub struct LanguageSelectorDelegate {
-    buffer: ModelHandle<Buffer>,
-    project: ModelHandle<Project>,
+    language_selector: WeakView<LanguageSelector>,
+    buffer: Model<Buffer>,
+    project: Model<Project>,
     language_registry: Arc<LanguageRegistry>,
     candidates: Vec<StringMatchCandidate>,
     matches: Vec<StringMatch>,
@@ -56,8 +94,9 @@ pub struct LanguageSelectorDelegate {
 
 impl LanguageSelectorDelegate {
     fn new(
-        buffer: ModelHandle<Buffer>,
-        project: ModelHandle<Project>,
+        language_selector: WeakView<LanguageSelector>,
+        buffer: Model<Buffer>,
+        project: Model<Project>,
         language_registry: Arc<LanguageRegistry>,
     ) -> Self {
         let candidates = language_registry
@@ -78,6 +117,7 @@ impl LanguageSelectorDelegate {
         matches.sort_unstable_by(|mat1, mat2| mat1.string.cmp(&mat2.string));
 
         Self {
+            language_selector,
             buffer,
             project,
             language_registry,
@@ -89,6 +129,8 @@ impl LanguageSelectorDelegate {
 }
 
 impl PickerDelegate for LanguageSelectorDelegate {
+    type ListItem = ListItem;
+
     fn placeholder_text(&self) -> Arc<str> {
         "Select a language...".into()
     }
@@ -106,23 +148,25 @@ impl PickerDelegate for LanguageSelectorDelegate {
             cx.spawn(|_, mut cx| async move {
                 let language = language.await?;
                 let project = project
-                    .upgrade(&cx)
+                    .upgrade()
                     .ok_or_else(|| anyhow!("project was dropped"))?;
                 let buffer = buffer
-                    .upgrade(&cx)
+                    .upgrade()
                     .ok_or_else(|| anyhow!("buffer was dropped"))?;
                 project.update(&mut cx, |project, cx| {
                     project.set_language_for_buffer(&buffer, language, cx);
-                });
-                anyhow::Ok(())
+                })
             })
             .detach_and_log_err(cx);
         }
-
-        cx.emit(PickerEvent::Dismiss);
+        self.dismissed(cx);
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        self.language_selector
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
+    }
 
     fn selected_index(&self) -> usize {
         self.selected_index
@@ -137,7 +181,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
         query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
-        let background = cx.background().clone();
+        let background = cx.background_executor().clone();
         let candidates = self.candidates.clone();
         cx.spawn(|this, mut cx| async move {
             let matches = if query.is_empty() {
@@ -164,7 +208,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
             };
 
             this.update(&mut cx, |this, cx| {
-                let delegate = this.delegate_mut();
+                let delegate = &mut this.delegate;
                 delegate.matches = matches;
                 delegate.selected_index = delegate
                     .selected_index
@@ -178,23 +222,21 @@ impl PickerDelegate for LanguageSelectorDelegate {
     fn render_match(
         &self,
         ix: usize,
-        mouse_state: &mut MouseState,
         selected: bool,
-        cx: &AppContext,
-    ) -> AnyElement<Picker<Self>> {
-        let theme = theme::current(cx);
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
         let mat = &self.matches[ix];
-        let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
         let mut label = mat.string.clone();
         if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
             label.push_str(" (current)");
         }
 
-        Label::new(label, style.label.clone())
-            .with_highlights(mat.positions.clone())
-            .contained()
-            .with_style(style.container)
-            .into_any()
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .selected(selected)
+                .child(HighlightedLabel::new(label, mat.positions.clone())),
+        )
     }
 }

crates/workspace2/src/workspace2.rs 🔗

@@ -2164,7 +2164,10 @@ impl Workspace {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
+            pane::Event::AddItem { item } => {
+                self.handle_pane_focused(pane.clone(), cx);
+                item.added_to_pane(self, pane, cx);
+            }
             pane::Event::Split(direction) => {
                 self.split_and_clone(pane, *direction, cx);
             }

crates/zed2/src/main.rs 🔗

@@ -216,7 +216,7 @@ fn main() {
         terminal_view::init(cx);
 
         // journal2::init(app_state.clone(), cx);
-        // language_selector::init(cx);
+        language_selector::init(cx);
         theme_selector::init(cx);
         // activity_indicator::init(cx);
         // language_tools::init(cx);

crates/zed2/src/zed2.rs 🔗

@@ -142,8 +142,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
         let activity_indicator =
             activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
-            let active_buffer_language =
-                cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
+        let active_buffer_language =
+            cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
         //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
         //     let feedback_button = cx.add_view(|_| {
         //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
@@ -155,7 +155,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
 
             // status_bar.add_right_item(feedback_button, cx);
             // status_bar.add_right_item(copilot, cx);
-        status_bar.add_right_item(active_buffer_language, cx);
+            status_bar.add_right_item(active_buffer_language, cx);
             // status_bar.add_right_item(vim_mode_indicator, cx);
             status_bar.add_right_item(cursor_position, cx);
         });