mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-04 06:34:26 +00:00
Added theme and dock anchor saving :D
This commit is contained in:
parent
5487f99ac7
commit
e7b6d1befe
9 changed files with 111 additions and 19 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2009,6 +2009,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
|
"tempfile",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5065,6 +5066,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"json_comments",
|
"json_comments",
|
||||||
"postage",
|
"postage",
|
||||||
|
"rope",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -15,6 +15,7 @@ util = { path = "../util" }
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
tempfile = "3"
|
||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
|
|
@ -12,6 +12,7 @@ use rope::Rope;
|
||||||
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
|
@ -20,6 +21,7 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -100,6 +102,7 @@ pub trait Fs: Send + Sync {
|
||||||
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||||
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
||||||
async fn load(&self, path: &Path) -> Result<String>;
|
async fn load(&self, path: &Path) -> Result<String>;
|
||||||
|
async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
|
||||||
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
|
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
|
||||||
async fn is_file(&self, path: &Path) -> bool;
|
async fn is_file(&self, path: &Path) -> bool;
|
||||||
|
@ -260,6 +263,18 @@ impl Fs for RealFs {
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||||
|
smol::unblock(move || {
|
||||||
|
let mut tmp_file = NamedTempFile::new()?;
|
||||||
|
tmp_file.write_all(data.as_bytes())?;
|
||||||
|
tmp_file.persist(path)?;
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||||
let buffer_size = text.summary().len.min(10 * 1024);
|
let buffer_size = text.summary().len.min(10 * 1024);
|
||||||
let file = smol::fs::File::create(path).await?;
|
let file = smol::fs::File::create(path).await?;
|
||||||
|
@ -880,6 +895,14 @@ impl Fs for FakeFs {
|
||||||
entry.file_content(&path).cloned()
|
entry.file_content(&path).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||||
|
self.simulate_random_delay().await;
|
||||||
|
let path = normalize_path(path.as_path());
|
||||||
|
self.insert_file(path, data.to_string()).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||||
self.simulate_random_delay().await;
|
self.simulate_random_delay().await;
|
||||||
let path = normalize_path(path);
|
let path = normalize_path(path);
|
||||||
|
|
|
@ -19,6 +19,7 @@ anyhow = "1.0.38"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
json_comments = "0.2"
|
json_comments = "0.2"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
@ -32,3 +33,4 @@ tree-sitter-json = "*"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
unindent = "0.1"
|
unindent = "0.1"
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
||||||
|
|
|
@ -503,7 +503,11 @@ pub fn settings_file_json_schema(
|
||||||
serde_json::to_value(root_schema).unwrap()
|
serde_json::to_value(root_schema).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_theme(mut settings_content: String, new_val: &str) -> String {
|
pub fn write_top_level_setting(
|
||||||
|
mut settings_content: String,
|
||||||
|
top_level_key: &str,
|
||||||
|
new_val: &str,
|
||||||
|
) -> String {
|
||||||
let mut parser = tree_sitter::Parser::new();
|
let mut parser = tree_sitter::Parser::new();
|
||||||
parser.set_language(tree_sitter_json::language()).unwrap();
|
parser.set_language(tree_sitter_json::language()).unwrap();
|
||||||
let tree = parser.parse(&settings_content, None).unwrap();
|
let tree = parser.parse(&settings_content, None).unwrap();
|
||||||
|
@ -536,7 +540,7 @@ pub fn write_theme(mut settings_content: String, new_val: &str) -> String {
|
||||||
first_key_start.get_or_insert_with(|| key.node.start_byte());
|
first_key_start.get_or_insert_with(|| key.node.start_byte());
|
||||||
|
|
||||||
if let Some(key_text) = settings_content.get(key.node.byte_range()) {
|
if let Some(key_text) = settings_content.get(key.node.byte_range()) {
|
||||||
if key_text == "\"theme\"" {
|
if key_text == format!("\"{top_level_key}\"") {
|
||||||
existing_value_range = Some(value.node.byte_range());
|
existing_value_range = Some(value.node.byte_range());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -547,7 +551,12 @@ pub fn write_theme(mut settings_content: String, new_val: &str) -> String {
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
// No document, create a new object and overwrite
|
// No document, create a new object and overwrite
|
||||||
settings_content.clear();
|
settings_content.clear();
|
||||||
write!(settings_content, "{{\n \"theme\": \"{new_val}\"\n}}\n").unwrap();
|
write!(
|
||||||
|
settings_content,
|
||||||
|
"{{\n \"{}\": \"{new_val}\"\n}}\n",
|
||||||
|
top_level_key
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, Some(existing_value_range)) => {
|
(_, Some(existing_value_range)) => {
|
||||||
|
@ -572,7 +581,7 @@ pub fn write_theme(mut settings_content: String, new_val: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = format!(r#""theme": "{new_val}","#);
|
let content = format!(r#""{top_level_key}": "{new_val}","#);
|
||||||
settings_content.insert_str(first_key_start, &content);
|
settings_content.insert_str(first_key_start, &content);
|
||||||
|
|
||||||
if row > 0 {
|
if row > 0 {
|
||||||
|
@ -603,7 +612,7 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::write_theme;
|
use crate::write_top_level_setting;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -622,7 +631,7 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
@ -642,7 +651,7 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
@ -658,7 +667,7 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
@ -668,7 +677,7 @@ mod tests {
|
||||||
let settings = r#"{ "a": "", "ok": true }"#.to_string();
|
let settings = r#"{ "a": "", "ok": true }"#.to_string();
|
||||||
let new_settings = r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
let new_settings = r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
@ -678,7 +687,7 @@ mod tests {
|
||||||
let settings = r#" { "a": "", "ok": true }"#.to_string();
|
let settings = r#" { "a": "", "ok": true }"#.to_string();
|
||||||
let new_settings = r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
let new_settings = r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
@ -700,7 +709,7 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let settings_after_theme = write_theme(settings, "summerfruit-light");
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
assert_eq!(settings_after_theme, new_settings)
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,54 @@ use std::{path::Path, sync::Arc, time::Duration};
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
use crate::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent};
|
use crate::{
|
||||||
|
parse_json_with_comments, write_top_level_setting, KeymapFileContent, Settings,
|
||||||
|
SettingsFileContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Switch SettingsFile to open a worktree and buffer for synchronization
|
||||||
|
// And instant updates in the Zed editor
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SettingsFile {
|
||||||
|
path: &'static Path,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsFile {
|
||||||
|
pub fn new(path: &'static Path, fs: Arc<dyn Fs>) -> Self {
|
||||||
|
SettingsFile { path, fs }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rewrite_settings_file<F>(&self, f: F) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(String) -> String,
|
||||||
|
{
|
||||||
|
let content = self.fs.load(self.path).await?;
|
||||||
|
|
||||||
|
let new_settings = f(content);
|
||||||
|
|
||||||
|
self.fs
|
||||||
|
.atomic_write(self.path.to_path_buf(), new_settings)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_setting(key: &'static str, val: String, cx: &mut MutableAppContext) {
|
||||||
|
let settings_file = cx.global::<SettingsFile>().clone();
|
||||||
|
cx.background()
|
||||||
|
.spawn(async move {
|
||||||
|
settings_file
|
||||||
|
.rewrite_settings_file(|settings| write_top_level_setting(settings, key, &val))
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
|
pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
|
||||||
|
|
||||||
// 1) Do the refactoring to pull WatchedJSON and fs out and into everything else
|
|
||||||
// 2) Scaffold this by making the basic structs we'll need SettingsFile::atomic_write_theme()
|
|
||||||
// 3) Fix the overeager settings writing, if that works, and there's no data loss, call it?
|
|
||||||
|
|
||||||
impl<T> WatchedJsonFile<T>
|
impl<T> WatchedJsonFile<T>
|
||||||
where
|
where
|
||||||
T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
|
T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
|
||||||
|
|
|
@ -153,6 +153,10 @@ impl PickerDelegate for ThemeSelector {
|
||||||
|
|
||||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.selection_completed = true;
|
self.selection_completed = true;
|
||||||
|
|
||||||
|
let theme_name = cx.global::<Settings>().theme.meta.name.clone();
|
||||||
|
settings::settings_file::write_setting("theme", theme_name, cx);
|
||||||
|
|
||||||
cx.emit(Event::Dismissed);
|
cx.emit(Event::Dismissed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,16 +35,23 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Dock::move_dock);
|
cx.add_action(Dock::move_dock);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
|
||||||
|
settings::settings_file::write_setting("default_dock_anchor", "right".to_string(), cx);
|
||||||
Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx)
|
Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
|
||||||
|
settings::settings_file::write_setting("default_dock_anchor", "bottom".to_string(), cx);
|
||||||
Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx)
|
Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
|
||||||
|
settings::settings_file::write_setting(
|
||||||
|
"default_dock_anchor",
|
||||||
|
"expanded".to_string(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
|
Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,7 +25,10 @@ use log::LevelFilter;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{Fs, ProjectStore};
|
use project::{Fs, ProjectStore};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{self, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory};
|
use settings::{
|
||||||
|
self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
|
||||||
|
WorkingDirectory,
|
||||||
|
};
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration};
|
use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration};
|
||||||
|
@ -62,6 +65,7 @@ fn main() {
|
||||||
let themes = ThemeRegistry::new(Assets, app.font_cache());
|
let themes = ThemeRegistry::new(Assets, app.font_cache());
|
||||||
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
|
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
|
||||||
|
|
||||||
|
let settings_file = SettingsFile::new(&*zed::paths::SETTINGS, fs.clone());
|
||||||
let config_files = load_config_files(&app, fs.clone());
|
let config_files = load_config_files(&app, fs.clone());
|
||||||
|
|
||||||
let login_shell_env_loaded = if stdout_is_a_pty() {
|
let login_shell_env_loaded = if stdout_is_a_pty() {
|
||||||
|
@ -94,10 +98,11 @@ fn main() {
|
||||||
.spawn(languages::init(languages.clone(), cx.background().clone()));
|
.spawn(languages::init(languages.clone(), cx.background().clone()));
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
||||||
|
|
||||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap();
|
||||||
|
|
||||||
//Setup settings global before binding actions
|
//Setup settings global before binding actions
|
||||||
watch_settings_file(default_settings, settings_file, themes.clone(), cx);
|
cx.set_global(settings_file);
|
||||||
|
watch_settings_file(default_settings, settings_file_content, themes.clone(), cx);
|
||||||
watch_keymap_file(keymap_file, cx);
|
watch_keymap_file(keymap_file, cx);
|
||||||
|
|
||||||
context_menu::init(cx);
|
context_menu::init(cx);
|
||||||
|
|
Loading…
Reference in a new issue