Fix up/down project_id confusion (#18099)

Release Notes:

- ssh remoting: Fix LSP queries run over collab
This commit is contained in:
Conrad Irwin 2024-09-23 09:11:58 -06:00 committed by GitHub
parent 35a80f07e0
commit a36706aed6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 161 additions and 100 deletions

View file

@ -50,7 +50,7 @@ use parking_lot::{Mutex, RwLock};
use postage::watch; use postage::watch;
use rand::prelude::*; use rand::prelude::*;
use rpc::AnyProtoClient; use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient};
use serde::Serialize; use serde::Serialize;
use settings::{Settings, SettingsLocation, SettingsStore}; use settings::{Settings, SettingsLocation, SettingsStore};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -132,6 +132,7 @@ impl LocalLspStore {
pub struct RemoteLspStore { pub struct RemoteLspStore {
upstream_client: AnyProtoClient, upstream_client: AnyProtoClient,
upstream_project_id: u64,
} }
impl RemoteLspStore {} impl RemoteLspStore {}
@ -164,8 +165,7 @@ impl LspStoreMode {
pub struct LspStore { pub struct LspStore {
mode: LspStoreMode, mode: LspStoreMode,
downstream_client: Option<AnyProtoClient>, downstream_client: Option<(AnyProtoClient, u64)>,
project_id: u64,
nonce: u128, nonce: u128,
buffer_store: Model<BufferStore>, buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>, worktree_store: Model<WorktreeStore>,
@ -302,14 +302,16 @@ impl LspStore {
} }
} }
pub fn upstream_client(&self) -> Option<AnyProtoClient> { pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.mode { match &self.mode {
LspStoreMode::Ssh(SshLspStore { LspStoreMode::Ssh(SshLspStore {
upstream_client, .. upstream_client, ..
}) }) => Some((upstream_client.clone(), SSH_PROJECT_ID)),
| LspStoreMode::Remote(RemoteLspStore { LspStoreMode::Remote(RemoteLspStore {
upstream_client, .. upstream_client,
}) => Some(upstream_client.clone()), upstream_project_id,
..
}) => Some((upstream_client.clone(), *upstream_project_id)),
LspStoreMode::Local(_) => None, LspStoreMode::Local(_) => None,
} }
} }
@ -374,7 +376,6 @@ impl LspStore {
}), }),
}), }),
downstream_client: None, downstream_client: None,
project_id: 0,
buffer_store, buffer_store,
worktree_store, worktree_store,
languages: languages.clone(), languages: languages.clone(),
@ -395,10 +396,11 @@ impl LspStore {
&self, &self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
client: AnyProtoClient, client: AnyProtoClient,
upstream_project_id: u64,
request: R, request: R,
cx: &mut ModelContext<'_, LspStore>, cx: &mut ModelContext<'_, LspStore>,
) -> Task<anyhow::Result<<R as LspCommand>::Response>> { ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
let message = request.to_proto(self.project_id, buffer.read(cx)); let message = request.to_proto(upstream_project_id, buffer.read(cx));
cx.spawn(move |this, cx| async move { cx.spawn(move |this, cx| async move {
let response = client.request(message).await?; let response = client.request(message).await?;
let this = this.upgrade().context("project dropped")?; let this = this.upgrade().context("project dropped")?;
@ -413,7 +415,6 @@ impl LspStore {
worktree_store: Model<WorktreeStore>, worktree_store: Model<WorktreeStore>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient, upstream_client: AnyProtoClient,
project_id: u64,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
cx.subscribe(&buffer_store, Self::on_buffer_store_event) cx.subscribe(&buffer_store, Self::on_buffer_store_event)
@ -429,7 +430,6 @@ impl LspStore {
current_lsp_settings: Default::default(), current_lsp_settings: Default::default(),
}), }),
downstream_client: None, downstream_client: None,
project_id,
buffer_store, buffer_store,
worktree_store, worktree_store,
languages: languages.clone(), languages: languages.clone(),
@ -461,9 +461,11 @@ impl LspStore {
.detach(); .detach();
Self { Self {
mode: LspStoreMode::Remote(RemoteLspStore { upstream_client }), mode: LspStoreMode::Remote(RemoteLspStore {
upstream_client,
upstream_project_id: project_id,
}),
downstream_client: None, downstream_client: None,
project_id,
buffer_store, buffer_store,
worktree_store, worktree_store,
languages: languages.clone(), languages: languages.clone(),
@ -768,13 +770,13 @@ impl LspStore {
} }
pub(crate) fn send_diagnostic_summaries(&self, worktree: &mut Worktree) { pub(crate) fn send_diagnostic_summaries(&self, worktree: &mut Worktree) {
if let Some(client) = self.downstream_client.clone() { if let Some((client, downstream_project_id)) = self.downstream_client.clone() {
if let Some(summaries) = self.diagnostic_summaries.get(&worktree.id()) { if let Some(summaries) = self.diagnostic_summaries.get(&worktree.id()) {
for (path, summaries) in summaries { for (path, summaries) in summaries {
for (&server_id, summary) in summaries { for (&server_id, summary) in summaries {
client client
.send(proto::UpdateDiagnosticSummary { .send(proto::UpdateDiagnosticSummary {
project_id: self.project_id, project_id: downstream_project_id,
worktree_id: worktree.id().to_proto(), worktree_id: worktree.id().to_proto(),
summary: Some(summary.to_proto(server_id, path)), summary: Some(summary.to_proto(server_id, path)),
}) })
@ -798,8 +800,14 @@ impl LspStore {
{ {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
if let Some(upstream_client) = self.upstream_client() { if let Some((upstream_client, upstream_project_id)) = self.upstream_client() {
return self.send_lsp_proto_request(buffer_handle, upstream_client, request, cx); return self.send_lsp_proto_request(
buffer_handle,
upstream_client,
upstream_project_id,
request,
cx,
);
} }
let language_server = match server { let language_server = match server {
@ -1077,9 +1085,9 @@ impl LspStore {
push_to_history: bool, push_to_history: bool,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<ProjectTransaction>> { ) -> Task<Result<ProjectTransaction>> {
if let Some(upstream_client) = self.upstream_client() { if let Some((upstream_client, project_id)) = self.upstream_client() {
let request = proto::ApplyCodeAction { let request = proto::ApplyCodeAction {
project_id: self.project_id, project_id,
buffer_id: buffer_handle.read(cx).remote_id().into(), buffer_id: buffer_handle.read(cx).remote_id().into(),
action: Some(Self::serialize_code_action(&action)), action: Some(Self::serialize_code_action(&action)),
}; };
@ -1163,9 +1171,9 @@ impl LspStore {
server_id: LanguageServerId, server_id: LanguageServerId,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<InlayHint>> { ) -> Task<anyhow::Result<InlayHint>> {
if let Some(upstream_client) = self.upstream_client() { if let Some((upstream_client, project_id)) = self.upstream_client() {
let request = proto::ResolveInlayHint { let request = proto::ResolveInlayHint {
project_id: self.project_id, project_id,
buffer_id: buffer_handle.read(cx).remote_id().into(), buffer_id: buffer_handle.read(cx).remote_id().into(),
language_server_id: server_id.0 as u64, language_server_id: server_id.0 as u64,
hint: Some(InlayHints::project_to_proto_hint(hint.clone())), hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
@ -1274,9 +1282,9 @@ impl LspStore {
trigger: String, trigger: String,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> { ) -> Task<Result<Option<Transaction>>> {
if let Some(client) = self.upstream_client() { if let Some((client, project_id)) = self.upstream_client() {
let request = proto::OnTypeFormatting { let request = proto::OnTypeFormatting {
project_id: self.project_id, project_id,
buffer_id: buffer.read(cx).remote_id().into(), buffer_id: buffer.read(cx).remote_id().into(),
position: Some(serialize_anchor(&position)), position: Some(serialize_anchor(&position)),
trigger, trigger,
@ -1424,11 +1432,11 @@ impl LspStore {
range: Range<Anchor>, range: Range<Anchor>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Vec<CodeAction>> { ) -> Task<Vec<CodeAction>> {
if let Some(upstream_client) = self.upstream_client() { if let Some((upstream_client, project_id)) = self.upstream_client() {
let request_task = upstream_client.request(proto::MultiLspQuery { let request_task = upstream_client.request(proto::MultiLspQuery {
buffer_id: buffer_handle.read(cx).remote_id().into(), buffer_id: buffer_handle.read(cx).remote_id().into(),
version: serialize_version(&buffer_handle.read(cx).version()), version: serialize_version(&buffer_handle.read(cx).version()),
project_id: self.project_id, project_id,
strategy: Some(proto::multi_lsp_query::Strategy::All( strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {}, proto::AllLanguageServers {},
)), )),
@ -1437,7 +1445,7 @@ impl LspStore {
range: range.clone(), range: range.clone(),
kinds: None, kinds: None,
} }
.to_proto(self.project_id, buffer_handle.read(cx)), .to_proto(project_id, buffer_handle.read(cx)),
)), )),
}); });
let buffer = buffer_handle.clone(); let buffer = buffer_handle.clone();
@ -1504,10 +1512,11 @@ impl LspStore {
) -> Task<Result<Vec<Completion>>> { ) -> Task<Result<Vec<Completion>>> {
let language_registry = self.languages.clone(); let language_registry = self.languages.clone();
if let Some(upstream_client) = self.upstream_client() { if let Some((upstream_client, project_id)) = self.upstream_client() {
let task = self.send_lsp_proto_request( let task = self.send_lsp_proto_request(
buffer.clone(), buffer.clone(),
upstream_client, upstream_client,
project_id,
GetCompletions { position, context }, GetCompletions { position, context },
cx, cx,
); );
@ -1603,14 +1612,13 @@ impl LspStore {
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
let client = self.upstream_client(); let client = self.upstream_client();
let language_registry = self.languages.clone(); let language_registry = self.languages.clone();
let project_id = self.project_id;
let buffer_id = buffer.read(cx).remote_id(); let buffer_id = buffer.read(cx).remote_id();
let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_snapshot = buffer.read(cx).snapshot();
cx.spawn(move |this, cx| async move { cx.spawn(move |this, cx| async move {
let mut did_resolve = false; let mut did_resolve = false;
if let Some(client) = client { if let Some((client, project_id)) = client {
for completion_index in completion_indices { for completion_index in completion_indices {
let (server_id, completion) = { let (server_id, completion) = {
let completions_guard = completions.read(); let completions_guard = completions.read();
@ -1811,8 +1819,7 @@ impl LspStore {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
if let Some(client) = self.upstream_client() { if let Some((client, project_id)) = self.upstream_client() {
let project_id = self.project_id;
cx.spawn(move |_, mut cx| async move { cx.spawn(move |_, mut cx| async move {
let response = client let response = client
.request(proto::ApplyCompletionAdditionalEdits { .request(proto::ApplyCompletionAdditionalEdits {
@ -1927,9 +1934,9 @@ impl LspStore {
let buffer_id = buffer.remote_id().into(); let buffer_id = buffer.remote_id().into();
let lsp_request = InlayHints { range }; let lsp_request = InlayHints { range };
if let Some(client) = self.upstream_client() { if let Some((client, project_id)) = self.upstream_client() {
let request = proto::InlayHints { let request = proto::InlayHints {
project_id: self.project_id, project_id,
buffer_id, buffer_id,
start: Some(serialize_anchor(&range_start)), start: Some(serialize_anchor(&range_start)),
end: Some(serialize_anchor(&range_end)), end: Some(serialize_anchor(&range_end)),
@ -1977,16 +1984,16 @@ impl LspStore {
) -> Task<Vec<SignatureHelp>> { ) -> Task<Vec<SignatureHelp>> {
let position = position.to_point_utf16(buffer.read(cx)); let position = position.to_point_utf16(buffer.read(cx));
if let Some(client) = self.upstream_client() { if let Some((client, upstream_project_id)) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery { let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer.read(cx).remote_id().into(), buffer_id: buffer.read(cx).remote_id().into(),
version: serialize_version(&buffer.read(cx).version()), version: serialize_version(&buffer.read(cx).version()),
project_id: self.project_id, project_id: upstream_project_id,
strategy: Some(proto::multi_lsp_query::Strategy::All( strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {}, proto::AllLanguageServers {},
)), )),
request: Some(proto::multi_lsp_query::Request::GetSignatureHelp( request: Some(proto::multi_lsp_query::Request::GetSignatureHelp(
GetSignatureHelp { position }.to_proto(self.project_id, buffer.read(cx)), GetSignatureHelp { position }.to_proto(upstream_project_id, buffer.read(cx)),
)), )),
}); });
let buffer = buffer.clone(); let buffer = buffer.clone();
@ -2049,16 +2056,16 @@ impl LspStore {
position: PointUtf16, position: PointUtf16,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Vec<Hover>> { ) -> Task<Vec<Hover>> {
if let Some(client) = self.upstream_client() { if let Some((client, upstream_project_id)) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery { let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer.read(cx).remote_id().into(), buffer_id: buffer.read(cx).remote_id().into(),
version: serialize_version(&buffer.read(cx).version()), version: serialize_version(&buffer.read(cx).version()),
project_id: self.project_id, project_id: upstream_project_id,
strategy: Some(proto::multi_lsp_query::Strategy::All( strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {}, proto::AllLanguageServers {},
)), )),
request: Some(proto::multi_lsp_query::Request::GetHover( request: Some(proto::multi_lsp_query::Request::GetHover(
GetHover { position }.to_proto(self.project_id, buffer.read(cx)), GetHover { position }.to_proto(upstream_project_id, buffer.read(cx)),
)), )),
}); });
let buffer = buffer.clone(); let buffer = buffer.clone();
@ -2123,9 +2130,9 @@ impl LspStore {
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> { pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
let language_registry = self.languages.clone(); let language_registry = self.languages.clone();
if let Some(upstream_client) = self.upstream_client().as_ref() { if let Some((upstream_client, project_id)) = self.upstream_client().as_ref() {
let request = upstream_client.request(proto::GetProjectSymbols { let request = upstream_client.request(proto::GetProjectSymbols {
project_id: self.project_id, project_id: *project_id,
query: query.to_string(), query: query.to_string(),
}); });
cx.foreground_executor().spawn(async move { cx.foreground_executor().spawn(async move {
@ -2598,8 +2605,7 @@ impl LspStore {
downstream_client: AnyProtoClient, downstream_client: AnyProtoClient,
_: &mut ModelContext<Self>, _: &mut ModelContext<Self>,
) { ) {
self.project_id = project_id; self.downstream_client = Some((downstream_client.clone(), project_id));
self.downstream_client = Some(downstream_client.clone());
for (server_id, status) in &self.language_server_statuses { for (server_id, status) in &self.language_server_statuses {
downstream_client downstream_client
@ -2857,10 +2863,10 @@ impl LspStore {
} }
if !old_summary.is_empty() || !new_summary.is_empty() { if !old_summary.is_empty() || !new_summary.is_empty() {
if let Some(downstream_client) = &self.downstream_client { if let Some((downstream_client, project_id)) = &self.downstream_client {
downstream_client downstream_client
.send(proto::UpdateDiagnosticSummary { .send(proto::UpdateDiagnosticSummary {
project_id: self.project_id, project_id: *project_id,
worktree_id: worktree_id.to_proto(), worktree_id: worktree_id.to_proto(),
summary: Some(proto::DiagnosticSummary { summary: Some(proto::DiagnosticSummary {
path: worktree_path.to_string_lossy().to_string(), path: worktree_path.to_string_lossy().to_string(),
@ -2881,9 +2887,9 @@ impl LspStore {
symbol: &Symbol, symbol: &Symbol,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> { ) -> Task<Result<Model<Buffer>>> {
if let Some(client) = self.upstream_client() { if let Some((client, project_id)) = self.upstream_client() {
let request = client.request(proto::OpenBufferForSymbol { let request = client.request(proto::OpenBufferForSymbol {
project_id: self.project_id, project_id,
symbol: Some(Self::serialize_symbol(symbol)), symbol: Some(Self::serialize_symbol(symbol)),
}); });
cx.spawn(move |this, mut cx| async move { cx.spawn(move |this, mut cx| async move {
@ -3184,6 +3190,17 @@ impl LspStore {
envelope: TypedEnvelope<proto::MultiLspQuery>, envelope: TypedEnvelope<proto::MultiLspQuery>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::MultiLspQueryResponse> { ) -> Result<proto::MultiLspQueryResponse> {
let response_from_ssh = this.update(&mut cx, |this, _| {
let ssh = this.as_ssh()?;
let mut payload = envelope.payload.clone();
payload.project_id = SSH_PROJECT_ID;
Some(ssh.upstream_client.request(payload))
})?;
if let Some(response_from_ssh) = response_from_ssh {
return response_from_ssh.await;
}
let sender_id = envelope.original_sender_id().unwrap_or_default(); let sender_id = envelope.original_sender_id().unwrap_or_default();
let buffer_id = BufferId::new(envelope.payload.buffer_id)?; let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let version = deserialize_version(&envelope.payload.version); let version = deserialize_version(&envelope.payload.version);
@ -4779,10 +4796,11 @@ impl LspStore {
// TODO: We should use `adapter` here instead of reaching through the `CachedLspAdapter`. // TODO: We should use `adapter` here instead of reaching through the `CachedLspAdapter`.
let lsp_adapter = adapter.adapter.clone(); let lsp_adapter = adapter.adapter.clone();
let project_id = self.project_id; let Some((upstream_client, project_id)) = self.upstream_client() else {
return;
};
let worktree_id = worktree.read(cx).id().to_proto(); let worktree_id = worktree.read(cx).id().to_proto();
let upstream_client = ssh.upstream_client.clone(); let name = adapter.name().to_string();
let name = adapter.name();
let Some(available_language) = self.languages.available_language_for_name(&language) else { let Some(available_language) = self.languages.available_language_for_name(&language) else {
log::error!("failed to find available language {language}"); log::error!("failed to find available language {language}");
@ -5165,12 +5183,11 @@ impl LspStore {
} }
}); });
let project_id = self.project_id;
for (worktree_id, summaries) in self.diagnostic_summaries.iter_mut() { for (worktree_id, summaries) in self.diagnostic_summaries.iter_mut() {
summaries.retain(|path, summaries_by_server_id| { summaries.retain(|path, summaries_by_server_id| {
if summaries_by_server_id.remove(&server_id).is_some() { if summaries_by_server_id.remove(&server_id).is_some() {
if let Some(downstream_client) = self.downstream_client.clone() { if let Some((client, project_id)) = self.downstream_client.clone() {
downstream_client client
.send(proto::UpdateDiagnosticSummary { .send(proto::UpdateDiagnosticSummary {
project_id, project_id,
worktree_id: worktree_id.to_proto(), worktree_id: worktree_id.to_proto(),
@ -5236,9 +5253,9 @@ impl LspStore {
buffers: impl IntoIterator<Item = Model<Buffer>>, buffers: impl IntoIterator<Item = Model<Buffer>>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
if let Some(client) = self.upstream_client() { if let Some((client, project_id)) = self.upstream_client() {
let request = client.request(proto::RestartLanguageServers { let request = client.request(proto::RestartLanguageServers {
project_id: self.project_id, project_id,
buffer_ids: buffers buffer_ids: buffers
.into_iter() .into_iter()
.map(|b| b.read(cx).remote_id().to_proto()) .map(|b| b.read(cx).remote_id().to_proto())
@ -5694,9 +5711,9 @@ impl LspStore {
async move { async move {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
cx.emit(LspStoreEvent::RefreshInlayHints); cx.emit(LspStoreEvent::RefreshInlayHints);
this.downstream_client.as_ref().map(|client| { this.downstream_client.as_ref().map(|(client, project_id)| {
client.send(proto::RefreshInlayHints { client.send(proto::RefreshInlayHints {
project_id: this.project_id, project_id: *project_id,
}) })
}) })
})? })?
@ -6073,9 +6090,9 @@ impl LspStore {
cx.emit(LspStoreEvent::LanguageServerAdded(server_id)); cx.emit(LspStoreEvent::LanguageServerAdded(server_id));
if let Some(downstream_client) = self.downstream_client.as_ref() { if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() {
downstream_client.send(proto::StartLanguageServer { downstream_client.send(proto::StartLanguageServer {
project_id: self.project_id, project_id: *project_id,
server: Some(proto::LanguageServer { server: Some(proto::LanguageServer {
id: server_id.0 as u64, id: server_id.0 as u64,
name: language_server.name().to_string(), name: language_server.name().to_string(),

View file

@ -625,7 +625,7 @@ impl Project {
let snippets = let snippets =
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx); SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
let worktree_store = cx.new_model(|_| WorktreeStore::new(None, false, fs.clone())); let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone()));
cx.subscribe(&worktree_store, Self::on_worktree_store_event) cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach(); .detach();
@ -722,7 +722,7 @@ impl Project {
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx); SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
let worktree_store = let worktree_store =
cx.new_model(|_| WorktreeStore::new(Some(ssh.clone().into()), false, fs.clone())); cx.new_model(|_| WorktreeStore::remote(false, ssh.clone().into(), 0, None));
cx.subscribe(&worktree_store, Self::on_worktree_store_event) cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach(); .detach();
@ -744,7 +744,6 @@ impl Project {
worktree_store.clone(), worktree_store.clone(),
languages.clone(), languages.clone(),
ssh.clone().into(), ssh.clone().into(),
0,
cx, cx,
) )
}); });
@ -874,11 +873,15 @@ impl Project {
let role = response.payload.role(); let role = response.payload.role();
let worktree_store = cx.new_model(|_| { let worktree_store = cx.new_model(|_| {
let mut store = WorktreeStore::new(Some(client.clone().into()), true, fs.clone()); WorktreeStore::remote(
if let Some(dev_server_project_id) = response.payload.dev_server_project_id { true,
store.set_dev_server_project_id(DevServerProjectId(dev_server_project_id)); client.clone().into(),
} response.payload.project_id,
store response
.payload
.dev_server_project_id
.map(DevServerProjectId),
)
})?; })?;
let buffer_store = let buffer_store =
cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?; cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?;

View file

@ -36,19 +36,27 @@ struct MatchingEntry {
respond: oneshot::Sender<ProjectPath>, respond: oneshot::Sender<ProjectPath>,
} }
enum WorktreeStoreState {
Local {
fs: Arc<dyn Fs>,
},
Remote {
dev_server_project_id: Option<DevServerProjectId>,
upstream_client: AnyProtoClient,
upstream_project_id: u64,
},
}
pub struct WorktreeStore { pub struct WorktreeStore {
next_entry_id: Arc<AtomicUsize>, next_entry_id: Arc<AtomicUsize>,
upstream_client: Option<AnyProtoClient>, downstream_client: Option<(AnyProtoClient, u64)>,
downstream_client: Option<AnyProtoClient>,
remote_id: u64,
dev_server_project_id: Option<DevServerProjectId>,
retain_worktrees: bool, retain_worktrees: bool,
worktrees: Vec<WorktreeHandle>, worktrees: Vec<WorktreeHandle>,
worktrees_reordered: bool, worktrees_reordered: bool,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
loading_worktrees: loading_worktrees:
HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>, HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
fs: Arc<dyn Fs>, state: WorktreeStoreState,
} }
pub enum WorktreeStoreEvent { pub enum WorktreeStoreEvent {
@ -69,27 +77,37 @@ impl WorktreeStore {
client.add_model_request_handler(Self::handle_expand_project_entry); client.add_model_request_handler(Self::handle_expand_project_entry);
} }
pub fn new( pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self {
upstream_client: Option<AnyProtoClient>,
retain_worktrees: bool,
fs: Arc<dyn Fs>,
) -> Self {
Self { Self {
next_entry_id: Default::default(), next_entry_id: Default::default(),
loading_worktrees: Default::default(), loading_worktrees: Default::default(),
dev_server_project_id: None,
downstream_client: None, downstream_client: None,
worktrees: Vec::new(), worktrees: Vec::new(),
worktrees_reordered: false, worktrees_reordered: false,
retain_worktrees, retain_worktrees,
remote_id: 0, state: WorktreeStoreState::Local { fs },
upstream_client,
fs,
} }
} }
pub fn set_dev_server_project_id(&mut self, id: DevServerProjectId) { pub fn remote(
self.dev_server_project_id = Some(id); retain_worktrees: bool,
upstream_client: AnyProtoClient,
upstream_project_id: u64,
dev_server_project_id: Option<DevServerProjectId>,
) -> Self {
Self {
next_entry_id: Default::default(),
loading_worktrees: Default::default(),
downstream_client: None,
worktrees: Vec::new(),
worktrees_reordered: false,
retain_worktrees,
state: WorktreeStoreState::Remote {
upstream_client,
upstream_project_id,
dev_server_project_id,
},
}
} }
/// Iterates through all worktrees, including ones that don't appear in the project panel /// Iterates through all worktrees, including ones that don't appear in the project panel
@ -159,14 +177,28 @@ impl WorktreeStore {
) -> Task<Result<Model<Worktree>>> { ) -> Task<Result<Model<Worktree>>> {
let path: Arc<Path> = abs_path.as_ref().into(); let path: Arc<Path> = abs_path.as_ref().into();
if !self.loading_worktrees.contains_key(&path) { if !self.loading_worktrees.contains_key(&path) {
let task = if let Some(client) = self.upstream_client.clone() { let task = match &self.state {
if let Some(dev_server_project_id) = self.dev_server_project_id { WorktreeStoreState::Remote {
self.create_dev_server_worktree(client, dev_server_project_id, abs_path, cx) upstream_client,
dev_server_project_id,
..
} => {
if let Some(dev_server_project_id) = dev_server_project_id {
self.create_dev_server_worktree(
upstream_client.clone(),
*dev_server_project_id,
abs_path,
cx,
)
} else if upstream_client.is_via_collab() {
Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))
} else { } else {
self.create_ssh_worktree(client, abs_path, visible, cx) self.create_ssh_worktree(upstream_client.clone(), abs_path, visible, cx)
}
}
WorktreeStoreState::Local { fs } => {
self.create_local_worktree(fs.clone(), abs_path, visible, cx)
} }
} else {
self.create_local_worktree(abs_path, visible, cx)
}; };
self.loading_worktrees.insert(path.clone(), task.shared()); self.loading_worktrees.insert(path.clone(), task.shared());
@ -236,11 +268,11 @@ impl WorktreeStore {
fn create_local_worktree( fn create_local_worktree(
&mut self, &mut self,
fs: Arc<dyn Fs>,
abs_path: impl AsRef<Path>, abs_path: impl AsRef<Path>,
visible: bool, visible: bool,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> { ) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
let fs = self.fs.clone();
let next_entry_id = self.next_entry_id.clone(); let next_entry_id = self.next_entry_id.clone();
let path: Arc<Path> = abs_path.as_ref().into(); let path: Arc<Path> = abs_path.as_ref().into();
@ -374,6 +406,17 @@ impl WorktreeStore {
self.worktrees_reordered = worktrees_reordered; self.worktrees_reordered = worktrees_reordered;
} }
fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.state {
WorktreeStoreState::Remote {
upstream_client,
upstream_project_id,
..
} => Some((upstream_client.clone(), *upstream_project_id)),
WorktreeStoreState::Local { .. } => None,
}
}
pub fn set_worktrees_from_proto( pub fn set_worktrees_from_proto(
&mut self, &mut self,
worktrees: Vec<proto::WorktreeMetadata>, worktrees: Vec<proto::WorktreeMetadata>,
@ -389,8 +432,8 @@ impl WorktreeStore {
}) })
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
let client = self let (client, project_id) = self
.upstream_client .upstream_client()
.clone() .clone()
.ok_or_else(|| anyhow!("invalid project"))?; .ok_or_else(|| anyhow!("invalid project"))?;
@ -408,7 +451,7 @@ impl WorktreeStore {
self.worktrees.push(handle); self.worktrees.push(handle);
} else { } else {
self.add( self.add(
&Worktree::remote(self.remote_id, replica_id, worktree, client.clone(), cx), &Worktree::remote(project_id, replica_id, worktree, client.clone(), cx),
cx, cx,
); );
} }
@ -477,10 +520,9 @@ impl WorktreeStore {
} }
pub fn send_project_updates(&mut self, cx: &mut ModelContext<Self>) { pub fn send_project_updates(&mut self, cx: &mut ModelContext<Self>) {
let Some(downstream_client) = self.downstream_client.clone() else { let Some((downstream_client, project_id)) = self.downstream_client.clone() else {
return; return;
}; };
let project_id = self.remote_id;
let update = proto::UpdateProject { let update = proto::UpdateProject {
project_id, project_id,
@ -549,8 +591,7 @@ impl WorktreeStore {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
self.retain_worktrees = true; self.retain_worktrees = true;
self.remote_id = remote_id; self.downstream_client = Some((downsteam_client, remote_id));
self.downstream_client = Some(downsteam_client);
// When shared, retain all worktrees // When shared, retain all worktrees
for worktree_handle in self.worktrees.iter_mut() { for worktree_handle in self.worktrees.iter_mut() {

View file

@ -45,7 +45,7 @@ impl HeadlessProject {
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone())); let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
let worktree_store = cx.new_model(|cx| { let worktree_store = cx.new_model(|cx| {
let mut store = WorktreeStore::new(None, true, fs.clone()); let mut store = WorktreeStore::local(true, fs.clone());
store.shared(SSH_PROJECT_ID, session.clone().into(), cx); store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
store store
}); });