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:
Max Brunsfeld 2023-06-06 09:27:36 -07:00 committed by GitHub
commit 53906fd3da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 41 deletions

View 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.
{}

View file

@ -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.connection_id,
proto::UpdateWorktreeSettings {
@ -1554,8 +1554,6 @@ async fn update_worktree_settings(
message: proto::UpdateWorktreeSettings,
session: Session,
) -> Result<()> {
dbg!(&message);
let guest_connection_ids = session
.db()
.await

View file

@ -9,10 +9,23 @@ pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
use std::{borrow::Cow, str};
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> {
match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
pub fn default_settings() -> Cow<'static, str> {
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::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
}

View file

@ -1,11 +1,10 @@
use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH};
use crate::{settings_store::SettingsStore, Setting};
use anyhow::Result;
use assets::Assets;
use fs::Fs;
use futures::{channel::mpsc, StreamExt};
use gpui::{executor::Background, AppContext, AssetSource};
use gpui::{executor::Background, AppContext};
use std::{
borrow::Cow,
io::ErrorKind,
path::{Path, PathBuf},
str,
@ -28,19 +27,12 @@ pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppCo
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";
#[cfg(any(test, feature = "test-support"))]
pub fn test_settings() -> String {
let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
default_settings().as_ref(),
crate::default_settings().as_ref(),
)
.unwrap();
util::merge_non_null_json_value_into(

View file

@ -15,7 +15,6 @@ mod toolbar;
mod workspace_settings;
use anyhow::{anyhow, Context, Result};
use assets::Assets;
use call::ActiveCall;
use client::{
proto::{self, PeerId},
@ -83,7 +82,7 @@ use status_bar::StatusBar;
pub use status_bar::StatusItemView;
use theme::{Theme, ThemeSettings};
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
use util::{async_iife, paths, ResultExt};
use util::{async_iife, ResultExt};
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
lazy_static! {
@ -133,8 +132,6 @@ actions!(
]
);
actions!(zed, [OpenSettings]);
#[derive(Clone, PartialEq)]
pub struct OpenPaths {
pub paths: Vec<PathBuf>,
@ -295,17 +292,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
.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;
client.add_view_request_handler(Workspace::handle_follow);
client.add_view_message_handler(Workspace::handle_unfollow);

View file

@ -56,9 +56,7 @@ use fs::RealFs;
#[cfg(debug_assertions)]
use staff_mode::StaffMode;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{
item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace,
};
use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
use zed::{
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),
("Open command palette", &command_palette::Toggle),
("Open recent projects", &recent_projects::OpenRecent),
("Change your settings", &OpenSettings),
("Change your settings", &zed::OpenSettings),
]
}

View file

@ -12,10 +12,11 @@ pub fn menus() -> Vec<Menu<'static>> {
MenuItem::submenu(Menu {
name: "Preferences",
items: vec![
MenuItem::action("Open Settings", workspace::OpenSettings),
MenuItem::action("Open Settings", super::OpenSettings),
MenuItem::action("Open Key Bindings", super::OpenKeymap),
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
MenuItem::action("Open Local Settings", super::OpenLocalSettings),
MenuItem::action("Select Theme", theme_selector::Toggle),
],
}),

View file

@ -30,16 +30,23 @@ use project_panel::ProjectPanel;
use search::{BufferSearchBar, ProjectSearchBar};
use serde::Deserialize;
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 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 welcome::BaseKeymap;
pub use workspace;
use workspace::{
create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow,
Workspace, WorkspaceSettings,
create_and_open_local_file, dock::PanelHandle,
notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
NewWindow, Workspace, WorkspaceSettings,
};
#[derive(Deserialize, Clone, PartialEq)]
@ -65,6 +72,8 @@ actions!(
OpenLicenses,
OpenTelemetryLog,
OpenKeymap,
OpenSettings,
OpenLocalSettings,
OpenDefaultSettings,
OpenDefaultKeymap,
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);
},
);
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(
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
open_bundled_file(
@ -544,6 +564,76 @@ pub fn handle_keymap_file_changes(
.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>) {
workspace.with_local_workspace(cx, move |workspace, cx| {
let app_state = workspace.app_state().clone();