Cargo.lock 🔗
@@ -8353,6 +8353,8 @@ dependencies = [
"log",
"picker",
"project",
+ "schemars",
+ "serde",
"settings",
"theme",
"theme_selector",
Max Brunsfeld created
Cargo.lock | 2
assets/settings/default.json | 9
crates/settings/src/keymap_file.rs | 20 -
crates/settings/src/settings.rs | 49 -----
crates/settings/src/settings_file.rs | 218 -----------------------
crates/vim/src/test/vim_test_context.rs | 2
crates/welcome/Cargo.toml | 7
crates/welcome/src/base_keymap_picker.rs | 11
crates/welcome/src/base_keymap_setting.rs | 65 +++++++
crates/welcome/src/welcome.rs | 10
crates/zed/src/main.rs | 7
crates/zed/src/zed.rs | 231 ++++++++++++++++++++++++
12 files changed, 334 insertions(+), 297 deletions(-)
@@ -8353,6 +8353,8 @@ dependencies = [
"log",
"picker",
"project",
+ "schemars",
+ "serde",
"settings",
"theme",
"theme_selector",
@@ -1,6 +1,15 @@
{
// The name of the Zed theme to use for the UI
"theme": "One Dark",
+ // The name of a base set of key bindings to use.
+ // This setting can take four values, each named after another
+ // text editor:
+ //
+ // 1. "VSCode"
+ // 2. "JetBrains"
+ // 3. "SublimeText"
+ // 4. "Atom"
+ "base_keymap": "VSCode",
// Features that can be globally enabled or disabled
"features": {
// Show Copilot icon in status bar
@@ -1,4 +1,4 @@
-use crate::{settings_store::parse_json_with_comments, Settings};
+use crate::settings_store::parse_json_with_comments;
use anyhow::{Context, Result};
use assets::Assets;
use collections::BTreeMap;
@@ -41,20 +41,14 @@ impl JsonSchema for KeymapAction {
struct ActionWithData(Box<str>, Box<RawValue>);
impl KeymapFileContent {
- pub fn load_defaults(cx: &mut AppContext) {
- for path in ["keymaps/default.json", "keymaps/vim.json"] {
- Self::load(path, cx).unwrap();
- }
-
- if let Some(asset_path) = cx.global::<Settings>().base_keymap.asset_path() {
- Self::load(asset_path, cx).log_err();
- }
- }
-
- pub fn load(asset_path: &str, cx: &mut AppContext) -> Result<()> {
+ pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> {
let content = Assets::get(asset_path).unwrap().data;
let content_str = std::str::from_utf8(content.as_ref()).unwrap();
- parse_json_with_comments::<Self>(content_str)?.add_to_cx(cx)
+ Self::parse(content_str)?.add_to_cx(cx)
+ }
+
+ pub fn parse(content: &str) -> Result<Self> {
+ parse_json_with_comments::<Self>(content)
}
pub fn add_to_cx(self, cx: &mut AppContext) -> Result<()> {
@@ -34,7 +34,6 @@ pub struct Settings {
pub buffer_font_family: FamilyId,
pub buffer_font_size: f32,
pub theme: Arc<Theme>,
- pub base_keymap: BaseKeymap,
}
impl Setting for Settings {
@@ -62,7 +61,6 @@ impl Setting for Settings {
buffer_font_features,
buffer_font_size: defaults.buffer_font_size.unwrap(),
theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
- base_keymap: Default::default(),
};
for value in user_values.into_iter().copied().cloned() {
@@ -111,48 +109,6 @@ impl Setting for Settings {
}
}
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
-pub enum BaseKeymap {
- #[default]
- VSCode,
- JetBrains,
- SublimeText,
- Atom,
- TextMate,
-}
-
-impl BaseKeymap {
- pub const OPTIONS: [(&'static str, Self); 5] = [
- ("VSCode (Default)", Self::VSCode),
- ("Atom", Self::Atom),
- ("JetBrains", Self::JetBrains),
- ("Sublime Text", Self::SublimeText),
- ("TextMate", Self::TextMate),
- ];
-
- pub fn asset_path(&self) -> Option<&'static str> {
- match self {
- BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
- BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
- BaseKeymap::Atom => Some("keymaps/atom.json"),
- BaseKeymap::TextMate => Some("keymaps/textmate.json"),
- BaseKeymap::VSCode => None,
- }
- }
-
- pub fn names() -> impl Iterator<Item = &'static str> {
- Self::OPTIONS.iter().map(|(name, _)| *name)
- }
-
- pub fn from_names(option: &str) -> BaseKeymap {
- Self::OPTIONS
- .iter()
- .copied()
- .find_map(|(name, value)| (name == option).then(|| value))
- .unwrap_or_default()
- }
-}
-
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct SettingsFileContent {
#[serde(default)]
@@ -163,8 +119,6 @@ pub struct SettingsFileContent {
pub buffer_font_features: Option<fonts::Features>,
#[serde(default)]
pub theme: Option<String>,
- #[serde(default)]
- pub base_keymap: Option<BaseKeymap>,
}
impl Settings {
@@ -198,7 +152,6 @@ impl Settings {
buffer_font_features,
buffer_font_size: defaults.buffer_font_size.unwrap(),
theme: themes.get(&defaults.theme.unwrap()).unwrap(),
- base_keymap: Default::default(),
}
}
@@ -234,7 +187,6 @@ impl Settings {
}
merge(&mut self.buffer_font_size, data.buffer_font_size);
- merge(&mut self.base_keymap, data.base_keymap);
}
#[cfg(any(test, feature = "test-support"))]
@@ -248,7 +200,6 @@ impl Settings {
.unwrap(),
buffer_font_size: 14.,
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
- base_keymap: Default::default(),
}
}
@@ -1,6 +1,6 @@
use crate::{
- settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent,
- Setting, Settings, DEFAULT_SETTINGS_ASSET_PATH,
+ settings_store::parse_json_with_comments, settings_store::SettingsStore, Setting, Settings,
+ DEFAULT_SETTINGS_ASSET_PATH,
};
use anyhow::Result;
use assets::Assets;
@@ -76,43 +76,6 @@ pub fn watch_config_file(
rx
}
-pub fn handle_keymap_file_changes(
- mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
- cx: &mut AppContext,
-) {
- cx.spawn(move |mut cx| async move {
- let mut settings_subscription = None;
- while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
- if let Ok(keymap_content) =
- parse_json_with_comments::<KeymapFileContent>(&user_keymap_content)
- {
- cx.update(|cx| {
- cx.clear_bindings();
- KeymapFileContent::load_defaults(cx);
- keymap_content.clone().add_to_cx(cx).log_err();
- });
-
- let mut old_base_keymap = cx.read(|cx| cx.global::<Settings>().base_keymap.clone());
- drop(settings_subscription);
- settings_subscription = Some(cx.update(|cx| {
- cx.observe_global::<Settings, _>(move |cx| {
- let settings = cx.global::<Settings>();
- if settings.base_keymap != old_base_keymap {
- old_base_keymap = settings.base_keymap.clone();
-
- cx.clear_bindings();
- KeymapFileContent::load_defaults(cx);
- keymap_content.clone().add_to_cx(cx).log_err();
- }
- })
- .detach();
- }));
- }
- }
- })
- .detach();
-}
-
pub fn handle_settings_file_changes(
mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
cx: &mut AppContext,
@@ -184,180 +147,3 @@ pub fn update_settings_file<T: Setting>(
})
.detach_and_log_err(cx);
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use fs::FakeFs;
- use gpui::{actions, elements::*, Action, Entity, TestAppContext, View, ViewContext};
- use theme::ThemeRegistry;
-
- struct TestView;
-
- impl Entity for TestView {
- type Event = ();
- }
-
- impl View for TestView {
- fn ui_name() -> &'static str {
- "TestView"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- }
-
- #[gpui::test]
- async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
- let executor = cx.background();
- let fs = FakeFs::new(executor.clone());
-
- actions!(test, [A, B]);
- // From the Atom keymap
- actions!(workspace, [ActivatePreviousPane]);
- // From the JetBrains keymap
- actions!(pane, [ActivatePrevItem]);
-
- fs.save(
- "/settings.json".as_ref(),
- &r#"
- {
- "base_keymap": "Atom"
- }
- "#
- .into(),
- Default::default(),
- )
- .await
- .unwrap();
-
- fs.save(
- "/keymap.json".as_ref(),
- &r#"
- [
- {
- "bindings": {
- "backspace": "test::A"
- }
- }
- ]
- "#
- .into(),
- Default::default(),
- )
- .await
- .unwrap();
-
- cx.update(|cx| {
- let mut store = SettingsStore::default();
- store.set_default_settings(&test_settings(), cx).unwrap();
- cx.set_global(store);
- cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone()));
- cx.add_global_action(|_: &A, _cx| {});
- cx.add_global_action(|_: &B, _cx| {});
- cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
- cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
-
- let settings_rx = watch_config_file(
- executor.clone(),
- fs.clone(),
- PathBuf::from("/settings.json"),
- );
- let keymap_rx =
- watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
-
- handle_keymap_file_changes(keymap_rx, cx);
- handle_settings_file_changes(settings_rx, cx);
- });
-
- cx.foreground().run_until_parked();
-
- let (window_id, _view) = cx.add_window(|_| TestView);
-
- // Test loading the keymap base at all
- assert_key_bindings_for(
- window_id,
- cx,
- vec![("backspace", &A), ("k", &ActivatePreviousPane)],
- line!(),
- );
-
- // Test modifying the users keymap, while retaining the base keymap
- fs.save(
- "/keymap.json".as_ref(),
- &r#"
- [
- {
- "bindings": {
- "backspace": "test::B"
- }
- }
- ]
- "#
- .into(),
- Default::default(),
- )
- .await
- .unwrap();
-
- cx.foreground().run_until_parked();
-
- assert_key_bindings_for(
- window_id,
- cx,
- vec![("backspace", &B), ("k", &ActivatePreviousPane)],
- line!(),
- );
-
- // Test modifying the base, while retaining the users keymap
- fs.save(
- "/settings.json".as_ref(),
- &r#"
- {
- "base_keymap": "JetBrains"
- }
- "#
- .into(),
- Default::default(),
- )
- .await
- .unwrap();
-
- cx.foreground().run_until_parked();
-
- assert_key_bindings_for(
- window_id,
- cx,
- vec![("backspace", &B), ("[", &ActivatePrevItem)],
- line!(),
- );
- }
-
- fn assert_key_bindings_for<'a>(
- window_id: usize,
- cx: &TestAppContext,
- actions: Vec<(&'static str, &'a dyn Action)>,
- line: u32,
- ) {
- for (key, action) in actions {
- // assert that...
- assert!(
- cx.available_actions(window_id, 0)
- .into_iter()
- .any(|(_, bound_action, b)| {
- // action names match...
- bound_action.name() == action.name()
- && bound_action.namespace() == action.namespace()
- // and key strokes contain the given key
- && b.iter()
- .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
- }),
- "On {} Failed to find {} with key binding {}",
- line,
- action.name(),
- key
- );
- }
- }
-}
@@ -27,7 +27,7 @@ impl<'a> VimTestContext<'a> {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
});
- settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
+ settings::KeymapFileContent::load_asset("keymaps/vim.json", cx).unwrap();
});
// Setup search toolbars and keypress hook
@@ -11,8 +11,6 @@ path = "src/welcome.rs"
test-support = []
[dependencies]
-anyhow.workspace = true
-log.workspace = true
client = { path = "../client" }
editor = { path = "../editor" }
fs = { path = "../fs" }
@@ -27,3 +25,8 @@ theme_selector = { path = "../theme_selector" }
util = { path = "../util" }
picker = { path = "../picker" }
workspace = { path = "../workspace" }
+
+anyhow.workspace = true
+log.workspace = true
+schemars.workspace = true
+serde.workspace = true
@@ -1,3 +1,4 @@
+use super::base_keymap_setting::BaseKeymap;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions,
@@ -6,7 +7,7 @@ use gpui::{
};
use picker::{Picker, PickerDelegate, PickerEvent};
use project::Fs;
-use settings::{update_settings_file, BaseKeymap, Settings};
+use settings::{update_settings_file, Settings};
use std::sync::Arc;
use util::ResultExt;
use workspace::Workspace;
@@ -39,10 +40,10 @@ pub struct BaseKeymapSelectorDelegate {
impl BaseKeymapSelectorDelegate {
fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<BaseKeymapSelector>) -> Self {
- let base = cx.global::<Settings>().base_keymap;
+ let base = settings::get_setting::<BaseKeymap>(None, cx);
let selected_index = BaseKeymap::OPTIONS
.iter()
- .position(|(_, value)| *value == base)
+ .position(|(_, value)| value == base)
.unwrap_or(0);
Self {
matches: Vec::new(),
@@ -122,8 +123,8 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
if let Some(selection) = self.matches.get(self.selected_index) {
let base_keymap = BaseKeymap::from_names(&selection.string);
- update_settings_file::<Settings>(self.fs.clone(), cx, move |settings| {
- settings.base_keymap = Some(base_keymap)
+ update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
+ *setting = Some(base_keymap)
});
}
cx.emit(PickerEvent::Dismiss);
@@ -0,0 +1,65 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::Setting;
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
+pub enum BaseKeymap {
+ #[default]
+ VSCode,
+ JetBrains,
+ SublimeText,
+ Atom,
+ TextMate,
+}
+
+impl BaseKeymap {
+ pub const OPTIONS: [(&'static str, Self); 5] = [
+ ("VSCode (Default)", Self::VSCode),
+ ("Atom", Self::Atom),
+ ("JetBrains", Self::JetBrains),
+ ("Sublime Text", Self::SublimeText),
+ ("TextMate", Self::TextMate),
+ ];
+
+ pub fn asset_path(&self) -> Option<&'static str> {
+ match self {
+ BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
+ BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
+ BaseKeymap::Atom => Some("keymaps/atom.json"),
+ BaseKeymap::TextMate => Some("keymaps/textmate.json"),
+ BaseKeymap::VSCode => None,
+ }
+ }
+
+ pub fn names() -> impl Iterator<Item = &'static str> {
+ Self::OPTIONS.iter().map(|(name, _)| *name)
+ }
+
+ pub fn from_names(option: &str) -> BaseKeymap {
+ Self::OPTIONS
+ .iter()
+ .copied()
+ .find_map(|(name, value)| (name == option).then(|| value))
+ .unwrap_or_default()
+ }
+}
+
+impl Setting for BaseKeymap {
+ const KEY: Option<&'static str> = Some("base_keymap");
+
+ type FileContent = Option<Self>;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &gpui::AppContext,
+ ) -> anyhow::Result<Self>
+ where
+ Self: Sized,
+ {
+ Ok(user_values
+ .first()
+ .and_then(|v| **v)
+ .unwrap_or(default_value.unwrap()))
+ }
+}
@@ -1,7 +1,7 @@
mod base_keymap_picker;
+mod base_keymap_setting;
-use std::{borrow::Cow, sync::Arc};
-
+use crate::base_keymap_picker::ToggleBaseKeymapSelector;
use client::TelemetrySettings;
use db::kvp::KEY_VALUE_STORE;
use gpui::{
@@ -9,17 +9,19 @@ use gpui::{
AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle,
};
use settings::{update_settings_file, Settings};
-
+use std::{borrow::Cow, sync::Arc};
use workspace::{
item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
WorkspaceId,
};
-use crate::base_keymap_picker::ToggleBaseKeymapSelector;
+pub use base_keymap_setting::BaseKeymap;
pub const FIRST_OPEN: &str = "first_open";
pub fn init(cx: &mut AppContext) {
+ settings::register_setting::<BaseKeymap>(cx);
+
cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| {
let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
workspace.add_item(Box::new(welcome_page), cx)
@@ -24,8 +24,7 @@ use parking_lot::Mutex;
use project::Fs;
use serde::{Deserialize, Serialize};
use settings::{
- default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file,
- Settings, SettingsStore,
+ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
};
use simplelog::ConfigBuilder;
use smol::process::Command;
@@ -63,7 +62,9 @@ use workspace::{
dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
Workspace,
};
-use zed::{self, build_window_options, initialize_workspace, languages, menus};
+use zed::{
+ self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
+};
fn main() {
let http = http::client();
@@ -15,7 +15,7 @@ use anyhow::anyhow;
use feedback::{
feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton,
};
-use futures::StreamExt;
+use futures::{channel::mpsc, StreamExt};
use gpui::{
actions,
geometry::vector::vec2f,
@@ -29,11 +29,14 @@ use project_panel::ProjectPanel;
use search::{BufferSearchBar, ProjectSearchBar};
use serde::Deserialize;
use serde_json::to_string_pretty;
-use settings::{adjust_font_size_delta, Settings, DEFAULT_SETTINGS_ASSET_PATH};
+use settings::{
+ adjust_font_size_delta, KeymapFileContent, Settings, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH,
+};
use std::{borrow::Cow, str, sync::Arc};
use terminal_view::terminal_button::TerminalButton;
use util::{channel::ReleaseChannel, paths, ResultExt};
use uuid::Uuid;
+use welcome::BaseKeymap;
pub use workspace;
use workspace::{
create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow,
@@ -258,7 +261,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
activity_indicator::init(cx);
lsp_log::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
- settings::KeymapFileContent::load_defaults(cx);
+ load_default_keymap(cx);
}
pub fn initialize_workspace(
@@ -478,6 +481,52 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
.detach();
}
+pub fn load_default_keymap(cx: &mut AppContext) {
+ for path in ["keymaps/default.json", "keymaps/vim.json"] {
+ KeymapFileContent::load_asset(path, cx).unwrap();
+ }
+
+ if let Some(asset_path) = settings::get_setting::<BaseKeymap>(None, cx).asset_path() {
+ KeymapFileContent::load_asset(asset_path, cx).unwrap();
+ }
+}
+
+pub fn handle_keymap_file_changes(
+ mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
+ cx: &mut AppContext,
+) {
+ cx.spawn(move |mut cx| async move {
+ let mut settings_subscription = None;
+ while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
+ if let Ok(keymap_content) = KeymapFileContent::parse(&user_keymap_content) {
+ cx.update(|cx| {
+ cx.clear_bindings();
+ load_default_keymap(cx);
+ keymap_content.clone().add_to_cx(cx).log_err();
+ });
+
+ let mut old_base_keymap =
+ cx.read(|cx| *settings::get_setting::<BaseKeymap>(None, cx));
+ drop(settings_subscription);
+ settings_subscription = Some(cx.update(|cx| {
+ cx.observe_global::<SettingsStore, _>(move |cx| {
+ let new_base_keymap = *settings::get_setting::<BaseKeymap>(None, cx);
+ if new_base_keymap != old_base_keymap {
+ old_base_keymap = new_base_keymap.clone();
+
+ cx.clear_bindings();
+ load_default_keymap(cx);
+ keymap_content.clone().add_to_cx(cx).log_err();
+ }
+ })
+ .detach();
+ }));
+ }
+ }
+ })
+ .detach();
+}
+
fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
workspace.with_local_workspace(cx, move |workspace, cx| {
let app_state = workspace.app_state().clone();
@@ -579,11 +628,16 @@ mod tests {
use super::*;
use assets::Assets;
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
- use gpui::{executor::Deterministic, AppContext, AssetSource, TestAppContext, ViewHandle};
+ use fs::{FakeFs, Fs};
+ use gpui::{
+ elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, AssetSource,
+ Element, Entity, TestAppContext, View, ViewHandle,
+ };
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
use project::{Project, ProjectPath};
use serde_json::json;
+ use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
use std::{
collections::HashSet,
path::{Path, PathBuf},
@@ -1797,6 +1851,175 @@ mod tests {
}
}
+ #[gpui::test]
+ async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
+ struct TestView;
+
+ impl Entity for TestView {
+ type Event = ();
+ }
+
+ impl View for TestView {
+ fn ui_name() -> &'static str {
+ "TestView"
+ }
+
+ fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
+ Empty::new().into_any()
+ }
+ }
+
+ let executor = cx.background();
+ let fs = FakeFs::new(executor.clone());
+
+ actions!(test, [A, B]);
+ // From the Atom keymap
+ actions!(workspace, [ActivatePreviousPane]);
+ // From the JetBrains keymap
+ actions!(pane, [ActivatePrevItem]);
+
+ fs.save(
+ "/settings.json".as_ref(),
+ &r#"
+ {
+ "base_keymap": "Atom"
+ }
+ "#
+ .into(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+
+ fs.save(
+ "/keymap.json".as_ref(),
+ &r#"
+ [
+ {
+ "bindings": {
+ "backspace": "test::A"
+ }
+ }
+ ]
+ "#
+ .into(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+
+ cx.update(|cx| {
+ cx.set_global(SettingsStore::test(cx));
+ cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone()));
+ welcome::init(cx);
+
+ cx.add_global_action(|_: &A, _cx| {});
+ cx.add_global_action(|_: &B, _cx| {});
+ cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
+ cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
+
+ let settings_rx = watch_config_file(
+ executor.clone(),
+ fs.clone(),
+ PathBuf::from("/settings.json"),
+ );
+ let keymap_rx =
+ watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
+
+ handle_keymap_file_changes(keymap_rx, cx);
+ handle_settings_file_changes(settings_rx, cx);
+ });
+
+ cx.foreground().run_until_parked();
+
+ let (window_id, _view) = cx.add_window(|_| TestView);
+
+ // Test loading the keymap base at all
+ assert_key_bindings_for(
+ window_id,
+ cx,
+ vec![("backspace", &A), ("k", &ActivatePreviousPane)],
+ line!(),
+ );
+
+ // Test modifying the users keymap, while retaining the base keymap
+ fs.save(
+ "/keymap.json".as_ref(),
+ &r#"
+ [
+ {
+ "bindings": {
+ "backspace": "test::B"
+ }
+ }
+ ]
+ "#
+ .into(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+
+ cx.foreground().run_until_parked();
+
+ assert_key_bindings_for(
+ window_id,
+ cx,
+ vec![("backspace", &B), ("k", &ActivatePreviousPane)],
+ line!(),
+ );
+
+ // Test modifying the base, while retaining the users keymap
+ fs.save(
+ "/settings.json".as_ref(),
+ &r#"
+ {
+ "base_keymap": "JetBrains"
+ }
+ "#
+ .into(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+
+ cx.foreground().run_until_parked();
+
+ assert_key_bindings_for(
+ window_id,
+ cx,
+ vec![("backspace", &B), ("[", &ActivatePrevItem)],
+ line!(),
+ );
+
+ fn assert_key_bindings_for<'a>(
+ window_id: usize,
+ cx: &TestAppContext,
+ actions: Vec<(&'static str, &'a dyn Action)>,
+ line: u32,
+ ) {
+ for (key, action) in actions {
+ // assert that...
+ assert!(
+ cx.available_actions(window_id, 0)
+ .into_iter()
+ .any(|(_, bound_action, b)| {
+ // action names match...
+ bound_action.name() == action.name()
+ && bound_action.namespace() == action.namespace()
+ // and key strokes contain the given key
+ && b.iter()
+ .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
+ }),
+ "On {} Failed to find {} with key binding {}",
+ line,
+ action.name(),
+ key
+ );
+ }
+ }
+ }
+
#[gpui::test]
fn test_bundled_settings_and_themes(cx: &mut AppContext) {
cx.platform()