Define base keymap setting in welcome crate
This commit is contained in:
parent
89204e85c0
commit
5c729c0e56
12 changed files with 334 additions and 297 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -8353,6 +8353,8 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"picker",
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
"theme_selector",
|
"theme_selector",
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
{
|
{
|
||||||
// The name of the Zed theme to use for the UI
|
// The name of the Zed theme to use for the UI
|
||||||
"theme": "One Dark",
|
"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 that can be globally enabled or disabled
|
||||||
"features": {
|
"features": {
|
||||||
// Show Copilot icon in status bar
|
// 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 anyhow::{Context, Result};
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
@ -41,20 +41,14 @@ impl JsonSchema for KeymapAction {
|
||||||
struct ActionWithData(Box<str>, Box<RawValue>);
|
struct ActionWithData(Box<str>, Box<RawValue>);
|
||||||
|
|
||||||
impl KeymapFileContent {
|
impl KeymapFileContent {
|
||||||
pub fn load_defaults(cx: &mut AppContext) {
|
pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> {
|
||||||
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<()> {
|
|
||||||
let content = Assets::get(asset_path).unwrap().data;
|
let content = Assets::get(asset_path).unwrap().data;
|
||||||
let content_str = std::str::from_utf8(content.as_ref()).unwrap();
|
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<()> {
|
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_family: FamilyId,
|
||||||
pub buffer_font_size: f32,
|
pub buffer_font_size: f32,
|
||||||
pub theme: Arc<Theme>,
|
pub theme: Arc<Theme>,
|
||||||
pub base_keymap: BaseKeymap,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Setting for Settings {
|
impl Setting for Settings {
|
||||||
|
@ -62,7 +61,6 @@ impl Setting for Settings {
|
||||||
buffer_font_features,
|
buffer_font_features,
|
||||||
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
||||||
theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
|
theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
|
||||||
base_keymap: Default::default(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for value in user_values.into_iter().copied().cloned() {
|
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)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct SettingsFileContent {
|
pub struct SettingsFileContent {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -163,8 +119,6 @@ pub struct SettingsFileContent {
|
||||||
pub buffer_font_features: Option<fonts::Features>,
|
pub buffer_font_features: Option<fonts::Features>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
#[serde(default)]
|
|
||||||
pub base_keymap: Option<BaseKeymap>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
@ -198,7 +152,6 @@ impl Settings {
|
||||||
buffer_font_features,
|
buffer_font_features,
|
||||||
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
||||||
theme: themes.get(&defaults.theme.unwrap()).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.buffer_font_size, data.buffer_font_size);
|
||||||
merge(&mut self.base_keymap, data.base_keymap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -248,7 +200,6 @@ impl Settings {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
buffer_font_size: 14.,
|
buffer_font_size: 14.,
|
||||||
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
|
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
|
||||||
base_keymap: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent,
|
settings_store::parse_json_with_comments, settings_store::SettingsStore, Setting, Settings,
|
||||||
Setting, Settings, DEFAULT_SETTINGS_ASSET_PATH,
|
DEFAULT_SETTINGS_ASSET_PATH,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
|
@ -76,43 +76,6 @@ pub fn watch_config_file(
|
||||||
rx
|
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(
|
pub fn handle_settings_file_changes(
|
||||||
mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
|
mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
|
@ -184,180 +147,3 @@ pub fn update_settings_file<T: Setting>(
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.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| {
|
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||||
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
|
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
|
// Setup search toolbars and keypress hook
|
||||||
|
|
|
@ -11,8 +11,6 @@ path = "src/welcome.rs"
|
||||||
test-support = []
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fs = { path = "../fs" }
|
fs = { path = "../fs" }
|
||||||
|
@ -27,3 +25,8 @@ theme_selector = { path = "../theme_selector" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
picker = { path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
workspace = { path = "../workspace" }
|
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 fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
|
@ -6,7 +7,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use settings::{update_settings_file, BaseKeymap, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
@ -39,10 +40,10 @@ pub struct BaseKeymapSelectorDelegate {
|
||||||
|
|
||||||
impl BaseKeymapSelectorDelegate {
|
impl BaseKeymapSelectorDelegate {
|
||||||
fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<BaseKeymapSelector>) -> Self {
|
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
|
let selected_index = BaseKeymap::OPTIONS
|
||||||
.iter()
|
.iter()
|
||||||
.position(|(_, value)| *value == base)
|
.position(|(_, value)| value == base)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
Self {
|
Self {
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
|
@ -122,8 +123,8 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||||
fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
|
fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
|
||||||
if let Some(selection) = self.matches.get(self.selected_index) {
|
if let Some(selection) = self.matches.get(self.selected_index) {
|
||||||
let base_keymap = BaseKeymap::from_names(&selection.string);
|
let base_keymap = BaseKeymap::from_names(&selection.string);
|
||||||
update_settings_file::<Settings>(self.fs.clone(), cx, move |settings| {
|
update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
|
||||||
settings.base_keymap = Some(base_keymap)
|
*setting = Some(base_keymap)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cx.emit(PickerEvent::Dismiss);
|
cx.emit(PickerEvent::Dismiss);
|
||||||
|
|
65
crates/welcome/src/base_keymap_setting.rs
Normal file
65
crates/welcome/src/base_keymap_setting.rs
Normal file
|
@ -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_picker;
|
||||||
|
mod base_keymap_setting;
|
||||||
|
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use crate::base_keymap_picker::ToggleBaseKeymapSelector;
|
||||||
|
|
||||||
use client::TelemetrySettings;
|
use client::TelemetrySettings;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -9,17 +9,19 @@ use gpui::{
|
||||||
AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle,
|
AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
|
item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
|
||||||
WorkspaceId,
|
WorkspaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::base_keymap_picker::ToggleBaseKeymapSelector;
|
pub use base_keymap_setting::BaseKeymap;
|
||||||
|
|
||||||
pub const FIRST_OPEN: &str = "first_open";
|
pub const FIRST_OPEN: &str = "first_open";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
settings::register_setting::<BaseKeymap>(cx);
|
||||||
|
|
||||||
cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| {
|
cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| {
|
||||||
let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
|
let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
|
||||||
workspace.add_item(Box::new(welcome_page), cx)
|
workspace.add_item(Box::new(welcome_page), cx)
|
||||||
|
|
|
@ -24,8 +24,7 @@ use parking_lot::Mutex;
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file,
|
default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
|
||||||
Settings, SettingsStore,
|
|
||||||
};
|
};
|
||||||
use simplelog::ConfigBuilder;
|
use simplelog::ConfigBuilder;
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
|
@ -63,7 +62,9 @@ use workspace::{
|
||||||
dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
|
dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
|
||||||
Workspace,
|
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() {
|
fn main() {
|
||||||
let http = http::client();
|
let http = http::client();
|
||||||
|
|
|
@ -15,7 +15,7 @@ use anyhow::anyhow;
|
||||||
use feedback::{
|
use feedback::{
|
||||||
feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton,
|
feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
|
@ -29,11 +29,14 @@ use project_panel::ProjectPanel;
|
||||||
use search::{BufferSearchBar, ProjectSearchBar};
|
use search::{BufferSearchBar, ProjectSearchBar};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::to_string_pretty;
|
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 std::{borrow::Cow, str, sync::Arc};
|
||||||
use terminal_view::terminal_button::TerminalButton;
|
use terminal_view::terminal_button::TerminalButton;
|
||||||
use util::{channel::ReleaseChannel, paths, ResultExt};
|
use util::{channel::ReleaseChannel, paths, ResultExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use welcome::BaseKeymap;
|
||||||
pub use workspace;
|
pub use workspace;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow,
|
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);
|
activity_indicator::init(cx);
|
||||||
lsp_log::init(cx);
|
lsp_log::init(cx);
|
||||||
call::init(app_state.client.clone(), app_state.user_store.clone(), 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(
|
pub fn initialize_workspace(
|
||||||
|
@ -478,6 +481,52 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
.detach();
|
.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>) {
|
fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
workspace.with_local_workspace(cx, move |workspace, cx| {
|
workspace.with_local_workspace(cx, move |workspace, cx| {
|
||||||
let app_state = workspace.app_state().clone();
|
let app_state = workspace.app_state().clone();
|
||||||
|
@ -579,11 +628,16 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
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 language::LanguageRegistry;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
path::{Path, PathBuf},
|
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]
|
#[gpui::test]
|
||||||
fn test_bundled_settings_and_themes(cx: &mut AppContext) {
|
fn test_bundled_settings_and_themes(cx: &mut AppContext) {
|
||||||
cx.platform()
|
cx.platform()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue