Start work on respecting project-specific settings
This commit is contained in:
parent
e4530471de
commit
89446c7fd4
16 changed files with 326 additions and 83 deletions
|
@ -787,6 +787,7 @@ impl Copilot {
|
||||||
let position = position.to_point_utf16(buffer);
|
let position = position.to_point_utf16(buffer);
|
||||||
let settings = language_settings(
|
let settings = language_settings(
|
||||||
buffer.language_at(position).map(|l| l.name()).as_deref(),
|
buffer.language_at(position).map(|l| l.name()).as_deref(),
|
||||||
|
buffer.file().map(|f| f.as_ref()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let tab_size = settings.tab_size;
|
let tab_size = settings.tab_size;
|
||||||
|
@ -1175,6 +1176,10 @@ mod tests {
|
||||||
fn to_proto(&self) -> rpc::proto::File {
|
fn to_proto(&self) -> rpc::proto::File {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn worktree_id(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl language::LocalFile for File {
|
impl language::LocalFile for File {
|
||||||
|
|
|
@ -198,7 +198,7 @@ impl CopilotButton {
|
||||||
if let Some(language) = self.language.clone() {
|
if let Some(language) = self.language.clone() {
|
||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
let language_enabled =
|
let language_enabled =
|
||||||
language_settings::language_settings(Some(language.as_ref()), cx)
|
language_settings::language_settings(Some(language.as_ref()), None, cx)
|
||||||
.show_copilot_suggestions;
|
.show_copilot_suggestions;
|
||||||
menu_options.push(ContextMenuItem::handler(
|
menu_options.push(ContextMenuItem::handler(
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -277,7 +277,7 @@ impl DisplayMap {
|
||||||
.as_singleton()
|
.as_singleton()
|
||||||
.and_then(|buffer| buffer.read(cx).language())
|
.and_then(|buffer| buffer.read(cx).language())
|
||||||
.map(|language| language.name());
|
.map(|language| language.name());
|
||||||
language_settings(language_name.as_deref(), cx).tab_size
|
language_settings(language_name.as_deref(), None, cx).tab_size
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1231,6 +1231,10 @@ mod tests {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn worktree_id(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
fn is_deleted(&self) -> bool {
|
fn is_deleted(&self) -> bool {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1377,8 +1377,14 @@ impl MultiBuffer {
|
||||||
point: T,
|
point: T,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> &'a LanguageSettings {
|
) -> &'a LanguageSettings {
|
||||||
let language = self.language_at(point, cx);
|
let mut language = None;
|
||||||
language_settings(language.map(|l| l.name()).as_deref(), cx)
|
let mut file = None;
|
||||||
|
if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
language = buffer.language_at(offset).map(|l| l.name());
|
||||||
|
file = buffer.file().map(|f| f.as_ref());
|
||||||
|
}
|
||||||
|
language_settings(language.as_deref(), file, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
|
pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
|
||||||
|
@ -2785,9 +2791,13 @@ impl MultiBufferSnapshot {
|
||||||
point: T,
|
point: T,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> &'a LanguageSettings {
|
) -> &'a LanguageSettings {
|
||||||
self.point_to_buffer_offset(point)
|
let mut language = None;
|
||||||
.map(|(buffer, offset)| buffer.settings_at(offset, cx))
|
let mut file = None;
|
||||||
.unwrap_or_else(|| language_settings(None, cx))
|
if let Some((buffer, offset)) = self.point_to_buffer_offset(point) {
|
||||||
|
language = buffer.language_at(offset).map(|l| l.name());
|
||||||
|
file = buffer.file().map(|f| f.as_ref());
|
||||||
|
}
|
||||||
|
language_settings(language.as_deref(), file, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
|
pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
|
||||||
|
|
|
@ -216,6 +216,11 @@ pub trait File: Send + Sync {
|
||||||
/// of its worktree, then this method will return the name of the worktree itself.
|
/// of its worktree, then this method will return the name of the worktree itself.
|
||||||
fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr;
|
fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr;
|
||||||
|
|
||||||
|
/// Returns the id of the worktree to which this file belongs.
|
||||||
|
///
|
||||||
|
/// This is needed for looking up project-specific settings.
|
||||||
|
fn worktree_id(&self) -> usize;
|
||||||
|
|
||||||
fn is_deleted(&self) -> bool;
|
fn is_deleted(&self) -> bool;
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
@ -1803,7 +1808,11 @@ impl BufferSnapshot {
|
||||||
|
|
||||||
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
|
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
|
||||||
let language_name = self.language_at(position).map(|language| language.name());
|
let language_name = self.language_at(position).map(|language| language.name());
|
||||||
let settings = language_settings(language_name.as_deref(), cx);
|
let settings = language_settings(
|
||||||
|
language_name.as_deref(),
|
||||||
|
self.file().map(|f| f.as_ref()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
if settings.hard_tabs {
|
if settings.hard_tabs {
|
||||||
IndentSize::tab()
|
IndentSize::tab()
|
||||||
} else {
|
} else {
|
||||||
|
@ -2128,7 +2137,11 @@ impl BufferSnapshot {
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> &'a LanguageSettings {
|
) -> &'a LanguageSettings {
|
||||||
let language = self.language_at(position);
|
let language = self.language_at(position);
|
||||||
language_settings(language.map(|l| l.name()).as_deref(), cx)
|
language_settings(
|
||||||
|
language.map(|l| l.name()).as_deref(),
|
||||||
|
self.file.as_ref().map(AsRef::as_ref),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
|
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::File;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use globset::GlobMatcher;
|
use globset::GlobMatcher;
|
||||||
|
@ -13,8 +14,16 @@ pub fn init(cx: &mut AppContext) {
|
||||||
settings::register::<AllLanguageSettings>(cx);
|
settings::register::<AllLanguageSettings>(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings {
|
pub fn language_settings<'a>(
|
||||||
settings::get::<AllLanguageSettings>(cx).language(language)
|
language: Option<&str>,
|
||||||
|
file: Option<&dyn File>,
|
||||||
|
cx: &'a AppContext,
|
||||||
|
) -> &'a LanguageSettings {
|
||||||
|
settings::get_local::<AllLanguageSettings>(
|
||||||
|
file.map(|f| (f.worktree_id(), f.path().as_ref())),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.language(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings {
|
pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings {
|
||||||
|
|
|
@ -1717,7 +1717,8 @@ impl LspCommand for OnTypeFormatting {
|
||||||
|
|
||||||
let tab_size = buffer.read_with(&cx, |buffer, cx| {
|
let tab_size = buffer.read_with(&cx, |buffer, cx| {
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
language_settings(language_name.as_deref(), cx).tab_size
|
let file = buffer.file().map(|f| f.as_ref());
|
||||||
|
language_settings(language_name.as_deref(), file, cx).tab_size
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -71,7 +71,10 @@ use std::{
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
use terminals::Terminals;
|
use terminals::Terminals;
|
||||||
use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _};
|
use util::{
|
||||||
|
debug_panic, defer, merge_json_value_into, paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc,
|
||||||
|
ResultExt, TryFutureExt as _,
|
||||||
|
};
|
||||||
|
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use worktree::*;
|
pub use worktree::*;
|
||||||
|
@ -697,12 +700,7 @@ impl Project {
|
||||||
.language(Some(&language.name()))
|
.language(Some(&language.name()))
|
||||||
.enable_language_server
|
.enable_language_server
|
||||||
{
|
{
|
||||||
let worktree = file.worktree.read(cx);
|
language_servers_to_start.push((file.worktree.clone(), language.clone()));
|
||||||
language_servers_to_start.push((
|
|
||||||
worktree.id(),
|
|
||||||
worktree.as_local().unwrap().abs_path().clone(),
|
|
||||||
language.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,8 +730,9 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start all the newly-enabled language servers.
|
// Start all the newly-enabled language servers.
|
||||||
for (worktree_id, worktree_path, language) in language_servers_to_start {
|
for (worktree, language) in language_servers_to_start {
|
||||||
self.start_language_servers(worktree_id, worktree_path, language, cx);
|
let worktree_path = worktree.read(cx).abs_path();
|
||||||
|
self.start_language_servers(&worktree, worktree_path, language, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.copilot_enabled && Copilot::global(cx).is_some() {
|
if !self.copilot_enabled && Copilot::global(cx).is_some() {
|
||||||
|
@ -2320,25 +2319,34 @@ impl Project {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
|
if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
|
||||||
if let Some(worktree) = file.worktree.read(cx).as_local() {
|
let worktree = file.worktree.clone();
|
||||||
let worktree_id = worktree.id();
|
if let Some(tree) = worktree.read(cx).as_local() {
|
||||||
let worktree_abs_path = worktree.abs_path().clone();
|
self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx);
|
||||||
self.start_language_servers(worktree_id, worktree_abs_path, new_language, cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_language_servers(
|
fn start_language_servers(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree_id: WorktreeId,
|
worktree: &ModelHandle<Worktree>,
|
||||||
worktree_path: Arc<Path>,
|
worktree_path: Arc<Path>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !language_settings(Some(&language.name()), cx).enable_language_server {
|
if !language_settings(
|
||||||
|
Some(&language.name()),
|
||||||
|
worktree
|
||||||
|
.update(cx, |tree, cx| tree.root_file(cx))
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| f as _),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.enable_language_server
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let worktree_id = worktree.read(cx).id();
|
||||||
for adapter in language.lsp_adapters() {
|
for adapter in language.lsp_adapters() {
|
||||||
let key = (worktree_id, adapter.name.clone());
|
let key = (worktree_id, adapter.name.clone());
|
||||||
if self.language_server_ids.contains_key(&key) {
|
if self.language_server_ids.contains_key(&key) {
|
||||||
|
@ -2747,23 +2755,22 @@ impl Project {
|
||||||
buffers: impl IntoIterator<Item = ModelHandle<Buffer>>,
|
buffers: impl IntoIterator<Item = ModelHandle<Buffer>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let language_server_lookup_info: HashSet<(WorktreeId, Arc<Path>, Arc<Language>)> = buffers
|
let language_server_lookup_info: HashSet<(ModelHandle<Worktree>, Arc<Language>)> = buffers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|buffer| {
|
.filter_map(|buffer| {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let file = File::from_dyn(buffer.file())?;
|
let file = File::from_dyn(buffer.file())?;
|
||||||
let worktree = file.worktree.read(cx).as_local()?;
|
|
||||||
let full_path = file.full_path(cx);
|
let full_path = file.full_path(cx);
|
||||||
let language = self
|
let language = self
|
||||||
.languages
|
.languages
|
||||||
.language_for_file(&full_path, Some(buffer.as_rope()))
|
.language_for_file(&full_path, Some(buffer.as_rope()))
|
||||||
.now_or_never()?
|
.now_or_never()?
|
||||||
.ok()?;
|
.ok()?;
|
||||||
Some((worktree.id(), worktree.abs_path().clone(), language))
|
Some((file.worktree.clone(), language))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for (worktree_id, worktree_abs_path, language) in language_server_lookup_info {
|
for (worktree, language) in language_server_lookup_info {
|
||||||
self.restart_language_servers(worktree_id, worktree_abs_path, language, cx);
|
self.restart_language_servers(worktree, language, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
@ -2772,11 +2779,13 @@ impl Project {
|
||||||
// TODO This will break in the case where the adapter's root paths and worktrees are not equal
|
// TODO This will break in the case where the adapter's root paths and worktrees are not equal
|
||||||
fn restart_language_servers(
|
fn restart_language_servers(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree_id: WorktreeId,
|
worktree: ModelHandle<Worktree>,
|
||||||
fallback_path: Arc<Path>,
|
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
let worktree_id = worktree.read(cx).id();
|
||||||
|
let fallback_path = worktree.read(cx).abs_path();
|
||||||
|
|
||||||
let mut stops = Vec::new();
|
let mut stops = Vec::new();
|
||||||
for adapter in language.lsp_adapters() {
|
for adapter in language.lsp_adapters() {
|
||||||
stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
|
stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
|
||||||
|
@ -2806,7 +2815,7 @@ impl Project {
|
||||||
.map(|path_buf| Arc::from(path_buf.as_path()))
|
.map(|path_buf| Arc::from(path_buf.as_path()))
|
||||||
.unwrap_or(fallback_path);
|
.unwrap_or(fallback_path);
|
||||||
|
|
||||||
this.start_language_servers(worktree_id, root_path, language.clone(), cx);
|
this.start_language_servers(&worktree, root_path, language.clone(), cx);
|
||||||
|
|
||||||
// Lookup new server ids and set them for each of the orphaned worktrees
|
// Lookup new server ids and set them for each of the orphaned worktrees
|
||||||
for adapter in language.lsp_adapters() {
|
for adapter in language.lsp_adapters() {
|
||||||
|
@ -3430,7 +3439,12 @@ impl Project {
|
||||||
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
|
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
|
||||||
let settings = buffer.read_with(&cx, |buffer, cx| {
|
let settings = buffer.read_with(&cx, |buffer, cx| {
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
language_settings(language_name.as_deref(), cx).clone()
|
language_settings(
|
||||||
|
language_name.as_deref(),
|
||||||
|
buffer.file().map(|f| f.as_ref()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
|
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
|
||||||
|
@ -4439,11 +4453,15 @@ impl Project {
|
||||||
push_to_history: bool,
|
push_to_history: bool,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Transaction>>> {
|
) -> Task<Result<Option<Transaction>>> {
|
||||||
let tab_size = buffer.read_with(cx, |buffer, cx| {
|
let (position, tab_size) = buffer.read_with(cx, |buffer, cx| {
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let position = position.to_point_utf16(buffer);
|
||||||
language_settings(language_name.as_deref(), cx).tab_size
|
let language_name = buffer.language_at(position).map(|l| l.name());
|
||||||
|
let file = buffer.file().map(|f| f.as_ref());
|
||||||
|
(
|
||||||
|
position,
|
||||||
|
language_settings(language_name.as_deref(), file, cx).tab_size,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
|
||||||
self.request_lsp(
|
self.request_lsp(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
OnTypeFormatting {
|
OnTypeFormatting {
|
||||||
|
@ -4849,6 +4867,7 @@ impl Project {
|
||||||
worktree::Event::UpdatedEntries(changes) => {
|
worktree::Event::UpdatedEntries(changes) => {
|
||||||
this.update_local_worktree_buffers(&worktree, changes, cx);
|
this.update_local_worktree_buffers(&worktree, changes, cx);
|
||||||
this.update_local_worktree_language_servers(&worktree, changes, cx);
|
this.update_local_worktree_language_servers(&worktree, changes, cx);
|
||||||
|
this.update_local_worktree_settings(&worktree, changes, cx);
|
||||||
}
|
}
|
||||||
worktree::Event::UpdatedGitRepositories(updated_repos) => {
|
worktree::Event::UpdatedGitRepositories(updated_repos) => {
|
||||||
this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
|
this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
|
||||||
|
@ -5155,6 +5174,61 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_local_worktree_settings(
|
||||||
|
&mut self,
|
||||||
|
worktree: &ModelHandle<Worktree>,
|
||||||
|
changes: &UpdatedEntriesSet,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let worktree_id = worktree.id();
|
||||||
|
let worktree = worktree.read(cx).as_local().unwrap();
|
||||||
|
|
||||||
|
let mut settings_contents = Vec::new();
|
||||||
|
for (path, _, change) in changes.iter() {
|
||||||
|
if path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) {
|
||||||
|
let settings_dir = Arc::from(
|
||||||
|
path.ancestors()
|
||||||
|
.nth(LOCAL_SETTINGS_RELATIVE_PATH.components().count())
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
let removed = *change == PathChange::Removed;
|
||||||
|
let abs_path = worktree.absolutize(path);
|
||||||
|
settings_contents.push(async move {
|
||||||
|
anyhow::Ok((
|
||||||
|
settings_dir,
|
||||||
|
(!removed).then_some(fs.load(&abs_path).await?),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings_contents.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.spawn_weak(move |_, mut cx| async move {
|
||||||
|
let settings_contents = futures::future::join_all(settings_contents).await;
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
for entry in settings_contents {
|
||||||
|
if let Some((directory, file_content)) = entry.log_err() {
|
||||||
|
store
|
||||||
|
.set_local_settings(
|
||||||
|
worktree_id,
|
||||||
|
directory,
|
||||||
|
file_content.as_ref().map(String::as_str),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
||||||
let new_active_entry = entry.and_then(|project_path| {
|
let new_active_entry = entry.and_then(|project_path| {
|
||||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
||||||
|
|
|
@ -63,6 +63,62 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_managing_project_specific_settings(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/the-root",
|
||||||
|
json!({
|
||||||
|
".zed": {
|
||||||
|
"settings.json": r#"{ "tab_size": 8 }"#
|
||||||
|
},
|
||||||
|
"a": {
|
||||||
|
"a.rs": "fn a() {\n A\n}"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
".zed": {
|
||||||
|
"settings.json": r#"{ "tab_size": 2 }"#
|
||||||
|
},
|
||||||
|
"b.rs": "fn b() {\n B\n}"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||||
|
let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.read(|cx| {
|
||||||
|
let tree = worktree.read(cx);
|
||||||
|
|
||||||
|
let settings_a = language_settings(
|
||||||
|
None,
|
||||||
|
Some(&File::for_entry(
|
||||||
|
tree.entry_for_path("a/a.rs").unwrap().clone(),
|
||||||
|
worktree.clone(),
|
||||||
|
)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
let settings_b = language_settings(
|
||||||
|
None,
|
||||||
|
Some(&File::for_entry(
|
||||||
|
tree.entry_for_path("b/b.rs").unwrap().clone(),
|
||||||
|
worktree.clone(),
|
||||||
|
)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(settings_a.tab_size.get(), 8);
|
||||||
|
assert_eq!(settings_b.tab_size.get(), 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_managing_language_servers(
|
async fn test_managing_language_servers(
|
||||||
deterministic: Arc<Deterministic>,
|
deterministic: Arc<Deterministic>,
|
||||||
|
|
|
@ -677,6 +677,18 @@ impl Worktree {
|
||||||
Worktree::Remote(worktree) => worktree.abs_path.clone(),
|
Worktree::Remote(worktree) => worktree.abs_path.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root_file(&self, cx: &mut ModelContext<Self>) -> Option<File> {
|
||||||
|
let entry = self.entry_for_path("")?;
|
||||||
|
Some(File {
|
||||||
|
worktree: cx.handle(),
|
||||||
|
path: entry.path.clone(),
|
||||||
|
mtime: entry.mtime,
|
||||||
|
entry_id: entry.id,
|
||||||
|
is_local: self.is_local(),
|
||||||
|
is_deleted: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalWorktree {
|
impl LocalWorktree {
|
||||||
|
@ -684,14 +696,6 @@ impl LocalWorktree {
|
||||||
path.starts_with(&self.abs_path)
|
path.starts_with(&self.abs_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn absolutize(&self, path: &Path) -> PathBuf {
|
|
||||||
if path.file_name().is_some() {
|
|
||||||
self.abs_path.join(path)
|
|
||||||
} else {
|
|
||||||
self.abs_path.to_path_buf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_buffer(
|
pub(crate) fn load_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: u64,
|
id: u64,
|
||||||
|
@ -1544,6 +1548,14 @@ impl Snapshot {
|
||||||
&self.abs_path
|
&self.abs_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn absolutize(&self, path: &Path) -> PathBuf {
|
||||||
|
if path.file_name().is_some() {
|
||||||
|
self.abs_path.join(path)
|
||||||
|
} else {
|
||||||
|
self.abs_path.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
|
pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
|
||||||
self.entries_by_id.get(&entry_id, &()).is_some()
|
self.entries_by_id.get(&entry_id, &()).is_some()
|
||||||
}
|
}
|
||||||
|
@ -2383,6 +2395,10 @@ impl language::File for File {
|
||||||
.unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
|
.unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn worktree_id(&self) -> usize {
|
||||||
|
self.worktree.id()
|
||||||
|
}
|
||||||
|
|
||||||
fn is_deleted(&self) -> bool {
|
fn is_deleted(&self) -> bool {
|
||||||
self.is_deleted
|
self.is_deleted
|
||||||
}
|
}
|
||||||
|
@ -2447,6 +2463,17 @@ impl language::LocalFile for File {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
|
pub fn for_entry(entry: Entry, worktree: ModelHandle<Worktree>) -> Self {
|
||||||
|
Self {
|
||||||
|
worktree,
|
||||||
|
path: entry.path.clone(),
|
||||||
|
mtime: entry.mtime,
|
||||||
|
entry_id: entry.id,
|
||||||
|
is_local: true,
|
||||||
|
is_deleted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_proto(
|
pub fn from_proto(
|
||||||
proto: rpc::proto::File,
|
proto: rpc::proto::File,
|
||||||
worktree: ModelHandle<Worktree>,
|
worktree: ModelHandle<Worktree>,
|
||||||
|
@ -2507,7 +2534,7 @@ pub enum EntryKind {
|
||||||
File(CharBag),
|
File(CharBag),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum PathChange {
|
pub enum PathChange {
|
||||||
/// A filesystem entry was was created.
|
/// A filesystem entry was was created.
|
||||||
Added,
|
Added,
|
||||||
|
|
|
@ -4,7 +4,14 @@ 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, AssetSource};
|
||||||
use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
io::ErrorKind,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str,
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use util::{paths, ResultExt};
|
use util::{paths, ResultExt};
|
||||||
|
|
||||||
pub fn register<T: Setting>(cx: &mut AppContext) {
|
pub fn register<T: Setting>(cx: &mut AppContext) {
|
||||||
|
@ -17,6 +24,10 @@ pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T {
|
||||||
cx.global::<SettingsStore>().get(None)
|
cx.global::<SettingsStore>().get(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a T {
|
||||||
|
cx.global::<SettingsStore>().get(location)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default_settings() -> Cow<'static, str> {
|
pub fn default_settings() -> Cow<'static, str> {
|
||||||
match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
|
match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
|
||||||
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
||||||
|
|
|
@ -89,14 +89,14 @@ pub struct SettingsStore {
|
||||||
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
|
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
|
||||||
default_deserialized_settings: Option<serde_json::Value>,
|
default_deserialized_settings: Option<serde_json::Value>,
|
||||||
user_deserialized_settings: Option<serde_json::Value>,
|
user_deserialized_settings: Option<serde_json::Value>,
|
||||||
local_deserialized_settings: BTreeMap<Arc<Path>, serde_json::Value>,
|
local_deserialized_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
|
||||||
tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
|
tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SettingValue<T> {
|
struct SettingValue<T> {
|
||||||
global_value: Option<T>,
|
global_value: Option<T>,
|
||||||
local_values: Vec<(Arc<Path>, T)>,
|
local_values: Vec<(usize, Arc<Path>, T)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait AnySettingValue {
|
trait AnySettingValue {
|
||||||
|
@ -109,9 +109,9 @@ trait AnySettingValue {
|
||||||
custom: &[DeserializedSetting],
|
custom: &[DeserializedSetting],
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Result<Box<dyn Any>>;
|
) -> Result<Box<dyn Any>>;
|
||||||
fn value_for_path(&self, path: Option<&Path>) -> &dyn Any;
|
fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any;
|
||||||
fn set_global_value(&mut self, value: Box<dyn Any>);
|
fn set_global_value(&mut self, value: Box<dyn Any>);
|
||||||
fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>);
|
fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>);
|
||||||
fn json_schema(
|
fn json_schema(
|
||||||
&self,
|
&self,
|
||||||
generator: &mut SchemaGenerator,
|
generator: &mut SchemaGenerator,
|
||||||
|
@ -165,7 +165,7 @@ impl SettingsStore {
|
||||||
///
|
///
|
||||||
/// Panics if the given setting type has not been registered, or if there is no
|
/// Panics if the given setting type has not been registered, or if there is no
|
||||||
/// value for this setting.
|
/// value for this setting.
|
||||||
pub fn get<T: Setting>(&self, path: Option<&Path>) -> &T {
|
pub fn get<T: Setting>(&self, path: Option<(usize, &Path)>) -> &T {
|
||||||
self.setting_values
|
self.setting_values
|
||||||
.get(&TypeId::of::<T>())
|
.get(&TypeId::of::<T>())
|
||||||
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
|
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
|
||||||
|
@ -343,17 +343,19 @@ impl SettingsStore {
|
||||||
/// Add or remove a set of local settings via a JSON string.
|
/// Add or remove a set of local settings via a JSON string.
|
||||||
pub fn set_local_settings(
|
pub fn set_local_settings(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
root_id: usize,
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
settings_content: Option<&str>,
|
settings_content: Option<&str>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(content) = settings_content {
|
if let Some(content) = settings_content {
|
||||||
self.local_deserialized_settings
|
self.local_deserialized_settings
|
||||||
.insert(path.clone(), parse_json_with_comments(content)?);
|
.insert((root_id, path.clone()), parse_json_with_comments(content)?);
|
||||||
} else {
|
} else {
|
||||||
self.local_deserialized_settings.remove(&path);
|
self.local_deserialized_settings
|
||||||
|
.remove(&(root_id, path.clone()));
|
||||||
}
|
}
|
||||||
self.recompute_values(Some(&path), cx)?;
|
self.recompute_values(Some((root_id, &path)), cx)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,12 +438,12 @@ impl SettingsStore {
|
||||||
|
|
||||||
fn recompute_values(
|
fn recompute_values(
|
||||||
&mut self,
|
&mut self,
|
||||||
changed_local_path: Option<&Path>,
|
changed_local_path: Option<(usize, &Path)>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Reload the global and local values for every setting.
|
// Reload the global and local values for every setting.
|
||||||
let mut user_settings_stack = Vec::<DeserializedSetting>::new();
|
let mut user_settings_stack = Vec::<DeserializedSetting>::new();
|
||||||
let mut paths_stack = Vec::<Option<&Path>>::new();
|
let mut paths_stack = Vec::<Option<(usize, &Path)>>::new();
|
||||||
for setting_value in self.setting_values.values_mut() {
|
for setting_value in self.setting_values.values_mut() {
|
||||||
if let Some(default_settings) = &self.default_deserialized_settings {
|
if let Some(default_settings) = &self.default_deserialized_settings {
|
||||||
let default_settings = setting_value.deserialize_setting(default_settings)?;
|
let default_settings = setting_value.deserialize_setting(default_settings)?;
|
||||||
|
@ -469,11 +471,11 @@ impl SettingsStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the local values for the setting.
|
// Reload the local values for the setting.
|
||||||
for (path, local_settings) in &self.local_deserialized_settings {
|
for ((root_id, path), local_settings) in &self.local_deserialized_settings {
|
||||||
// Build a stack of all of the local values for that setting.
|
// Build a stack of all of the local values for that setting.
|
||||||
while let Some(prev_path) = paths_stack.last() {
|
while let Some(prev_entry) = paths_stack.last() {
|
||||||
if let Some(prev_path) = prev_path {
|
if let Some((prev_root_id, prev_path)) = prev_entry {
|
||||||
if !path.starts_with(prev_path) {
|
if root_id != prev_root_id || !path.starts_with(prev_path) {
|
||||||
paths_stack.pop();
|
paths_stack.pop();
|
||||||
user_settings_stack.pop();
|
user_settings_stack.pop();
|
||||||
continue;
|
continue;
|
||||||
|
@ -485,14 +487,17 @@ impl SettingsStore {
|
||||||
if let Some(local_settings) =
|
if let Some(local_settings) =
|
||||||
setting_value.deserialize_setting(&local_settings).log_err()
|
setting_value.deserialize_setting(&local_settings).log_err()
|
||||||
{
|
{
|
||||||
paths_stack.push(Some(path.as_ref()));
|
paths_stack.push(Some((*root_id, path.as_ref())));
|
||||||
user_settings_stack.push(local_settings);
|
user_settings_stack.push(local_settings);
|
||||||
|
|
||||||
// If a local settings file changed, then avoid recomputing local
|
// If a local settings file changed, then avoid recomputing local
|
||||||
// settings for any path outside of that directory.
|
// settings for any path outside of that directory.
|
||||||
if changed_local_path.map_or(false, |changed_local_path| {
|
if changed_local_path.map_or(
|
||||||
!path.starts_with(changed_local_path)
|
false,
|
||||||
}) {
|
|(changed_root_id, changed_local_path)| {
|
||||||
|
*root_id != changed_root_id || !path.starts_with(changed_local_path)
|
||||||
|
},
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +505,7 @@ impl SettingsStore {
|
||||||
.load_setting(&default_settings, &user_settings_stack, cx)
|
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||||
.log_err()
|
.log_err()
|
||||||
{
|
{
|
||||||
setting_value.set_local_value(path.clone(), value);
|
setting_value.set_local_value(*root_id, path.clone(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,6 +515,24 @@ impl SettingsStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for SettingsStore {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("SettingsStore")
|
||||||
|
.field(
|
||||||
|
"types",
|
||||||
|
&self
|
||||||
|
.setting_values
|
||||||
|
.values()
|
||||||
|
.map(|value| value.setting_type_name())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.field("default_settings", &self.default_deserialized_settings)
|
||||||
|
.field("user_settings", &self.user_deserialized_settings)
|
||||||
|
.field("local_settings", &self.local_deserialized_settings)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Setting> AnySettingValue for SettingValue<T> {
|
impl<T: Setting> AnySettingValue for SettingValue<T> {
|
||||||
fn key(&self) -> Option<&'static str> {
|
fn key(&self) -> Option<&'static str> {
|
||||||
T::KEY
|
T::KEY
|
||||||
|
@ -546,10 +569,10 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
|
||||||
Ok(DeserializedSetting(Box::new(value)))
|
Ok(DeserializedSetting(Box::new(value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_for_path(&self, path: Option<&Path>) -> &dyn Any {
|
fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any {
|
||||||
if let Some(path) = path {
|
if let Some((root_id, path)) = path {
|
||||||
for (settings_path, value) in self.local_values.iter().rev() {
|
for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
|
||||||
if path.starts_with(&settings_path) {
|
if root_id == *settings_root_id && path.starts_with(&settings_path) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -563,11 +586,14 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
|
||||||
self.global_value = Some(*value.downcast().unwrap());
|
self.global_value = Some(*value.downcast().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>) {
|
fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>) {
|
||||||
let value = *value.downcast().unwrap();
|
let value = *value.downcast().unwrap();
|
||||||
match self.local_values.binary_search_by_key(&&path, |e| &e.0) {
|
match self
|
||||||
Ok(ix) => self.local_values[ix].1 = value,
|
.local_values
|
||||||
Err(ix) => self.local_values.insert(ix, (path, value)),
|
.binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
|
||||||
|
{
|
||||||
|
Ok(ix) => self.local_values[ix].2 = value,
|
||||||
|
Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -884,6 +910,7 @@ mod tests {
|
||||||
|
|
||||||
store
|
store
|
||||||
.set_local_settings(
|
.set_local_settings(
|
||||||
|
1,
|
||||||
Path::new("/root1").into(),
|
Path::new("/root1").into(),
|
||||||
Some(r#"{ "user": { "staff": true } }"#),
|
Some(r#"{ "user": { "staff": true } }"#),
|
||||||
cx,
|
cx,
|
||||||
|
@ -891,6 +918,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
store
|
store
|
||||||
.set_local_settings(
|
.set_local_settings(
|
||||||
|
1,
|
||||||
Path::new("/root1/subdir").into(),
|
Path::new("/root1/subdir").into(),
|
||||||
Some(r#"{ "user": { "name": "Jane Doe" } }"#),
|
Some(r#"{ "user": { "name": "Jane Doe" } }"#),
|
||||||
cx,
|
cx,
|
||||||
|
@ -899,6 +927,7 @@ mod tests {
|
||||||
|
|
||||||
store
|
store
|
||||||
.set_local_settings(
|
.set_local_settings(
|
||||||
|
1,
|
||||||
Path::new("/root2").into(),
|
Path::new("/root2").into(),
|
||||||
Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
|
Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
|
||||||
cx,
|
cx,
|
||||||
|
@ -906,7 +935,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store.get::<UserSettings>(Some(Path::new("/root1/something"))),
|
store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
|
||||||
&UserSettings {
|
&UserSettings {
|
||||||
name: "John Doe".to_string(),
|
name: "John Doe".to_string(),
|
||||||
age: 31,
|
age: 31,
|
||||||
|
@ -914,7 +943,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store.get::<UserSettings>(Some(Path::new("/root1/subdir/something"))),
|
store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
|
||||||
&UserSettings {
|
&UserSettings {
|
||||||
name: "Jane Doe".to_string(),
|
name: "Jane Doe".to_string(),
|
||||||
age: 31,
|
age: 31,
|
||||||
|
@ -922,7 +951,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store.get::<UserSettings>(Some(Path::new("/root2/something"))),
|
store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
|
||||||
&UserSettings {
|
&UserSettings {
|
||||||
name: "John Doe".to_string(),
|
name: "John Doe".to_string(),
|
||||||
age: 42,
|
age: 42,
|
||||||
|
@ -930,7 +959,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store.get::<MultiKeySettings>(Some(Path::new("/root2/something"))),
|
store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
|
||||||
&MultiKeySettings {
|
&MultiKeySettings {
|
||||||
key1: "a".to_string(),
|
key1: "a".to_string(),
|
||||||
key2: "b".to_string(),
|
key2: "b".to_string(),
|
||||||
|
|
|
@ -905,7 +905,10 @@ mod tests {
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
|
) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
|
||||||
let params = cx.update(AppState::test);
|
let params = cx.update(AppState::test);
|
||||||
cx.update(|cx| theme::init((), cx));
|
cx.update(|cx| {
|
||||||
|
theme::init((), cx);
|
||||||
|
language::init(cx);
|
||||||
|
});
|
||||||
|
|
||||||
let project = Project::test(params.fs.clone(), [], cx).await;
|
let project = Project::test(params.fs.clone(), [], cx).await;
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
|
@ -15,6 +15,7 @@ lazy_static::lazy_static! {
|
||||||
pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt");
|
pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt");
|
||||||
pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log");
|
pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log");
|
||||||
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
|
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
|
||||||
|
pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".zed/settings.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod legacy {
|
pub mod legacy {
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl LspAdapter for YamlLspAdapter {
|
||||||
"keyOrdering": false
|
"keyOrdering": false
|
||||||
},
|
},
|
||||||
"[yaml]": {
|
"[yaml]": {
|
||||||
"editor.tabSize": language_settings(Some("YAML"), cx).tab_size,
|
"editor.tabSize": language_settings(Some("YAML"), None, cx).tab_size,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue