Merge branch 'main' into collab-titlebar-fixes

Marshall Bowers created

Change summary

crates/assistant/src/assistant_panel.rs       |  1 
crates/auto_update/src/auto_update.rs         |  2 
crates/command_palette/src/command_palette.rs | 16 ++++
crates/diagnostics/src/diagnostics.rs         |  9 ++
crates/editor/src/editor.rs                   |  2 
crates/editor/src/element.rs                  | 12 +++
crates/gpui/src/app.rs                        | 16 +++-
crates/gpui/src/platform/mac/shaders.metal    | 30 ++++----
crates/gpui/src/text_system.rs                | 41 ++++++++++++
crates/language/src/language.rs               |  3 
crates/language_tools/src/lsp_log.rs          | 12 ++
crates/language_tools/src/syntax_tree_view.rs | 10 ++
crates/project/src/project.rs                 | 26 +++----
crates/project_panel/src/project_panel.rs     | 31 +++------
crates/settings/src/settings_file.rs          | 14 ----
crates/terminal_view/src/terminal_element.rs  |  2 
crates/ui/src/components/tab.rs               |  3 
crates/zed/src/app_menus.rs                   |  8 --
crates/zed/src/zed.rs                         | 66 +++++++++++++-------
19 files changed, 187 insertions(+), 117 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1157,6 +1157,7 @@ impl Render for AssistantPanel {
                 });
 
             v_stack()
+                .key_context("AssistantPanel")
                 .size_full()
                 .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
                     this.new_conversation(cx);

crates/auto_update/src/auto_update.rs 🔗

@@ -270,7 +270,7 @@ impl AutoUpdater {
             ReleaseChannel::Nightly => cx
                 .try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
                 .unwrap_or(true),
-            _ => release.version.parse::<SemanticVersion>()? <= current_version,
+            _ => release.version.parse::<SemanticVersion>()? > current_version,
         };
 
         if !should_download {

crates/command_palette/src/command_palette.rs 🔗

@@ -370,6 +370,7 @@ mod tests {
     use gpui::TestAppContext;
     use language::Point;
     use project::Project;
+    use settings::KeymapFile;
     use workspace::{AppState, Workspace};
 
     #[test]
@@ -503,7 +504,20 @@ mod tests {
             workspace::init(app_state.clone(), cx);
             init(cx);
             Project::init_settings(cx);
-            settings::load_default_keymap(cx);
+            KeymapFile::parse(
+                r#"[
+                    {
+                        "bindings": {
+                            "cmd-n": "workspace::NewFile",
+                            "enter": "menu::Confirm",
+                            "cmd-shift-p": "command_palette::Toggle"
+                        }
+                    }
+                ]"#,
+            )
+            .unwrap()
+            .add_to_cx(cx)
+            .unwrap();
             app_state
         })
     }

crates/diagnostics/src/diagnostics.rs 🔗

@@ -641,8 +641,13 @@ impl Item for ProjectDiagnosticsEditor {
 
     fn tab_content(&self, _detail: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
         if self.summary.error_count == 0 && self.summary.warning_count == 0 {
-            let label = Label::new("No problems");
-            label.into_any_element()
+            Label::new("No problems")
+                .color(if selected {
+                    Color::Default
+                } else {
+                    Color::Muted
+                })
+                .into_any_element()
         } else {
             h_stack()
                 .gap_1()

crates/editor/src/editor.rs 🔗

@@ -9565,7 +9565,7 @@ impl InputHandler for Editor {
     ) -> Option<gpui::Bounds<Pixels>> {
         let text_layout_details = self.text_layout_details(cx);
         let style = &text_layout_details.editor_style;
-        let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+        let font_id = cx.text_system().resolve_font(&style.text.font());
         let font_size = style.text.font_size.to_pixels(cx.rem_size());
         let line_height = style.text.line_height_in_pixels(cx.rem_size());
         let em_width = cx

crates/editor/src/element.rs 🔗

@@ -1230,6 +1230,14 @@ impl EditorElement {
             return;
         }
 
+        // If a drag took place after we started dragging the scrollbar,
+        // cancel the scrollbar drag.
+        if cx.has_active_drag() {
+            self.editor.update(cx, |editor, cx| {
+                editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
+            });
+        }
+
         let top = bounds.origin.y;
         let bottom = bounds.lower_left().y;
         let right = bounds.lower_right().x;
@@ -1767,7 +1775,7 @@ impl EditorElement {
             let snapshot = editor.snapshot(cx);
             let style = self.style.clone();
 
-            let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+            let font_id = cx.text_system().resolve_font(&style.text.font());
             let font_size = style.text.font_size.to_pixels(cx.rem_size());
             let line_height = style.text.line_height_in_pixels(cx.rem_size());
             let em_width = cx
@@ -3774,7 +3782,7 @@ fn compute_auto_height_layout(
     }
 
     let style = editor.style.as_ref().unwrap();
-    let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+    let font_id = cx.text_system().resolve_font(&style.text.font());
     let font_size = style.text.font_size.to_pixels(cx.rem_size());
     let line_height = style.text.line_height_in_pixels(cx.rem_size());
     let em_width = cx

crates/gpui/src/app.rs 🔗

@@ -327,6 +327,7 @@ impl AppContext {
     pub fn refresh(&mut self) {
         self.pending_effects.push_back(Effect::Refresh);
     }
+
     pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
         self.pending_updates += 1;
         let result = update(self);
@@ -840,10 +841,12 @@ impl AppContext {
     /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
     /// your closure with mutable access to the `AppContext` and the global simultaneously.
     pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
-        let mut global = self.lease_global::<G>();
-        let result = f(&mut global, self);
-        self.end_global_lease(global);
-        result
+        self.update(|cx| {
+            let mut global = cx.lease_global::<G>();
+            let result = f(&mut global, cx);
+            cx.end_global_lease(global);
+            result
+        })
     }
 
     /// Register a callback to be invoked when a global of the given type is updated.
@@ -941,6 +944,11 @@ impl AppContext {
         self.pending_effects.push_back(Effect::Refresh);
     }
 
+    pub fn clear_key_bindings(&mut self) {
+        self.keymap.lock().clear();
+        self.pending_effects.push_back(Effect::Refresh);
+    }
+
     /// Register a global listener for actions invoked via the keyboard.
     pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
         self.global_action_listeners

crates/gpui/src/platform/mac/shaders.metal 🔗

@@ -16,6 +16,7 @@ float gaussian(float x, float sigma);
 float2 erf(float2 x);
 float blur_along_x(float x, float y, float sigma, float corner,
                    float2 half_size);
+float4 over(float4 below, float4 above);
 
 struct QuadVertexOutput {
   float4 position [[position]];
@@ -108,21 +109,11 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
     color = input.background_color;
   } else {
     float inset_distance = distance + border_width;
-
-    // Decrease border's opacity as we move inside the background.
-    input.border_color.a *= 1. - saturate(0.5 - inset_distance);
-
-    // Alpha-blend the border and the background.
-    float output_alpha = input.border_color.a +
-                         input.background_color.a * (1. - input.border_color.a);
-    float3 premultiplied_border_rgb =
-        input.border_color.rgb * input.border_color.a;
-    float3 premultiplied_background_rgb =
-        input.background_color.rgb * input.background_color.a;
-    float3 premultiplied_output_rgb =
-        premultiplied_border_rgb +
-        premultiplied_background_rgb * (1. - input.border_color.a);
-    color = float4(premultiplied_output_rgb, output_alpha);
+    // Blend the border on top of the background and then linearly interpolate
+    // between the two as we slide inside the background.
+    float4 blended_border = over(input.background_color, input.border_color);
+    color = mix(blended_border, input.background_color,
+                saturate(0.5 - inset_distance));
   }
 
   return color * float4(1., 1., 1., saturate(0.5 - distance));
@@ -653,3 +644,12 @@ float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
                 position.y - clip_bounds.origin.y,
                 clip_bounds.origin.y + clip_bounds.size.height - position.y);
 }
+
+float4 over(float4 below, float4 above) {
+  float4 result;
+  float alpha = above.a + below.a * (1.0 - above.a);
+  result.rgb =
+      (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
+  result.a = alpha;
+  return result;
+}

crates/gpui/src/text_system.rs 🔗

@@ -15,8 +15,9 @@ use crate::{
 use anyhow::anyhow;
 use collections::FxHashMap;
 use core::fmt;
+use itertools::Itertools;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
 use std::{
     cmp,
     fmt::{Debug, Display, Formatter},
@@ -42,6 +43,7 @@ pub struct TextSystem {
     raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
     wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
     font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
+    fallback_font_stack: SmallVec<[Font; 2]>,
 }
 
 impl TextSystem {
@@ -54,6 +56,12 @@ impl TextSystem {
             font_ids_by_font: RwLock::default(),
             wrapper_pool: Mutex::default(),
             font_runs_pool: Mutex::default(),
+            fallback_font_stack: smallvec![
+                // TODO: This is currently Zed-specific.
+                // We should allow GPUI users to provide their own fallback font stack.
+                font("Zed Mono"),
+                font("Helvetica")
+            ],
         }
     }
 
@@ -72,6 +80,33 @@ impl TextSystem {
         }
     }
 
+    /// Resolves the specified font, falling back to the default font stack if
+    /// the font fails to load.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the font and none of the fallbacks can be resolved.
+    pub fn resolve_font(&self, font: &Font) -> FontId {
+        if let Ok(font_id) = self.font_id(font) {
+            return font_id;
+        }
+
+        for fallback in &self.fallback_font_stack {
+            if let Ok(font_id) = self.font_id(fallback) {
+                return font_id;
+            }
+        }
+
+        panic!(
+            "failed to resolve font '{}' or any of the fallbacks: {}",
+            font.family,
+            self.fallback_font_stack
+                .iter()
+                .map(|fallback| &fallback.family)
+                .join(", ")
+        );
+    }
+
     pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
         self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
     }
@@ -159,7 +194,7 @@ impl TextSystem {
     ) -> Result<Arc<LineLayout>> {
         let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
         for run in runs.iter() {
-            let font_id = self.font_id(&run.font)?;
+            let font_id = self.resolve_font(&run.font);
             if let Some(last_run) = font_runs.last_mut() {
                 if last_run.font_id == font_id {
                     last_run.len += run.len;
@@ -253,7 +288,7 @@ impl TextSystem {
                     last_font = Some(run.font.clone());
                     font_runs.push(FontRun {
                         len: run_len_within_line,
-                        font_id: self.platform_text_system.font_id(&run.font)?,
+                        font_id: self.resolve_font(&run.font),
                     });
                 }
 

crates/language/src/language.rs 🔗

@@ -113,7 +113,6 @@ pub struct LanguageServerName(pub Arc<str>);
 pub struct CachedLspAdapter {
     pub name: LanguageServerName,
     pub short_name: &'static str,
-    pub initialization_options: Option<Value>,
     pub disk_based_diagnostic_sources: Vec<String>,
     pub disk_based_diagnostics_progress_token: Option<String>,
     pub language_ids: HashMap<String, String>,
@@ -125,7 +124,6 @@ impl CachedLspAdapter {
     pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
         let name = adapter.name().await;
         let short_name = adapter.short_name();
-        let initialization_options = adapter.initialization_options().await;
         let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
         let disk_based_diagnostics_progress_token =
             adapter.disk_based_diagnostics_progress_token().await;
@@ -134,7 +132,6 @@ impl CachedLspAdapter {
         Arc::new(CachedLspAdapter {
             name,
             short_name,
-            initialization_options,
             disk_based_diagnostic_sources,
             disk_based_diagnostics_progress_token,
             language_ids,

crates/language_tools/src/lsp_log.rs 🔗

@@ -10,7 +10,7 @@ use language::{LanguageServerId, LanguageServerName};
 use lsp::IoKind;
 use project::{search::SearchQuery, Project};
 use std::{borrow::Cow, sync::Arc};
-use ui::{h_stack, popover_menu, Button, Checkbox, Clickable, ContextMenu, Label, Selection};
+use ui::{popover_menu, prelude::*, Button, Checkbox, ContextMenu, Label, Selection};
 use workspace::{
     item::{Item, ItemHandle},
     searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
@@ -614,8 +614,14 @@ impl Item for LspLogView {
         Editor::to_item_events(event, f)
     }
 
-    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("LSP Logs").into_any_element()
+    fn tab_content(&self, _: Option<usize>, selected: bool, _: &WindowContext<'_>) -> AnyElement {
+        Label::new("LSP Logs")
+            .color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            })
+            .into_any_element()
     }
 
     fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -405,8 +405,14 @@ impl Item for SyntaxTreeView {
 
     fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
 
-    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("Syntax Tree").into_any_element()
+    fn tab_content(&self, _: Option<usize>, selected: bool, _: &WindowContext<'_>) -> AnyElement {
+        Label::new("Syntax Tree")
+            .color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            })
+            .into_any_element()
     }
 
     fn clone_on_split(

crates/project/src/project.rs 🔗

@@ -2816,15 +2816,6 @@ impl Project {
         let lsp = project_settings.lsp.get(&adapter.name.0);
         let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
 
-        let mut initialization_options = adapter.initialization_options.clone();
-        match (&mut initialization_options, override_options) {
-            (Some(initialization_options), Some(override_options)) => {
-                merge_json_value_into(override_options, initialization_options);
-            }
-            (None, override_options) => initialization_options = override_options,
-            _ => {}
-        }
-
         let server_id = pending_server.server_id;
         let container_dir = pending_server.container_dir.clone();
         let state = LanguageServerState::Starting({
@@ -2837,7 +2828,7 @@ impl Project {
                 let result = Self::setup_and_insert_language_server(
                     this.clone(),
                     &worktree_path,
-                    initialization_options,
+                    override_options,
                     pending_server,
                     adapter.clone(),
                     language.clone(),
@@ -2958,7 +2949,7 @@ impl Project {
     async fn setup_and_insert_language_server(
         this: WeakModel<Self>,
         worktree_path: &Path,
-        initialization_options: Option<serde_json::Value>,
+        override_initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
         language: Arc<Language>,
@@ -2968,7 +2959,7 @@ impl Project {
     ) -> Result<Option<Arc<LanguageServer>>> {
         let language_server = Self::setup_pending_language_server(
             this.clone(),
-            initialization_options,
+            override_initialization_options,
             pending_server,
             worktree_path,
             adapter.clone(),
@@ -2998,7 +2989,7 @@ impl Project {
 
     async fn setup_pending_language_server(
         this: WeakModel<Self>,
-        initialization_options: Option<serde_json::Value>,
+        override_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         worktree_path: &Path,
         adapter: Arc<CachedLspAdapter>,
@@ -3164,7 +3155,14 @@ impl Project {
                 }
             })
             .detach();
-
+        let mut initialization_options = adapter.adapter.initialization_options().await;
+        match (&mut initialization_options, override_options) {
+            (Some(initialization_options), Some(override_options)) => {
+                merge_json_value_into(override_options, initialization_options);
+            }
+            (None, override_options) => initialization_options = override_options,
+            _ => {}
+        }
         let language_server = language_server.initialize(initialization_options).await?;
 
         language_server

crates/project_panel/src/project_panel.rs 🔗

@@ -971,25 +971,16 @@ impl ProjectPanel {
         }
     }
 
-    fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext<Self>) {
-        todo!()
-        // if let Some((worktree, entry)) = self.selected_entry(cx) {
-        //     let window = cx.window();
-        //     let view_id = cx.view_id();
-        //     let path = worktree.abs_path().join(&entry.path);
-
-        //     cx.app_context()
-        //         .spawn(|mut cx| async move {
-        //             window.dispatch_action(
-        //                 view_id,
-        //                 &workspace::OpenTerminal {
-        //                     working_directory: path,
-        //                 },
-        //                 &mut cx,
-        //             );
-        //         })
-        //         .detach();
-        // }
+    fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
+        if let Some((worktree, entry)) = self.selected_entry(cx) {
+            let path = worktree.abs_path().join(&entry.path);
+            cx.dispatch_action(
+                workspace::OpenTerminal {
+                    working_directory: path,
+                }
+                .boxed_clone(),
+            )
+        }
     }
 
     pub fn new_search_in_directory(
@@ -1404,7 +1395,7 @@ impl ProjectPanel {
                     .child(if let Some(icon) = &icon {
                         div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
                     } else {
-                        div()
+                        div().size(IconSize::default().rems()).invisible()
                     })
                     .child(
                         if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {

crates/settings/src/settings_file.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{settings_store::SettingsStore, KeymapFile, Settings};
+use crate::{settings_store::SettingsStore, Settings};
 use anyhow::Result;
 use fs::Fs;
 use futures::{channel::mpsc, StreamExt};
@@ -77,7 +77,6 @@ pub fn handle_settings_file_changes(
     });
     cx.spawn(move |mut cx| async move {
         while let Some(user_settings_content) = user_settings_file_rx.next().await {
-            eprintln!("settings file changed");
             let result = cx.update_global(|store: &mut SettingsStore, cx| {
                 store
                     .set_user_settings(&user_settings_content, cx)
@@ -121,14 +120,3 @@ pub fn update_settings_file<T: Settings>(
     })
     .detach_and_log_err(cx);
 }
-
-pub fn load_default_keymap(cx: &mut AppContext) {
-    for path in ["keymaps/default.json", "keymaps/vim.json"] {
-        KeymapFile::load_asset(path, cx).unwrap();
-    }
-
-    // todo!()
-    // if let Some(asset_path) = settings::get::<BaseKeymap>(cx).asset_path() {
-    //     KeymapFile::load_asset(asset_path, cx).unwrap();
-    // }
-}

crates/terminal_view/src/terminal_element.rs 🔗

@@ -421,7 +421,7 @@ impl TerminalElement {
             let rem_size = cx.rem_size();
             let font_pixels = text_style.font_size.to_pixels(rem_size);
             let line_height = font_pixels * line_height.to_pixels(rem_size);
-            let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
+            let font_id = cx.text_system().resolve_font(&text_style.font());
 
             // todo!(do we need to keep this unwrap?)
             let cell_width = text_system

crates/ui/src/components/tab.rs 🔗

@@ -126,13 +126,14 @@ impl RenderOnce for Tab {
                     if self.selected {
                         this.border_l().border_r().pb_px()
                     } else {
-                        this.pr_px().pl_px().border_b()
+                        this.pr_px().pl_px().border_b().border_r()
                     }
                 }
                 TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
                 TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
                 TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
             })
+            .cursor_pointer()
             .child(
                 h_stack()
                     .group("")

crates/zed/src/app_menus.rs 🔗

@@ -150,14 +150,6 @@ pub fn app_menus() -> Vec<Menu<'static>> {
                 MenuItem::action("View Dependency Licenses", crate::OpenLicenses),
                 MenuItem::action("Show Welcome", workspace::Welcome),
                 MenuItem::separator(),
-                // todo!(): Needs `feedback` crate.
-                // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback),
-                // MenuItem::action(
-                //     "Copy System Specs Into Clipboard",
-                //     feedback::CopySystemSpecsIntoClipboard,
-                // ),
-                // MenuItem::action("File Bug Report", feedback::FileBugReport),
-                // MenuItem::action("Request Feature", feedback::RequestFeature),
                 MenuItem::separator(),
                 MenuItem::action(
                     "Documentation",

crates/zed/src/zed.rs 🔗

@@ -18,11 +18,11 @@ pub use only_instance::*;
 pub use open_listener::*;
 
 use anyhow::{anyhow, Context as _};
-use futures::{channel::mpsc, StreamExt};
+use futures::{channel::mpsc, select_biased, StreamExt};
 use project_panel::ProjectPanel;
 use quick_action_bar::QuickActionBar;
 use search::project_search::ProjectSearchBar;
-use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
+use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore};
 use std::{borrow::Cow, ops::Deref, sync::Arc};
 use terminal_view::terminal_panel::TerminalPanel;
 use util::{
@@ -32,6 +32,7 @@ use util::{
     ResultExt,
 };
 use uuid::Uuid;
+use welcome::BaseKeymap;
 use workspace::Pane;
 use workspace::{
     create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
@@ -399,8 +400,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             });
 
         workspace.focus_handle(cx).focus(cx);
-        //todo!()
-        // load_default_keymap(cx);
+        load_default_keymap(cx);
     })
     .detach();
 }
@@ -558,38 +558,58 @@ pub fn handle_keymap_file_changes(
     mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
     cx: &mut AppContext,
 ) {
+    BaseKeymap::register(cx);
+
+    let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded();
+    let mut old_base_keymap = *BaseKeymap::get_global(cx);
+    cx.observe_global::<SettingsStore>(move |cx| {
+        let new_base_keymap = *BaseKeymap::get_global(cx);
+        if new_base_keymap != old_base_keymap {
+            old_base_keymap = new_base_keymap.clone();
+            base_keymap_tx.unbounded_send(()).unwrap();
+        }
+    })
+    .detach();
+
     cx.spawn(move |cx| async move {
-        //  let mut settings_subscription = None;
-        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
-            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
-                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
-
-                // todo!()
-                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
-                // drop(settings_subscription);
-                // settings_subscription = Some(cx.update(|cx| {
-                //     cx.observe_global::<SettingsStore, _>(move |cx| {
-                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
-                //         if new_base_keymap != old_base_keymap {
-                //             old_base_keymap = new_base_keymap.clone();
-                //             reload_keymaps(cx, &keymap_content);
-                //         }
-                //     })
-                // }));
+        let mut user_keymap = KeymapFile::default();
+        loop {
+            select_biased! {
+                _ = base_keymap_rx.next() => {}
+                user_keymap_content = user_keymap_file_rx.next() => {
+                    if let Some(user_keymap_content) = user_keymap_content {
+                        if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
+                            user_keymap = keymap_content;
+                        } else {
+                            continue
+                        }
+                    }
+                }
             }
+
+            cx.update(|cx| reload_keymaps(cx, &user_keymap)).ok();
         }
     })
     .detach();
 }
 
 fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
-    // todo!()
-    // cx.clear_bindings();
+    cx.clear_key_bindings();
     load_default_keymap(cx);
     keymap_content.clone().add_to_cx(cx).log_err();
     cx.set_menus(app_menus());
 }
 
+pub fn load_default_keymap(cx: &mut AppContext) {
+    for path in ["keymaps/default.json", "keymaps/vim.json"] {
+        KeymapFile::load_asset(path, cx).unwrap();
+    }
+
+    if let Some(asset_path) = BaseKeymap::get_global(cx).asset_path() {
+        KeymapFile::load_asset(asset_path, cx).unwrap();
+    }
+}
+
 fn open_local_settings_file(
     workspace: &mut Workspace,
     _: &OpenLocalSettings,