Merge branch 'main' into 2064-remove-contacts

Petros Amoiridis created

Change summary

Cargo.lock                                    |   2 
crates/editor/src/items.rs                    |   2 
crates/feedback/src/feedback.rs               |   6 
crates/feedback/src/feedback_editor.rs        | 105 ++++----
crates/gpui/src/app.rs                        |  11 
crates/gpui/src/platform.rs                   |   1 
crates/gpui/src/platform/mac/event.rs         |   2 
crates/gpui/src/platform/mac/platform.rs      |   4 
crates/gpui/src/platform/mac/status_item.rs   |   4 
crates/gpui/src/platform/mac/window.rs        |  37 ++
crates/gpui/src/platform/test.rs              |   4 
crates/gpui/src/presenter.rs                  |   5 
crates/gpui/src/presenter/event_dispatcher.rs |   1 
crates/language/src/buffer_tests.rs           |  20 +
crates/language/src/language.rs               | 256 +++++++++++++++-----
crates/language/src/syntax_map.rs             |  26 +-
crates/live_kit_client/src/prod.rs            |  11 
crates/project/src/project.rs                 |  13 
crates/workspace/src/workspace.rs             |  13 
crates/zed/Cargo.toml                         |   2 
crates/zed/src/languages.rs                   | 122 ++++-----
crates/zed/src/languages/c.rs                 |  14 
crates/zed/src/languages/go.rs                |   5 
crates/zed/src/languages/python.rs            |  15 
crates/zed/src/languages/rust.rs              |  27 +
crates/zed/src/languages/typescript.rs        |  10 
crates/zed/src/main.rs                        |  16 
crates/zed/src/zed.rs                         |   4 
28 files changed, 456 insertions(+), 282 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -8216,7 +8216,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
 
 [[package]]
 name = "zed"
-version = "0.71.0"
+version = "0.72.0"
 dependencies = [
  "activity_indicator",
  "anyhow",

crates/editor/src/items.rs 🔗

@@ -1094,7 +1094,7 @@ impl StatusItemView for CursorPosition {
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
+        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_position));
             self.update_position(editor, cx);
         } else {

crates/feedback/src/feedback.rs 🔗

@@ -5,7 +5,7 @@ mod system_specs;
 use gpui::{actions, impl_actions, ClipboardItem, ViewContext};
 use serde::Deserialize;
 use system_specs::SystemSpecs;
-use workspace::Workspace;
+use workspace::{AppState, Workspace};
 
 #[derive(Deserialize, Clone, PartialEq)]
 pub struct OpenBrowser {
@@ -19,8 +19,8 @@ actions!(
     [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature,]
 );
 
-pub fn init(cx: &mut gpui::MutableAppContext) {
-    feedback_editor::init(cx);
+pub fn init(app_state: Arc<AppState>, cx: &mut gpui::MutableAppContext) {
+    feedback_editor::init(app_state, cx);
 
     cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
 

crates/feedback/src/feedback_editor.rs 🔗

@@ -1,4 +1,5 @@
 use std::{
+    any::TypeId,
     ops::{Range, RangeInclusive},
     sync::Arc,
 };
@@ -12,7 +13,7 @@ use gpui::{
     elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text},
     serde_json, AnyViewHandle, AppContext, CursorStyle, Element, ElementBox, Entity, ModelHandle,
     MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
-    ViewHandle,
+    ViewHandle, WeakViewHandle,
 };
 use isahc::Request;
 use language::Buffer;
@@ -25,7 +26,7 @@ use settings::Settings;
 use workspace::{
     item::{Item, ItemHandle},
     searchable::{SearchableItem, SearchableItemHandle},
-    StatusItemView, Workspace,
+    AppState, StatusItemView, Workspace,
 };
 
 use crate::system_specs::SystemSpecs;
@@ -42,8 +43,12 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
 
 actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]);
 
-pub fn init(cx: &mut MutableAppContext) {
-    cx.add_action(FeedbackEditor::deploy);
+pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
+    cx.add_action({
+        move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext<Workspace>| {
+            FeedbackEditor::deploy(workspace, app_state.clone(), cx);
+        }
+    });
 }
 
 pub struct FeedbackButton;
@@ -79,12 +84,7 @@ impl View for FeedbackButton {
 }
 
 impl StatusItemView for FeedbackButton {
-    fn set_active_pane_item(
-        &mut self,
-        _: Option<&dyn ItemHandle>,
-        _: &mut gpui::ViewContext<Self>,
-    ) {
-    }
+    fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
 }
 
 #[derive(Serialize)]
@@ -102,7 +102,7 @@ struct FeedbackEditor {
 }
 
 impl FeedbackEditor {
-    fn new_with_buffer(
+    fn new(
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
         cx: &mut ViewContext<Self>,
@@ -117,28 +117,15 @@ impl FeedbackEditor {
         cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()))
             .detach();
 
-        let this = Self { editor, project };
-        this
-    }
-
-    fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
-        let markdown_language = project.read(cx).languages().language_for_name("Markdown");
-
-        let buffer = project
-            .update(cx, |project, cx| {
-                project.create_buffer("", markdown_language, cx)
-            })
-            .expect("creating buffers on a local workspace always succeeds");
-
-        Self::new_with_buffer(project, buffer, cx)
+        Self { editor, project }
     }
 
     fn handle_save(
         &mut self,
-        _: gpui::ModelHandle<Project>,
+        _: ModelHandle<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
-        let feedback_char_count = self.editor.read(cx).buffer().read(cx).len(cx);
+        let feedback_char_count = self.editor.read(cx).text(cx).chars().count();
 
         let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() {
             Some(format!(
@@ -241,10 +228,24 @@ impl FeedbackEditor {
 }
 
 impl FeedbackEditor {
-    pub fn deploy(workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext<Workspace>) {
-        let feedback_editor =
-            cx.add_view(|cx| FeedbackEditor::new(workspace.project().clone(), cx));
-        workspace.add_item(Box::new(feedback_editor), cx);
+    pub fn deploy(
+        workspace: &mut Workspace,
+        app_state: Arc<AppState>,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        workspace
+            .with_local_workspace(&app_state, cx, |workspace, cx| {
+                let project = workspace.project().clone();
+                let markdown_language = project.read(cx).languages().language_for_name("Markdown");
+                let buffer = project
+                    .update(cx, |project, cx| {
+                        project.create_buffer("", markdown_language, cx)
+                    })
+                    .expect("creating buffers on a local workspace always succeeds");
+                let feedback_editor = cx.add_view(|cx| FeedbackEditor::new(project, buffer, cx));
+                workspace.add_item(Box::new(feedback_editor), cx);
+            })
+            .detach();
     }
 }
 
@@ -269,12 +270,7 @@ impl Entity for FeedbackEditor {
 }
 
 impl Item for FeedbackEditor {
-    fn tab_content(
-        &self,
-        _: Option<usize>,
-        style: &theme::Tab,
-        _: &gpui::AppContext,
-    ) -> ElementBox {
+    fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
         Flex::row()
             .with_child(
                 Label::new("Feedback".to_string(), style.label.clone())
@@ -293,19 +289,19 @@ impl Item for FeedbackEditor {
         Vec::new()
     }
 
-    fn is_singleton(&self, _: &gpui::AppContext) -> bool {
+    fn is_singleton(&self, _: &AppContext) -> bool {
         true
     }
 
     fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
 
-    fn can_save(&self, _: &gpui::AppContext) -> bool {
+    fn can_save(&self, _: &AppContext) -> bool {
         true
     }
 
     fn save(
         &mut self,
-        project: gpui::ModelHandle<Project>,
+        project: ModelHandle<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
         self.handle_save(project, cx)
@@ -313,7 +309,7 @@ impl Item for FeedbackEditor {
 
     fn save_as(
         &mut self,
-        project: gpui::ModelHandle<Project>,
+        project: ModelHandle<Project>,
         _: std::path::PathBuf,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
@@ -322,7 +318,7 @@ impl Item for FeedbackEditor {
 
     fn reload(
         &mut self,
-        _: gpui::ModelHandle<Project>,
+        _: ModelHandle<Project>,
         _: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
         unreachable!("reload should not have been called")
@@ -344,11 +340,7 @@ impl Item for FeedbackEditor {
             .as_singleton()
             .expect("Feedback buffer is only ever singleton");
 
-        Some(Self::new_with_buffer(
-            self.project.clone(),
-            buffer.clone(),
-            cx,
-        ))
+        Some(Self::new(self.project.clone(), buffer.clone(), cx))
     }
 
     fn serialized_item_kind() -> Option<&'static str> {
@@ -356,8 +348,8 @@ impl Item for FeedbackEditor {
     }
 
     fn deserialize(
-        _: gpui::ModelHandle<Project>,
-        _: gpui::WeakViewHandle<Workspace>,
+        _: ModelHandle<Project>,
+        _: WeakViewHandle<Workspace>,
         _: workspace::WorkspaceId,
         _: workspace::ItemId,
         _: &mut ViewContext<workspace::Pane>,
@@ -368,6 +360,21 @@ impl Item for FeedbackEditor {
     fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         Some(Box::new(handle.clone()))
     }
+
+    fn act_as_type(
+        &self,
+        type_id: TypeId,
+        self_handle: &ViewHandle<Self>,
+        _: &AppContext,
+    ) -> Option<AnyViewHandle> {
+        if type_id == TypeId::of::<Self>() {
+            Some(self_handle.into())
+        } else if type_id == TypeId::of::<Editor>() {
+            Some((&self.editor).into())
+        } else {
+            None
+        }
+    }
 }
 
 impl SearchableItem for FeedbackEditor {

crates/gpui/src/app.rs 🔗

@@ -21,6 +21,7 @@ use std::{
 use anyhow::{anyhow, Context, Result};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
+use pathfinder_geometry::vector::Vector2F;
 use postage::oneshot;
 use smallvec::SmallVec;
 use smol::prelude::*;
@@ -865,8 +866,16 @@ impl MutableAppContext {
         }
     }
 
+    pub fn is_topmost_window_for_position(&self, window_id: usize, position: Vector2F) -> bool {
+        self.presenters_and_platform_windows
+            .get(&window_id)
+            .map_or(false, |(_, window)| {
+                window.is_topmost_for_position(position)
+            })
+    }
+
     pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
-        self.cx.windows.keys().cloned()
+        self.cx.windows.keys().copied()
     }
 
     pub fn activate_window(&self, window_id: usize) {

crates/gpui/src/platform.rs 🔗

@@ -145,6 +145,7 @@ pub trait Window {
     fn present_scene(&mut self, scene: Scene);
     fn appearance(&self) -> Appearance;
     fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
+    fn is_topmost_for_position(&self, position: Vector2F) -> bool;
 }
 
 #[derive(Debug)]

crates/gpui/src/platform/mac/event.rs 🔗

@@ -125,6 +125,7 @@ impl Event {
                         button,
                         position: vec2f(
                             native_event.locationInWindow().x as f32,
+                            // MacOS screen coordinates are relative to bottom left
                             window_height - native_event.locationInWindow().y as f32,
                         ),
                         modifiers: read_modifiers(native_event),
@@ -150,6 +151,7 @@ impl Event {
                         button,
                         position: vec2f(
                             native_event.locationInWindow().x as f32,
+                            // MacOS view coordinates are relative to bottom left
                             window_height - native_event.locationInWindow().y as f32,
                         ),
                         modifiers: read_modifiers(native_event),

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -699,7 +699,9 @@ impl platform::Platform for MacPlatform {
         unsafe {
             let cursor: id = match style {
                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
-                CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
+                CursorStyle::ResizeLeftRight => {
+                    msg_send![class!(NSCursor), resizeLeftRightCursor]
+                }
                 CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
                 CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
                 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],

crates/gpui/src/platform/mac/status_item.rs 🔗

@@ -258,6 +258,10 @@ impl platform::Window for StatusItem {
             crate::Appearance::from_native(appearance)
         }
     }
+
+    fn is_topmost_for_position(&self, _: Vector2F) -> bool {
+        true
+    }
 }
 
 impl StatusItemState {

crates/gpui/src/platform/mac/window.rs 🔗

@@ -17,9 +17,9 @@ use crate::{
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
-        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
-        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
-        NSWindowStyleMask,
+        CGFloat, CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView,
+        NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton,
+        NSWindowCollectionBehavior, NSWindowStyleMask,
     },
     base::{id, nil},
     foundation::{
@@ -755,6 +755,37 @@ impl platform::Window for Window {
     fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
         self.0.borrow_mut().appearance_changed_callback = Some(callback);
     }
+
+    fn is_topmost_for_position(&self, position: Vector2F) -> bool {
+        let window_bounds = self.bounds();
+        let self_borrow = self.0.borrow();
+        let self_id = self_borrow.id;
+
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+
+            // Convert back to bottom-left coordinates
+            let point = NSPoint::new(
+                position.x() as CGFloat,
+                (window_bounds.height() - position.y()) as CGFloat,
+            );
+
+            let screen_point: NSPoint =
+                msg_send![self_borrow.native_window, convertPointToScreen: point];
+            let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
+            let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
+
+            let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
+            let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
+            if is_panel == YES || is_window == YES {
+                let topmost_window_id = get_window_state(&*top_most_window).borrow().id;
+                topmost_window_id == self_id
+            } else {
+                // Someone else's window is on top
+                false
+            }
+        }
+    }
 }
 
 impl WindowState {

crates/gpui/src/platform/test.rs 🔗

@@ -332,6 +332,10 @@ impl super::Window for Window {
     }
 
     fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
+
+    fn is_topmost_for_position(&self, _position: Vector2F) -> bool {
+        true
+    }
 }
 
 pub fn platform() -> Platform {

crates/gpui/src/presenter.rs 🔗

@@ -316,7 +316,10 @@ impl Presenter {
                         break;
                     }
                 }
-                cx.platform().set_cursor_style(style_to_assign);
+
+                if cx.is_topmost_window_for_position(self.window_id, *position) {
+                    cx.platform().set_cursor_style(style_to_assign);
+                }
 
                 if !event_reused {
                     if pressed_button.is_some() {

crates/language/src/buffer_tests.rs 🔗

@@ -51,7 +51,7 @@ fn test_line_endings(cx: &mut gpui::MutableAppContext) {
 
 #[gpui::test]
 fn test_select_language() {
-    let registry = LanguageRegistry::test();
+    let registry = Arc::new(LanguageRegistry::test());
     registry.add(Arc::new(Language::new(
         LanguageConfig {
             name: "Rust".into(),
@@ -71,27 +71,33 @@ fn test_select_language() {
 
     // matching file extension
     assert_eq!(
-        registry.select_language("zed/lib.rs").map(|l| l.name()),
+        registry.language_for_path("zed/lib.rs").map(|l| l.name()),
         Some("Rust".into())
     );
     assert_eq!(
-        registry.select_language("zed/lib.mk").map(|l| l.name()),
+        registry.language_for_path("zed/lib.mk").map(|l| l.name()),
         Some("Make".into())
     );
 
     // matching filename
     assert_eq!(
-        registry.select_language("zed/Makefile").map(|l| l.name()),
+        registry.language_for_path("zed/Makefile").map(|l| l.name()),
         Some("Make".into())
     );
 
     // matching suffix that is not the full file extension or filename
-    assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
     assert_eq!(
-        registry.select_language("zed/a.cars").map(|l| l.name()),
+        registry.language_for_path("zed/cars").map(|l| l.name()),
+        None
+    );
+    assert_eq!(
+        registry.language_for_path("zed/a.cars").map(|l| l.name()),
+        None
+    );
+    assert_eq!(
+        registry.language_for_path("zed/sumk").map(|l| l.name()),
         None
     );
-    assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
 }
 
 #[gpui::test]

crates/language/src/language.rs 🔗

@@ -16,7 +16,7 @@ use futures::{
     future::{BoxFuture, Shared},
     FutureExt, TryFutureExt,
 };
-use gpui::{MutableAppContext, Task};
+use gpui::{executor::Background, MutableAppContext, Task};
 use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
 use parking_lot::{Mutex, RwLock};
@@ -26,6 +26,7 @@ use serde::{de, Deserialize, Deserializer};
 use serde_json::Value;
 use std::{
     any::Any,
+    borrow::Cow,
     cell::RefCell,
     fmt::Debug,
     hash::Hash,
@@ -89,8 +90,7 @@ pub struct CachedLspAdapter {
 }
 
 impl CachedLspAdapter {
-    pub async fn new<T: LspAdapter>(adapter: T) -> Arc<Self> {
-        let adapter = Box::new(adapter);
+    pub async fn new(adapter: Box<dyn LspAdapter>) -> Arc<Self> {
         let name = adapter.name().await;
         let server_args = adapter.server_args().await;
         let initialization_options = adapter.initialization_options().await;
@@ -248,6 +248,16 @@ pub struct LanguageConfig {
     pub overrides: HashMap<String, LanguageConfigOverride>,
 }
 
+#[derive(Debug, Default)]
+pub struct LanguageQueries {
+    pub highlights: Option<Cow<'static, str>>,
+    pub brackets: Option<Cow<'static, str>>,
+    pub indents: Option<Cow<'static, str>>,
+    pub outline: Option<Cow<'static, str>>,
+    pub injections: Option<Cow<'static, str>>,
+    pub overrides: Option<Cow<'static, str>>,
+}
+
 #[derive(Clone)]
 pub struct LanguageScope {
     language: Arc<Language>,
@@ -407,8 +417,17 @@ pub enum LanguageServerBinaryStatus {
     Failed { error: String },
 }
 
+struct AvailableLanguage {
+    path: &'static str,
+    config: LanguageConfig,
+    grammar: tree_sitter::Language,
+    lsp_adapter: Option<Box<dyn LspAdapter>>,
+    get_queries: fn(&str) -> LanguageQueries,
+}
+
 pub struct LanguageRegistry {
     languages: RwLock<Vec<Arc<Language>>>,
+    available_languages: RwLock<Vec<AvailableLanguage>>,
     language_server_download_dir: Option<Arc<Path>>,
     lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
     lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
@@ -422,6 +441,7 @@ pub struct LanguageRegistry {
     >,
     subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>,
     theme: RwLock<Option<Arc<Theme>>>,
+    executor: Option<Arc<Background>>,
     version: AtomicUsize,
 }
 
@@ -431,6 +451,7 @@ impl LanguageRegistry {
         Self {
             language_server_download_dir: None,
             languages: Default::default(),
+            available_languages: Default::default(),
             lsp_binary_statuses_tx,
             lsp_binary_statuses_rx,
             login_shell_env_loaded: login_shell_env_loaded.shared(),
@@ -438,6 +459,7 @@ impl LanguageRegistry {
             subscription: RwLock::new(watch::channel()),
             theme: Default::default(),
             version: Default::default(),
+            executor: None,
         }
     }
 
@@ -446,6 +468,44 @@ impl LanguageRegistry {
         Self::new(Task::ready(()))
     }
 
+    pub fn set_executor(&mut self, executor: Arc<Background>) {
+        self.executor = Some(executor);
+    }
+
+    pub fn register(
+        &self,
+        path: &'static str,
+        config: LanguageConfig,
+        grammar: tree_sitter::Language,
+        lsp_adapter: Option<Box<dyn LspAdapter>>,
+        get_queries: fn(&str) -> LanguageQueries,
+    ) {
+        self.available_languages.write().push(AvailableLanguage {
+            path,
+            config,
+            grammar,
+            lsp_adapter,
+            get_queries,
+        });
+    }
+
+    pub fn language_names(&self) -> Vec<String> {
+        let mut result = self
+            .available_languages
+            .read()
+            .iter()
+            .map(|l| l.config.name.to_string())
+            .chain(
+                self.languages
+                    .read()
+                    .iter()
+                    .map(|l| l.config.name.to_string()),
+            )
+            .collect::<Vec<_>>();
+        result.sort_unstable();
+        result
+    }
+
     pub fn add(&self, language: Arc<Language>) {
         if let Some(theme) = self.theme.read().clone() {
             language.set_theme(&theme.editor.syntax);
@@ -474,58 +534,79 @@ impl LanguageRegistry {
         self.language_server_download_dir = Some(path.into());
     }
 
-    pub fn language_for_name(&self, name: &str) -> Option<Arc<Language>> {
+    pub fn language_for_name(self: &Arc<Self>, name: &str) -> Option<Arc<Language>> {
         let name = UniCase::new(name);
-        self.languages
-            .read()
-            .iter()
-            .find(|language| UniCase::new(language.name()) == name)
-            .cloned()
+        self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
     }
 
-    pub fn language_for_extension(&self, extension: &str) -> Option<Arc<Language>> {
-        let extension = UniCase::new(extension);
-        self.languages
-            .read()
-            .iter()
-            .find(|language| {
-                language
-                    .config
+    pub fn language_for_name_or_extension(self: &Arc<Self>, string: &str) -> Option<Arc<Language>> {
+        let string = UniCase::new(string);
+        self.get_or_load_language(|config| {
+            UniCase::new(config.name.as_ref()) == string
+                || config
                     .path_suffixes
                     .iter()
-                    .any(|suffix| UniCase::new(suffix) == extension)
-            })
-            .cloned()
-    }
-
-    pub fn to_vec(&self) -> Vec<Arc<Language>> {
-        self.languages.read().iter().cloned().collect()
-    }
-
-    pub fn language_names(&self) -> Vec<String> {
-        self.languages
-            .read()
-            .iter()
-            .map(|language| language.name().to_string())
-            .collect()
+                    .any(|suffix| UniCase::new(suffix) == string)
+        })
     }
 
-    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<Arc<Language>> {
+    pub fn language_for_path(self: &Arc<Self>, path: impl AsRef<Path>) -> Option<Arc<Language>> {
         let path = path.as_ref();
         let filename = path.file_name().and_then(|name| name.to_str());
         let extension = path.extension().and_then(|name| name.to_str());
         let path_suffixes = [extension, filename];
-        self.languages
+        self.get_or_load_language(|config| {
+            config
+                .path_suffixes
+                .iter()
+                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
+        })
+    }
+
+    fn get_or_load_language(
+        self: &Arc<Self>,
+        callback: impl Fn(&LanguageConfig) -> bool,
+    ) -> Option<Arc<Language>> {
+        if let Some(language) = self
+            .languages
             .read()
             .iter()
-            .find(|language| {
-                language
-                    .config
-                    .path_suffixes
-                    .iter()
-                    .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
-            })
-            .cloned()
+            .find(|language| callback(&language.config))
+        {
+            return Some(language.clone());
+        }
+
+        if let Some(executor) = self.executor.clone() {
+            let mut available_languages = self.available_languages.write();
+
+            if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) {
+                let language = available_languages.remove(ix);
+                drop(available_languages);
+                let name = language.config.name.clone();
+                let this = self.clone();
+                executor
+                    .spawn(async move {
+                        let queries = (language.get_queries)(&language.path);
+                        let language = Language::new(language.config, Some(language.grammar))
+                            .with_lsp_adapter(language.lsp_adapter)
+                            .await;
+                        match language.with_queries(queries) {
+                            Ok(language) => this.add(Arc::new(language)),
+                            Err(err) => {
+                                log::error!("failed  to load language {}: {}", name, err);
+                                return;
+                            }
+                        };
+                    })
+                    .detach();
+            }
+        }
+
+        None
+    }
+
+    pub fn to_vec(&self) -> Vec<Arc<Language>> {
+        self.languages.read().iter().cloned().collect()
     }
 
     pub fn start_language_server(
@@ -729,12 +810,70 @@ impl Language {
         self.grammar.as_ref().map(|g| g.id)
     }
 
+    pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
+        if let Some(query) = queries.highlights {
+            self = self
+                .with_highlights_query(query.as_ref())
+                .expect("failed to evaluate highlights query");
+        }
+        if let Some(query) = queries.brackets {
+            self = self
+                .with_brackets_query(query.as_ref())
+                .expect("failed to load brackets query");
+        }
+        if let Some(query) = queries.indents {
+            self = self
+                .with_indents_query(query.as_ref())
+                .expect("failed to load indents query");
+        }
+        if let Some(query) = queries.outline {
+            self = self
+                .with_outline_query(query.as_ref())
+                .expect("failed to load outline query");
+        }
+        if let Some(query) = queries.injections {
+            self = self
+                .with_injection_query(query.as_ref())
+                .expect("failed to load injection query");
+        }
+        if let Some(query) = queries.overrides {
+            self = self
+                .with_override_query(query.as_ref())
+                .expect("failed to load override query");
+        }
+        Ok(self)
+    }
     pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
+    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
+        let grammar = self.grammar_mut();
+        let query = Query::new(grammar.ts_language, source)?;
+        let mut item_capture_ix = None;
+        let mut name_capture_ix = None;
+        let mut context_capture_ix = None;
+        get_capture_indices(
+            &query,
+            &mut [
+                ("item", &mut item_capture_ix),
+                ("name", &mut name_capture_ix),
+                ("context", &mut context_capture_ix),
+            ],
+        );
+        if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
+            grammar.outline_config = Some(OutlineConfig {
+                query,
+                item_capture_ix,
+                name_capture_ix,
+                context_capture_ix,
+            });
+        }
+        Ok(self)
+    }
+
     pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         let query = Query::new(grammar.ts_language, source)?;
@@ -785,31 +924,6 @@ impl Language {
         Ok(self)
     }
 
-    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self.grammar_mut();
-        let query = Query::new(grammar.ts_language, source)?;
-        let mut item_capture_ix = None;
-        let mut name_capture_ix = None;
-        let mut context_capture_ix = None;
-        get_capture_indices(
-            &query,
-            &mut [
-                ("item", &mut item_capture_ix),
-                ("name", &mut name_capture_ix),
-                ("context", &mut context_capture_ix),
-            ],
-        );
-        if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
-            grammar.outline_config = Some(OutlineConfig {
-                query,
-                item_capture_ix,
-                name_capture_ix,
-                context_capture_ix,
-            });
-        }
-        Ok(self)
-    }
-
     pub fn with_injection_query(mut self, source: &str) -> Result<Self> {
         let grammar = self.grammar_mut();
         let query = Query::new(grammar.ts_language, source)?;
@@ -882,8 +996,10 @@ impl Language {
         Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
     }
 
-    pub fn with_lsp_adapter(mut self, lsp_adapter: Arc<CachedLspAdapter>) -> Self {
-        self.adapter = Some(lsp_adapter);
+    pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Box<dyn LspAdapter>>) -> Self {
+        if let Some(adapter) = lsp_adapter {
+            self.adapter = Some(CachedLspAdapter::new(adapter).await);
+        }
         self
     }
 
@@ -894,7 +1010,7 @@ impl Language {
     ) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
         let (servers_tx, servers_rx) = mpsc::unbounded();
         self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
-        let adapter = CachedLspAdapter::new(fake_lsp_adapter).await;
+        let adapter = CachedLspAdapter::new(Box::new(fake_lsp_adapter)).await;
         self.adapter = Some(adapter);
         servers_rx
     }

crates/language/src/syntax_map.rs 🔗

@@ -381,7 +381,12 @@ impl SyntaxSnapshot {
                 cursor.next(text);
                 while let Some(layer) = cursor.item() {
                     let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() };
-                    if language_for_injection(language_name, &registry).is_some() {
+                    if {
+                        let language_registry = &registry;
+                        language_registry.language_for_name_or_extension(language_name)
+                    }
+                    .is_some()
+                    {
                         resolved_injection_ranges.push(layer.range.to_offset(text));
                     }
 
@@ -1066,7 +1071,7 @@ fn get_injections(
     config: &InjectionConfig,
     text: &BufferSnapshot,
     node: Node,
-    language_registry: &LanguageRegistry,
+    language_registry: &Arc<LanguageRegistry>,
     depth: usize,
     changed_ranges: &[Range<usize>],
     combined_injection_ranges: &mut HashMap<Arc<Language>, Vec<tree_sitter::Range>>,
@@ -1078,7 +1083,8 @@ fn get_injections(
     combined_injection_ranges.clear();
     for pattern in &config.patterns {
         if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
-            if let Some(language) = language_for_injection(language_name, language_registry) {
+            if let Some(language) = language_registry.language_for_name_or_extension(language_name)
+            {
                 combined_injection_ranges.insert(language, Vec::new());
             }
         }
@@ -1123,7 +1129,10 @@ fn get_injections(
             };
 
             if let Some(language_name) = language_name {
-                let language = language_for_injection(&language_name, language_registry);
+                let language = {
+                    let language_name: &str = &language_name;
+                    language_registry.language_for_name_or_extension(language_name)
+                };
                 let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end);
                 if let Some(language) = language {
                     if combined {
@@ -1171,15 +1180,6 @@ fn get_injections(
     }
 }
 
-fn language_for_injection(
-    language_name: &str,
-    language_registry: &LanguageRegistry,
-) -> Option<Arc<Language>> {
-    language_registry
-        .language_for_name(language_name)
-        .or_else(|| language_registry.language_for_extension(language_name))
-}
-
 fn splice_included_ranges(
     mut ranges: Vec<tree_sitter::Range>,
     changed_ranges: &[Range<usize>],

crates/live_kit_client/src/prod.rs 🔗

@@ -128,14 +128,9 @@ impl Room {
         let url = url.to_string();
         let token = token.to_string();
         async move {
-            match rx.await.unwrap().context("error connecting to room") {
-                Ok(()) => {
-                    *this.connection.lock().0.borrow_mut() =
-                        ConnectionState::Connected { url, token };
-                    Ok(())
-                }
-                Err(err) => Err(err),
-            }
+            rx.await.unwrap().context("error connecting to room")?;
+            *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token };
+            Ok(())
         }
     }
 

crates/project/src/project.rs 🔗

@@ -1480,6 +1480,10 @@ impl Project {
         buffer: &ModelHandle<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
+        buffer.update(cx, |buffer, _| {
+            buffer.set_language_registry(self.languages.clone())
+        });
+
         let remote_id = buffer.read(cx).remote_id();
         let open_buffer = if self.is_remote() || self.is_shared() {
             OpenBuffer::Strong(buffer.clone())
@@ -1798,12 +1802,11 @@ impl Project {
     ) -> Option<()> {
         // If the buffer has a language, set it and start the language server if we haven't already.
         let full_path = buffer.read(cx).file()?.full_path(cx);
-        let new_language = self.languages.select_language(&full_path)?;
+        let new_language = self.languages.language_for_path(&full_path)?;
         buffer.update(cx, |buffer, cx| {
             if buffer.language().map_or(true, |old_language| {
                 !Arc::ptr_eq(old_language, &new_language)
             }) {
-                buffer.set_language_registry(self.languages.clone());
                 buffer.set_language(Some(new_language.clone()), cx);
             }
         });
@@ -2208,7 +2211,7 @@ impl Project {
             })
             .collect();
         for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info {
-            let language = self.languages.select_language(&full_path)?;
+            let language = self.languages.language_for_path(&full_path)?;
             self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
         }
 
@@ -3168,7 +3171,7 @@ impl Project {
                             let signature = this.symbol_signature(&project_path);
                             let language = this
                                 .languages
-                                .select_language(&project_path.path)
+                                .language_for_path(&project_path.path)
                                 .unwrap_or(adapter_language.clone());
                             let language_server_name = adapter.name.clone();
                             Some(async move {
@@ -5944,7 +5947,7 @@ impl Project {
                 worktree_id,
                 path: PathBuf::from(serialized_symbol.path).into(),
             };
-            let language = languages.select_language(&path.path);
+            let language = languages.language_for_path(&path.path);
             Ok(Symbol {
                 language_server_name: LanguageServerName(
                     serialized_symbol.language_server_name.into(),

crates/workspace/src/workspace.rs 🔗

@@ -100,7 +100,6 @@ actions!(
         NewTerminal,
         NewSearch,
         Feedback,
-        ShowNotif,
     ]
 );
 
@@ -199,6 +198,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     cx.add_async_action(Workspace::toggle_follow);
     cx.add_async_action(Workspace::follow_next_collaborator);
     cx.add_async_action(Workspace::close);
+    cx.add_global_action(Workspace::close_global);
     cx.add_async_action(Workspace::save_all);
     cx.add_action(Workspace::open_shared_screen);
     cx.add_action(Workspace::add_folder_to_project);
@@ -824,6 +824,15 @@ impl Workspace {
         }
     }
 
+    pub fn close_global(_: &CloseWindow, cx: &mut MutableAppContext) {
+        let id = cx.window_ids().find(|&id| cx.window_is_active(id));
+        if let Some(id) = id {
+            //This can only get called when the window's project connection has been lost
+            //so we don't need to prompt the user for anything and instead just close the window
+            cx.remove_window(id);
+        }
+    }
+
     pub fn close(
         &mut self,
         _: &CloseWindow,
@@ -852,6 +861,7 @@ impl Workspace {
             .window_ids()
             .flat_map(|window_id| cx.root_view::<Workspace>(window_id))
             .count();
+
         cx.spawn(|this, mut cx| async move {
             if let Some(active_call) = active_call {
                 if !quitting
@@ -867,6 +877,7 @@ impl Workspace {
                         )
                         .next()
                         .await;
+
                     if answer == Some(1) {
                         return anyhow::Ok(false);
                     } else {

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.71.0"
+version = "0.72.0"
 publish = false
 
 [lib]

crates/zed/src/languages.rs 🔗

@@ -1,7 +1,5 @@
 use anyhow::Context;
-use gpui::executor::Background;
 pub use language::*;
-use lazy_static::lazy_static;
 use rust_embed::RustEmbed;
 use std::{borrow::Cow, str, sync::Arc};
 
@@ -32,32 +30,17 @@ mod typescript;
 #[exclude = "*.rs"]
 struct LanguageDir;
 
-// TODO - Remove this once the `init` function is synchronous again.
-lazy_static! {
-    pub static ref LANGUAGE_NAMES: Vec<String> = LanguageDir::iter()
-        .filter_map(|path| {
-            if path.ends_with("config.toml") {
-                let config = LanguageDir::get(&path)?;
-                let config = toml::from_slice::<LanguageConfig>(&config.data).ok()?;
-                Some(config.name.to_string())
-            } else {
-                None
-            }
-        })
-        .collect();
-}
-
-pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>) {
+pub fn init(languages: Arc<LanguageRegistry>) {
     for (name, grammar, lsp_adapter) in [
         (
             "c",
             tree_sitter_c::language(),
-            Some(CachedLspAdapter::new(c::CLspAdapter).await),
+            Some(Box::new(c::CLspAdapter) as Box<dyn LspAdapter>),
         ),
         (
             "cpp",
             tree_sitter_cpp::language(),
-            Some(CachedLspAdapter::new(c::CLspAdapter).await),
+            Some(Box::new(c::CLspAdapter)),
         ),
         (
             "css",
@@ -67,17 +50,17 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "elixir",
             tree_sitter_elixir::language(),
-            Some(CachedLspAdapter::new(elixir::ElixirLspAdapter).await),
+            Some(Box::new(elixir::ElixirLspAdapter)),
         ),
         (
             "go",
             tree_sitter_go::language(),
-            Some(CachedLspAdapter::new(go::GoLspAdapter).await),
+            Some(Box::new(go::GoLspAdapter)),
         ),
         (
             "json",
             tree_sitter_json::language(),
-            Some(CachedLspAdapter::new(json::JsonLspAdapter).await),
+            Some(Box::new(json::JsonLspAdapter)),
         ),
         (
             "markdown",
@@ -87,12 +70,12 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "python",
             tree_sitter_python::language(),
-            Some(CachedLspAdapter::new(python::PythonLspAdapter).await),
+            Some(Box::new(python::PythonLspAdapter)),
         ),
         (
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(rust::RustLspAdapter).await),
+            Some(Box::new(rust::RustLspAdapter)),
         ),
         (
             "toml",
@@ -102,89 +85,82 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
         (
             "tsx",
             tree_sitter_typescript::language_tsx(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "typescript",
             tree_sitter_typescript::language_typescript(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "javascript",
             tree_sitter_typescript::language_tsx(),
-            Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
+            Some(Box::new(typescript::TypeScriptLspAdapter)),
         ),
         (
             "html",
             tree_sitter_html::language(),
-            Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
+            Some(Box::new(html::HtmlLspAdapter)),
         ),
         (
             "ruby",
             tree_sitter_ruby::language(),
-            Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await),
+            Some(Box::new(ruby::RubyLanguageServer)),
         ),
         (
             "erb",
             tree_sitter_embedded_template::language(),
-            Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await),
+            Some(Box::new(ruby::RubyLanguageServer)),
+        ),
+        (
+            "scheme",
+            tree_sitter_scheme::language(),
+            None, //
+        ),
+        (
+            "racket",
+            tree_sitter_racket::language(),
+            None, //
         ),
-        ("scheme", tree_sitter_scheme::language(), None),
-        ("racket", tree_sitter_racket::language(), None),
     ] {
-        languages.add(language(name, grammar, lsp_adapter));
+        languages.register(name, load_config(name), grammar, lsp_adapter, load_queries);
     }
 }
 
-pub(crate) fn language(
+#[cfg(any(test, feature = "test-support"))]
+pub async fn language(
     name: &str,
     grammar: tree_sitter::Language,
-    lsp_adapter: Option<Arc<CachedLspAdapter>>,
+    lsp_adapter: Option<Box<dyn LspAdapter>>,
 ) -> Arc<Language> {
-    let config = toml::from_slice(
+    Arc::new(
+        Language::new(load_config(name), Some(grammar))
+            .with_lsp_adapter(lsp_adapter)
+            .await
+            .with_queries(load_queries(name))
+            .unwrap(),
+    )
+}
+
+fn load_config(name: &str) -> LanguageConfig {
+    toml::from_slice(
         &LanguageDir::get(&format!("{}/config.toml", name))
             .unwrap()
             .data,
     )
     .with_context(|| format!("failed to load config.toml for language {name:?}"))
-    .unwrap();
-
-    let mut language = Language::new(config, Some(grammar));
+    .unwrap()
+}
 
-    if let Some(query) = load_query(name, "/highlights") {
-        language = language
-            .with_highlights_query(query.as_ref())
-            .expect("failed to evaluate highlights query");
-    }
-    if let Some(query) = load_query(name, "/brackets") {
-        language = language
-            .with_brackets_query(query.as_ref())
-            .expect("failed to load brackets query");
-    }
-    if let Some(query) = load_query(name, "/indents") {
-        language = language
-            .with_indents_query(query.as_ref())
-            .expect("failed to load indents query");
-    }
-    if let Some(query) = load_query(name, "/outline") {
-        language = language
-            .with_outline_query(query.as_ref())
-            .expect("failed to load outline query");
-    }
-    if let Some(query) = load_query(name, "/injections") {
-        language = language
-            .with_injection_query(query.as_ref())
-            .expect("failed to load injection query");
-    }
-    if let Some(query) = load_query(name, "/overrides") {
-        language = language
-            .with_override_query(query.as_ref())
-            .expect("failed to load override query");
-    }
-    if let Some(lsp_adapter) = lsp_adapter {
-        language = language.with_lsp_adapter(lsp_adapter)
+fn load_queries(name: &str) -> LanguageQueries {
+    LanguageQueries {
+        highlights: load_query(name, "/highlights"),
+        brackets: load_query(name, "/brackets"),
+        indents: load_query(name, "/indents"),
+        outline: load_query(name, "/outline"),
+        injections: load_query(name, "/injections"),
+        overrides: load_query(name, "/overrides"),
     }
-    Arc::new(language)
 }
 
 fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {

crates/zed/src/languages/c.rs 🔗

@@ -248,17 +248,19 @@ impl super::LspAdapter for CLspAdapter {
 
 #[cfg(test)]
 mod tests {
-    use gpui::MutableAppContext;
+    use gpui::TestAppContext;
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
 
     #[gpui::test]
-    fn test_c_autoindent(cx: &mut MutableAppContext) {
+    async fn test_c_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
-        let language = crate::languages::language("c", tree_sitter_c::language(), None);
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
+        let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/go.rs 🔗

@@ -314,8 +314,9 @@ mod tests {
         let language = language(
             "go",
             tree_sitter_go::language(),
-            Some(CachedLspAdapter::new(GoLspAdapter).await),
-        );
+            Some(Box::new(GoLspAdapter)),
+        )
+        .await;
 
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),

crates/zed/src/languages/python.rs 🔗

@@ -165,17 +165,20 @@ impl LspAdapter for PythonLspAdapter {
 
 #[cfg(test)]
 mod tests {
-    use gpui::{ModelContext, MutableAppContext};
+    use gpui::{ModelContext, TestAppContext};
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
 
     #[gpui::test]
-    fn test_python_autoindent(cx: &mut MutableAppContext) {
+    async fn test_python_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let language = crate::languages::language("python", tree_sitter_python::language(), None);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
+        let language =
+            crate::languages::language("python", tree_sitter_python::language(), None).await;
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/rust.rs 🔗

@@ -255,8 +255,8 @@ impl LspAdapter for RustLspAdapter {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::languages::{language, CachedLspAdapter};
-    use gpui::{color::Color, MutableAppContext};
+    use crate::languages::language;
+    use gpui::{color::Color, TestAppContext};
     use settings::Settings;
     use theme::SyntaxTheme;
 
@@ -306,8 +306,9 @@ mod tests {
         let language = language(
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(RustLspAdapter).await),
-        );
+            Some(Box::new(RustLspAdapter)),
+        )
+        .await;
         let grammar = language.grammar().unwrap();
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),
@@ -391,8 +392,9 @@ mod tests {
         let language = language(
             "rust",
             tree_sitter_rust::language(),
-            Some(CachedLspAdapter::new(RustLspAdapter).await),
-        );
+            Some(Box::new(RustLspAdapter)),
+        )
+        .await;
         let grammar = language.grammar().unwrap();
         let theme = SyntaxTheme::new(vec![
             ("type".into(), Color::green().into()),
@@ -431,12 +433,15 @@ mod tests {
     }
 
     #[gpui::test]
-    fn test_rust_autoindent(cx: &mut MutableAppContext) {
+    async fn test_rust_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        let language = crate::languages::language("rust", tree_sitter_rust::language(), None);
-        let mut settings = Settings::test(cx);
-        settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-        cx.set_global(settings);
+        cx.update(|cx| {
+            let mut settings = Settings::test(cx);
+            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
+            cx.set_global(settings);
+        });
+
+        let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await;
 
         cx.add_model(|cx| {
             let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);

crates/zed/src/languages/typescript.rs 🔗

@@ -154,17 +154,17 @@ impl LspAdapter for TypeScriptLspAdapter {
 
 #[cfg(test)]
 mod tests {
-
-    use gpui::MutableAppContext;
+    use gpui::TestAppContext;
     use unindent::Unindent;
 
     #[gpui::test]
-    fn test_outline(cx: &mut MutableAppContext) {
+    async fn test_outline(cx: &mut TestAppContext) {
         let language = crate::languages::language(
             "typescript",
             tree_sitter_typescript::language_typescript(),
             None,
-        );
+        )
+        .await;
 
         let text = r#"
             function a() {
@@ -183,7 +183,7 @@ mod tests {
 
         let buffer =
             cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx));
-        let outline = buffer.read(cx).snapshot().outline(None).unwrap();
+        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
         assert_eq!(
             outline
                 .items

crates/zed/src/main.rs 🔗

@@ -120,11 +120,10 @@ fn main() {
 
         let client = client::Client::new(http.clone(), cx);
         let mut languages = LanguageRegistry::new(login_shell_env_loaded);
+        languages.set_executor(cx.background().clone());
         languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
         let languages = Arc::new(languages);
-        let init_languages = cx
-            .background()
-            .spawn(languages::init(languages.clone(), cx.background().clone()));
+        languages::init(languages.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
         watch_keymap_file(keymap_file, cx);
@@ -136,7 +135,6 @@ fn main() {
         client::init(client.clone(), cx);
         command_palette::init(cx);
         editor::init(cx);
-        feedback::init(cx);
         go_to_line::init(cx);
         file_finder::init(cx);
         outline::init(cx);
@@ -152,14 +150,7 @@ fn main() {
         cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
             .detach();
 
-        cx.spawn({
-            let languages = languages.clone();
-            |cx| async move {
-                cx.read(|cx| languages.set_theme(cx.global::<Settings>().theme.clone()));
-                init_languages.await;
-            }
-        })
-        .detach();
+        languages.set_theme(cx.global::<Settings>().theme.clone());
         cx.observe_global::<Settings, _>({
             let languages = languages.clone();
             move |cx| languages.set_theme(cx.global::<Settings>().theme.clone())
@@ -191,6 +182,7 @@ fn main() {
         theme_selector::init(app_state.clone(), cx);
         zed::init(&app_state, cx);
         collab_ui::init(app_state.clone(), cx);
+        feedback::init(app_state.clone(), cx);
 
         cx.set_menus(menus::menus());
 

crates/zed/src/zed.rs 🔗

@@ -306,7 +306,7 @@ pub fn initialize_workspace(
         )
         .map(|meta| meta.name)
         .collect();
-    let language_names = &languages::LANGUAGE_NAMES;
+    let language_names = app_state.languages.language_names();
 
     workspace.project().update(cx, |project, cx| {
         let action_names = cx.all_action_names().collect::<Vec<_>>();
@@ -318,7 +318,7 @@ pub fn initialize_workspace(
                 "schemas": [
                     {
                         "fileMatch": [schema_file_match(&paths::SETTINGS)],
-                        "schema": settings_file_json_schema(theme_names, language_names),
+                        "schema": settings_file_json_schema(theme_names, &language_names),
                     },
                     {
                         "fileMatch": [schema_file_match(&paths::KEYMAP)],