mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Sync config with ssh remotes (#17349)
Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
4b094798e0
commit
7fb94c4c4d
@ -2,6 +2,7 @@ use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use gpui::{Context as _, TestAppContext};
|
||||
use language::language_settings::all_language_settings;
|
||||
use remote::SshSession;
|
||||
use remote_server::HeadlessProject;
|
||||
use serde_json::json;
|
||||
@ -29,6 +30,9 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
"/code",
|
||||
json!({
|
||||
"project1": {
|
||||
".zed": {
|
||||
"settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
|
||||
},
|
||||
"README.md": "# project 1",
|
||||
"src": {
|
||||
"lib.rs": "fn one() -> usize { 1 }"
|
||||
@ -68,6 +72,8 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".zed"),
|
||||
Path::new(".zed/settings.json"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
@ -88,6 +94,18 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
buffer.edit([(ix..ix + 1, "100")], None, cx);
|
||||
});
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
cx_b.read(|cx| {
|
||||
let file = buffer_b.read(cx).file();
|
||||
assert_eq!(
|
||||
all_language_settings(file, cx)
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["override-rust-analyzer".into()]
|
||||
)
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| project.save_buffer(buffer_b, cx))
|
||||
.await
|
||||
|
@ -11,39 +11,49 @@ use gpui::{AppContext, Context, Model, ModelContext, Task};
|
||||
use settings::Settings as _;
|
||||
use worktree::WorktreeId;
|
||||
|
||||
use crate::project_settings::{DirenvSettings, ProjectSettings};
|
||||
use crate::{
|
||||
project_settings::{DirenvSettings, ProjectSettings},
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
};
|
||||
|
||||
pub(crate) struct ProjectEnvironment {
|
||||
pub struct ProjectEnvironment {
|
||||
cli_environment: Option<HashMap<String, String>>,
|
||||
get_environment_task: Option<Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
cached_shell_environments: HashMap<WorktreeId, HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl ProjectEnvironment {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
worktree_store: &Model<WorktreeStore>,
|
||||
cli_environment: Option<HashMap<String, String>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
cx.new_model(|_| Self {
|
||||
cli_environment,
|
||||
get_environment_task: None,
|
||||
cached_shell_environments: Default::default(),
|
||||
cx.new_model(|cx| {
|
||||
cx.subscribe(worktree_store, |this: &mut Self, _, event, _| match event {
|
||||
WorktreeStoreEvent::WorktreeRemoved(_, id) => {
|
||||
this.remove_worktree_environment(*id);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
cli_environment,
|
||||
get_environment_task: None,
|
||||
cached_shell_environments: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn test(
|
||||
pub(crate) fn set_cached(
|
||||
&mut self,
|
||||
shell_environments: &[(WorktreeId, HashMap<String, String>)],
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
cx.new_model(|_| Self {
|
||||
cli_environment: None,
|
||||
get_environment_task: None,
|
||||
cached_shell_environments: shell_environments
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
})
|
||||
) {
|
||||
self.cached_shell_environments = shell_environments
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<_, _>>();
|
||||
}
|
||||
|
||||
pub(crate) fn remove_worktree_environment(&mut self, worktree_id: WorktreeId) {
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
lsp_ext_command,
|
||||
project_settings::ProjectSettings,
|
||||
relativize_path, resolve_path,
|
||||
worktree_store::WorktreeStore,
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
yarn::YarnPathStore,
|
||||
CodeAction, Completion, CoreCompletion, Hover, InlayHint, Item as _, ProjectPath,
|
||||
ProjectTransaction, ResolveState, Symbol,
|
||||
@ -89,7 +89,7 @@ pub struct LspStore {
|
||||
downstream_client: Option<AnyProtoClient>,
|
||||
upstream_client: Option<AnyProtoClient>,
|
||||
project_id: u64,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
http_client: Option<Arc<dyn HttpClient>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
nonce: u128,
|
||||
buffer_store: Model<BufferStore>,
|
||||
@ -210,12 +210,12 @@ impl LspStore {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
environment: Option<Model<ProjectEnvironment>>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
http_client: Option<Arc<dyn HttpClient>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
downstream_client: Option<AnyProtoClient>,
|
||||
upstream_client: Option<AnyProtoClient>,
|
||||
@ -225,6 +225,8 @@ impl LspStore {
|
||||
let yarn = YarnPathStore::new(fs.clone(), cx);
|
||||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
downstream_client,
|
||||
@ -278,6 +280,31 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_worktree_store_event(
|
||||
&mut self,
|
||||
_: Model<WorktreeStore>,
|
||||
event: &WorktreeStoreEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
WorktreeStoreEvent::WorktreeAdded(worktree) => {
|
||||
if !worktree.read(cx).is_local() {
|
||||
return;
|
||||
}
|
||||
cx.subscribe(worktree, |this, worktree, event, cx| match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
this.update_local_worktree_language_servers(&worktree, changes, cx);
|
||||
}
|
||||
worktree::Event::UpdatedGitRepositories(_)
|
||||
| worktree::Event::DeletedEntry(_) => {}
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
WorktreeStoreEvent::WorktreeRemoved(_, id) => self.remove_worktree(*id, cx),
|
||||
WorktreeStoreEvent::WorktreeOrderChanged => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
@ -463,11 +490,6 @@ impl LspStore {
|
||||
self.buffer_store.clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn set_environment(&mut self, environment: Model<ProjectEnvironment>) {
|
||||
self.environment = Some(environment);
|
||||
}
|
||||
|
||||
pub fn set_active_entry(&mut self, active_entry: Option<ProjectEntryId>) {
|
||||
self.active_entry = active_entry;
|
||||
}
|
||||
@ -6105,11 +6127,15 @@ impl ProjectLspAdapterDelegate {
|
||||
Task::ready(None).shared()
|
||||
};
|
||||
|
||||
let Some(http_client) = lsp_store.http_client.clone() else {
|
||||
panic!("ProjectLspAdapterDelegate cannot be constructedd on an ssh-remote yet")
|
||||
};
|
||||
|
||||
Arc::new(Self {
|
||||
lsp_store: cx.weak_model(),
|
||||
worktree: worktree.read(cx).snapshot(),
|
||||
fs: lsp_store.fs.clone(),
|
||||
http_client: lsp_store.http_client.clone(),
|
||||
http_client,
|
||||
language_registry: lsp_store.languages.clone(),
|
||||
load_shell_env_task,
|
||||
})
|
||||
|
@ -27,7 +27,7 @@ use client::{
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
use environment::ProjectEnvironment;
|
||||
pub use environment::ProjectEnvironment;
|
||||
use futures::{
|
||||
channel::mpsc::{self, UnboundedReceiver},
|
||||
future::try_join_all,
|
||||
@ -58,12 +58,9 @@ use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServ
|
||||
use lsp_command::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use paths::{
|
||||
local_settings_file_relative_path, local_tasks_file_relative_path,
|
||||
local_vscode_tasks_file_relative_path,
|
||||
};
|
||||
use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
|
||||
use prettier_support::{DefaultPrettier, PrettierInstance};
|
||||
use project_settings::{LspSettings, ProjectSettings};
|
||||
use project_settings::{LspSettings, ProjectSettings, SettingsObserver};
|
||||
use remote::SshSession;
|
||||
use rpc::{
|
||||
proto::{AnyProtoClient, SSH_PROJECT_ID},
|
||||
@ -174,6 +171,7 @@ pub struct Project {
|
||||
last_formatting_failure: Option<String>,
|
||||
buffers_being_formatted: HashSet<BufferId>,
|
||||
environment: Model<ProjectEnvironment>,
|
||||
settings_observer: Model<SettingsObserver>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -505,6 +503,14 @@ impl FormatTrigger {
|
||||
}
|
||||
}
|
||||
|
||||
enum EntitySubscription {
|
||||
Project(PendingEntitySubscription<Project>),
|
||||
BufferStore(PendingEntitySubscription<BufferStore>),
|
||||
WorktreeStore(PendingEntitySubscription<WorktreeStore>),
|
||||
LspStore(PendingEntitySubscription<LspStore>),
|
||||
SettingsObserver(PendingEntitySubscription<SettingsObserver>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DirectoryLister {
|
||||
Project(Model<Project>),
|
||||
@ -584,7 +590,6 @@ impl Project {
|
||||
client.add_model_message_handler(Self::handle_unshare_project);
|
||||
client.add_model_request_handler(Self::handle_update_buffer);
|
||||
client.add_model_message_handler(Self::handle_update_worktree);
|
||||
client.add_model_message_handler(Self::handle_update_worktree_settings);
|
||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
||||
client.add_model_request_handler(Self::handle_format_buffers);
|
||||
@ -600,6 +605,7 @@ impl Project {
|
||||
WorktreeStore::init(&client);
|
||||
BufferStore::init(&client);
|
||||
LspStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
@ -629,14 +635,18 @@ impl Project {
|
||||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
|
||||
let environment = ProjectEnvironment::new(env, cx);
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
|
||||
});
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, env, cx);
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
Some(environment.clone()),
|
||||
languages.clone(),
|
||||
client.http_client(),
|
||||
Some(client.http_client()),
|
||||
fs.clone(),
|
||||
None,
|
||||
None,
|
||||
@ -665,6 +675,7 @@ impl Project {
|
||||
languages,
|
||||
client,
|
||||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
buffers_needing_diff: Default::default(),
|
||||
@ -704,14 +715,21 @@ impl Project {
|
||||
this.worktree_store.update(cx, |store, _cx| {
|
||||
store.set_upstream_client(client.clone());
|
||||
});
|
||||
this.settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_ssh(ssh.clone().into(), this.worktree_store.clone(), cx)
|
||||
});
|
||||
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
|
||||
client.add_model_message_handler(Self::handle_update_worktree);
|
||||
client.add_model_message_handler(Self::handle_create_buffer_for_peer);
|
||||
client.add_model_message_handler(BufferStore::handle_update_buffer_file);
|
||||
client.add_model_message_handler(BufferStore::handle_update_diff_base);
|
||||
LspStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
|
||||
this.ssh_session = Some(ssh);
|
||||
});
|
||||
@ -746,12 +764,17 @@ impl Project {
|
||||
) -> Result<Model<Self>> {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
|
||||
let subscriptions = (
|
||||
client.subscribe_to_entity::<Self>(remote_id)?,
|
||||
client.subscribe_to_entity::<BufferStore>(remote_id)?,
|
||||
client.subscribe_to_entity::<WorktreeStore>(remote_id)?,
|
||||
client.subscribe_to_entity::<LspStore>(remote_id)?,
|
||||
);
|
||||
let subscriptions = [
|
||||
EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id)?),
|
||||
EntitySubscription::BufferStore(client.subscribe_to_entity::<BufferStore>(remote_id)?),
|
||||
EntitySubscription::WorktreeStore(
|
||||
client.subscribe_to_entity::<WorktreeStore>(remote_id)?,
|
||||
),
|
||||
EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id)?),
|
||||
EntitySubscription::SettingsObserver(
|
||||
client.subscribe_to_entity::<SettingsObserver>(remote_id)?,
|
||||
),
|
||||
];
|
||||
let response = client
|
||||
.request_envelope(proto::JoinProject {
|
||||
project_id: remote_id,
|
||||
@ -771,12 +794,7 @@ impl Project {
|
||||
|
||||
async fn from_join_project_response(
|
||||
response: TypedEnvelope<proto::JoinProjectResponse>,
|
||||
subscription: (
|
||||
PendingEntitySubscription<Project>,
|
||||
PendingEntitySubscription<BufferStore>,
|
||||
PendingEntitySubscription<WorktreeStore>,
|
||||
PendingEntitySubscription<LspStore>,
|
||||
),
|
||||
subscriptions: [EntitySubscription; 5],
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
@ -803,7 +821,7 @@ impl Project {
|
||||
worktree_store.clone(),
|
||||
None,
|
||||
languages.clone(),
|
||||
client.http_client(),
|
||||
Some(client.http_client()),
|
||||
fs.clone(),
|
||||
None,
|
||||
Some(client.clone().into()),
|
||||
@ -814,6 +832,9 @@ impl Project {
|
||||
lsp_store
|
||||
})?;
|
||||
|
||||
let settings_observer =
|
||||
cx.new_model(|cx| SettingsObserver::new_remote(worktree_store.clone(), cx))?;
|
||||
|
||||
let this = cx.new_model(|cx| {
|
||||
let replica_id = response.payload.replica_id as ReplicaId;
|
||||
let tasks = Inventory::new(cx);
|
||||
@ -850,6 +871,7 @@ impl Project {
|
||||
snippets,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
settings_observer: settings_observer.clone(),
|
||||
client_subscriptions: Default::default(),
|
||||
_subscriptions: vec![cx.on_release(Self::release)],
|
||||
client: client.clone(),
|
||||
@ -876,7 +898,7 @@ impl Project {
|
||||
.dev_server_project_id
|
||||
.map(|dev_server_project_id| DevServerProjectId(dev_server_project_id)),
|
||||
search_history: Self::new_search_history(),
|
||||
environment: ProjectEnvironment::new(None, cx),
|
||||
environment: ProjectEnvironment::new(&worktree_store, None, cx),
|
||||
remotely_created_buffers: Arc::new(Mutex::new(RemotelyCreatedBuffers::default())),
|
||||
last_formatting_failure: None,
|
||||
buffers_being_formatted: Default::default(),
|
||||
@ -888,12 +910,24 @@ impl Project {
|
||||
this
|
||||
})?;
|
||||
|
||||
let subscriptions = [
|
||||
subscription.0.set_model(&this, &mut cx),
|
||||
subscription.1.set_model(&buffer_store, &mut cx),
|
||||
subscription.2.set_model(&worktree_store, &mut cx),
|
||||
subscription.3.set_model(&lsp_store, &mut cx),
|
||||
];
|
||||
let subscriptions = subscriptions
|
||||
.into_iter()
|
||||
.map(|s| match s {
|
||||
EntitySubscription::BufferStore(subscription) => {
|
||||
subscription.set_model(&buffer_store, &mut cx)
|
||||
}
|
||||
EntitySubscription::WorktreeStore(subscription) => {
|
||||
subscription.set_model(&worktree_store, &mut cx)
|
||||
}
|
||||
EntitySubscription::SettingsObserver(subscription) => {
|
||||
subscription.set_model(&settings_observer, &mut cx)
|
||||
}
|
||||
EntitySubscription::Project(subscription) => subscription.set_model(&this, &mut cx),
|
||||
EntitySubscription::LspStore(subscription) => {
|
||||
subscription.set_model(&lsp_store, &mut cx)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let user_ids = response
|
||||
.payload
|
||||
@ -924,12 +958,19 @@ impl Project {
|
||||
) -> Result<Model<Self>> {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
|
||||
let subscriptions = (
|
||||
client.subscribe_to_entity::<Self>(remote_id.0)?,
|
||||
client.subscribe_to_entity::<BufferStore>(remote_id.0)?,
|
||||
client.subscribe_to_entity::<WorktreeStore>(remote_id.0)?,
|
||||
client.subscribe_to_entity::<LspStore>(remote_id.0)?,
|
||||
);
|
||||
let subscriptions = [
|
||||
EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id.0)?),
|
||||
EntitySubscription::BufferStore(
|
||||
client.subscribe_to_entity::<BufferStore>(remote_id.0)?,
|
||||
),
|
||||
EntitySubscription::WorktreeStore(
|
||||
client.subscribe_to_entity::<WorktreeStore>(remote_id.0)?,
|
||||
),
|
||||
EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id.0)?),
|
||||
EntitySubscription::SettingsObserver(
|
||||
client.subscribe_to_entity::<SettingsObserver>(remote_id.0)?,
|
||||
),
|
||||
];
|
||||
let response = client
|
||||
.request_envelope(proto::JoinHostedProject {
|
||||
project_id: remote_id.0,
|
||||
@ -1047,13 +1088,10 @@ impl Project {
|
||||
.unwrap();
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
// In tests we always populate the environment to be empty so we don't run the shell
|
||||
let tree_id = tree.read(cx).id();
|
||||
let environment = ProjectEnvironment::test(&[(tree_id, HashMap::default())], cx);
|
||||
project.environment = environment.clone();
|
||||
project
|
||||
.lsp_store
|
||||
.update(cx, |lsp_store, _| lsp_store.set_environment(environment));
|
||||
project.environment.update(cx, |environment, _| {
|
||||
environment.set_cached(&[(tree_id, HashMap::default())])
|
||||
});
|
||||
});
|
||||
|
||||
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
@ -1066,6 +1104,10 @@ impl Project {
|
||||
self.lsp_store.clone()
|
||||
}
|
||||
|
||||
pub fn worktree_store(&self) -> Model<WorktreeStore> {
|
||||
self.worktree_store.clone()
|
||||
}
|
||||
|
||||
fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let mut language_servers_to_start = Vec::new();
|
||||
let mut language_formatters_to_check = Vec::new();
|
||||
@ -1499,6 +1541,9 @@ impl Project {
|
||||
self.client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_model(&self.lsp_store, &mut cx.to_async()),
|
||||
self.client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_model(&self.settings_observer, &mut cx.to_async()),
|
||||
]);
|
||||
|
||||
self.buffer_store.update(cx, |buffer_store, cx| {
|
||||
@ -1510,21 +1555,9 @@ impl Project {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.shared(project_id, self.client.clone().into(), cx)
|
||||
});
|
||||
|
||||
let store = cx.global::<SettingsStore>();
|
||||
for worktree in self.worktrees(cx) {
|
||||
let worktree_id = worktree.read(cx).id().to_proto();
|
||||
for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
|
||||
self.client
|
||||
.send(proto::UpdateWorktreeSettings {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: path.to_string_lossy().into(),
|
||||
content: Some(content),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
self.settings_observer.update(cx, |settings_observer, cx| {
|
||||
settings_observer.shared(project_id, self.client.clone().into(), cx)
|
||||
});
|
||||
|
||||
self.client_state = ProjectClientState::Shared {
|
||||
remote_id: project_id,
|
||||
@ -1608,6 +1641,9 @@ impl Project {
|
||||
buffer_store.forget_shared_buffers();
|
||||
buffer_store.unshared(cx)
|
||||
});
|
||||
self.settings_observer.update(cx, |settings_observer, cx| {
|
||||
settings_observer.unshared(cx);
|
||||
});
|
||||
self.client
|
||||
.send(proto::UnshareProject {
|
||||
project_id: remote_id,
|
||||
@ -2147,10 +2183,6 @@ impl Project {
|
||||
match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
if is_local {
|
||||
this.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_local_worktree_language_servers(&worktree, changes, cx);
|
||||
});
|
||||
this.update_local_worktree_settings(&worktree, changes, cx);
|
||||
this.update_prettier_settings(&worktree, changes, cx);
|
||||
}
|
||||
@ -2198,12 +2230,6 @@ impl Project {
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.environment.update(cx, |environment, _| {
|
||||
environment.remove_worktree_environment(id_to_remove);
|
||||
});
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.remove_worktree(id_to_remove, cx);
|
||||
});
|
||||
|
||||
let mut prettier_instances_to_clean = FuturesUnordered::new();
|
||||
if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
|
||||
@ -3818,11 +3844,8 @@ impl Project {
|
||||
if worktree.read(cx).is_remote() {
|
||||
return;
|
||||
}
|
||||
let project_id = self.remote_id();
|
||||
let worktree_id = worktree.entity_id();
|
||||
let remote_worktree_id = worktree.read(cx).id();
|
||||
|
||||
let mut settings_contents = Vec::new();
|
||||
for (path, _, change) in changes.iter() {
|
||||
let removed = change == &PathChange::Removed;
|
||||
let abs_path = match worktree.read(cx).absolutize(path) {
|
||||
@ -3833,24 +3856,7 @@ impl Project {
|
||||
}
|
||||
};
|
||||
|
||||
if path.ends_with(local_settings_file_relative_path()) {
|
||||
let settings_dir = Arc::from(
|
||||
path.ancestors()
|
||||
.nth(local_settings_file_relative_path().components().count())
|
||||
.unwrap(),
|
||||
);
|
||||
let fs = self.fs.clone();
|
||||
settings_contents.push(async move {
|
||||
(
|
||||
settings_dir,
|
||||
if removed {
|
||||
None
|
||||
} else {
|
||||
Some(async move { fs.load(&abs_path).await }.await)
|
||||
},
|
||||
)
|
||||
});
|
||||
} else if path.ends_with(local_tasks_file_relative_path()) {
|
||||
if path.ends_with(local_tasks_file_relative_path()) {
|
||||
self.task_inventory().update(cx, |task_inventory, cx| {
|
||||
if removed {
|
||||
task_inventory.remove_local_static_source(&abs_path);
|
||||
@ -3898,43 +3904,6 @@ impl Project {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if settings_contents.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_, cx| async move {
|
||||
let settings_contents: Vec<(Arc<Path>, _)> =
|
||||
futures::future::join_all(settings_contents).await;
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
for (directory, file_content) in settings_contents {
|
||||
let file_content = file_content.and_then(|content| content.log_err());
|
||||
store
|
||||
.set_local_settings(
|
||||
worktree_id.as_u64() as usize,
|
||||
directory.clone(),
|
||||
file_content.as_deref(),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
if let Some(remote_id) = project_id {
|
||||
client
|
||||
.send(proto::UpdateWorktreeSettings {
|
||||
project_id: remote_id,
|
||||
worktree_id: remote_worktree_id.to_proto(),
|
||||
path: directory.to_string_lossy().into_owned(),
|
||||
content: file_content,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
||||
@ -4236,29 +4205,6 @@ impl Project {
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_update_worktree_settings(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store
|
||||
.set_local_settings(
|
||||
worktree.entity_id().as_u64() as usize,
|
||||
PathBuf::from(&envelope.payload.path).into(),
|
||||
envelope.payload.content.as_deref(),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_update_buffer(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateBuffer>,
|
||||
|
@ -1,9 +1,23 @@
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Model, ModelContext};
|
||||
use paths::local_settings_file_relative_path;
|
||||
use rpc::{
|
||||
proto::{self, AnyProtoClient},
|
||||
TypedEnvelope,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
|
||||
|
||||
use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ProjectSettings {
|
||||
@ -157,3 +171,276 @@ impl Settings for ProjectSettings {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SettingsObserverMode {
|
||||
Local(Arc<dyn Fs>),
|
||||
Ssh(AnyProtoClient),
|
||||
Remote,
|
||||
}
|
||||
|
||||
pub struct SettingsObserver {
|
||||
mode: SettingsObserverMode,
|
||||
downstream_client: Option<AnyProtoClient>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
project_id: u64,
|
||||
}
|
||||
|
||||
/// SettingsObserver observers changes to .zed/settings.json files in local worktrees
|
||||
/// (or the equivalent protobuf messages from upstream) and updates local settings
|
||||
/// and sends notifications downstream.
|
||||
/// In ssh mode it also monitors ~/.config/zed/settings.json and sends the content
|
||||
/// upstream.
|
||||
impl SettingsObserver {
|
||||
pub fn init(client: &AnyProtoClient) {
|
||||
client.add_model_message_handler(Self::handle_update_worktree_settings);
|
||||
client.add_model_message_handler(Self::handle_update_user_settings)
|
||||
}
|
||||
|
||||
pub fn new_local(
|
||||
fs: Arc<dyn Fs>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
worktree_store,
|
||||
mode: SettingsObserverMode::Local(fs),
|
||||
downstream_client: None,
|
||||
project_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ssh(
|
||||
client: AnyProtoClient,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let this = Self {
|
||||
worktree_store,
|
||||
mode: SettingsObserverMode::Ssh(client.clone()),
|
||||
downstream_client: None,
|
||||
project_id: 0,
|
||||
};
|
||||
this.maintain_ssh_settings(client, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn new_remote(worktree_store: Model<WorktreeStore>, _: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
worktree_store,
|
||||
mode: SettingsObserverMode::Remote,
|
||||
downstream_client: None,
|
||||
project_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
downstream_client: AnyProtoClient,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.project_id = project_id;
|
||||
self.downstream_client = Some(downstream_client.clone());
|
||||
|
||||
let store = cx.global::<SettingsStore>();
|
||||
for worktree in self.worktree_store.read(cx).worktrees() {
|
||||
let worktree_id = worktree.read(cx).id().to_proto();
|
||||
for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
|
||||
downstream_client
|
||||
.send(proto::UpdateWorktreeSettings {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: path.to_string_lossy().into(),
|
||||
content: Some(content),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unshared(&mut self, _: &mut ModelContext<Self>) {
|
||||
self.downstream_client = None;
|
||||
}
|
||||
|
||||
async fn handle_update_worktree_settings(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> anyhow::Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let Some(worktree) = this
|
||||
.worktree_store
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
this.update_settings(
|
||||
worktree,
|
||||
[(
|
||||
PathBuf::from(&envelope.payload.path).into(),
|
||||
envelope.payload.content,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_update_user_settings(
|
||||
_: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateUserSettings>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.update_global(move |settings_store: &mut SettingsStore, cx| {
|
||||
settings_store.set_user_settings(&envelope.payload.content, cx)
|
||||
})??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn maintain_ssh_settings(&self, ssh: AnyProtoClient, cx: &mut ModelContext<Self>) {
|
||||
let mut settings = cx.global::<SettingsStore>().raw_user_settings().clone();
|
||||
if let Some(content) = serde_json::to_string(&settings).log_err() {
|
||||
ssh.send(proto::UpdateUserSettings {
|
||||
project_id: 0,
|
||||
content,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |_, cx| {
|
||||
let new_settings = cx.global::<SettingsStore>().raw_user_settings();
|
||||
if &settings != new_settings {
|
||||
settings = new_settings.clone()
|
||||
}
|
||||
if let Some(content) = serde_json::to_string(&settings).log_err() {
|
||||
ssh.send(proto::UpdateUserSettings {
|
||||
project_id: 0,
|
||||
content,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_worktree_store_event(
|
||||
&mut self,
|
||||
_: Model<WorktreeStore>,
|
||||
event: &WorktreeStoreEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
WorktreeStoreEvent::WorktreeAdded(worktree) => cx
|
||||
.subscribe(worktree, |this, worktree, event, cx| match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
this.update_local_worktree_settings(&worktree, changes, cx)
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.detach(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_local_worktree_settings(
|
||||
&mut self,
|
||||
worktree: &Model<Worktree>,
|
||||
changes: &UpdatedEntriesSet,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let SettingsObserverMode::Local(fs) = &self.mode else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut settings_contents = Vec::new();
|
||||
for (path, _, change) in changes.iter() {
|
||||
let removed = change == &PathChange::Removed;
|
||||
let abs_path = match worktree.read(cx).absolutize(path) {
|
||||
Ok(abs_path) => abs_path,
|
||||
Err(e) => {
|
||||
log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if path.ends_with(local_settings_file_relative_path()) {
|
||||
let settings_dir = Arc::from(
|
||||
path.ancestors()
|
||||
.nth(local_settings_file_relative_path().components().count())
|
||||
.unwrap(),
|
||||
);
|
||||
let fs = fs.clone();
|
||||
settings_contents.push(async move {
|
||||
(
|
||||
settings_dir,
|
||||
if removed {
|
||||
None
|
||||
} else {
|
||||
Some(async move { fs.load(&abs_path).await }.await)
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if settings_contents.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let worktree = worktree.clone();
|
||||
cx.spawn(move |this, cx| async move {
|
||||
let settings_contents: Vec<(Arc<Path>, _)> =
|
||||
futures::future::join_all(settings_contents).await;
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.update_settings(
|
||||
worktree,
|
||||
settings_contents
|
||||
.into_iter()
|
||||
.map(|(path, content)| (path, content.and_then(|c| c.log_err()))),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_settings(
|
||||
&mut self,
|
||||
worktree: Model<Worktree>,
|
||||
settings_contents: impl IntoIterator<Item = (Arc<Path>, Option<String>)>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let worktree_id = worktree.entity_id();
|
||||
let remote_worktree_id = worktree.read(cx).id();
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
for (directory, file_content) in settings_contents {
|
||||
store
|
||||
.set_local_settings(
|
||||
worktree_id.as_u64() as usize,
|
||||
directory.clone(),
|
||||
file_content.as_deref(),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
if let Some(downstream_client) = &self.downstream_client {
|
||||
downstream_client
|
||||
.send(proto::UpdateWorktreeSettings {
|
||||
project_id: self.project_id,
|
||||
worktree_id: remote_worktree_id.to_proto(),
|
||||
path: directory.to_string_lossy().into_owned(),
|
||||
content: file_content,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +280,8 @@ message Envelope {
|
||||
FindSearchCandidates find_search_candidates = 243;
|
||||
FindSearchCandidatesResponse find_search_candidates_response = 244;
|
||||
|
||||
CloseBuffer close_buffer = 245; // current max
|
||||
CloseBuffer close_buffer = 245;
|
||||
UpdateUserSettings update_user_settings = 246; // current max
|
||||
}
|
||||
|
||||
reserved 158 to 161;
|
||||
@ -2491,3 +2492,8 @@ message AddWorktree {
|
||||
message AddWorktreeResponse {
|
||||
uint64 worktree_id = 1;
|
||||
}
|
||||
|
||||
message UpdateUserSettings {
|
||||
uint64 project_id = 1;
|
||||
string content = 2;
|
||||
}
|
||||
|
@ -365,7 +365,8 @@ messages!(
|
||||
(AddWorktreeResponse, Foreground),
|
||||
(FindSearchCandidates, Background),
|
||||
(FindSearchCandidatesResponse, Background),
|
||||
(CloseBuffer, Foreground)
|
||||
(CloseBuffer, Foreground),
|
||||
(UpdateUserSettings, Foreground)
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
@ -560,7 +561,8 @@ entity_messages!(
|
||||
CreateContext,
|
||||
UpdateContext,
|
||||
SynchronizeContexts,
|
||||
LspExtSwitchSourceHeader
|
||||
LspExtSwitchSourceHeader,
|
||||
UpdateUserSettings
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
@ -36,6 +36,7 @@ serde_json.workspace = true
|
||||
shellexpand.workspace = true
|
||||
smol.workspace = true
|
||||
worktree.workspace = true
|
||||
language.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
|
@ -1,16 +1,17 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task};
|
||||
use language::LanguageRegistry;
|
||||
use project::{
|
||||
buffer_store::BufferStore, search::SearchQuery, worktree_store::WorktreeStore, ProjectPath,
|
||||
WorktreeId, WorktreeSettings,
|
||||
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
|
||||
worktree_store::WorktreeStore, LspStore, ProjectPath, WorktreeId, WorktreeSettings,
|
||||
};
|
||||
use remote::SshSession;
|
||||
use rpc::{
|
||||
proto::{self, AnyProtoClient, SSH_PEER_ID, SSH_PROJECT_ID},
|
||||
TypedEnvelope,
|
||||
};
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use settings::Settings as _;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@ -23,16 +24,25 @@ pub struct HeadlessProject {
|
||||
pub session: AnyProtoClient,
|
||||
pub worktree_store: Model<WorktreeStore>,
|
||||
pub buffer_store: Model<BufferStore>,
|
||||
pub lsp_store: Model<LspStore>,
|
||||
pub settings_observer: Model<SettingsObserver>,
|
||||
pub next_entry_id: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl HeadlessProject {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(SettingsStore::new(cx));
|
||||
settings::init(cx);
|
||||
language::init(cx);
|
||||
WorktreeSettings::register(cx);
|
||||
}
|
||||
|
||||
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
||||
// TODO: we should load the env correctly (as we do in login_shell_env_loaded when stdout is not a pty). Can we re-use the ProjectEnvironment for that?
|
||||
let languages = Arc::new(LanguageRegistry::new(
|
||||
Task::ready(()),
|
||||
cx.background_executor().clone(),
|
||||
));
|
||||
|
||||
let worktree_store = cx.new_model(|_| WorktreeStore::new(true, fs.clone()));
|
||||
let buffer_store = cx.new_model(|cx| {
|
||||
let mut buffer_store =
|
||||
@ -40,12 +50,34 @@ impl HeadlessProject {
|
||||
buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
|
||||
buffer_store
|
||||
});
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
let mut observer = SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx);
|
||||
observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
|
||||
observer
|
||||
});
|
||||
let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
Some(environment),
|
||||
languages,
|
||||
None,
|
||||
fs.clone(),
|
||||
Some(session.clone().into()),
|
||||
None,
|
||||
Some(0),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let client: AnyProtoClient = session.clone().into();
|
||||
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &buffer_store);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &lsp_store);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
|
||||
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_list_remote_directory);
|
||||
|
||||
@ -58,12 +90,15 @@ impl HeadlessProject {
|
||||
|
||||
BufferStore::init(&client);
|
||||
WorktreeStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
|
||||
HeadlessProject {
|
||||
session: client,
|
||||
settings_observer,
|
||||
fs,
|
||||
worktree_store,
|
||||
buffer_store,
|
||||
lsp_store,
|
||||
next_entry_id: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ fn main() {
|
||||
}
|
||||
|
||||
gpui::App::headless().run(move |cx| {
|
||||
settings::init(cx);
|
||||
HeadlessProject::init(cx);
|
||||
|
||||
let (incoming_tx, incoming_rx) = mpsc::unbounded();
|
||||
|
@ -4,7 +4,10 @@ use clock::FakeSystemClock;
|
||||
use fs::{FakeFs, Fs};
|
||||
use gpui::{Context, Model, TestAppContext};
|
||||
use http_client::FakeHttpClient;
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use language::{
|
||||
language_settings::{all_language_settings, AllLanguageSettings},
|
||||
Buffer, LanguageRegistry,
|
||||
};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use project::{
|
||||
search::{SearchQuery, SearchResult},
|
||||
@ -12,7 +15,7 @@ use project::{
|
||||
};
|
||||
use remote::SshSession;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use settings::{Settings, SettingsLocation, SettingsStore};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
@ -33,7 +36,6 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".git"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
@ -84,7 +86,6 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".git"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
@ -184,6 +185,85 @@ async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut Tes
|
||||
do_search(&project, cx.clone()).await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
let (project, headless, fs) = init_test(cx, server_cx).await;
|
||||
|
||||
cx.update_global(|settings_store: &mut SettingsStore, cx| {
|
||||
settings_store.set_user_settings(
|
||||
r#"{"languages":{"Rust":{"language_servers":["custom-rust-analyzer"]}}}"#,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
server_cx.read(|cx| {
|
||||
assert_eq!(
|
||||
AllLanguageSettings::get_global(cx)
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["custom-rust-analyzer".into()]
|
||||
)
|
||||
});
|
||||
|
||||
fs.insert_tree("/code/project1/.zed", json!({
|
||||
"settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
|
||||
})).await;
|
||||
|
||||
let worktree_id = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project1", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.read_with(cx, |worktree, _| worktree.id());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
server_cx.read(|cx| {
|
||||
let worktree_id = headless
|
||||
.read(cx)
|
||||
.worktree_store
|
||||
.read(cx)
|
||||
.worktrees()
|
||||
.next()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.id();
|
||||
assert_eq!(
|
||||
AllLanguageSettings::get(
|
||||
Some(SettingsLocation {
|
||||
worktree_id: worktree_id.into(),
|
||||
path: Path::new("src/lib.rs")
|
||||
}),
|
||||
cx
|
||||
)
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["override-rust-analyzer".into()]
|
||||
)
|
||||
});
|
||||
|
||||
cx.read(|cx| {
|
||||
let file = buffer.read(cx).file();
|
||||
assert_eq!(
|
||||
all_language_settings(file, cx)
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["override-rust-analyzer".into()]
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::try_init().ok();
|
||||
|
@ -151,7 +151,7 @@ impl<'a, T: Serialize> SettingsSources<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SettingsLocation<'a> {
|
||||
pub worktree_id: usize,
|
||||
pub path: &'a Path,
|
||||
@ -309,8 +309,8 @@ impl SettingsStore {
|
||||
|
||||
/// Get the user's settings as a raw JSON value.
|
||||
///
|
||||
/// This is only for debugging and reporting. For user-facing functionality,
|
||||
/// use the typed setting interface.
|
||||
/// For user-facing functionality use the typed setting interface.
|
||||
/// (e.g. ProjectSettings::get_global(cx))
|
||||
pub fn raw_user_settings(&self) -> &serde_json::Value {
|
||||
&self.raw_user_settings
|
||||
}
|
||||
|
@ -983,6 +983,10 @@ impl Worktree {
|
||||
}
|
||||
|
||||
impl LocalWorktree {
|
||||
pub fn fs(&self) -> &Arc<dyn Fs> {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
pub fn contains_abs_path(&self, path: &Path) -> bool {
|
||||
path.starts_with(&self.abs_path)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user