Add an application menu item for creating/opening a project-specific settings file (#2572)
Previously, project-specific settings were not discoverable. This PR adds a `Zed > Preferences > Local Settings` application menu command that creates a `.zed/settings.json` at the root of your current worktree. This command works in both local and remote projects. Limitations: * Currently, if you have an empty project open, the command just shows a notification that there are no folders open. * The JSON-schema-based autocomplete is the same in local settings files as in your main settings, even though not all settings can be locally customized. Release Notes: - Added an application menu command - `Zed > Preferences > Local Settings` for creating a folder-specific settings file.
This commit is contained in:
commit
53906fd3da
8 changed files with 130 additions and 41 deletions
11
assets/settings/initial_local_settings.json
Normal file
11
assets/settings/initial_local_settings.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Folder-specific Zed settings
|
||||||
|
//
|
||||||
|
// A subset of Zed's settings can be configured on a per-folder basis.
|
||||||
|
//
|
||||||
|
// For information on how to configure Zed, see the Zed
|
||||||
|
// documentation: https://zed.dev/docs/configuring-zed
|
||||||
|
//
|
||||||
|
// To see all of Zed's default settings without changing your
|
||||||
|
// custom settings, run the `open default settings` command
|
||||||
|
// from the command palette or from `Zed` application menu.
|
||||||
|
{}
|
|
@ -1424,7 +1424,7 @@ async fn join_project(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for settings_file in dbg!(worktree.settings_files) {
|
for settings_file in worktree.settings_files {
|
||||||
session.peer.send(
|
session.peer.send(
|
||||||
session.connection_id,
|
session.connection_id,
|
||||||
proto::UpdateWorktreeSettings {
|
proto::UpdateWorktreeSettings {
|
||||||
|
@ -1554,8 +1554,6 @@ async fn update_worktree_settings(
|
||||||
message: proto::UpdateWorktreeSettings,
|
message: proto::UpdateWorktreeSettings,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
dbg!(&message);
|
|
||||||
|
|
||||||
let guest_connection_ids = session
|
let guest_connection_ids = session
|
||||||
.db()
|
.db()
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -9,10 +9,23 @@ pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
|
||||||
use std::{borrow::Cow, str};
|
use std::{borrow::Cow, str};
|
||||||
|
|
||||||
pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
|
pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
|
||||||
pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
|
const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
|
||||||
|
const INITIAL_LOCAL_SETTINGS_ASSET_PATH: &str = "settings/initial_local_settings.json";
|
||||||
|
|
||||||
pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
|
pub fn default_settings() -> Cow<'static, str> {
|
||||||
match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
|
asset_str(&assets::Assets, DEFAULT_SETTINGS_ASSET_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initial_user_settings_content(assets: &dyn AssetSource) -> Cow<'_, str> {
|
||||||
|
asset_str(assets, INITIAL_USER_SETTINGS_ASSET_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initial_local_settings_content(assets: &dyn AssetSource) -> Cow<'_, str> {
|
||||||
|
asset_str(assets, INITIAL_LOCAL_SETTINGS_ASSET_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn asset_str<'a>(assets: &'a dyn AssetSource, path: &str) -> Cow<'a, str> {
|
||||||
|
match assets.load(path).unwrap() {
|
||||||
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
||||||
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH};
|
use crate::{settings_store::SettingsStore, Setting};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{channel::mpsc, StreamExt};
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use gpui::{executor::Background, AppContext, AssetSource};
|
use gpui::{executor::Background, AppContext};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
io::ErrorKind,
|
io::ErrorKind,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
str,
|
||||||
|
@ -28,19 +27,12 @@ pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppCo
|
||||||
cx.global::<SettingsStore>().get(location)
|
cx.global::<SettingsStore>().get(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_settings() -> Cow<'static, str> {
|
|
||||||
match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
|
|
||||||
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
|
||||||
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
|
pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn test_settings() -> String {
|
pub fn test_settings() -> String {
|
||||||
let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
|
let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
|
||||||
default_settings().as_ref(),
|
crate::default_settings().as_ref(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
util::merge_non_null_json_value_into(
|
util::merge_non_null_json_value_into(
|
||||||
|
|
|
@ -15,7 +15,6 @@ mod toolbar;
|
||||||
mod workspace_settings;
|
mod workspace_settings;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assets::Assets;
|
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{
|
use client::{
|
||||||
proto::{self, PeerId},
|
proto::{self, PeerId},
|
||||||
|
@ -83,7 +82,7 @@ use status_bar::StatusBar;
|
||||||
pub use status_bar::StatusItemView;
|
pub use status_bar::StatusItemView;
|
||||||
use theme::{Theme, ThemeSettings};
|
use theme::{Theme, ThemeSettings};
|
||||||
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
||||||
use util::{async_iife, paths, ResultExt};
|
use util::{async_iife, ResultExt};
|
||||||
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
|
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -133,8 +132,6 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
actions!(zed, [OpenSettings]);
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct OpenPaths {
|
pub struct OpenPaths {
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
|
@ -295,17 +292,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_action(
|
|
||||||
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
|
||||||
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
|
||||||
settings::initial_user_settings_content(&Assets)
|
|
||||||
.as_ref()
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let client = &app_state.client;
|
let client = &app_state.client;
|
||||||
client.add_view_request_handler(Workspace::handle_follow);
|
client.add_view_request_handler(Workspace::handle_follow);
|
||||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||||
|
|
|
@ -56,9 +56,7 @@ use fs::RealFs;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
use staff_mode::StaffMode;
|
use staff_mode::StaffMode;
|
||||||
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
|
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
|
||||||
item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace,
|
|
||||||
};
|
|
||||||
use zed::{
|
use zed::{
|
||||||
self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
|
self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
|
||||||
};
|
};
|
||||||
|
@ -877,6 +875,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
|
||||||
("Go to file", &file_finder::Toggle),
|
("Go to file", &file_finder::Toggle),
|
||||||
("Open command palette", &command_palette::Toggle),
|
("Open command palette", &command_palette::Toggle),
|
||||||
("Open recent projects", &recent_projects::OpenRecent),
|
("Open recent projects", &recent_projects::OpenRecent),
|
||||||
("Change your settings", &OpenSettings),
|
("Change your settings", &zed::OpenSettings),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,11 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||||
MenuItem::submenu(Menu {
|
MenuItem::submenu(Menu {
|
||||||
name: "Preferences",
|
name: "Preferences",
|
||||||
items: vec![
|
items: vec![
|
||||||
MenuItem::action("Open Settings", workspace::OpenSettings),
|
MenuItem::action("Open Settings", super::OpenSettings),
|
||||||
MenuItem::action("Open Key Bindings", super::OpenKeymap),
|
MenuItem::action("Open Key Bindings", super::OpenKeymap),
|
||||||
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
||||||
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
||||||
|
MenuItem::action("Open Local Settings", super::OpenLocalSettings),
|
||||||
MenuItem::action("Select Theme", theme_selector::Toggle),
|
MenuItem::action("Select Theme", theme_selector::Toggle),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -30,16 +30,23 @@ 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::{KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH};
|
use settings::{
|
||||||
|
initial_local_settings_content, KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH,
|
||||||
|
};
|
||||||
use std::{borrow::Cow, str, sync::Arc};
|
use std::{borrow::Cow, str, sync::Arc};
|
||||||
use terminal_view::terminal_panel::{self, TerminalPanel};
|
use terminal_view::terminal_panel::{self, TerminalPanel};
|
||||||
use util::{channel::ReleaseChannel, paths, ResultExt};
|
use util::{
|
||||||
|
channel::ReleaseChannel,
|
||||||
|
paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
|
||||||
|
ResultExt,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use welcome::BaseKeymap;
|
use welcome::BaseKeymap;
|
||||||
pub use workspace;
|
pub use workspace;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow,
|
create_and_open_local_file, dock::PanelHandle,
|
||||||
Workspace, WorkspaceSettings,
|
notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
|
||||||
|
NewWindow, Workspace, WorkspaceSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, PartialEq)]
|
#[derive(Deserialize, Clone, PartialEq)]
|
||||||
|
@ -65,6 +72,8 @@ actions!(
|
||||||
OpenLicenses,
|
OpenLicenses,
|
||||||
OpenTelemetryLog,
|
OpenTelemetryLog,
|
||||||
OpenKeymap,
|
OpenKeymap,
|
||||||
|
OpenSettings,
|
||||||
|
OpenLocalSettings,
|
||||||
OpenDefaultSettings,
|
OpenDefaultSettings,
|
||||||
OpenDefaultKeymap,
|
OpenDefaultKeymap,
|
||||||
IncreaseBufferFontSize,
|
IncreaseBufferFontSize,
|
||||||
|
@ -157,6 +166,17 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
|
||||||
create_and_open_local_file(&paths::KEYMAP, cx, Default::default).detach_and_log_err(cx);
|
create_and_open_local_file(&paths::KEYMAP, cx, Default::default).detach_and_log_err(cx);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
cx.add_action(
|
||||||
|
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||||
|
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||||
|
settings::initial_user_settings_content(&Assets)
|
||||||
|
.as_ref()
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cx.add_action(open_local_settings_file);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
|
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
|
||||||
open_bundled_file(
|
open_bundled_file(
|
||||||
|
@ -544,6 +564,76 @@ pub fn handle_keymap_file_changes(
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_local_settings_file(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: &OpenLocalSettings,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let worktree = project
|
||||||
|
.read(cx)
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
|
||||||
|
if let Some(worktree) = worktree {
|
||||||
|
let tree_id = worktree.read(cx).id();
|
||||||
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
|
||||||
|
|
||||||
|
if let Some(dir_path) = file_path.parent() {
|
||||||
|
if worktree.read_with(&cx, |tree, _| tree.entry_for_path(dir_path).is_none()) {
|
||||||
|
project
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.create_entry((tree_id, dir_path), true, cx)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow!("worktree was removed"))?
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if worktree.read_with(&cx, |tree, _| tree.entry_for_path(file_path).is_none()) {
|
||||||
|
project
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.create_entry((tree_id, file_path), false, cx)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow!("worktree was removed"))?
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let editor = workspace
|
||||||
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.open_path((tree_id, file_path), None, true, cx)
|
||||||
|
})?
|
||||||
|
.await?
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.ok_or_else(|| anyhow!("unexpected item type"))?;
|
||||||
|
|
||||||
|
editor
|
||||||
|
.downgrade()
|
||||||
|
.update(&mut cx, |editor, cx| {
|
||||||
|
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||||
|
if buffer.read(cx).is_empty() {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit(
|
||||||
|
[(0..0, initial_local_settings_content(&Assets))],
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
} else {
|
||||||
|
workspace.show_notification(0, cx, |cx| {
|
||||||
|
cx.add_view(|_| MessageNotification::new("This project has no folders open."))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue