1pub mod buffer_store;
2mod color_extractor;
3pub mod connection_manager;
4pub mod debounced_delay;
5pub mod lsp_command;
6pub mod lsp_ext_command;
7pub mod lsp_store;
8pub mod prettier_store;
9pub mod project_settings;
10pub mod search;
11mod task_inventory;
12pub mod terminals;
13pub mod worktree_store;
14
15#[cfg(test)]
16mod project_tests;
17
18mod direnv;
19mod environment;
20pub use environment::EnvironmentErrorMessage;
21pub mod search_history;
22mod yarn;
23
24use anyhow::{anyhow, Context as _, Result};
25use buffer_store::{BufferStore, BufferStoreEvent};
26use client::{
27 proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId,
28 TypedEnvelope, UserStore,
29};
30use clock::ReplicaId;
31use collections::{BTreeSet, HashMap, HashSet};
32use debounced_delay::DebouncedDelay;
33pub use environment::ProjectEnvironment;
34use futures::{
35 channel::mpsc::{self, UnboundedReceiver},
36 future::try_join_all,
37 StreamExt,
38};
39
40use git::{blame::Blame, repository::GitRepository};
41use gpui::{
42 AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context, EventEmitter, Hsla, Model,
43 ModelContext, SharedString, Task, WeakModel, WindowContext,
44};
45use itertools::Itertools;
46use language::{
47 language_settings::InlayHintKind,
48 proto::{deserialize_anchor, serialize_anchor, split_operations},
49 Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
50 Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset,
51 ToPointUtf16, Transaction, Unclipped,
52};
53use lsp::{
54 CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
55};
56use lsp_command::*;
57use node_runtime::NodeRuntime;
58use parking_lot::{Mutex, RwLock};
59use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
60pub use prettier_store::PrettierStore;
61use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
62use remote::SshRemoteClient;
63use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
64use search::{SearchInputKind, SearchQuery, SearchResult};
65use search_history::SearchHistory;
66use settings::{
67 watch_config_file, InvalidSettingsError, Settings, SettingsLocation, SettingsStore,
68};
69use smol::channel::Receiver;
70use snippet::Snippet;
71use snippet_provider::SnippetProvider;
72use std::{
73 borrow::Cow,
74 ops::Range,
75 path::{Component, Path, PathBuf},
76 str,
77 sync::Arc,
78 time::Duration,
79};
80use task::{
81 static_source::{StaticSource, TrackedFile},
82 HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName,
83};
84use terminals::Terminals;
85use text::{Anchor, BufferId};
86use util::{paths::compare_paths, ResultExt as _};
87use worktree::{CreatedEntry, Snapshot, Traversal};
88use worktree_store::{WorktreeStore, WorktreeStoreEvent};
89
90pub use fs::*;
91pub use language::Location;
92#[cfg(any(test, feature = "test-support"))]
93pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
94pub use task_inventory::{
95 BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
96};
97pub use worktree::{
98 Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
99 UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
100 FS_WATCH_LATENCY,
101};
102
103pub use buffer_store::ProjectTransaction;
104pub use lsp_store::{
105 DiagnosticSummary, LanguageServerLogType, LanguageServerProgress, LanguageServerPromptRequest,
106 LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
107 SERVER_PROGRESS_THROTTLE_TIMEOUT,
108};
109
110const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
111const MAX_SEARCH_RESULT_FILES: usize = 5_000;
112const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
113
114pub trait Item {
115 fn try_open(
116 project: &Model<Project>,
117 path: &ProjectPath,
118 cx: &mut AppContext,
119 ) -> Option<Task<Result<Model<Self>>>>
120 where
121 Self: Sized;
122 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
123 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
124}
125
126#[derive(Clone)]
127pub enum OpenedBufferEvent {
128 Disconnected,
129 Ok(BufferId),
130 Err(BufferId, Arc<anyhow::Error>),
131}
132
133/// Semantics-aware entity that is relevant to one or more [`Worktree`] with the files.
134/// `Project` is responsible for tasks, LSP and collab queries, synchronizing worktree states accordingly.
135/// Maps [`Worktree`] entries with its own logic using [`ProjectEntryId`] and [`ProjectPath`] structs.
136///
137/// Can be either local (for the project opened on the same host) or remote.(for collab projects, browsed by multiple remote users).
138pub struct Project {
139 active_entry: Option<ProjectEntryId>,
140 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
141 languages: Arc<LanguageRegistry>,
142 client: Arc<client::Client>,
143 join_project_response_message_id: u32,
144 user_store: Model<UserStore>,
145 fs: Arc<dyn Fs>,
146 ssh_client: Option<Model<SshRemoteClient>>,
147 client_state: ProjectClientState,
148 collaborators: HashMap<proto::PeerId, Collaborator>,
149 client_subscriptions: Vec<client::Subscription>,
150 worktree_store: Model<WorktreeStore>,
151 buffer_store: Model<BufferStore>,
152 lsp_store: Model<LspStore>,
153 _subscriptions: Vec<gpui::Subscription>,
154 buffers_needing_diff: HashSet<WeakModel<Buffer>>,
155 git_diff_debouncer: DebouncedDelay<Self>,
156 remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
157 terminals: Terminals,
158 node: Option<NodeRuntime>,
159 tasks: Model<Inventory>,
160 hosted_project_id: Option<ProjectId>,
161 dev_server_project_id: Option<client::DevServerProjectId>,
162 search_history: SearchHistory,
163 search_included_history: SearchHistory,
164 search_excluded_history: SearchHistory,
165 snippets: Model<SnippetProvider>,
166 environment: Model<ProjectEnvironment>,
167 settings_observer: Model<SettingsObserver>,
168}
169
170#[derive(Default)]
171struct RemotelyCreatedModels {
172 worktrees: Vec<Model<Worktree>>,
173 buffers: Vec<Model<Buffer>>,
174 retain_count: usize,
175}
176
177struct RemotelyCreatedModelGuard {
178 remote_models: std::sync::Weak<Mutex<RemotelyCreatedModels>>,
179}
180
181impl Drop for RemotelyCreatedModelGuard {
182 fn drop(&mut self) {
183 if let Some(remote_models) = self.remote_models.upgrade() {
184 let mut remote_models = remote_models.lock();
185 assert!(
186 remote_models.retain_count > 0,
187 "RemotelyCreatedModelGuard dropped too many times"
188 );
189 remote_models.retain_count -= 1;
190 if remote_models.retain_count == 0 {
191 remote_models.buffers.clear();
192 remote_models.worktrees.clear();
193 }
194 }
195 }
196}
197/// Message ordered with respect to buffer operations
198#[derive(Debug)]
199enum BufferOrderedMessage {
200 Operation {
201 buffer_id: BufferId,
202 operation: proto::Operation,
203 },
204 LanguageServerUpdate {
205 language_server_id: LanguageServerId,
206 message: proto::update_language_server::Variant,
207 },
208 Resync,
209}
210
211#[derive(Debug)]
212enum ProjectClientState {
213 Local,
214 Shared {
215 remote_id: u64,
216 },
217 Remote {
218 sharing_has_stopped: bool,
219 capability: Capability,
220 remote_id: u64,
221 replica_id: ReplicaId,
222 in_room: bool,
223 },
224}
225
226#[derive(Clone, Debug, PartialEq)]
227pub enum Event {
228 LanguageServerAdded(LanguageServerId),
229 LanguageServerRemoved(LanguageServerId),
230 LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
231 Notification(String),
232 LocalSettingsUpdated(Result<(), InvalidSettingsError>),
233 LanguageServerPrompt(LanguageServerPromptRequest),
234 LanguageNotFound(Model<Buffer>),
235 ActiveEntryChanged(Option<ProjectEntryId>),
236 ActivateProjectPanel,
237 WorktreeAdded,
238 WorktreeOrderChanged,
239 WorktreeRemoved(WorktreeId),
240 WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
241 WorktreeUpdatedGitRepositories,
242 DiskBasedDiagnosticsStarted {
243 language_server_id: LanguageServerId,
244 },
245 DiskBasedDiagnosticsFinished {
246 language_server_id: LanguageServerId,
247 },
248 DiagnosticsUpdated {
249 path: ProjectPath,
250 language_server_id: LanguageServerId,
251 },
252 RemoteIdChanged(Option<u64>),
253 DisconnectedFromHost,
254 Closed,
255 DeletedEntry(ProjectEntryId),
256 CollaboratorUpdated {
257 old_peer_id: proto::PeerId,
258 new_peer_id: proto::PeerId,
259 },
260 CollaboratorJoined(proto::PeerId),
261 CollaboratorLeft(proto::PeerId),
262 HostReshared,
263 Reshared,
264 Rejoined,
265 RefreshInlayHints,
266 RevealInProjectPanel(ProjectEntryId),
267 SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
268}
269
270#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
271pub struct ProjectPath {
272 pub worktree_id: WorktreeId,
273 pub path: Arc<Path>,
274}
275
276impl ProjectPath {
277 pub fn from_proto(p: proto::ProjectPath) -> Self {
278 Self {
279 worktree_id: WorktreeId::from_proto(p.worktree_id),
280 path: Arc::from(PathBuf::from(p.path)),
281 }
282 }
283
284 pub fn to_proto(&self) -> proto::ProjectPath {
285 proto::ProjectPath {
286 worktree_id: self.worktree_id.to_proto(),
287 path: self.path.to_string_lossy().to_string(),
288 }
289 }
290}
291
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct InlayHint {
294 pub position: language::Anchor,
295 pub label: InlayHintLabel,
296 pub kind: Option<InlayHintKind>,
297 pub padding_left: bool,
298 pub padding_right: bool,
299 pub tooltip: Option<InlayHintTooltip>,
300 pub resolve_state: ResolveState,
301}
302
303/// The user's intent behind a given completion confirmation
304#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
305pub enum CompletionIntent {
306 /// The user intends to 'commit' this result, if possible
307 /// completion confirmations should run side effects
308 Complete,
309 /// The user intends to continue 'composing' this completion
310 /// completion confirmations should not run side effects and
311 /// let the user continue composing their action
312 Compose,
313}
314
315impl CompletionIntent {
316 pub fn is_complete(&self) -> bool {
317 self == &Self::Complete
318 }
319
320 pub fn is_compose(&self) -> bool {
321 self == &Self::Compose
322 }
323}
324
325/// A completion provided by a language server
326#[derive(Clone)]
327pub struct Completion {
328 /// The range of the buffer that will be replaced.
329 pub old_range: Range<Anchor>,
330 /// The new text that will be inserted.
331 pub new_text: String,
332 /// A label for this completion that is shown in the menu.
333 pub label: CodeLabel,
334 /// The id of the language server that produced this completion.
335 pub server_id: LanguageServerId,
336 /// The documentation for this completion.
337 pub documentation: Option<Documentation>,
338 /// The raw completion provided by the language server.
339 pub lsp_completion: lsp::CompletionItem,
340 /// An optional callback to invoke when this completion is confirmed.
341 /// Returns, whether new completions should be retriggered after the current one.
342 /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
343 /// if no confirmation is provided or `false` is returned, the completion will be committed.
344 pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut WindowContext) -> bool>>,
345}
346
347impl std::fmt::Debug for Completion {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 f.debug_struct("Completion")
350 .field("old_range", &self.old_range)
351 .field("new_text", &self.new_text)
352 .field("label", &self.label)
353 .field("server_id", &self.server_id)
354 .field("documentation", &self.documentation)
355 .field("lsp_completion", &self.lsp_completion)
356 .finish()
357 }
358}
359
360/// A completion provided by a language server
361#[derive(Clone, Debug)]
362pub(crate) struct CoreCompletion {
363 old_range: Range<Anchor>,
364 new_text: String,
365 server_id: LanguageServerId,
366 lsp_completion: lsp::CompletionItem,
367}
368
369/// A code action provided by a language server.
370#[derive(Clone, Debug)]
371pub struct CodeAction {
372 /// The id of the language server that produced this code action.
373 pub server_id: LanguageServerId,
374 /// The range of the buffer where this code action is applicable.
375 pub range: Range<Anchor>,
376 /// The raw code action provided by the language server.
377 pub lsp_action: lsp::CodeAction,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq)]
381pub enum ResolveState {
382 Resolved,
383 CanResolve(LanguageServerId, Option<lsp::LSPAny>),
384 Resolving,
385}
386
387impl InlayHint {
388 pub fn text(&self) -> String {
389 match &self.label {
390 InlayHintLabel::String(s) => s.to_owned(),
391 InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
392 }
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Eq)]
397pub enum InlayHintLabel {
398 String(String),
399 LabelParts(Vec<InlayHintLabelPart>),
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct InlayHintLabelPart {
404 pub value: String,
405 pub tooltip: Option<InlayHintLabelPartTooltip>,
406 pub location: Option<(LanguageServerId, lsp::Location)>,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
410pub enum InlayHintTooltip {
411 String(String),
412 MarkupContent(MarkupContent),
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
416pub enum InlayHintLabelPartTooltip {
417 String(String),
418 MarkupContent(MarkupContent),
419}
420
421#[derive(Debug, Clone, PartialEq, Eq)]
422pub struct MarkupContent {
423 pub kind: HoverBlockKind,
424 pub value: String,
425}
426
427#[derive(Debug, Clone)]
428pub struct LocationLink {
429 pub origin: Option<Location>,
430 pub target: Location,
431}
432
433#[derive(Debug)]
434pub struct DocumentHighlight {
435 pub range: Range<language::Anchor>,
436 pub kind: DocumentHighlightKind,
437}
438
439#[derive(Clone, Debug)]
440pub struct Symbol {
441 pub language_server_name: LanguageServerName,
442 pub source_worktree_id: WorktreeId,
443 pub path: ProjectPath,
444 pub label: CodeLabel,
445 pub name: String,
446 pub kind: lsp::SymbolKind,
447 pub range: Range<Unclipped<PointUtf16>>,
448 pub signature: [u8; 32],
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub struct HoverBlock {
453 pub text: String,
454 pub kind: HoverBlockKind,
455}
456
457#[derive(Clone, Debug, PartialEq, Eq)]
458pub enum HoverBlockKind {
459 PlainText,
460 Markdown,
461 Code { language: String },
462}
463
464#[derive(Debug, Clone)]
465pub struct Hover {
466 pub contents: Vec<HoverBlock>,
467 pub range: Option<Range<language::Anchor>>,
468 pub language: Option<Arc<Language>>,
469}
470
471impl Hover {
472 pub fn is_empty(&self) -> bool {
473 self.contents.iter().all(|block| block.text.is_empty())
474 }
475}
476
477enum EntitySubscription {
478 Project(PendingEntitySubscription<Project>),
479 BufferStore(PendingEntitySubscription<BufferStore>),
480 WorktreeStore(PendingEntitySubscription<WorktreeStore>),
481 LspStore(PendingEntitySubscription<LspStore>),
482 SettingsObserver(PendingEntitySubscription<SettingsObserver>),
483}
484
485#[derive(Clone)]
486pub enum DirectoryLister {
487 Project(Model<Project>),
488 Local(Arc<dyn Fs>),
489}
490
491impl DirectoryLister {
492 pub fn is_local(&self, cx: &AppContext) -> bool {
493 match self {
494 DirectoryLister::Local(_) => true,
495 DirectoryLister::Project(project) => project.read(cx).is_local(),
496 }
497 }
498
499 pub fn resolve_tilde<'a>(&self, path: &'a String, cx: &AppContext) -> Cow<'a, str> {
500 if self.is_local(cx) {
501 shellexpand::tilde(path)
502 } else {
503 Cow::from(path)
504 }
505 }
506
507 pub fn default_query(&self, cx: &mut AppContext) -> String {
508 if let DirectoryLister::Project(project) = self {
509 if let Some(worktree) = project.read(cx).visible_worktrees(cx).next() {
510 return worktree.read(cx).abs_path().to_string_lossy().to_string();
511 }
512 };
513 "~/".to_string()
514 }
515
516 pub fn list_directory(&self, path: String, cx: &mut AppContext) -> Task<Result<Vec<PathBuf>>> {
517 match self {
518 DirectoryLister::Project(project) => {
519 project.update(cx, |project, cx| project.list_directory(path, cx))
520 }
521 DirectoryLister::Local(fs) => {
522 let fs = fs.clone();
523 cx.background_executor().spawn(async move {
524 let mut results = vec![];
525 let expanded = shellexpand::tilde(&path);
526 let query = Path::new(expanded.as_ref());
527 let mut response = fs.read_dir(query).await?;
528 while let Some(path) = response.next().await {
529 if let Some(file_name) = path?.file_name() {
530 results.push(PathBuf::from(file_name.to_os_string()));
531 }
532 }
533 Ok(results)
534 })
535 }
536 }
537 }
538}
539
540#[cfg(any(test, feature = "test-support"))]
541pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
542 trigger_kind: lsp::CompletionTriggerKind::INVOKED,
543 trigger_character: None,
544};
545
546impl Project {
547 pub fn init_settings(cx: &mut AppContext) {
548 WorktreeSettings::register(cx);
549 ProjectSettings::register(cx);
550 }
551
552 pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
553 connection_manager::init(client.clone(), cx);
554 Self::init_settings(cx);
555
556 let client: AnyProtoClient = client.clone().into();
557 client.add_model_message_handler(Self::handle_add_collaborator);
558 client.add_model_message_handler(Self::handle_update_project_collaborator);
559 client.add_model_message_handler(Self::handle_remove_collaborator);
560 client.add_model_message_handler(Self::handle_update_project);
561 client.add_model_message_handler(Self::handle_unshare_project);
562 client.add_model_request_handler(Self::handle_update_buffer);
563 client.add_model_message_handler(Self::handle_update_worktree);
564 client.add_model_request_handler(Self::handle_synchronize_buffers);
565
566 client.add_model_request_handler(Self::handle_search_candidate_buffers);
567 client.add_model_request_handler(Self::handle_open_buffer_by_id);
568 client.add_model_request_handler(Self::handle_open_buffer_by_path);
569 client.add_model_request_handler(Self::handle_open_new_buffer);
570 client.add_model_request_handler(Self::handle_task_context_for_location);
571 client.add_model_request_handler(Self::handle_task_templates);
572 client.add_model_message_handler(Self::handle_create_buffer_for_peer);
573
574 WorktreeStore::init(&client);
575 BufferStore::init(&client);
576 LspStore::init(&client);
577 SettingsObserver::init(&client);
578 }
579
580 pub fn local(
581 client: Arc<Client>,
582 node: NodeRuntime,
583 user_store: Model<UserStore>,
584 languages: Arc<LanguageRegistry>,
585 fs: Arc<dyn Fs>,
586 env: Option<HashMap<String, String>>,
587 cx: &mut AppContext,
588 ) -> Model<Self> {
589 cx.new_model(|cx: &mut ModelContext<Self>| {
590 let (tx, rx) = mpsc::unbounded();
591 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
592 .detach();
593 let tasks = Inventory::new(cx);
594 let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
595 let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone()));
596 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
597 .detach();
598
599 let buffer_store = cx.new_model(|cx| BufferStore::local(worktree_store.clone(), cx));
600 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
601 .detach();
602
603 let prettier_store = cx.new_model(|cx| {
604 PrettierStore::new(
605 node.clone(),
606 fs.clone(),
607 languages.clone(),
608 worktree_store.clone(),
609 cx,
610 )
611 });
612
613 let settings_observer = cx.new_model(|cx| {
614 SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
615 });
616 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
617 .detach();
618
619 let environment = ProjectEnvironment::new(&worktree_store, env, cx);
620 let lsp_store = cx.new_model(|cx| {
621 LspStore::new_local(
622 buffer_store.clone(),
623 worktree_store.clone(),
624 prettier_store.clone(),
625 environment.clone(),
626 languages.clone(),
627 Some(client.http_client()),
628 fs.clone(),
629 cx,
630 )
631 });
632 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
633
634 Self {
635 buffer_ordered_messages_tx: tx,
636 collaborators: Default::default(),
637 worktree_store,
638 buffer_store,
639 lsp_store,
640 join_project_response_message_id: 0,
641 client_state: ProjectClientState::Local,
642 client_subscriptions: Vec::new(),
643 _subscriptions: vec![cx.on_release(Self::release)],
644 active_entry: None,
645 snippets,
646 languages,
647 client,
648 user_store,
649 settings_observer,
650 fs,
651 ssh_client: None,
652 buffers_needing_diff: Default::default(),
653 git_diff_debouncer: DebouncedDelay::new(),
654 terminals: Terminals {
655 local_handles: Vec::new(),
656 },
657 node: Some(node),
658 tasks,
659 hosted_project_id: None,
660 dev_server_project_id: None,
661 search_history: Self::new_search_history(),
662 environment,
663 remotely_created_models: Default::default(),
664
665 search_included_history: Self::new_search_history(),
666 search_excluded_history: Self::new_search_history(),
667 }
668 })
669 }
670
671 pub fn ssh(
672 ssh: Model<SshRemoteClient>,
673 client: Arc<Client>,
674 node: NodeRuntime,
675 user_store: Model<UserStore>,
676 languages: Arc<LanguageRegistry>,
677 fs: Arc<dyn Fs>,
678 cx: &mut AppContext,
679 ) -> Model<Self> {
680 cx.new_model(|cx: &mut ModelContext<Self>| {
681 let (tx, rx) = mpsc::unbounded();
682 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
683 .detach();
684 let tasks = Inventory::new(cx);
685 let global_snippets_dir = paths::config_dir().join("snippets");
686 let snippets =
687 SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
688
689 let ssh_proto = ssh.read(cx).to_proto_client();
690 let worktree_store =
691 cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), 0, None));
692 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
693 .detach();
694
695 let buffer_store = cx.new_model(|cx| {
696 BufferStore::remote(
697 worktree_store.clone(),
698 ssh.read(cx).to_proto_client(),
699 SSH_PROJECT_ID,
700 cx,
701 )
702 });
703 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
704 .detach();
705
706 let settings_observer = cx.new_model(|cx| {
707 SettingsObserver::new_ssh(ssh_proto.clone(), worktree_store.clone(), cx)
708 });
709 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
710 .detach();
711
712 let environment = ProjectEnvironment::new(&worktree_store, None, cx);
713 let lsp_store = cx.new_model(|cx| {
714 LspStore::new_remote(
715 buffer_store.clone(),
716 worktree_store.clone(),
717 languages.clone(),
718 ssh_proto.clone(),
719 SSH_PROJECT_ID,
720 cx,
721 )
722 });
723 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
724
725 cx.on_release(|this, cx| {
726 if let Some(ssh_client) = this.ssh_client.as_ref() {
727 ssh_client
728 .read(cx)
729 .to_proto_client()
730 .send(proto::ShutdownRemoteServer {})
731 .log_err();
732 }
733 })
734 .detach();
735 cx.observe(&ssh, |_, _, cx| cx.notify()).detach();
736
737 let this = Self {
738 buffer_ordered_messages_tx: tx,
739 collaborators: Default::default(),
740 worktree_store,
741 buffer_store,
742 lsp_store,
743 join_project_response_message_id: 0,
744 client_state: ProjectClientState::Local,
745 client_subscriptions: Vec::new(),
746 _subscriptions: vec![cx.on_release(Self::release)],
747 active_entry: None,
748 snippets,
749 languages,
750 client,
751 user_store,
752 settings_observer,
753 fs,
754 ssh_client: Some(ssh.clone()),
755 buffers_needing_diff: Default::default(),
756 git_diff_debouncer: DebouncedDelay::new(),
757 terminals: Terminals {
758 local_handles: Vec::new(),
759 },
760 node: Some(node),
761 tasks,
762 hosted_project_id: None,
763 dev_server_project_id: None,
764 search_history: Self::new_search_history(),
765 environment,
766 remotely_created_models: Default::default(),
767
768 search_included_history: Self::new_search_history(),
769 search_excluded_history: Self::new_search_history(),
770 };
771
772 let ssh = ssh.read(cx);
773 ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
774 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
775 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
776 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
777 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
778
779 ssh_proto.add_model_message_handler(Self::handle_create_buffer_for_peer);
780 ssh_proto.add_model_message_handler(Self::handle_update_worktree);
781 ssh_proto.add_model_message_handler(Self::handle_update_project);
782 ssh_proto.add_model_request_handler(BufferStore::handle_update_buffer);
783 BufferStore::init(&ssh_proto);
784 LspStore::init(&ssh_proto);
785 SettingsObserver::init(&ssh_proto);
786
787 this
788 })
789 }
790
791 pub async fn remote(
792 remote_id: u64,
793 client: Arc<Client>,
794 user_store: Model<UserStore>,
795 languages: Arc<LanguageRegistry>,
796 fs: Arc<dyn Fs>,
797 cx: AsyncAppContext,
798 ) -> Result<Model<Self>> {
799 let project =
800 Self::in_room(remote_id, client, user_store, languages, fs, cx.clone()).await?;
801 cx.update(|cx| {
802 connection_manager::Manager::global(cx).update(cx, |manager, cx| {
803 manager.maintain_project_connection(&project, cx)
804 })
805 })?;
806 Ok(project)
807 }
808
809 pub async fn in_room(
810 remote_id: u64,
811 client: Arc<Client>,
812 user_store: Model<UserStore>,
813 languages: Arc<LanguageRegistry>,
814 fs: Arc<dyn Fs>,
815 cx: AsyncAppContext,
816 ) -> Result<Model<Self>> {
817 client.authenticate_and_connect(true, &cx).await?;
818
819 let subscriptions = [
820 EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id)?),
821 EntitySubscription::BufferStore(client.subscribe_to_entity::<BufferStore>(remote_id)?),
822 EntitySubscription::WorktreeStore(
823 client.subscribe_to_entity::<WorktreeStore>(remote_id)?,
824 ),
825 EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id)?),
826 EntitySubscription::SettingsObserver(
827 client.subscribe_to_entity::<SettingsObserver>(remote_id)?,
828 ),
829 ];
830 let response = client
831 .request_envelope(proto::JoinProject {
832 project_id: remote_id,
833 })
834 .await?;
835 Self::from_join_project_response(
836 response,
837 subscriptions,
838 client,
839 user_store,
840 languages,
841 fs,
842 cx,
843 )
844 .await
845 }
846
847 async fn from_join_project_response(
848 response: TypedEnvelope<proto::JoinProjectResponse>,
849 subscriptions: [EntitySubscription; 5],
850 client: Arc<Client>,
851 user_store: Model<UserStore>,
852 languages: Arc<LanguageRegistry>,
853 fs: Arc<dyn Fs>,
854 mut cx: AsyncAppContext,
855 ) -> Result<Model<Self>> {
856 let remote_id = response.payload.project_id;
857 let role = response.payload.role();
858
859 let worktree_store = cx.new_model(|_| {
860 WorktreeStore::remote(
861 true,
862 client.clone().into(),
863 response.payload.project_id,
864 response
865 .payload
866 .dev_server_project_id
867 .map(DevServerProjectId),
868 )
869 })?;
870 let buffer_store = cx.new_model(|cx| {
871 BufferStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
872 })?;
873
874 let lsp_store = cx.new_model(|cx| {
875 let mut lsp_store = LspStore::new_remote(
876 buffer_store.clone(),
877 worktree_store.clone(),
878 languages.clone(),
879 client.clone().into(),
880 remote_id,
881 cx,
882 );
883 lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);
884 lsp_store
885 })?;
886
887 let settings_observer =
888 cx.new_model(|cx| SettingsObserver::new_remote(worktree_store.clone(), cx))?;
889
890 let this = cx.new_model(|cx| {
891 let replica_id = response.payload.replica_id as ReplicaId;
892 let tasks = Inventory::new(cx);
893
894 let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
895
896 let mut worktrees = Vec::new();
897 for worktree in response.payload.worktrees {
898 let worktree =
899 Worktree::remote(remote_id, replica_id, worktree, client.clone().into(), cx);
900 worktrees.push(worktree);
901 }
902
903 let (tx, rx) = mpsc::unbounded();
904 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
905 .detach();
906
907 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
908 .detach();
909
910 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
911 .detach();
912 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
913 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
914 .detach();
915
916 let mut this = Self {
917 buffer_ordered_messages_tx: tx,
918 buffer_store: buffer_store.clone(),
919 worktree_store: worktree_store.clone(),
920 lsp_store: lsp_store.clone(),
921 active_entry: None,
922 collaborators: Default::default(),
923 join_project_response_message_id: response.message_id,
924 languages,
925 user_store: user_store.clone(),
926 snippets,
927 fs,
928 ssh_client: None,
929 settings_observer: settings_observer.clone(),
930 client_subscriptions: Default::default(),
931 _subscriptions: vec![cx.on_release(Self::release)],
932 client: client.clone(),
933 client_state: ProjectClientState::Remote {
934 sharing_has_stopped: false,
935 capability: Capability::ReadWrite,
936 remote_id,
937 replica_id,
938 in_room: response.payload.dev_server_project_id.is_none(),
939 },
940 buffers_needing_diff: Default::default(),
941 git_diff_debouncer: DebouncedDelay::new(),
942 terminals: Terminals {
943 local_handles: Vec::new(),
944 },
945 node: None,
946 tasks,
947 hosted_project_id: None,
948 dev_server_project_id: response
949 .payload
950 .dev_server_project_id
951 .map(DevServerProjectId),
952 search_history: Self::new_search_history(),
953 search_included_history: Self::new_search_history(),
954 search_excluded_history: Self::new_search_history(),
955 environment: ProjectEnvironment::new(&worktree_store, None, cx),
956 remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
957 };
958 this.set_role(role, cx);
959 for worktree in worktrees {
960 this.add_worktree(&worktree, cx);
961 }
962 this
963 })?;
964
965 let subscriptions = subscriptions
966 .into_iter()
967 .map(|s| match s {
968 EntitySubscription::BufferStore(subscription) => {
969 subscription.set_model(&buffer_store, &mut cx)
970 }
971 EntitySubscription::WorktreeStore(subscription) => {
972 subscription.set_model(&worktree_store, &mut cx)
973 }
974 EntitySubscription::SettingsObserver(subscription) => {
975 subscription.set_model(&settings_observer, &mut cx)
976 }
977 EntitySubscription::Project(subscription) => subscription.set_model(&this, &mut cx),
978 EntitySubscription::LspStore(subscription) => {
979 subscription.set_model(&lsp_store, &mut cx)
980 }
981 })
982 .collect::<Vec<_>>();
983
984 let user_ids = response
985 .payload
986 .collaborators
987 .iter()
988 .map(|peer| peer.user_id)
989 .collect();
990 user_store
991 .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
992 .await?;
993
994 this.update(&mut cx, |this, cx| {
995 this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
996 this.client_subscriptions.extend(subscriptions);
997 anyhow::Ok(())
998 })??;
999
1000 Ok(this)
1001 }
1002
1003 pub async fn hosted(
1004 remote_id: ProjectId,
1005 user_store: Model<UserStore>,
1006 client: Arc<Client>,
1007 languages: Arc<LanguageRegistry>,
1008 fs: Arc<dyn Fs>,
1009 cx: AsyncAppContext,
1010 ) -> Result<Model<Self>> {
1011 client.authenticate_and_connect(true, &cx).await?;
1012
1013 let subscriptions = [
1014 EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id.0)?),
1015 EntitySubscription::BufferStore(
1016 client.subscribe_to_entity::<BufferStore>(remote_id.0)?,
1017 ),
1018 EntitySubscription::WorktreeStore(
1019 client.subscribe_to_entity::<WorktreeStore>(remote_id.0)?,
1020 ),
1021 EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id.0)?),
1022 EntitySubscription::SettingsObserver(
1023 client.subscribe_to_entity::<SettingsObserver>(remote_id.0)?,
1024 ),
1025 ];
1026 let response = client
1027 .request_envelope(proto::JoinHostedProject {
1028 project_id: remote_id.0,
1029 })
1030 .await?;
1031 Self::from_join_project_response(
1032 response,
1033 subscriptions,
1034 client,
1035 user_store,
1036 languages,
1037 fs,
1038 cx,
1039 )
1040 .await
1041 }
1042
1043 fn new_search_history() -> SearchHistory {
1044 SearchHistory::new(
1045 Some(MAX_PROJECT_SEARCH_HISTORY_SIZE),
1046 search_history::QueryInsertionBehavior::AlwaysInsert,
1047 )
1048 }
1049
1050 fn release(&mut self, cx: &mut AppContext) {
1051 match &self.client_state {
1052 ProjectClientState::Local => {}
1053 ProjectClientState::Shared { .. } => {
1054 let _ = self.unshare_internal(cx);
1055 }
1056 ProjectClientState::Remote { remote_id, .. } => {
1057 let _ = self.client.send(proto::LeaveProject {
1058 project_id: *remote_id,
1059 });
1060 self.disconnected_from_host_internal(cx);
1061 }
1062 }
1063 }
1064
1065 #[cfg(any(test, feature = "test-support"))]
1066 pub async fn example(
1067 root_paths: impl IntoIterator<Item = &Path>,
1068 cx: &mut AsyncAppContext,
1069 ) -> Model<Project> {
1070 use clock::FakeSystemClock;
1071
1072 let fs = Arc::new(RealFs::default());
1073 let languages = LanguageRegistry::test(cx.background_executor().clone());
1074 let clock = Arc::new(FakeSystemClock::default());
1075 let http_client = http_client::FakeHttpClient::with_404_response();
1076 let client = cx
1077 .update(|cx| client::Client::new(clock, http_client.clone(), cx))
1078 .unwrap();
1079 let user_store = cx
1080 .new_model(|cx| UserStore::new(client.clone(), cx))
1081 .unwrap();
1082 let project = cx
1083 .update(|cx| {
1084 Project::local(
1085 client,
1086 node_runtime::NodeRuntime::unavailable(),
1087 user_store,
1088 Arc::new(languages),
1089 fs,
1090 None,
1091 cx,
1092 )
1093 })
1094 .unwrap();
1095 for path in root_paths {
1096 let (tree, _) = project
1097 .update(cx, |project, cx| {
1098 project.find_or_create_worktree(path, true, cx)
1099 })
1100 .unwrap()
1101 .await
1102 .unwrap();
1103 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1104 .unwrap()
1105 .await;
1106 }
1107 project
1108 }
1109
1110 #[cfg(any(test, feature = "test-support"))]
1111 pub async fn test(
1112 fs: Arc<dyn Fs>,
1113 root_paths: impl IntoIterator<Item = &Path>,
1114 cx: &mut gpui::TestAppContext,
1115 ) -> Model<Project> {
1116 use clock::FakeSystemClock;
1117
1118 let languages = LanguageRegistry::test(cx.executor());
1119 let clock = Arc::new(FakeSystemClock::default());
1120 let http_client = http_client::FakeHttpClient::with_404_response();
1121 let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
1122 let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
1123 let project = cx.update(|cx| {
1124 Project::local(
1125 client,
1126 node_runtime::NodeRuntime::unavailable(),
1127 user_store,
1128 Arc::new(languages),
1129 fs,
1130 None,
1131 cx,
1132 )
1133 });
1134 for path in root_paths {
1135 let (tree, _) = project
1136 .update(cx, |project, cx| {
1137 project.find_or_create_worktree(path, true, cx)
1138 })
1139 .await
1140 .unwrap();
1141
1142 project.update(cx, |project, cx| {
1143 let tree_id = tree.read(cx).id();
1144 project.environment.update(cx, |environment, _| {
1145 environment.set_cached(&[(tree_id, HashMap::default())])
1146 });
1147 });
1148
1149 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1150 .await;
1151 }
1152 project
1153 }
1154
1155 pub fn lsp_store(&self) -> Model<LspStore> {
1156 self.lsp_store.clone()
1157 }
1158
1159 pub fn worktree_store(&self) -> Model<WorktreeStore> {
1160 self.worktree_store.clone()
1161 }
1162
1163 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {
1164 self.buffer_store.read(cx).get(remote_id)
1165 }
1166
1167 pub fn languages(&self) -> &Arc<LanguageRegistry> {
1168 &self.languages
1169 }
1170
1171 pub fn client(&self) -> Arc<Client> {
1172 self.client.clone()
1173 }
1174
1175 pub fn user_store(&self) -> Model<UserStore> {
1176 self.user_store.clone()
1177 }
1178
1179 pub fn node_runtime(&self) -> Option<&NodeRuntime> {
1180 self.node.as_ref()
1181 }
1182
1183 pub fn opened_buffers(&self, cx: &AppContext) -> Vec<Model<Buffer>> {
1184 self.buffer_store.read(cx).buffers().collect()
1185 }
1186
1187 pub fn cli_environment(&self, cx: &AppContext) -> Option<HashMap<String, String>> {
1188 self.environment.read(cx).get_cli_environment()
1189 }
1190
1191 pub fn shell_environment_errors<'a>(
1192 &'a self,
1193 cx: &'a AppContext,
1194 ) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
1195 self.environment.read(cx).environment_errors()
1196 }
1197
1198 pub fn remove_environment_error(
1199 &mut self,
1200 cx: &mut ModelContext<Self>,
1201 worktree_id: WorktreeId,
1202 ) {
1203 self.environment.update(cx, |environment, _| {
1204 environment.remove_environment_error(worktree_id);
1205 });
1206 }
1207
1208 #[cfg(any(test, feature = "test-support"))]
1209 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
1210 self.buffer_store
1211 .read(cx)
1212 .get_by_path(&path.into(), cx)
1213 .is_some()
1214 }
1215
1216 pub fn fs(&self) -> &Arc<dyn Fs> {
1217 &self.fs
1218 }
1219
1220 pub fn remote_id(&self) -> Option<u64> {
1221 match self.client_state {
1222 ProjectClientState::Local => None,
1223 ProjectClientState::Shared { remote_id, .. }
1224 | ProjectClientState::Remote { remote_id, .. } => Some(remote_id),
1225 }
1226 }
1227
1228 pub fn hosted_project_id(&self) -> Option<ProjectId> {
1229 self.hosted_project_id
1230 }
1231
1232 pub fn dev_server_project_id(&self) -> Option<DevServerProjectId> {
1233 self.dev_server_project_id
1234 }
1235
1236 pub fn supports_terminal(&self, cx: &AppContext) -> bool {
1237 if self.is_local() {
1238 return true;
1239 }
1240 if self.is_via_ssh() {
1241 return true;
1242 }
1243 let Some(id) = self.dev_server_project_id else {
1244 return false;
1245 };
1246 let Some(server) = dev_server_projects::Store::global(cx)
1247 .read(cx)
1248 .dev_server_for_project(id)
1249 else {
1250 return false;
1251 };
1252 server.ssh_connection_string.is_some()
1253 }
1254
1255 pub fn ssh_connection_string(&self, cx: &AppContext) -> Option<SharedString> {
1256 if let Some(ssh_state) = &self.ssh_client {
1257 return Some(ssh_state.read(cx).connection_string().into());
1258 }
1259 let dev_server_id = self.dev_server_project_id()?;
1260 dev_server_projects::Store::global(cx)
1261 .read(cx)
1262 .dev_server_for_project(dev_server_id)?
1263 .ssh_connection_string
1264 .clone()
1265 }
1266
1267 pub fn ssh_connection_state(&self, cx: &AppContext) -> Option<remote::ConnectionState> {
1268 self.ssh_client
1269 .as_ref()
1270 .map(|ssh| ssh.read(cx).connection_state())
1271 }
1272
1273 pub fn replica_id(&self) -> ReplicaId {
1274 match self.client_state {
1275 ProjectClientState::Remote { replica_id, .. } => replica_id,
1276 _ => {
1277 if self.ssh_client.is_some() {
1278 1
1279 } else {
1280 0
1281 }
1282 }
1283 }
1284 }
1285
1286 pub fn task_inventory(&self) -> &Model<Inventory> {
1287 &self.tasks
1288 }
1289
1290 pub fn snippets(&self) -> &Model<SnippetProvider> {
1291 &self.snippets
1292 }
1293
1294 pub fn search_history(&self, kind: SearchInputKind) -> &SearchHistory {
1295 match kind {
1296 SearchInputKind::Query => &self.search_history,
1297 SearchInputKind::Include => &self.search_included_history,
1298 SearchInputKind::Exclude => &self.search_excluded_history,
1299 }
1300 }
1301
1302 pub fn search_history_mut(&mut self, kind: SearchInputKind) -> &mut SearchHistory {
1303 match kind {
1304 SearchInputKind::Query => &mut self.search_history,
1305 SearchInputKind::Include => &mut self.search_included_history,
1306 SearchInputKind::Exclude => &mut self.search_excluded_history,
1307 }
1308 }
1309
1310 pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
1311 &self.collaborators
1312 }
1313
1314 pub fn host(&self) -> Option<&Collaborator> {
1315 self.collaborators.values().find(|c| c.replica_id == 0)
1316 }
1317
1318 pub fn set_worktrees_reordered(&mut self, worktrees_reordered: bool, cx: &mut AppContext) {
1319 self.worktree_store.update(cx, |store, _| {
1320 store.set_worktrees_reordered(worktrees_reordered);
1321 });
1322 }
1323
1324 /// Collect all worktrees, including ones that don't appear in the project panel
1325 pub fn worktrees<'a>(
1326 &self,
1327 cx: &'a AppContext,
1328 ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
1329 self.worktree_store.read(cx).worktrees()
1330 }
1331
1332 /// Collect all user-visible worktrees, the ones that appear in the project panel.
1333 pub fn visible_worktrees<'a>(
1334 &'a self,
1335 cx: &'a AppContext,
1336 ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
1337 self.worktree_store.read(cx).visible_worktrees(cx)
1338 }
1339
1340 pub fn worktree_root_names<'a>(&'a self, cx: &'a AppContext) -> impl Iterator<Item = &'a str> {
1341 self.visible_worktrees(cx)
1342 .map(|tree| tree.read(cx).root_name())
1343 }
1344
1345 pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
1346 self.worktree_store.read(cx).worktree_for_id(id, cx)
1347 }
1348
1349 pub fn worktree_for_entry(
1350 &self,
1351 entry_id: ProjectEntryId,
1352 cx: &AppContext,
1353 ) -> Option<Model<Worktree>> {
1354 self.worktree_store
1355 .read(cx)
1356 .worktree_for_entry(entry_id, cx)
1357 }
1358
1359 pub fn worktree_id_for_entry(
1360 &self,
1361 entry_id: ProjectEntryId,
1362 cx: &AppContext,
1363 ) -> Option<WorktreeId> {
1364 self.worktree_for_entry(entry_id, cx)
1365 .map(|worktree| worktree.read(cx).id())
1366 }
1367
1368 /// Checks if the entry is the root of a worktree.
1369 pub fn entry_is_worktree_root(&self, entry_id: ProjectEntryId, cx: &AppContext) -> bool {
1370 self.worktree_for_entry(entry_id, cx)
1371 .map(|worktree| {
1372 worktree
1373 .read(cx)
1374 .root_entry()
1375 .is_some_and(|e| e.id == entry_id)
1376 })
1377 .unwrap_or(false)
1378 }
1379
1380 pub fn visibility_for_paths(&self, paths: &[PathBuf], cx: &AppContext) -> Option<bool> {
1381 paths
1382 .iter()
1383 .map(|path| self.visibility_for_path(path, cx))
1384 .max()
1385 .flatten()
1386 }
1387
1388 pub fn visibility_for_path(&self, path: &Path, cx: &AppContext) -> Option<bool> {
1389 self.worktrees(cx)
1390 .filter_map(|worktree| {
1391 let worktree = worktree.read(cx);
1392 worktree
1393 .as_local()?
1394 .contains_abs_path(path)
1395 .then(|| worktree.is_visible())
1396 })
1397 .max()
1398 }
1399
1400 pub fn create_entry(
1401 &mut self,
1402 project_path: impl Into<ProjectPath>,
1403 is_directory: bool,
1404 cx: &mut ModelContext<Self>,
1405 ) -> Task<Result<CreatedEntry>> {
1406 let project_path = project_path.into();
1407 let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
1408 return Task::ready(Err(anyhow!(format!(
1409 "No worktree for path {project_path:?}"
1410 ))));
1411 };
1412 worktree.update(cx, |worktree, cx| {
1413 worktree.create_entry(project_path.path, is_directory, cx)
1414 })
1415 }
1416
1417 pub fn copy_entry(
1418 &mut self,
1419 entry_id: ProjectEntryId,
1420 relative_worktree_source_path: Option<PathBuf>,
1421 new_path: impl Into<Arc<Path>>,
1422 cx: &mut ModelContext<Self>,
1423 ) -> Task<Result<Option<Entry>>> {
1424 let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
1425 return Task::ready(Ok(None));
1426 };
1427 worktree.update(cx, |worktree, cx| {
1428 worktree.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
1429 })
1430 }
1431
1432 pub fn rename_entry(
1433 &mut self,
1434 entry_id: ProjectEntryId,
1435 new_path: impl Into<Arc<Path>>,
1436 cx: &mut ModelContext<Self>,
1437 ) -> Task<Result<CreatedEntry>> {
1438 let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
1439 return Task::ready(Err(anyhow!(format!("No worktree for entry {entry_id:?}"))));
1440 };
1441 worktree.update(cx, |worktree, cx| {
1442 worktree.rename_entry(entry_id, new_path, cx)
1443 })
1444 }
1445
1446 pub fn delete_entry(
1447 &mut self,
1448 entry_id: ProjectEntryId,
1449 trash: bool,
1450 cx: &mut ModelContext<Self>,
1451 ) -> Option<Task<Result<()>>> {
1452 let worktree = self.worktree_for_entry(entry_id, cx)?;
1453 worktree.update(cx, |worktree, cx| {
1454 worktree.delete_entry(entry_id, trash, cx)
1455 })
1456 }
1457
1458 pub fn expand_entry(
1459 &mut self,
1460 worktree_id: WorktreeId,
1461 entry_id: ProjectEntryId,
1462 cx: &mut ModelContext<Self>,
1463 ) -> Option<Task<Result<()>>> {
1464 let worktree = self.worktree_for_id(worktree_id, cx)?;
1465 worktree.update(cx, |worktree, cx| worktree.expand_entry(entry_id, cx))
1466 }
1467
1468 pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
1469 if !matches!(self.client_state, ProjectClientState::Local) {
1470 if let ProjectClientState::Remote { in_room, .. } = &mut self.client_state {
1471 if *in_room || self.dev_server_project_id.is_none() {
1472 return Err(anyhow!("project was already shared"));
1473 } else {
1474 *in_room = true;
1475 return Ok(());
1476 }
1477 } else {
1478 return Err(anyhow!("project was already shared"));
1479 }
1480 }
1481 self.client_subscriptions.extend([
1482 self.client
1483 .subscribe_to_entity(project_id)?
1484 .set_model(&cx.handle(), &mut cx.to_async()),
1485 self.client
1486 .subscribe_to_entity(project_id)?
1487 .set_model(&self.worktree_store, &mut cx.to_async()),
1488 self.client
1489 .subscribe_to_entity(project_id)?
1490 .set_model(&self.buffer_store, &mut cx.to_async()),
1491 self.client
1492 .subscribe_to_entity(project_id)?
1493 .set_model(&self.lsp_store, &mut cx.to_async()),
1494 self.client
1495 .subscribe_to_entity(project_id)?
1496 .set_model(&self.settings_observer, &mut cx.to_async()),
1497 ]);
1498
1499 self.buffer_store.update(cx, |buffer_store, cx| {
1500 buffer_store.shared(project_id, self.client.clone().into(), cx)
1501 });
1502 self.worktree_store.update(cx, |worktree_store, cx| {
1503 worktree_store.shared(project_id, self.client.clone().into(), cx);
1504 });
1505 self.lsp_store.update(cx, |lsp_store, cx| {
1506 lsp_store.shared(project_id, self.client.clone().into(), cx)
1507 });
1508 self.settings_observer.update(cx, |settings_observer, cx| {
1509 settings_observer.shared(project_id, self.client.clone().into(), cx)
1510 });
1511
1512 self.client_state = ProjectClientState::Shared {
1513 remote_id: project_id,
1514 };
1515
1516 cx.emit(Event::RemoteIdChanged(Some(project_id)));
1517 cx.notify();
1518 Ok(())
1519 }
1520
1521 pub fn reshared(
1522 &mut self,
1523 message: proto::ResharedProject,
1524 cx: &mut ModelContext<Self>,
1525 ) -> Result<()> {
1526 self.buffer_store
1527 .update(cx, |buffer_store, _| buffer_store.forget_shared_buffers());
1528 self.set_collaborators_from_proto(message.collaborators, cx)?;
1529
1530 self.worktree_store.update(cx, |worktree_store, cx| {
1531 worktree_store.send_project_updates(cx);
1532 });
1533 cx.notify();
1534 cx.emit(Event::Reshared);
1535 Ok(())
1536 }
1537
1538 pub fn rejoined(
1539 &mut self,
1540 message: proto::RejoinedProject,
1541 message_id: u32,
1542 cx: &mut ModelContext<Self>,
1543 ) -> Result<()> {
1544 cx.update_global::<SettingsStore, _>(|store, cx| {
1545 self.worktree_store.update(cx, |worktree_store, cx| {
1546 for worktree in worktree_store.worktrees() {
1547 store
1548 .clear_local_settings(worktree.read(cx).id(), cx)
1549 .log_err();
1550 }
1551 });
1552 });
1553
1554 self.join_project_response_message_id = message_id;
1555 self.set_worktrees_from_proto(message.worktrees, cx)?;
1556 self.set_collaborators_from_proto(message.collaborators, cx)?;
1557 self.lsp_store.update(cx, |lsp_store, _| {
1558 lsp_store.set_language_server_statuses_from_proto(message.language_servers)
1559 });
1560 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
1561 .unwrap();
1562 cx.emit(Event::Rejoined);
1563 cx.notify();
1564 Ok(())
1565 }
1566
1567 pub fn unshare(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
1568 self.unshare_internal(cx)?;
1569 cx.notify();
1570 Ok(())
1571 }
1572
1573 fn unshare_internal(&mut self, cx: &mut AppContext) -> Result<()> {
1574 if self.is_via_collab() {
1575 if self.dev_server_project_id().is_some() {
1576 if let ProjectClientState::Remote { in_room, .. } = &mut self.client_state {
1577 *in_room = false
1578 }
1579 return Ok(());
1580 } else {
1581 return Err(anyhow!("attempted to unshare a remote project"));
1582 }
1583 }
1584
1585 if let ProjectClientState::Shared { remote_id, .. } = self.client_state {
1586 self.client_state = ProjectClientState::Local;
1587 self.collaborators.clear();
1588 self.client_subscriptions.clear();
1589 self.worktree_store.update(cx, |store, cx| {
1590 store.unshared(cx);
1591 });
1592 self.buffer_store.update(cx, |buffer_store, cx| {
1593 buffer_store.forget_shared_buffers();
1594 buffer_store.unshared(cx)
1595 });
1596 self.settings_observer.update(cx, |settings_observer, cx| {
1597 settings_observer.unshared(cx);
1598 });
1599 self.client
1600 .send(proto::UnshareProject {
1601 project_id: remote_id,
1602 })
1603 .ok();
1604 Ok(())
1605 } else {
1606 Err(anyhow!("attempted to unshare an unshared project"))
1607 }
1608 }
1609
1610 pub fn disconnected_from_host(&mut self, cx: &mut ModelContext<Self>) {
1611 if self.is_disconnected() {
1612 return;
1613 }
1614 self.disconnected_from_host_internal(cx);
1615 cx.emit(Event::DisconnectedFromHost);
1616 cx.notify();
1617 }
1618
1619 pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut ModelContext<Self>) {
1620 let new_capability =
1621 if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin {
1622 Capability::ReadWrite
1623 } else {
1624 Capability::ReadOnly
1625 };
1626 if let ProjectClientState::Remote { capability, .. } = &mut self.client_state {
1627 if *capability == new_capability {
1628 return;
1629 }
1630
1631 *capability = new_capability;
1632 for buffer in self.opened_buffers(cx) {
1633 buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx));
1634 }
1635 }
1636 }
1637
1638 fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) {
1639 if let ProjectClientState::Remote {
1640 sharing_has_stopped,
1641 ..
1642 } = &mut self.client_state
1643 {
1644 *sharing_has_stopped = true;
1645 self.collaborators.clear();
1646 self.worktree_store.update(cx, |store, cx| {
1647 store.disconnected_from_host(cx);
1648 });
1649 self.buffer_store.update(cx, |buffer_store, cx| {
1650 buffer_store.disconnected_from_host(cx)
1651 });
1652 self.lsp_store
1653 .update(cx, |lsp_store, _cx| lsp_store.disconnected_from_host());
1654 }
1655 }
1656
1657 pub fn close(&mut self, cx: &mut ModelContext<Self>) {
1658 cx.emit(Event::Closed);
1659 }
1660
1661 pub fn is_disconnected(&self) -> bool {
1662 match &self.client_state {
1663 ProjectClientState::Remote {
1664 sharing_has_stopped,
1665 ..
1666 } => *sharing_has_stopped,
1667 _ => false,
1668 }
1669 }
1670
1671 pub fn capability(&self) -> Capability {
1672 match &self.client_state {
1673 ProjectClientState::Remote { capability, .. } => *capability,
1674 ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite,
1675 }
1676 }
1677
1678 pub fn is_read_only(&self) -> bool {
1679 self.is_disconnected() || self.capability() == Capability::ReadOnly
1680 }
1681
1682 pub fn is_local(&self) -> bool {
1683 match &self.client_state {
1684 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
1685 self.ssh_client.is_none()
1686 }
1687 ProjectClientState::Remote { .. } => false,
1688 }
1689 }
1690
1691 pub fn is_via_ssh(&self) -> bool {
1692 match &self.client_state {
1693 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
1694 self.ssh_client.is_some()
1695 }
1696 ProjectClientState::Remote { .. } => false,
1697 }
1698 }
1699
1700 pub fn is_via_collab(&self) -> bool {
1701 match &self.client_state {
1702 ProjectClientState::Local | ProjectClientState::Shared { .. } => false,
1703 ProjectClientState::Remote { .. } => true,
1704 }
1705 }
1706
1707 pub fn create_buffer(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<Model<Buffer>>> {
1708 self.buffer_store
1709 .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))
1710 }
1711
1712 pub fn create_local_buffer(
1713 &mut self,
1714 text: &str,
1715 language: Option<Arc<Language>>,
1716 cx: &mut ModelContext<Self>,
1717 ) -> Model<Buffer> {
1718 if self.is_via_collab() || self.is_via_ssh() {
1719 panic!("called create_local_buffer on a remote project")
1720 }
1721 self.buffer_store.update(cx, |buffer_store, cx| {
1722 buffer_store.create_local_buffer(text, language, cx)
1723 })
1724 }
1725
1726 pub fn open_path(
1727 &mut self,
1728 path: ProjectPath,
1729 cx: &mut ModelContext<Self>,
1730 ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
1731 let task = self.open_buffer(path.clone(), cx);
1732 cx.spawn(move |_, cx| async move {
1733 let buffer = task.await?;
1734 let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
1735 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
1736 })?;
1737
1738 let buffer: &AnyModel = &buffer;
1739 Ok((project_entry_id, buffer.clone()))
1740 })
1741 }
1742
1743 pub fn open_local_buffer(
1744 &mut self,
1745 abs_path: impl AsRef<Path>,
1746 cx: &mut ModelContext<Self>,
1747 ) -> Task<Result<Model<Buffer>>> {
1748 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
1749 self.open_buffer((worktree.read(cx).id(), relative_path), cx)
1750 } else {
1751 Task::ready(Err(anyhow!("no such path")))
1752 }
1753 }
1754
1755 pub fn open_buffer(
1756 &mut self,
1757 path: impl Into<ProjectPath>,
1758 cx: &mut ModelContext<Self>,
1759 ) -> Task<Result<Model<Buffer>>> {
1760 if self.is_via_collab() && self.is_disconnected() {
1761 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
1762 }
1763
1764 self.buffer_store.update(cx, |buffer_store, cx| {
1765 buffer_store.open_buffer(path.into(), cx)
1766 })
1767 }
1768
1769 pub fn open_buffer_by_id(
1770 &mut self,
1771 id: BufferId,
1772 cx: &mut ModelContext<Self>,
1773 ) -> Task<Result<Model<Buffer>>> {
1774 if let Some(buffer) = self.buffer_for_id(id, cx) {
1775 Task::ready(Ok(buffer))
1776 } else if self.is_local() || self.is_via_ssh() {
1777 Task::ready(Err(anyhow!("buffer {} does not exist", id)))
1778 } else if let Some(project_id) = self.remote_id() {
1779 let request = self.client.request(proto::OpenBufferById {
1780 project_id,
1781 id: id.into(),
1782 });
1783 cx.spawn(move |this, mut cx| async move {
1784 let buffer_id = BufferId::new(request.await?.buffer_id)?;
1785 this.update(&mut cx, |this, cx| {
1786 this.wait_for_remote_buffer(buffer_id, cx)
1787 })?
1788 .await
1789 })
1790 } else {
1791 Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
1792 }
1793 }
1794
1795 pub fn save_buffers(
1796 &self,
1797 buffers: HashSet<Model<Buffer>>,
1798 cx: &mut ModelContext<Self>,
1799 ) -> Task<Result<()>> {
1800 cx.spawn(move |this, mut cx| async move {
1801 let save_tasks = buffers.into_iter().filter_map(|buffer| {
1802 this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
1803 .ok()
1804 });
1805 try_join_all(save_tasks).await?;
1806 Ok(())
1807 })
1808 }
1809
1810 pub fn save_buffer(
1811 &self,
1812 buffer: Model<Buffer>,
1813 cx: &mut ModelContext<Self>,
1814 ) -> Task<Result<()>> {
1815 self.buffer_store
1816 .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
1817 }
1818
1819 pub fn save_buffer_as(
1820 &mut self,
1821 buffer: Model<Buffer>,
1822 path: ProjectPath,
1823 cx: &mut ModelContext<Self>,
1824 ) -> Task<Result<()>> {
1825 self.buffer_store.update(cx, |buffer_store, cx| {
1826 buffer_store.save_buffer_as(buffer.clone(), path, cx)
1827 })
1828 }
1829
1830 pub fn get_open_buffer(
1831 &mut self,
1832 path: &ProjectPath,
1833 cx: &mut ModelContext<Self>,
1834 ) -> Option<Model<Buffer>> {
1835 self.buffer_store.read(cx).get_by_path(path, cx)
1836 }
1837
1838 fn register_buffer(
1839 &mut self,
1840 buffer: &Model<Buffer>,
1841 cx: &mut ModelContext<Self>,
1842 ) -> Result<()> {
1843 {
1844 let mut remotely_created_models = self.remotely_created_models.lock();
1845 if remotely_created_models.retain_count > 0 {
1846 remotely_created_models.buffers.push(buffer.clone())
1847 }
1848 }
1849
1850 self.request_buffer_diff_recalculation(buffer, cx);
1851
1852 cx.subscribe(buffer, |this, buffer, event, cx| {
1853 this.on_buffer_event(buffer, event, cx);
1854 })
1855 .detach();
1856
1857 Ok(())
1858 }
1859
1860 async fn send_buffer_ordered_messages(
1861 this: WeakModel<Self>,
1862 rx: UnboundedReceiver<BufferOrderedMessage>,
1863 mut cx: AsyncAppContext,
1864 ) -> Result<()> {
1865 const MAX_BATCH_SIZE: usize = 128;
1866
1867 let mut operations_by_buffer_id = HashMap::default();
1868 async fn flush_operations(
1869 this: &WeakModel<Project>,
1870 operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
1871 needs_resync_with_host: &mut bool,
1872 is_local: bool,
1873 cx: &mut AsyncAppContext,
1874 ) -> Result<()> {
1875 for (buffer_id, operations) in operations_by_buffer_id.drain() {
1876 let request = this.update(cx, |this, _| {
1877 let project_id = this.remote_id()?;
1878 Some(this.client.request(proto::UpdateBuffer {
1879 buffer_id: buffer_id.into(),
1880 project_id,
1881 operations,
1882 }))
1883 })?;
1884 if let Some(request) = request {
1885 if request.await.is_err() && !is_local {
1886 *needs_resync_with_host = true;
1887 break;
1888 }
1889 }
1890 }
1891 Ok(())
1892 }
1893
1894 let mut needs_resync_with_host = false;
1895 let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
1896
1897 while let Some(changes) = changes.next().await {
1898 let is_local = this.update(&mut cx, |this, _| this.is_local())?;
1899
1900 for change in changes {
1901 match change {
1902 BufferOrderedMessage::Operation {
1903 buffer_id,
1904 operation,
1905 } => {
1906 if needs_resync_with_host {
1907 continue;
1908 }
1909
1910 operations_by_buffer_id
1911 .entry(buffer_id)
1912 .or_insert(Vec::new())
1913 .push(operation);
1914 }
1915
1916 BufferOrderedMessage::Resync => {
1917 operations_by_buffer_id.clear();
1918 if this
1919 .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))?
1920 .await
1921 .is_ok()
1922 {
1923 needs_resync_with_host = false;
1924 }
1925 }
1926
1927 BufferOrderedMessage::LanguageServerUpdate {
1928 language_server_id,
1929 message,
1930 } => {
1931 flush_operations(
1932 &this,
1933 &mut operations_by_buffer_id,
1934 &mut needs_resync_with_host,
1935 is_local,
1936 &mut cx,
1937 )
1938 .await?;
1939
1940 this.update(&mut cx, |this, _| {
1941 if let Some(project_id) = this.remote_id() {
1942 this.client
1943 .send(proto::UpdateLanguageServer {
1944 project_id,
1945 language_server_id: language_server_id.0 as u64,
1946 variant: Some(message),
1947 })
1948 .log_err();
1949 }
1950 })?;
1951 }
1952 }
1953 }
1954
1955 flush_operations(
1956 &this,
1957 &mut operations_by_buffer_id,
1958 &mut needs_resync_with_host,
1959 is_local,
1960 &mut cx,
1961 )
1962 .await?;
1963 }
1964
1965 Ok(())
1966 }
1967
1968 fn on_buffer_store_event(
1969 &mut self,
1970 _: Model<BufferStore>,
1971 event: &BufferStoreEvent,
1972 cx: &mut ModelContext<Self>,
1973 ) {
1974 match event {
1975 BufferStoreEvent::BufferAdded(buffer) => {
1976 self.register_buffer(buffer, cx).log_err();
1977 }
1978 BufferStoreEvent::BufferChangedFilePath { .. } => {}
1979 BufferStoreEvent::BufferDropped(buffer_id) => {
1980 if let Some(ref ssh_client) = self.ssh_client {
1981 ssh_client
1982 .read(cx)
1983 .to_proto_client()
1984 .send(proto::CloseBuffer {
1985 project_id: 0,
1986 buffer_id: buffer_id.to_proto(),
1987 })
1988 .log_err();
1989 }
1990 }
1991 }
1992 }
1993
1994 fn on_lsp_store_event(
1995 &mut self,
1996 _: Model<LspStore>,
1997 event: &LspStoreEvent,
1998 cx: &mut ModelContext<Self>,
1999 ) {
2000 match event {
2001 LspStoreEvent::DiagnosticsUpdated {
2002 language_server_id,
2003 path,
2004 } => cx.emit(Event::DiagnosticsUpdated {
2005 path: path.clone(),
2006 language_server_id: *language_server_id,
2007 }),
2008 LspStoreEvent::LanguageServerAdded(language_server_id) => {
2009 cx.emit(Event::LanguageServerAdded(*language_server_id))
2010 }
2011 LspStoreEvent::LanguageServerRemoved(language_server_id) => {
2012 cx.emit(Event::LanguageServerRemoved(*language_server_id))
2013 }
2014 LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
2015 Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
2016 ),
2017 LspStoreEvent::LanguageDetected {
2018 buffer,
2019 new_language,
2020 } => {
2021 let Some(_) = new_language else {
2022 cx.emit(Event::LanguageNotFound(buffer.clone()));
2023 return;
2024 };
2025 }
2026 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
2027 LspStoreEvent::LanguageServerPrompt(prompt) => {
2028 cx.emit(Event::LanguageServerPrompt(prompt.clone()))
2029 }
2030 LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
2031 cx.emit(Event::DiskBasedDiagnosticsStarted {
2032 language_server_id: *language_server_id,
2033 });
2034 }
2035 LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
2036 cx.emit(Event::DiskBasedDiagnosticsFinished {
2037 language_server_id: *language_server_id,
2038 });
2039 }
2040 LspStoreEvent::LanguageServerUpdate {
2041 language_server_id,
2042 message,
2043 } => {
2044 if self.is_local() {
2045 self.enqueue_buffer_ordered_message(
2046 BufferOrderedMessage::LanguageServerUpdate {
2047 language_server_id: *language_server_id,
2048 message: message.clone(),
2049 },
2050 )
2051 .ok();
2052 }
2053 }
2054 LspStoreEvent::Notification(message) => cx.emit(Event::Notification(message.clone())),
2055 LspStoreEvent::SnippetEdit {
2056 buffer_id,
2057 edits,
2058 most_recent_edit,
2059 } => {
2060 if most_recent_edit.replica_id == self.replica_id() {
2061 cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
2062 }
2063 }
2064 }
2065 }
2066
2067 fn on_settings_observer_event(
2068 &mut self,
2069 _: Model<SettingsObserver>,
2070 event: &SettingsObserverEvent,
2071 cx: &mut ModelContext<Self>,
2072 ) {
2073 match event {
2074 SettingsObserverEvent::LocalSettingsUpdated(error) => {
2075 cx.emit(Event::LocalSettingsUpdated(error.clone()))
2076 }
2077 }
2078 }
2079
2080 fn on_worktree_store_event(
2081 &mut self,
2082 _: Model<WorktreeStore>,
2083 event: &WorktreeStoreEvent,
2084 cx: &mut ModelContext<Self>,
2085 ) {
2086 match event {
2087 WorktreeStoreEvent::WorktreeAdded(worktree) => {
2088 self.on_worktree_added(worktree, cx);
2089 cx.emit(Event::WorktreeAdded);
2090 }
2091 WorktreeStoreEvent::WorktreeRemoved(_, id) => {
2092 self.on_worktree_removed(*id, cx);
2093 cx.emit(Event::WorktreeRemoved(*id));
2094 }
2095 WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
2096 WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
2097 }
2098 }
2099
2100 fn on_worktree_added(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
2101 {
2102 let mut remotely_created_models = self.remotely_created_models.lock();
2103 if remotely_created_models.retain_count > 0 {
2104 remotely_created_models.worktrees.push(worktree.clone())
2105 }
2106 }
2107 cx.observe(worktree, |_, _, cx| cx.notify()).detach();
2108 cx.subscribe(worktree, |this, worktree, event, cx| {
2109 let is_local = worktree.read(cx).is_local();
2110 match event {
2111 worktree::Event::UpdatedEntries(changes) => {
2112 if is_local {
2113 this.update_local_worktree_settings(&worktree, changes, cx);
2114 }
2115
2116 cx.emit(Event::WorktreeUpdatedEntries(
2117 worktree.read(cx).id(),
2118 changes.clone(),
2119 ));
2120
2121 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
2122 this.client()
2123 .telemetry()
2124 .report_discovered_project_events(worktree_id, changes);
2125 }
2126 worktree::Event::UpdatedGitRepositories(_) => {
2127 cx.emit(Event::WorktreeUpdatedGitRepositories);
2128 }
2129 worktree::Event::DeletedEntry(id) => cx.emit(Event::DeletedEntry(*id)),
2130 }
2131 })
2132 .detach();
2133 cx.notify();
2134 }
2135
2136 fn on_worktree_removed(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
2137 if let Some(dev_server_project_id) = self.dev_server_project_id {
2138 let paths: Vec<String> = self
2139 .visible_worktrees(cx)
2140 .filter_map(|worktree| {
2141 if worktree.read(cx).id() == id_to_remove {
2142 None
2143 } else {
2144 Some(worktree.read(cx).abs_path().to_string_lossy().to_string())
2145 }
2146 })
2147 .collect();
2148 if !paths.is_empty() {
2149 let request = self.client.request(proto::UpdateDevServerProject {
2150 dev_server_project_id: dev_server_project_id.0,
2151 paths,
2152 });
2153 cx.background_executor()
2154 .spawn(request)
2155 .detach_and_log_err(cx);
2156 }
2157 return;
2158 }
2159
2160 self.task_inventory().update(cx, |inventory, _| {
2161 inventory.remove_worktree_sources(id_to_remove);
2162 });
2163
2164 cx.notify();
2165 }
2166
2167 fn on_buffer_event(
2168 &mut self,
2169 buffer: Model<Buffer>,
2170 event: &BufferEvent,
2171 cx: &mut ModelContext<Self>,
2172 ) -> Option<()> {
2173 if matches!(
2174 event,
2175 BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
2176 ) {
2177 self.request_buffer_diff_recalculation(&buffer, cx);
2178 }
2179
2180 let buffer_id = buffer.read(cx).remote_id();
2181 match event {
2182 BufferEvent::Operation {
2183 operation,
2184 is_local: true,
2185 } => {
2186 let operation = language::proto::serialize_operation(operation);
2187
2188 if let Some(ssh) = &self.ssh_client {
2189 ssh.read(cx)
2190 .to_proto_client()
2191 .send(proto::UpdateBuffer {
2192 project_id: 0,
2193 buffer_id: buffer_id.to_proto(),
2194 operations: vec![operation.clone()],
2195 })
2196 .ok();
2197 }
2198
2199 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
2200 buffer_id,
2201 operation,
2202 })
2203 .ok();
2204 }
2205
2206 _ => {}
2207 }
2208
2209 None
2210 }
2211
2212 fn request_buffer_diff_recalculation(
2213 &mut self,
2214 buffer: &Model<Buffer>,
2215 cx: &mut ModelContext<Self>,
2216 ) {
2217 self.buffers_needing_diff.insert(buffer.downgrade());
2218 let first_insertion = self.buffers_needing_diff.len() == 1;
2219
2220 let settings = ProjectSettings::get_global(cx);
2221 let delay = if let Some(delay) = settings.git.gutter_debounce {
2222 delay
2223 } else {
2224 if first_insertion {
2225 let this = cx.weak_model();
2226 cx.defer(move |cx| {
2227 if let Some(this) = this.upgrade() {
2228 this.update(cx, |this, cx| {
2229 this.recalculate_buffer_diffs(cx).detach();
2230 });
2231 }
2232 });
2233 }
2234 return;
2235 };
2236
2237 const MIN_DELAY: u64 = 50;
2238 let delay = delay.max(MIN_DELAY);
2239 let duration = Duration::from_millis(delay);
2240
2241 self.git_diff_debouncer
2242 .fire_new(duration, cx, move |this, cx| {
2243 this.recalculate_buffer_diffs(cx)
2244 });
2245 }
2246
2247 fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
2248 let buffers = self.buffers_needing_diff.drain().collect::<Vec<_>>();
2249 cx.spawn(move |this, mut cx| async move {
2250 let tasks: Vec<_> = buffers
2251 .iter()
2252 .filter_map(|buffer| {
2253 let buffer = buffer.upgrade()?;
2254 buffer
2255 .update(&mut cx, |buffer, cx| buffer.recalculate_diff(cx))
2256 .ok()
2257 .flatten()
2258 })
2259 .collect();
2260
2261 futures::future::join_all(tasks).await;
2262
2263 this.update(&mut cx, |this, cx| {
2264 if this.buffers_needing_diff.is_empty() {
2265 // TODO: Would a `ModelContext<Project>.notify()` suffice here?
2266 for buffer in buffers {
2267 if let Some(buffer) = buffer.upgrade() {
2268 buffer.update(cx, |_, cx| cx.notify());
2269 }
2270 }
2271 } else {
2272 this.recalculate_buffer_diffs(cx).detach();
2273 }
2274 })
2275 .ok();
2276 })
2277 }
2278
2279 pub fn set_language_for_buffer(
2280 &mut self,
2281 buffer: &Model<Buffer>,
2282 new_language: Arc<Language>,
2283 cx: &mut ModelContext<Self>,
2284 ) {
2285 self.lsp_store.update(cx, |lsp_store, cx| {
2286 lsp_store.set_language_for_buffer(buffer, new_language, cx)
2287 })
2288 }
2289
2290 pub fn restart_language_servers_for_buffers(
2291 &mut self,
2292 buffers: impl IntoIterator<Item = Model<Buffer>>,
2293 cx: &mut ModelContext<Self>,
2294 ) {
2295 self.lsp_store.update(cx, |lsp_store, cx| {
2296 lsp_store.restart_language_servers_for_buffers(buffers, cx)
2297 })
2298 }
2299
2300 pub fn cancel_language_server_work_for_buffers(
2301 &mut self,
2302 buffers: impl IntoIterator<Item = Model<Buffer>>,
2303 cx: &mut ModelContext<Self>,
2304 ) {
2305 self.lsp_store.update(cx, |lsp_store, cx| {
2306 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
2307 })
2308 }
2309
2310 pub fn cancel_language_server_work(
2311 &mut self,
2312 server_id: LanguageServerId,
2313 token_to_cancel: Option<String>,
2314 cx: &mut ModelContext<Self>,
2315 ) {
2316 self.lsp_store.update(cx, |lsp_store, cx| {
2317 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
2318 })
2319 }
2320
2321 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
2322 self.buffer_ordered_messages_tx
2323 .unbounded_send(message)
2324 .map_err(|e| anyhow!(e))
2325 }
2326
2327 pub fn language_server_statuses<'a>(
2328 &'a self,
2329 cx: &'a AppContext,
2330 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
2331 self.lsp_store.read(cx).language_server_statuses()
2332 }
2333
2334 pub fn last_formatting_failure<'a>(&self, cx: &'a AppContext) -> Option<&'a str> {
2335 self.lsp_store.read(cx).last_formatting_failure()
2336 }
2337
2338 pub fn update_diagnostics(
2339 &mut self,
2340 language_server_id: LanguageServerId,
2341 params: lsp::PublishDiagnosticsParams,
2342 disk_based_sources: &[String],
2343 cx: &mut ModelContext<Self>,
2344 ) -> Result<()> {
2345 self.lsp_store.update(cx, |lsp_store, cx| {
2346 lsp_store.update_diagnostics(language_server_id, params, disk_based_sources, cx)
2347 })
2348 }
2349
2350 pub fn update_diagnostic_entries(
2351 &mut self,
2352 server_id: LanguageServerId,
2353 abs_path: PathBuf,
2354 version: Option<i32>,
2355 diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
2356 cx: &mut ModelContext<Project>,
2357 ) -> Result<(), anyhow::Error> {
2358 self.lsp_store.update(cx, |lsp_store, cx| {
2359 lsp_store.update_diagnostic_entries(server_id, abs_path, version, diagnostics, cx)
2360 })
2361 }
2362
2363 pub fn reload_buffers(
2364 &self,
2365 buffers: HashSet<Model<Buffer>>,
2366 push_to_history: bool,
2367 cx: &mut ModelContext<Self>,
2368 ) -> Task<Result<ProjectTransaction>> {
2369 self.buffer_store.update(cx, |buffer_store, cx| {
2370 buffer_store.reload_buffers(buffers, push_to_history, cx)
2371 })
2372 }
2373
2374 pub fn format(
2375 &mut self,
2376 buffers: HashSet<Model<Buffer>>,
2377 push_to_history: bool,
2378 trigger: lsp_store::FormatTrigger,
2379 cx: &mut ModelContext<Project>,
2380 ) -> Task<anyhow::Result<ProjectTransaction>> {
2381 self.lsp_store.update(cx, |lsp_store, cx| {
2382 lsp_store.format(buffers, push_to_history, trigger, cx)
2383 })
2384 }
2385
2386 #[inline(never)]
2387 fn definition_impl(
2388 &mut self,
2389 buffer: &Model<Buffer>,
2390 position: PointUtf16,
2391 cx: &mut ModelContext<Self>,
2392 ) -> Task<Result<Vec<LocationLink>>> {
2393 self.request_lsp(
2394 buffer.clone(),
2395 LanguageServerToQuery::Primary,
2396 GetDefinition { position },
2397 cx,
2398 )
2399 }
2400 pub fn definition<T: ToPointUtf16>(
2401 &mut self,
2402 buffer: &Model<Buffer>,
2403 position: T,
2404 cx: &mut ModelContext<Self>,
2405 ) -> Task<Result<Vec<LocationLink>>> {
2406 let position = position.to_point_utf16(buffer.read(cx));
2407 self.definition_impl(buffer, position, cx)
2408 }
2409
2410 fn declaration_impl(
2411 &mut self,
2412 buffer: &Model<Buffer>,
2413 position: PointUtf16,
2414 cx: &mut ModelContext<Self>,
2415 ) -> Task<Result<Vec<LocationLink>>> {
2416 self.request_lsp(
2417 buffer.clone(),
2418 LanguageServerToQuery::Primary,
2419 GetDeclaration { position },
2420 cx,
2421 )
2422 }
2423
2424 pub fn declaration<T: ToPointUtf16>(
2425 &mut self,
2426 buffer: &Model<Buffer>,
2427 position: T,
2428 cx: &mut ModelContext<Self>,
2429 ) -> Task<Result<Vec<LocationLink>>> {
2430 let position = position.to_point_utf16(buffer.read(cx));
2431 self.declaration_impl(buffer, position, cx)
2432 }
2433
2434 fn type_definition_impl(
2435 &mut self,
2436 buffer: &Model<Buffer>,
2437 position: PointUtf16,
2438 cx: &mut ModelContext<Self>,
2439 ) -> Task<Result<Vec<LocationLink>>> {
2440 self.request_lsp(
2441 buffer.clone(),
2442 LanguageServerToQuery::Primary,
2443 GetTypeDefinition { position },
2444 cx,
2445 )
2446 }
2447
2448 pub fn type_definition<T: ToPointUtf16>(
2449 &mut self,
2450 buffer: &Model<Buffer>,
2451 position: T,
2452 cx: &mut ModelContext<Self>,
2453 ) -> Task<Result<Vec<LocationLink>>> {
2454 let position = position.to_point_utf16(buffer.read(cx));
2455 self.type_definition_impl(buffer, position, cx)
2456 }
2457
2458 pub fn implementation<T: ToPointUtf16>(
2459 &mut self,
2460 buffer: &Model<Buffer>,
2461 position: T,
2462 cx: &mut ModelContext<Self>,
2463 ) -> Task<Result<Vec<LocationLink>>> {
2464 let position = position.to_point_utf16(buffer.read(cx));
2465 self.request_lsp(
2466 buffer.clone(),
2467 LanguageServerToQuery::Primary,
2468 GetImplementation { position },
2469 cx,
2470 )
2471 }
2472
2473 pub fn references<T: ToPointUtf16>(
2474 &mut self,
2475 buffer: &Model<Buffer>,
2476 position: T,
2477 cx: &mut ModelContext<Self>,
2478 ) -> Task<Result<Vec<Location>>> {
2479 let position = position.to_point_utf16(buffer.read(cx));
2480 self.request_lsp(
2481 buffer.clone(),
2482 LanguageServerToQuery::Primary,
2483 GetReferences { position },
2484 cx,
2485 )
2486 }
2487
2488 fn document_highlights_impl(
2489 &mut self,
2490 buffer: &Model<Buffer>,
2491 position: PointUtf16,
2492 cx: &mut ModelContext<Self>,
2493 ) -> Task<Result<Vec<DocumentHighlight>>> {
2494 self.request_lsp(
2495 buffer.clone(),
2496 LanguageServerToQuery::Primary,
2497 GetDocumentHighlights { position },
2498 cx,
2499 )
2500 }
2501
2502 pub fn document_highlights<T: ToPointUtf16>(
2503 &mut self,
2504 buffer: &Model<Buffer>,
2505 position: T,
2506 cx: &mut ModelContext<Self>,
2507 ) -> Task<Result<Vec<DocumentHighlight>>> {
2508 let position = position.to_point_utf16(buffer.read(cx));
2509 self.document_highlights_impl(buffer, position, cx)
2510 }
2511
2512 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
2513 self.lsp_store
2514 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
2515 }
2516
2517 pub fn open_buffer_for_symbol(
2518 &mut self,
2519 symbol: &Symbol,
2520 cx: &mut ModelContext<Self>,
2521 ) -> Task<Result<Model<Buffer>>> {
2522 self.lsp_store.update(cx, |lsp_store, cx| {
2523 lsp_store.open_buffer_for_symbol(symbol, cx)
2524 })
2525 }
2526
2527 pub fn open_local_buffer_via_lsp(
2528 &mut self,
2529 abs_path: lsp::Url,
2530 language_server_id: LanguageServerId,
2531 language_server_name: LanguageServerName,
2532 cx: &mut ModelContext<Self>,
2533 ) -> Task<Result<Model<Buffer>>> {
2534 self.lsp_store.update(cx, |lsp_store, cx| {
2535 lsp_store.open_local_buffer_via_lsp(
2536 abs_path,
2537 language_server_id,
2538 language_server_name,
2539 cx,
2540 )
2541 })
2542 }
2543
2544 pub fn signature_help<T: ToPointUtf16>(
2545 &self,
2546 buffer: &Model<Buffer>,
2547 position: T,
2548 cx: &mut ModelContext<Self>,
2549 ) -> Task<Vec<SignatureHelp>> {
2550 self.lsp_store.update(cx, |lsp_store, cx| {
2551 lsp_store.signature_help(buffer, position, cx)
2552 })
2553 }
2554
2555 pub fn hover<T: ToPointUtf16>(
2556 &self,
2557 buffer: &Model<Buffer>,
2558 position: T,
2559 cx: &mut ModelContext<Self>,
2560 ) -> Task<Vec<Hover>> {
2561 let position = position.to_point_utf16(buffer.read(cx));
2562 self.lsp_store
2563 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
2564 }
2565
2566 pub fn linked_edit(
2567 &self,
2568 buffer: &Model<Buffer>,
2569 position: Anchor,
2570 cx: &mut ModelContext<Self>,
2571 ) -> Task<Result<Vec<Range<Anchor>>>> {
2572 self.lsp_store.update(cx, |lsp_store, cx| {
2573 lsp_store.linked_edit(buffer, position, cx)
2574 })
2575 }
2576
2577 pub fn completions<T: ToOffset + ToPointUtf16>(
2578 &self,
2579 buffer: &Model<Buffer>,
2580 position: T,
2581 context: CompletionContext,
2582 cx: &mut ModelContext<Self>,
2583 ) -> Task<Result<Vec<Completion>>> {
2584 let position = position.to_point_utf16(buffer.read(cx));
2585 self.lsp_store.update(cx, |lsp_store, cx| {
2586 lsp_store.completions(buffer, position, context, cx)
2587 })
2588 }
2589
2590 pub fn resolve_completions(
2591 &self,
2592 buffer: Model<Buffer>,
2593 completion_indices: Vec<usize>,
2594 completions: Arc<RwLock<Box<[Completion]>>>,
2595 cx: &mut ModelContext<Self>,
2596 ) -> Task<Result<bool>> {
2597 self.lsp_store.update(cx, |lsp_store, cx| {
2598 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
2599 })
2600 }
2601
2602 pub fn apply_additional_edits_for_completion(
2603 &self,
2604 buffer_handle: Model<Buffer>,
2605 completion: Completion,
2606 push_to_history: bool,
2607 cx: &mut ModelContext<Self>,
2608 ) -> Task<Result<Option<Transaction>>> {
2609 self.lsp_store.update(cx, |lsp_store, cx| {
2610 lsp_store.apply_additional_edits_for_completion(
2611 buffer_handle,
2612 completion,
2613 push_to_history,
2614 cx,
2615 )
2616 })
2617 }
2618
2619 pub fn code_actions<T: Clone + ToOffset>(
2620 &mut self,
2621 buffer_handle: &Model<Buffer>,
2622 range: Range<T>,
2623 cx: &mut ModelContext<Self>,
2624 ) -> Task<Result<Vec<CodeAction>>> {
2625 let buffer = buffer_handle.read(cx);
2626 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2627 self.lsp_store.update(cx, |lsp_store, cx| {
2628 lsp_store.code_actions(buffer_handle, range, cx)
2629 })
2630 }
2631
2632 pub fn apply_code_action(
2633 &self,
2634 buffer_handle: Model<Buffer>,
2635 action: CodeAction,
2636 push_to_history: bool,
2637 cx: &mut ModelContext<Self>,
2638 ) -> Task<Result<ProjectTransaction>> {
2639 self.lsp_store.update(cx, |lsp_store, cx| {
2640 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
2641 })
2642 }
2643
2644 fn prepare_rename_impl(
2645 &mut self,
2646 buffer: Model<Buffer>,
2647 position: PointUtf16,
2648 cx: &mut ModelContext<Self>,
2649 ) -> Task<Result<Option<Range<Anchor>>>> {
2650 self.request_lsp(
2651 buffer,
2652 LanguageServerToQuery::Primary,
2653 PrepareRename { position },
2654 cx,
2655 )
2656 }
2657 pub fn prepare_rename<T: ToPointUtf16>(
2658 &mut self,
2659 buffer: Model<Buffer>,
2660 position: T,
2661 cx: &mut ModelContext<Self>,
2662 ) -> Task<Result<Option<Range<Anchor>>>> {
2663 let position = position.to_point_utf16(buffer.read(cx));
2664 self.prepare_rename_impl(buffer, position, cx)
2665 }
2666
2667 fn perform_rename_impl(
2668 &mut self,
2669 buffer: Model<Buffer>,
2670 position: PointUtf16,
2671 new_name: String,
2672 push_to_history: bool,
2673 cx: &mut ModelContext<Self>,
2674 ) -> Task<Result<ProjectTransaction>> {
2675 let position = position.to_point_utf16(buffer.read(cx));
2676 self.request_lsp(
2677 buffer,
2678 LanguageServerToQuery::Primary,
2679 PerformRename {
2680 position,
2681 new_name,
2682 push_to_history,
2683 },
2684 cx,
2685 )
2686 }
2687 pub fn perform_rename<T: ToPointUtf16>(
2688 &mut self,
2689 buffer: Model<Buffer>,
2690 position: T,
2691 new_name: String,
2692 push_to_history: bool,
2693 cx: &mut ModelContext<Self>,
2694 ) -> Task<Result<ProjectTransaction>> {
2695 let position = position.to_point_utf16(buffer.read(cx));
2696 self.perform_rename_impl(buffer, position, new_name, push_to_history, cx)
2697 }
2698
2699 pub fn on_type_format<T: ToPointUtf16>(
2700 &mut self,
2701 buffer: Model<Buffer>,
2702 position: T,
2703 trigger: String,
2704 push_to_history: bool,
2705 cx: &mut ModelContext<Self>,
2706 ) -> Task<Result<Option<Transaction>>> {
2707 self.lsp_store.update(cx, |lsp_store, cx| {
2708 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
2709 })
2710 }
2711
2712 pub fn inlay_hints<T: ToOffset>(
2713 &mut self,
2714 buffer_handle: Model<Buffer>,
2715 range: Range<T>,
2716 cx: &mut ModelContext<Self>,
2717 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
2718 let buffer = buffer_handle.read(cx);
2719 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2720 self.lsp_store.update(cx, |lsp_store, cx| {
2721 lsp_store.inlay_hints(buffer_handle, range, cx)
2722 })
2723 }
2724
2725 pub fn resolve_inlay_hint(
2726 &self,
2727 hint: InlayHint,
2728 buffer_handle: Model<Buffer>,
2729 server_id: LanguageServerId,
2730 cx: &mut ModelContext<Self>,
2731 ) -> Task<anyhow::Result<InlayHint>> {
2732 self.lsp_store.update(cx, |lsp_store, cx| {
2733 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
2734 })
2735 }
2736
2737 pub fn search(
2738 &mut self,
2739 query: SearchQuery,
2740 cx: &mut ModelContext<Self>,
2741 ) -> Receiver<SearchResult> {
2742 let (result_tx, result_rx) = smol::channel::unbounded();
2743
2744 let matching_buffers_rx = if query.is_opened_only() {
2745 self.sort_search_candidates(&query, cx)
2746 } else {
2747 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
2748 };
2749
2750 cx.spawn(|_, cx| async move {
2751 let mut range_count = 0;
2752 let mut buffer_count = 0;
2753 let mut limit_reached = false;
2754 let query = Arc::new(query);
2755 let mut chunks = matching_buffers_rx.ready_chunks(64);
2756
2757 // Now that we know what paths match the query, we will load at most
2758 // 64 buffers at a time to avoid overwhelming the main thread. For each
2759 // opened buffer, we will spawn a background task that retrieves all the
2760 // ranges in the buffer matched by the query.
2761 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
2762 let mut chunk_results = Vec::new();
2763 for buffer in matching_buffer_chunk {
2764 let buffer = buffer.clone();
2765 let query = query.clone();
2766 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
2767 chunk_results.push(cx.background_executor().spawn(async move {
2768 let ranges = query
2769 .search(&snapshot, None)
2770 .await
2771 .iter()
2772 .map(|range| {
2773 snapshot.anchor_before(range.start)
2774 ..snapshot.anchor_after(range.end)
2775 })
2776 .collect::<Vec<_>>();
2777 anyhow::Ok((buffer, ranges))
2778 }));
2779 }
2780
2781 let chunk_results = futures::future::join_all(chunk_results).await;
2782 for result in chunk_results {
2783 if let Some((buffer, ranges)) = result.log_err() {
2784 range_count += ranges.len();
2785 buffer_count += 1;
2786 result_tx
2787 .send(SearchResult::Buffer { buffer, ranges })
2788 .await?;
2789 if buffer_count > MAX_SEARCH_RESULT_FILES
2790 || range_count > MAX_SEARCH_RESULT_RANGES
2791 {
2792 limit_reached = true;
2793 break 'outer;
2794 }
2795 }
2796 }
2797 }
2798
2799 if limit_reached {
2800 result_tx.send(SearchResult::LimitReached).await?;
2801 }
2802
2803 anyhow::Ok(())
2804 })
2805 .detach();
2806
2807 result_rx
2808 }
2809
2810 fn find_search_candidate_buffers(
2811 &mut self,
2812 query: &SearchQuery,
2813 limit: usize,
2814 cx: &mut ModelContext<Project>,
2815 ) -> Receiver<Model<Buffer>> {
2816 if self.is_local() {
2817 let fs = self.fs.clone();
2818 self.buffer_store.update(cx, |buffer_store, cx| {
2819 buffer_store.find_search_candidates(query, limit, fs, cx)
2820 })
2821 } else {
2822 self.find_search_candidates_remote(query, limit, cx)
2823 }
2824 }
2825
2826 fn sort_search_candidates(
2827 &mut self,
2828 search_query: &SearchQuery,
2829 cx: &mut ModelContext<Project>,
2830 ) -> Receiver<Model<Buffer>> {
2831 let worktree_store = self.worktree_store.read(cx);
2832 let mut buffers = search_query
2833 .buffers()
2834 .into_iter()
2835 .flatten()
2836 .filter(|buffer| {
2837 let b = buffer.read(cx);
2838 if let Some(file) = b.file() {
2839 if !search_query.file_matches(file.path()) {
2840 return false;
2841 }
2842 if let Some(entry) = b
2843 .entry_id(cx)
2844 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
2845 {
2846 if entry.is_ignored && !search_query.include_ignored() {
2847 return false;
2848 }
2849 }
2850 }
2851 true
2852 })
2853 .collect::<Vec<_>>();
2854 let (tx, rx) = smol::channel::unbounded();
2855 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
2856 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
2857 (None, Some(_)) => std::cmp::Ordering::Less,
2858 (Some(_), None) => std::cmp::Ordering::Greater,
2859 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
2860 });
2861 for buffer in buffers {
2862 tx.send_blocking(buffer.clone()).unwrap()
2863 }
2864
2865 rx
2866 }
2867
2868 fn find_search_candidates_remote(
2869 &mut self,
2870 query: &SearchQuery,
2871 limit: usize,
2872 cx: &mut ModelContext<Project>,
2873 ) -> Receiver<Model<Buffer>> {
2874 let (tx, rx) = smol::channel::unbounded();
2875
2876 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
2877 (ssh_client.read(cx).to_proto_client(), 0)
2878 } else if let Some(remote_id) = self.remote_id() {
2879 (self.client.clone().into(), remote_id)
2880 } else {
2881 return rx;
2882 };
2883
2884 let request = client.request(proto::FindSearchCandidates {
2885 project_id: remote_id,
2886 query: Some(query.to_proto()),
2887 limit: limit as _,
2888 });
2889 let guard = self.retain_remotely_created_models(cx);
2890
2891 cx.spawn(move |this, mut cx| async move {
2892 let response = request.await?;
2893 for buffer_id in response.buffer_ids {
2894 let buffer_id = BufferId::new(buffer_id)?;
2895 let buffer = this
2896 .update(&mut cx, |this, cx| {
2897 this.wait_for_remote_buffer(buffer_id, cx)
2898 })?
2899 .await?;
2900 let _ = tx.send(buffer).await;
2901 }
2902
2903 drop(guard);
2904 anyhow::Ok(())
2905 })
2906 .detach_and_log_err(cx);
2907 rx
2908 }
2909
2910 pub fn request_lsp<R: LspCommand>(
2911 &mut self,
2912 buffer_handle: Model<Buffer>,
2913 server: LanguageServerToQuery,
2914 request: R,
2915 cx: &mut ModelContext<Self>,
2916 ) -> Task<Result<R::Response>>
2917 where
2918 <R::LspRequest as lsp::request::Request>::Result: Send,
2919 <R::LspRequest as lsp::request::Request>::Params: Send,
2920 {
2921 let guard = self.retain_remotely_created_models(cx);
2922 let task = self.lsp_store.update(cx, |lsp_store, cx| {
2923 lsp_store.request_lsp(buffer_handle, server, request, cx)
2924 });
2925 cx.spawn(|_, _| async move {
2926 let result = task.await;
2927 drop(guard);
2928 result
2929 })
2930 }
2931
2932 /// Move a worktree to a new position in the worktree order.
2933 ///
2934 /// The worktree will moved to the opposite side of the destination worktree.
2935 ///
2936 /// # Example
2937 ///
2938 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
2939 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
2940 ///
2941 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
2942 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
2943 ///
2944 /// # Errors
2945 ///
2946 /// An error will be returned if the worktree or destination worktree are not found.
2947 pub fn move_worktree(
2948 &mut self,
2949 source: WorktreeId,
2950 destination: WorktreeId,
2951 cx: &mut ModelContext<'_, Self>,
2952 ) -> Result<()> {
2953 self.worktree_store.update(cx, |worktree_store, cx| {
2954 worktree_store.move_worktree(source, destination, cx)
2955 })
2956 }
2957
2958 pub fn find_or_create_worktree(
2959 &mut self,
2960 abs_path: impl AsRef<Path>,
2961 visible: bool,
2962 cx: &mut ModelContext<Self>,
2963 ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
2964 let abs_path = abs_path.as_ref();
2965 if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) {
2966 Task::ready(Ok((tree, relative_path)))
2967 } else {
2968 let worktree = self.create_worktree(abs_path, visible, cx);
2969 cx.background_executor()
2970 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
2971 }
2972 }
2973
2974 pub fn find_worktree(
2975 &self,
2976 abs_path: &Path,
2977 cx: &AppContext,
2978 ) -> Option<(Model<Worktree>, PathBuf)> {
2979 self.worktree_store.read_with(cx, |worktree_store, cx| {
2980 worktree_store.find_worktree(abs_path, cx)
2981 })
2982 }
2983
2984 pub fn is_shared(&self) -> bool {
2985 match &self.client_state {
2986 ProjectClientState::Shared { .. } => true,
2987 ProjectClientState::Local => false,
2988 ProjectClientState::Remote { in_room, .. } => *in_room,
2989 }
2990 }
2991
2992 // Returns the resolved version of `path`, that was found in `buffer`, if it exists.
2993 pub fn resolve_existing_file_path(
2994 &self,
2995 path: &str,
2996 buffer: &Model<Buffer>,
2997 cx: &mut ModelContext<Self>,
2998 ) -> Task<Option<ResolvedPath>> {
2999 let path_buf = PathBuf::from(path);
3000 if path_buf.is_absolute() || path.starts_with("~") {
3001 if self.is_local() {
3002 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3003
3004 let fs = self.fs.clone();
3005 cx.background_executor().spawn(async move {
3006 let path = expanded.as_path();
3007 let exists = fs.is_file(path).await;
3008
3009 exists.then(|| ResolvedPath::AbsPath(expanded))
3010 })
3011 } else if let Some(ssh_client) = self.ssh_client.as_ref() {
3012 let request =
3013 ssh_client
3014 .read(cx)
3015 .to_proto_client()
3016 .request(proto::CheckFileExists {
3017 project_id: SSH_PROJECT_ID,
3018 path: path.to_string(),
3019 });
3020 cx.background_executor().spawn(async move {
3021 let response = request.await.log_err()?;
3022 if response.exists {
3023 Some(ResolvedPath::AbsPath(PathBuf::from(response.path)))
3024 } else {
3025 None
3026 }
3027 })
3028 } else {
3029 return Task::ready(None);
3030 }
3031 } else {
3032 self.resolve_path_in_worktrees(path_buf, buffer, cx)
3033 }
3034 }
3035
3036 fn resolve_path_in_worktrees(
3037 &self,
3038 path: PathBuf,
3039 buffer: &Model<Buffer>,
3040 cx: &mut ModelContext<Self>,
3041 ) -> Task<Option<ResolvedPath>> {
3042 let mut candidates = vec![path.clone()];
3043
3044 if let Some(file) = buffer.read(cx).file() {
3045 if let Some(dir) = file.path().parent() {
3046 let joined = dir.to_path_buf().join(path);
3047 candidates.push(joined);
3048 }
3049 }
3050
3051 let worktrees = self.worktrees(cx).collect::<Vec<_>>();
3052 cx.spawn(|_, mut cx| async move {
3053 for worktree in worktrees {
3054 for candidate in candidates.iter() {
3055 let path = worktree
3056 .update(&mut cx, |worktree, _| {
3057 let root_entry_path = &worktree.root_entry()?.path;
3058
3059 let resolved = resolve_path(root_entry_path, candidate);
3060
3061 let stripped =
3062 resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3063
3064 worktree.entry_for_path(stripped).map(|entry| {
3065 ResolvedPath::ProjectPath(ProjectPath {
3066 worktree_id: worktree.id(),
3067 path: entry.path.clone(),
3068 })
3069 })
3070 })
3071 .ok()?;
3072
3073 if path.is_some() {
3074 return path;
3075 }
3076 }
3077 }
3078 None
3079 })
3080 }
3081
3082 pub fn list_directory(
3083 &self,
3084 query: String,
3085 cx: &mut ModelContext<Self>,
3086 ) -> Task<Result<Vec<PathBuf>>> {
3087 if self.is_local() {
3088 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3089 } else if let Some(session) = self.ssh_client.as_ref() {
3090 let request = proto::ListRemoteDirectory {
3091 dev_server_id: SSH_PROJECT_ID,
3092 path: query,
3093 };
3094
3095 let response = session.read(cx).to_proto_client().request(request);
3096 cx.background_executor().spawn(async move {
3097 let response = response.await?;
3098 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3099 })
3100 } else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| {
3101 dev_server_projects::Store::global(cx)
3102 .read(cx)
3103 .dev_server_for_project(id)
3104 }) {
3105 let request = proto::ListRemoteDirectory {
3106 dev_server_id: dev_server.id.0,
3107 path: query,
3108 };
3109 let response = self.client.request(request);
3110 cx.background_executor().spawn(async move {
3111 let response = response.await?;
3112 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3113 })
3114 } else {
3115 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3116 }
3117 }
3118
3119 fn create_worktree(
3120 &mut self,
3121 abs_path: impl AsRef<Path>,
3122 visible: bool,
3123 cx: &mut ModelContext<Self>,
3124 ) -> Task<Result<Model<Worktree>>> {
3125 self.worktree_store.update(cx, |worktree_store, cx| {
3126 worktree_store.create_worktree(abs_path, visible, cx)
3127 })
3128 }
3129
3130 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
3131 self.worktree_store.update(cx, |worktree_store, cx| {
3132 worktree_store.remove_worktree(id_to_remove, cx);
3133 });
3134 }
3135
3136 fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
3137 self.worktree_store.update(cx, |worktree_store, cx| {
3138 worktree_store.add(worktree, cx);
3139 });
3140 }
3141
3142 fn update_local_worktree_settings(
3143 &mut self,
3144 worktree: &Model<Worktree>,
3145 changes: &UpdatedEntriesSet,
3146 cx: &mut ModelContext<Self>,
3147 ) {
3148 if worktree.read(cx).is_remote() {
3149 return;
3150 }
3151 let remote_worktree_id = worktree.read(cx).id();
3152
3153 for (path, _, change) in changes.iter() {
3154 let removed = change == &PathChange::Removed;
3155 let abs_path = match worktree.read(cx).absolutize(path) {
3156 Ok(abs_path) => abs_path,
3157 Err(e) => {
3158 log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");
3159 continue;
3160 }
3161 };
3162
3163 if path.ends_with(local_tasks_file_relative_path()) {
3164 self.task_inventory().update(cx, |task_inventory, cx| {
3165 if removed {
3166 task_inventory.remove_local_static_source(&abs_path);
3167 } else {
3168 let fs = self.fs.clone();
3169 let task_abs_path = abs_path.clone();
3170 let tasks_file_rx =
3171 watch_config_file(cx.background_executor(), fs, task_abs_path);
3172 task_inventory.add_source(
3173 TaskSourceKind::Worktree {
3174 id: remote_worktree_id,
3175 abs_path,
3176 id_base: "local_tasks_for_worktree".into(),
3177 },
3178 |tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
3179 cx,
3180 );
3181 }
3182 })
3183 } else if path.ends_with(local_vscode_tasks_file_relative_path()) {
3184 self.task_inventory().update(cx, |task_inventory, cx| {
3185 if removed {
3186 task_inventory.remove_local_static_source(&abs_path);
3187 } else {
3188 let fs = self.fs.clone();
3189 let task_abs_path = abs_path.clone();
3190 let tasks_file_rx =
3191 watch_config_file(cx.background_executor(), fs, task_abs_path);
3192 task_inventory.add_source(
3193 TaskSourceKind::Worktree {
3194 id: remote_worktree_id,
3195 abs_path,
3196 id_base: "local_vscode_tasks_for_worktree".into(),
3197 },
3198 |tx, cx| {
3199 StaticSource::new(TrackedFile::new_convertible::<
3200 task::VsCodeTaskFile,
3201 >(
3202 tasks_file_rx, tx, cx
3203 ))
3204 },
3205 cx,
3206 );
3207 }
3208 })
3209 }
3210 }
3211 }
3212
3213 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
3214 let new_active_entry = entry.and_then(|project_path| {
3215 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
3216 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
3217 Some(entry.id)
3218 });
3219 if new_active_entry != self.active_entry {
3220 self.active_entry = new_active_entry;
3221 self.lsp_store.update(cx, |lsp_store, _| {
3222 lsp_store.set_active_entry(new_active_entry);
3223 });
3224 cx.emit(Event::ActiveEntryChanged(new_active_entry));
3225 }
3226 }
3227
3228 pub fn language_servers_running_disk_based_diagnostics<'a>(
3229 &'a self,
3230 cx: &'a AppContext,
3231 ) -> impl Iterator<Item = LanguageServerId> + 'a {
3232 self.lsp_store
3233 .read(cx)
3234 .language_servers_running_disk_based_diagnostics()
3235 }
3236
3237 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
3238 let mut summary = DiagnosticSummary::default();
3239 for (_, _, path_summary) in self.diagnostic_summaries(include_ignored, cx) {
3240 summary.error_count += path_summary.error_count;
3241 summary.warning_count += path_summary.warning_count;
3242 }
3243 summary
3244 }
3245
3246 pub fn diagnostic_summaries<'a>(
3247 &'a self,
3248 include_ignored: bool,
3249 cx: &'a AppContext,
3250 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
3251 self.lsp_store
3252 .read(cx)
3253 .diagnostic_summaries(include_ignored, cx)
3254 }
3255
3256 pub fn active_entry(&self) -> Option<ProjectEntryId> {
3257 self.active_entry
3258 }
3259
3260 pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
3261 self.worktree_store.read(cx).entry_for_path(path, cx)
3262 }
3263
3264 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
3265 let worktree = self.worktree_for_entry(entry_id, cx)?;
3266 let worktree = worktree.read(cx);
3267 let worktree_id = worktree.id();
3268 let path = worktree.entry_for_id(entry_id)?.path.clone();
3269 Some(ProjectPath { worktree_id, path })
3270 }
3271
3272 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
3273 let workspace_root = self
3274 .worktree_for_id(project_path.worktree_id, cx)?
3275 .read(cx)
3276 .abs_path();
3277 let project_path = project_path.path.as_ref();
3278
3279 Some(if project_path == Path::new("") {
3280 workspace_root.to_path_buf()
3281 } else {
3282 workspace_root.join(project_path)
3283 })
3284 }
3285
3286 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
3287 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
3288 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
3289 /// the first visible worktree that has an entry for that relative path.
3290 ///
3291 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
3292 /// root name from paths.
3293 ///
3294 /// # Arguments
3295 ///
3296 /// * `path` - A full path that starts with a worktree root name, or alternatively a
3297 /// relative path within a visible worktree.
3298 /// * `cx` - A reference to the `AppContext`.
3299 ///
3300 /// # Returns
3301 ///
3302 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
3303 pub fn find_project_path(&self, path: &Path, cx: &AppContext) -> Option<ProjectPath> {
3304 let worktree_store = self.worktree_store.read(cx);
3305
3306 for worktree in worktree_store.visible_worktrees(cx) {
3307 let worktree_root_name = worktree.read(cx).root_name();
3308 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
3309 return Some(ProjectPath {
3310 worktree_id: worktree.read(cx).id(),
3311 path: relative_path.into(),
3312 });
3313 }
3314 }
3315
3316 for worktree in worktree_store.visible_worktrees(cx) {
3317 let worktree = worktree.read(cx);
3318 if let Some(entry) = worktree.entry_for_path(path) {
3319 return Some(ProjectPath {
3320 worktree_id: worktree.id(),
3321 path: entry.path.clone(),
3322 });
3323 }
3324 }
3325
3326 None
3327 }
3328
3329 pub fn get_workspace_root(
3330 &self,
3331 project_path: &ProjectPath,
3332 cx: &AppContext,
3333 ) -> Option<PathBuf> {
3334 Some(
3335 self.worktree_for_id(project_path.worktree_id, cx)?
3336 .read(cx)
3337 .abs_path()
3338 .to_path_buf(),
3339 )
3340 }
3341
3342 pub fn get_repo(
3343 &self,
3344 project_path: &ProjectPath,
3345 cx: &AppContext,
3346 ) -> Option<Arc<dyn GitRepository>> {
3347 self.worktree_for_id(project_path.worktree_id, cx)?
3348 .read(cx)
3349 .as_local()?
3350 .local_git_repo(&project_path.path)
3351 }
3352
3353 pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
3354 let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
3355 let root_entry = worktree.root_git_entry()?;
3356 worktree.get_local_repo(&root_entry)?.repo().clone().into()
3357 }
3358
3359 pub fn blame_buffer(
3360 &self,
3361 buffer: &Model<Buffer>,
3362 version: Option<clock::Global>,
3363 cx: &AppContext,
3364 ) -> Task<Result<Blame>> {
3365 self.buffer_store.read(cx).blame_buffer(buffer, version, cx)
3366 }
3367
3368 // RPC message handlers
3369
3370 async fn handle_unshare_project(
3371 this: Model<Self>,
3372 _: TypedEnvelope<proto::UnshareProject>,
3373 mut cx: AsyncAppContext,
3374 ) -> Result<()> {
3375 this.update(&mut cx, |this, cx| {
3376 if this.is_local() || this.is_via_ssh() {
3377 this.unshare(cx)?;
3378 } else {
3379 this.disconnected_from_host(cx);
3380 }
3381 Ok(())
3382 })?
3383 }
3384
3385 async fn handle_add_collaborator(
3386 this: Model<Self>,
3387 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
3388 mut cx: AsyncAppContext,
3389 ) -> Result<()> {
3390 let collaborator = envelope
3391 .payload
3392 .collaborator
3393 .take()
3394 .ok_or_else(|| anyhow!("empty collaborator"))?;
3395
3396 let collaborator = Collaborator::from_proto(collaborator)?;
3397 this.update(&mut cx, |this, cx| {
3398 this.buffer_store.update(cx, |buffer_store, _| {
3399 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
3400 });
3401 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
3402 this.collaborators
3403 .insert(collaborator.peer_id, collaborator);
3404 cx.notify();
3405 })?;
3406
3407 Ok(())
3408 }
3409
3410 async fn handle_update_project_collaborator(
3411 this: Model<Self>,
3412 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
3413 mut cx: AsyncAppContext,
3414 ) -> Result<()> {
3415 let old_peer_id = envelope
3416 .payload
3417 .old_peer_id
3418 .ok_or_else(|| anyhow!("missing old peer id"))?;
3419 let new_peer_id = envelope
3420 .payload
3421 .new_peer_id
3422 .ok_or_else(|| anyhow!("missing new peer id"))?;
3423 this.update(&mut cx, |this, cx| {
3424 let collaborator = this
3425 .collaborators
3426 .remove(&old_peer_id)
3427 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
3428 let is_host = collaborator.replica_id == 0;
3429 this.collaborators.insert(new_peer_id, collaborator);
3430
3431 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
3432 this.buffer_store.update(cx, |buffer_store, _| {
3433 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
3434 });
3435
3436 if is_host {
3437 this.buffer_store
3438 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
3439 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
3440 .unwrap();
3441 cx.emit(Event::HostReshared);
3442 }
3443
3444 cx.emit(Event::CollaboratorUpdated {
3445 old_peer_id,
3446 new_peer_id,
3447 });
3448 cx.notify();
3449 Ok(())
3450 })?
3451 }
3452
3453 async fn handle_remove_collaborator(
3454 this: Model<Self>,
3455 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
3456 mut cx: AsyncAppContext,
3457 ) -> Result<()> {
3458 this.update(&mut cx, |this, cx| {
3459 let peer_id = envelope
3460 .payload
3461 .peer_id
3462 .ok_or_else(|| anyhow!("invalid peer id"))?;
3463 let replica_id = this
3464 .collaborators
3465 .remove(&peer_id)
3466 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
3467 .replica_id;
3468 this.buffer_store.update(cx, |buffer_store, cx| {
3469 buffer_store.forget_shared_buffers_for(&peer_id);
3470 for buffer in buffer_store.buffers() {
3471 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
3472 }
3473 });
3474
3475 cx.emit(Event::CollaboratorLeft(peer_id));
3476 cx.notify();
3477 Ok(())
3478 })?
3479 }
3480
3481 async fn handle_update_project(
3482 this: Model<Self>,
3483 envelope: TypedEnvelope<proto::UpdateProject>,
3484 mut cx: AsyncAppContext,
3485 ) -> Result<()> {
3486 this.update(&mut cx, |this, cx| {
3487 // Don't handle messages that were sent before the response to us joining the project
3488 if envelope.message_id > this.join_project_response_message_id {
3489 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
3490 }
3491 Ok(())
3492 })?
3493 }
3494
3495 // Collab sends UpdateWorktree protos as messages
3496 async fn handle_update_worktree(
3497 this: Model<Self>,
3498 envelope: TypedEnvelope<proto::UpdateWorktree>,
3499 mut cx: AsyncAppContext,
3500 ) -> Result<()> {
3501 this.update(&mut cx, |this, cx| {
3502 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3503 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
3504 worktree.update(cx, |worktree, _| {
3505 let worktree = worktree.as_remote_mut().unwrap();
3506 worktree.update_from_remote(envelope.payload);
3507 });
3508 }
3509 Ok(())
3510 })?
3511 }
3512
3513 async fn handle_update_buffer(
3514 this: Model<Self>,
3515 envelope: TypedEnvelope<proto::UpdateBuffer>,
3516 cx: AsyncAppContext,
3517 ) -> Result<proto::Ack> {
3518 let buffer_store = this.read_with(&cx, |this, cx| {
3519 if let Some(ssh) = &this.ssh_client {
3520 let mut payload = envelope.payload.clone();
3521 payload.project_id = 0;
3522 cx.background_executor()
3523 .spawn(ssh.read(cx).to_proto_client().request(payload))
3524 .detach_and_log_err(cx);
3525 }
3526 this.buffer_store.clone()
3527 })?;
3528 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3529 }
3530
3531 fn retain_remotely_created_models(
3532 &mut self,
3533 cx: &mut ModelContext<Self>,
3534 ) -> RemotelyCreatedModelGuard {
3535 {
3536 let mut remotely_create_models = self.remotely_created_models.lock();
3537 if remotely_create_models.retain_count == 0 {
3538 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
3539 remotely_create_models.worktrees =
3540 self.worktree_store.read(cx).worktrees().collect();
3541 }
3542 remotely_create_models.retain_count += 1;
3543 }
3544 RemotelyCreatedModelGuard {
3545 remote_models: Arc::downgrade(&self.remotely_created_models),
3546 }
3547 }
3548
3549 async fn handle_create_buffer_for_peer(
3550 this: Model<Self>,
3551 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
3552 mut cx: AsyncAppContext,
3553 ) -> Result<()> {
3554 this.update(&mut cx, |this, cx| {
3555 this.buffer_store.update(cx, |buffer_store, cx| {
3556 buffer_store.handle_create_buffer_for_peer(
3557 envelope,
3558 this.replica_id(),
3559 this.capability(),
3560 cx,
3561 )
3562 })
3563 })?
3564 }
3565
3566 async fn handle_synchronize_buffers(
3567 this: Model<Self>,
3568 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
3569 mut cx: AsyncAppContext,
3570 ) -> Result<proto::SynchronizeBuffersResponse> {
3571 let response = this.update(&mut cx, |this, cx| {
3572 let client = this.client.clone();
3573 this.buffer_store.update(cx, |this, cx| {
3574 this.handle_synchronize_buffers(envelope, cx, client)
3575 })
3576 })??;
3577
3578 Ok(response)
3579 }
3580
3581 async fn handle_task_context_for_location(
3582 project: Model<Self>,
3583 envelope: TypedEnvelope<proto::TaskContextForLocation>,
3584 mut cx: AsyncAppContext,
3585 ) -> Result<proto::TaskContext> {
3586 let location = envelope
3587 .payload
3588 .location
3589 .context("no location given for task context handling")?;
3590 let location = cx
3591 .update(|cx| deserialize_location(&project, location, cx))?
3592 .await?;
3593 let context_task = project.update(&mut cx, |project, cx| {
3594 let captured_variables = {
3595 let mut variables = TaskVariables::default();
3596 for range in location
3597 .buffer
3598 .read(cx)
3599 .snapshot()
3600 .runnable_ranges(location.range.clone())
3601 {
3602 for (capture_name, value) in range.extra_captures {
3603 variables.insert(VariableName::Custom(capture_name.into()), value);
3604 }
3605 }
3606 variables
3607 };
3608 project.task_context_for_location(captured_variables, location, cx)
3609 })?;
3610 let task_context = context_task.await.unwrap_or_default();
3611 Ok(proto::TaskContext {
3612 project_env: task_context.project_env.into_iter().collect(),
3613 cwd: task_context
3614 .cwd
3615 .map(|cwd| cwd.to_string_lossy().to_string()),
3616 task_variables: task_context
3617 .task_variables
3618 .into_iter()
3619 .map(|(variable_name, variable_value)| (variable_name.to_string(), variable_value))
3620 .collect(),
3621 })
3622 }
3623
3624 async fn handle_task_templates(
3625 project: Model<Self>,
3626 envelope: TypedEnvelope<proto::TaskTemplates>,
3627 mut cx: AsyncAppContext,
3628 ) -> Result<proto::TaskTemplatesResponse> {
3629 let worktree = envelope.payload.worktree_id.map(WorktreeId::from_proto);
3630 let location = match envelope.payload.location {
3631 Some(location) => Some(
3632 cx.update(|cx| deserialize_location(&project, location, cx))?
3633 .await
3634 .context("task templates request location deserializing")?,
3635 ),
3636 None => None,
3637 };
3638
3639 let templates = project
3640 .update(&mut cx, |project, cx| {
3641 project.task_templates(worktree, location, cx)
3642 })?
3643 .await
3644 .context("receiving task templates")?
3645 .into_iter()
3646 .map(|(kind, template)| {
3647 let kind = Some(match kind {
3648 TaskSourceKind::UserInput => proto::task_source_kind::Kind::UserInput(
3649 proto::task_source_kind::UserInput {},
3650 ),
3651 TaskSourceKind::Worktree {
3652 id,
3653 abs_path,
3654 id_base,
3655 } => {
3656 proto::task_source_kind::Kind::Worktree(proto::task_source_kind::Worktree {
3657 id: id.to_proto(),
3658 abs_path: abs_path.to_string_lossy().to_string(),
3659 id_base: id_base.to_string(),
3660 })
3661 }
3662 TaskSourceKind::AbsPath { id_base, abs_path } => {
3663 proto::task_source_kind::Kind::AbsPath(proto::task_source_kind::AbsPath {
3664 abs_path: abs_path.to_string_lossy().to_string(),
3665 id_base: id_base.to_string(),
3666 })
3667 }
3668 TaskSourceKind::Language { name } => {
3669 proto::task_source_kind::Kind::Language(proto::task_source_kind::Language {
3670 name: name.to_string(),
3671 })
3672 }
3673 });
3674 let kind = Some(proto::TaskSourceKind { kind });
3675 let template = Some(proto::TaskTemplate {
3676 label: template.label,
3677 command: template.command,
3678 args: template.args,
3679 env: template.env.into_iter().collect(),
3680 cwd: template.cwd,
3681 use_new_terminal: template.use_new_terminal,
3682 allow_concurrent_runs: template.allow_concurrent_runs,
3683 reveal: match template.reveal {
3684 RevealStrategy::Always => proto::RevealStrategy::RevealAlways as i32,
3685 RevealStrategy::Never => proto::RevealStrategy::RevealNever as i32,
3686 },
3687 hide: match template.hide {
3688 HideStrategy::Always => proto::HideStrategy::HideAlways as i32,
3689 HideStrategy::Never => proto::HideStrategy::HideNever as i32,
3690 HideStrategy::OnSuccess => proto::HideStrategy::HideOnSuccess as i32,
3691 },
3692 shell: Some(proto::Shell {
3693 shell_type: Some(match template.shell {
3694 Shell::System => proto::shell::ShellType::System(proto::System {}),
3695 Shell::Program(program) => proto::shell::ShellType::Program(program),
3696 Shell::WithArguments { program, args } => {
3697 proto::shell::ShellType::WithArguments(
3698 proto::shell::WithArguments { program, args },
3699 )
3700 }
3701 }),
3702 }),
3703 tags: template.tags,
3704 });
3705 proto::TemplatePair { kind, template }
3706 })
3707 .collect();
3708
3709 Ok(proto::TaskTemplatesResponse { templates })
3710 }
3711
3712 async fn handle_search_candidate_buffers(
3713 this: Model<Self>,
3714 envelope: TypedEnvelope<proto::FindSearchCandidates>,
3715 mut cx: AsyncAppContext,
3716 ) -> Result<proto::FindSearchCandidatesResponse> {
3717 let peer_id = envelope.original_sender_id()?;
3718 let message = envelope.payload;
3719 let query = SearchQuery::from_proto(
3720 message
3721 .query
3722 .ok_or_else(|| anyhow!("missing query field"))?,
3723 )?;
3724 let mut results = this.update(&mut cx, |this, cx| {
3725 this.find_search_candidate_buffers(&query, message.limit as _, cx)
3726 })?;
3727
3728 let mut response = proto::FindSearchCandidatesResponse {
3729 buffer_ids: Vec::new(),
3730 };
3731
3732 while let Some(buffer) = results.next().await {
3733 this.update(&mut cx, |this, cx| {
3734 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
3735 response.buffer_ids.push(buffer_id.to_proto());
3736 })?;
3737 }
3738
3739 Ok(response)
3740 }
3741
3742 async fn handle_open_buffer_by_id(
3743 this: Model<Self>,
3744 envelope: TypedEnvelope<proto::OpenBufferById>,
3745 mut cx: AsyncAppContext,
3746 ) -> Result<proto::OpenBufferResponse> {
3747 let peer_id = envelope.original_sender_id()?;
3748 let buffer_id = BufferId::new(envelope.payload.id)?;
3749 let buffer = this
3750 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
3751 .await?;
3752 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3753 }
3754
3755 async fn handle_open_buffer_by_path(
3756 this: Model<Self>,
3757 envelope: TypedEnvelope<proto::OpenBufferByPath>,
3758 mut cx: AsyncAppContext,
3759 ) -> Result<proto::OpenBufferResponse> {
3760 let peer_id = envelope.original_sender_id()?;
3761 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3762 let open_buffer = this.update(&mut cx, |this, cx| {
3763 this.open_buffer(
3764 ProjectPath {
3765 worktree_id,
3766 path: PathBuf::from(envelope.payload.path).into(),
3767 },
3768 cx,
3769 )
3770 })?;
3771
3772 let buffer = open_buffer.await?;
3773 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3774 }
3775
3776 async fn handle_open_new_buffer(
3777 this: Model<Self>,
3778 envelope: TypedEnvelope<proto::OpenNewBuffer>,
3779 mut cx: AsyncAppContext,
3780 ) -> Result<proto::OpenBufferResponse> {
3781 let buffer = this
3782 .update(&mut cx, |this, cx| this.create_buffer(cx))?
3783 .await?;
3784 let peer_id = envelope.original_sender_id()?;
3785
3786 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3787 }
3788
3789 fn respond_to_open_buffer_request(
3790 this: Model<Self>,
3791 buffer: Model<Buffer>,
3792 peer_id: proto::PeerId,
3793 cx: &mut AsyncAppContext,
3794 ) -> Result<proto::OpenBufferResponse> {
3795 this.update(cx, |this, cx| {
3796 let is_private = buffer
3797 .read(cx)
3798 .file()
3799 .map(|f| f.is_private())
3800 .unwrap_or_default();
3801 if is_private {
3802 Err(anyhow!(ErrorCode::UnsharedItem))
3803 } else {
3804 Ok(proto::OpenBufferResponse {
3805 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
3806 })
3807 }
3808 })?
3809 }
3810
3811 fn create_buffer_for_peer(
3812 &mut self,
3813 buffer: &Model<Buffer>,
3814 peer_id: proto::PeerId,
3815 cx: &mut AppContext,
3816 ) -> BufferId {
3817 self.buffer_store
3818 .update(cx, |buffer_store, cx| {
3819 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
3820 })
3821 .detach_and_log_err(cx);
3822 buffer.read(cx).remote_id()
3823 }
3824
3825 fn wait_for_remote_buffer(
3826 &mut self,
3827 id: BufferId,
3828 cx: &mut ModelContext<Self>,
3829 ) -> Task<Result<Model<Buffer>>> {
3830 self.buffer_store.update(cx, |buffer_store, cx| {
3831 buffer_store.wait_for_remote_buffer(id, cx)
3832 })
3833 }
3834
3835 fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
3836 let project_id = match self.client_state {
3837 ProjectClientState::Remote {
3838 sharing_has_stopped,
3839 remote_id,
3840 ..
3841 } => {
3842 if sharing_has_stopped {
3843 return Task::ready(Err(anyhow!(
3844 "can't synchronize remote buffers on a readonly project"
3845 )));
3846 } else {
3847 remote_id
3848 }
3849 }
3850 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
3851 return Task::ready(Err(anyhow!(
3852 "can't synchronize remote buffers on a local project"
3853 )))
3854 }
3855 };
3856
3857 let client = self.client.clone();
3858 cx.spawn(move |this, mut cx| async move {
3859 let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
3860 this.buffer_store.read(cx).buffer_version_info(cx)
3861 })?;
3862 let response = client
3863 .request(proto::SynchronizeBuffers {
3864 project_id,
3865 buffers,
3866 })
3867 .await?;
3868
3869 let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
3870 response
3871 .buffers
3872 .into_iter()
3873 .map(|buffer| {
3874 let client = client.clone();
3875 let buffer_id = match BufferId::new(buffer.id) {
3876 Ok(id) => id,
3877 Err(e) => {
3878 return Task::ready(Err(e));
3879 }
3880 };
3881 let remote_version = language::proto::deserialize_version(&buffer.version);
3882 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
3883 let operations =
3884 buffer.read(cx).serialize_ops(Some(remote_version), cx);
3885 cx.background_executor().spawn(async move {
3886 let operations = operations.await;
3887 for chunk in split_operations(operations) {
3888 client
3889 .request(proto::UpdateBuffer {
3890 project_id,
3891 buffer_id: buffer_id.into(),
3892 operations: chunk,
3893 })
3894 .await?;
3895 }
3896 anyhow::Ok(())
3897 })
3898 } else {
3899 Task::ready(Ok(()))
3900 }
3901 })
3902 .collect::<Vec<_>>()
3903 })?;
3904
3905 // Any incomplete buffers have open requests waiting. Request that the host sends
3906 // creates these buffers for us again to unblock any waiting futures.
3907 for id in incomplete_buffer_ids {
3908 cx.background_executor()
3909 .spawn(client.request(proto::OpenBufferById {
3910 project_id,
3911 id: id.into(),
3912 }))
3913 .detach();
3914 }
3915
3916 futures::future::join_all(send_updates_for_buffers)
3917 .await
3918 .into_iter()
3919 .collect()
3920 })
3921 }
3922
3923 pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
3924 self.worktree_store.read(cx).worktree_metadata_protos(cx)
3925 }
3926
3927 fn set_worktrees_from_proto(
3928 &mut self,
3929 worktrees: Vec<proto::WorktreeMetadata>,
3930 cx: &mut ModelContext<Project>,
3931 ) -> Result<()> {
3932 cx.notify();
3933 self.worktree_store.update(cx, |worktree_store, cx| {
3934 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
3935 })
3936 }
3937
3938 fn set_collaborators_from_proto(
3939 &mut self,
3940 messages: Vec<proto::Collaborator>,
3941 cx: &mut ModelContext<Self>,
3942 ) -> Result<()> {
3943 let mut collaborators = HashMap::default();
3944 for message in messages {
3945 let collaborator = Collaborator::from_proto(message)?;
3946 collaborators.insert(collaborator.peer_id, collaborator);
3947 }
3948 for old_peer_id in self.collaborators.keys() {
3949 if !collaborators.contains_key(old_peer_id) {
3950 cx.emit(Event::CollaboratorLeft(*old_peer_id));
3951 }
3952 }
3953 self.collaborators = collaborators;
3954 Ok(())
3955 }
3956
3957 pub fn language_servers<'a>(
3958 &'a self,
3959 cx: &'a AppContext,
3960 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
3961 self.lsp_store.read(cx).language_servers()
3962 }
3963
3964 pub fn supplementary_language_servers<'a>(
3965 &'a self,
3966 cx: &'a AppContext,
3967 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
3968 self.lsp_store.read(cx).supplementary_language_servers()
3969 }
3970
3971 pub fn language_server_for_id(
3972 &self,
3973 id: LanguageServerId,
3974 cx: &AppContext,
3975 ) -> Option<Arc<LanguageServer>> {
3976 self.lsp_store.read(cx).language_server_for_id(id)
3977 }
3978
3979 pub fn language_servers_for_buffer<'a>(
3980 &'a self,
3981 buffer: &'a Buffer,
3982 cx: &'a AppContext,
3983 ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
3984 self.lsp_store
3985 .read(cx)
3986 .language_servers_for_buffer(buffer, cx)
3987 }
3988
3989 pub fn language_server_for_buffer<'a>(
3990 &'a self,
3991 buffer: &'a Buffer,
3992 server_id: LanguageServerId,
3993 cx: &'a AppContext,
3994 ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
3995 self.lsp_store
3996 .read(cx)
3997 .language_server_for_buffer(buffer, server_id, cx)
3998 }
3999
4000 pub fn task_context_for_location(
4001 &self,
4002 captured_variables: TaskVariables,
4003 location: Location,
4004 cx: &mut ModelContext<'_, Project>,
4005 ) -> Task<Option<TaskContext>> {
4006 if self.is_local() {
4007 let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) {
4008 (
4009 Some(worktree.read(cx).id()),
4010 Some(worktree.read(cx).abs_path()),
4011 )
4012 } else {
4013 (None, None)
4014 };
4015
4016 cx.spawn(|project, mut cx| async move {
4017 let project_env = project
4018 .update(&mut cx, |project, cx| {
4019 let worktree_abs_path = worktree_abs_path.clone();
4020 project.environment.update(cx, |environment, cx| {
4021 environment.get_environment(worktree_id, worktree_abs_path, cx)
4022 })
4023 })
4024 .ok()?
4025 .await;
4026
4027 let mut task_variables = cx
4028 .update(|cx| {
4029 combine_task_variables(
4030 captured_variables,
4031 location,
4032 project_env.as_ref(),
4033 BasicContextProvider::new(project.upgrade()?),
4034 cx,
4035 )
4036 .log_err()
4037 })
4038 .ok()
4039 .flatten()?;
4040 // Remove all custom entries starting with _, as they're not intended for use by the end user.
4041 task_variables.sweep();
4042
4043 Some(TaskContext {
4044 project_env: project_env.unwrap_or_default(),
4045 cwd: worktree_abs_path.map(|p| p.to_path_buf()),
4046 task_variables,
4047 })
4048 })
4049 } else if let Some(project_id) = self
4050 .remote_id()
4051 .filter(|_| self.ssh_connection_string(cx).is_some())
4052 {
4053 let task_context = self.client().request(proto::TaskContextForLocation {
4054 project_id,
4055 location: Some(proto::Location {
4056 buffer_id: location.buffer.read(cx).remote_id().into(),
4057 start: Some(serialize_anchor(&location.range.start)),
4058 end: Some(serialize_anchor(&location.range.end)),
4059 }),
4060 });
4061 cx.background_executor().spawn(async move {
4062 let task_context = task_context.await.log_err()?;
4063 Some(TaskContext {
4064 project_env: task_context.project_env.into_iter().collect(),
4065 cwd: task_context.cwd.map(PathBuf::from),
4066 task_variables: task_context
4067 .task_variables
4068 .into_iter()
4069 .filter_map(
4070 |(variable_name, variable_value)| match variable_name.parse() {
4071 Ok(variable_name) => Some((variable_name, variable_value)),
4072 Err(()) => {
4073 log::error!("Unknown variable name: {variable_name}");
4074 None
4075 }
4076 },
4077 )
4078 .collect(),
4079 })
4080 })
4081 } else {
4082 Task::ready(None)
4083 }
4084 }
4085
4086 pub fn task_templates(
4087 &self,
4088 worktree: Option<WorktreeId>,
4089 location: Option<Location>,
4090 cx: &mut ModelContext<Self>,
4091 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4092 if self.is_local() {
4093 let (file, language) = location
4094 .map(|location| {
4095 let buffer = location.buffer.read(cx);
4096 (
4097 buffer.file().cloned(),
4098 buffer.language_at(location.range.start),
4099 )
4100 })
4101 .unwrap_or_default();
4102 Task::ready(Ok(self
4103 .task_inventory()
4104 .read(cx)
4105 .list_tasks(file, language, worktree, cx)))
4106 } else if let Some(project_id) = self
4107 .remote_id()
4108 .filter(|_| self.ssh_connection_string(cx).is_some())
4109 {
4110 let remote_templates =
4111 self.query_remote_task_templates(project_id, worktree, location.as_ref(), cx);
4112 cx.background_executor().spawn(remote_templates)
4113 } else {
4114 Task::ready(Ok(Vec::new()))
4115 }
4116 }
4117
4118 pub fn query_remote_task_templates(
4119 &self,
4120 project_id: u64,
4121 worktree: Option<WorktreeId>,
4122 location: Option<&Location>,
4123 cx: &AppContext,
4124 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4125 let client = self.client();
4126 let location = location.map(|location| serialize_location(location, cx));
4127 cx.spawn(|_| async move {
4128 let response = client
4129 .request(proto::TaskTemplates {
4130 project_id,
4131 worktree_id: worktree.map(|id| id.to_proto()),
4132 location,
4133 })
4134 .await?;
4135
4136 Ok(response
4137 .templates
4138 .into_iter()
4139 .filter_map(|template_pair| {
4140 let task_source_kind = match template_pair.kind?.kind? {
4141 proto::task_source_kind::Kind::UserInput(_) => TaskSourceKind::UserInput,
4142 proto::task_source_kind::Kind::Worktree(worktree) => {
4143 TaskSourceKind::Worktree {
4144 id: WorktreeId::from_proto(worktree.id),
4145 abs_path: PathBuf::from(worktree.abs_path),
4146 id_base: Cow::Owned(worktree.id_base),
4147 }
4148 }
4149 proto::task_source_kind::Kind::AbsPath(abs_path) => {
4150 TaskSourceKind::AbsPath {
4151 id_base: Cow::Owned(abs_path.id_base),
4152 abs_path: PathBuf::from(abs_path.abs_path),
4153 }
4154 }
4155 proto::task_source_kind::Kind::Language(language) => {
4156 TaskSourceKind::Language {
4157 name: language.name.into(),
4158 }
4159 }
4160 };
4161
4162 let proto_template = template_pair.template?;
4163 let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
4164 .unwrap_or(proto::RevealStrategy::RevealAlways)
4165 {
4166 proto::RevealStrategy::RevealAlways => RevealStrategy::Always,
4167 proto::RevealStrategy::RevealNever => RevealStrategy::Never,
4168 };
4169 let hide = match proto::HideStrategy::from_i32(proto_template.hide)
4170 .unwrap_or(proto::HideStrategy::HideNever)
4171 {
4172 proto::HideStrategy::HideAlways => HideStrategy::Always,
4173 proto::HideStrategy::HideNever => HideStrategy::Never,
4174 proto::HideStrategy::HideOnSuccess => HideStrategy::OnSuccess,
4175 };
4176 let shell = match proto_template
4177 .shell
4178 .and_then(|shell| shell.shell_type)
4179 .unwrap_or(proto::shell::ShellType::System(proto::System {}))
4180 {
4181 proto::shell::ShellType::System(_) => Shell::System,
4182 proto::shell::ShellType::Program(program) => Shell::Program(program),
4183 proto::shell::ShellType::WithArguments(with_arguments) => {
4184 Shell::WithArguments {
4185 program: with_arguments.program,
4186 args: with_arguments.args,
4187 }
4188 }
4189 };
4190 let task_template = TaskTemplate {
4191 label: proto_template.label,
4192 command: proto_template.command,
4193 args: proto_template.args,
4194 env: proto_template.env.into_iter().collect(),
4195 cwd: proto_template.cwd,
4196 use_new_terminal: proto_template.use_new_terminal,
4197 allow_concurrent_runs: proto_template.allow_concurrent_runs,
4198 reveal,
4199 hide,
4200 shell,
4201 tags: proto_template.tags,
4202 };
4203 Some((task_source_kind, task_template))
4204 })
4205 .collect())
4206 })
4207 }
4208
4209 fn task_worktree(&self, cx: &AppContext) -> Option<Model<Worktree>> {
4210 let available_worktrees = self
4211 .worktrees(cx)
4212 .filter(|worktree| {
4213 let worktree = worktree.read(cx);
4214 worktree.is_visible()
4215 && worktree.is_local()
4216 && worktree.root_entry().map_or(false, |e| e.is_dir())
4217 })
4218 .collect::<Vec<_>>();
4219
4220 match available_worktrees.len() {
4221 0 => None,
4222 1 => Some(available_worktrees[0].clone()),
4223 _ => self.active_entry().and_then(|entry_id| {
4224 available_worktrees.into_iter().find_map(|worktree| {
4225 if worktree.read(cx).contains_entry(entry_id) {
4226 Some(worktree)
4227 } else {
4228 None
4229 }
4230 })
4231 }),
4232 }
4233 }
4234}
4235
4236fn combine_task_variables(
4237 mut captured_variables: TaskVariables,
4238 location: Location,
4239 project_env: Option<&HashMap<String, String>>,
4240 baseline: BasicContextProvider,
4241 cx: &mut AppContext,
4242) -> anyhow::Result<TaskVariables> {
4243 let language_context_provider = location
4244 .buffer
4245 .read(cx)
4246 .language()
4247 .and_then(|language| language.context_provider());
4248 let baseline = baseline
4249 .build_context(&captured_variables, &location, project_env, cx)
4250 .context("building basic default context")?;
4251 captured_variables.extend(baseline);
4252 if let Some(provider) = language_context_provider {
4253 captured_variables.extend(
4254 provider
4255 .build_context(&captured_variables, &location, project_env, cx)
4256 .context("building provider context")?,
4257 );
4258 }
4259 Ok(captured_variables)
4260}
4261
4262fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
4263 code_actions
4264 .iter()
4265 .flat_map(|(kind, enabled)| {
4266 if *enabled {
4267 Some(kind.clone().into())
4268 } else {
4269 None
4270 }
4271 })
4272 .collect()
4273}
4274
4275pub struct PathMatchCandidateSet {
4276 pub snapshot: Snapshot,
4277 pub include_ignored: bool,
4278 pub include_root_name: bool,
4279 pub candidates: Candidates,
4280}
4281
4282pub enum Candidates {
4283 /// Only consider directories.
4284 Directories,
4285 /// Only consider files.
4286 Files,
4287 /// Consider directories and files.
4288 Entries,
4289}
4290
4291impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4292 type Candidates = PathMatchCandidateSetIter<'a>;
4293
4294 fn id(&self) -> usize {
4295 self.snapshot.id().to_usize()
4296 }
4297
4298 fn len(&self) -> usize {
4299 match self.candidates {
4300 Candidates::Files => {
4301 if self.include_ignored {
4302 self.snapshot.file_count()
4303 } else {
4304 self.snapshot.visible_file_count()
4305 }
4306 }
4307
4308 Candidates::Directories => {
4309 if self.include_ignored {
4310 self.snapshot.dir_count()
4311 } else {
4312 self.snapshot.visible_dir_count()
4313 }
4314 }
4315
4316 Candidates::Entries => {
4317 if self.include_ignored {
4318 self.snapshot.entry_count()
4319 } else {
4320 self.snapshot.visible_entry_count()
4321 }
4322 }
4323 }
4324 }
4325
4326 fn prefix(&self) -> Arc<str> {
4327 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4328 self.snapshot.root_name().into()
4329 } else if self.include_root_name {
4330 format!("{}/", self.snapshot.root_name()).into()
4331 } else {
4332 Arc::default()
4333 }
4334 }
4335
4336 fn candidates(&'a self, start: usize) -> Self::Candidates {
4337 PathMatchCandidateSetIter {
4338 traversal: match self.candidates {
4339 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4340 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4341 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4342 },
4343 }
4344 }
4345}
4346
4347pub struct PathMatchCandidateSetIter<'a> {
4348 traversal: Traversal<'a>,
4349}
4350
4351impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4352 type Item = fuzzy::PathMatchCandidate<'a>;
4353
4354 fn next(&mut self) -> Option<Self::Item> {
4355 self.traversal
4356 .next()
4357 .map(|entry| fuzzy::PathMatchCandidate {
4358 is_dir: entry.kind.is_dir(),
4359 path: &entry.path,
4360 char_bag: entry.char_bag,
4361 })
4362 }
4363}
4364
4365impl EventEmitter<Event> for Project {}
4366
4367impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4368 fn from(val: &'a ProjectPath) -> Self {
4369 SettingsLocation {
4370 worktree_id: val.worktree_id,
4371 path: val.path.as_ref(),
4372 }
4373 }
4374}
4375
4376impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4377 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4378 Self {
4379 worktree_id,
4380 path: path.as_ref().into(),
4381 }
4382 }
4383}
4384
4385pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4386 let mut path_components = path.components();
4387 let mut base_components = base.components();
4388 let mut components: Vec<Component> = Vec::new();
4389 loop {
4390 match (path_components.next(), base_components.next()) {
4391 (None, None) => break,
4392 (Some(a), None) => {
4393 components.push(a);
4394 components.extend(path_components.by_ref());
4395 break;
4396 }
4397 (None, _) => components.push(Component::ParentDir),
4398 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4399 (Some(a), Some(Component::CurDir)) => components.push(a),
4400 (Some(a), Some(_)) => {
4401 components.push(Component::ParentDir);
4402 for _ in base_components {
4403 components.push(Component::ParentDir);
4404 }
4405 components.push(a);
4406 components.extend(path_components.by_ref());
4407 break;
4408 }
4409 }
4410 }
4411 components.iter().map(|c| c.as_os_str()).collect()
4412}
4413
4414fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4415 let mut result = base.to_path_buf();
4416 for component in path.components() {
4417 match component {
4418 Component::ParentDir => {
4419 result.pop();
4420 }
4421 Component::CurDir => (),
4422 _ => result.push(component),
4423 }
4424 }
4425 result
4426}
4427
4428/// ResolvedPath is a path that has been resolved to either a ProjectPath
4429/// or an AbsPath and that *exists*.
4430#[derive(Debug, Clone)]
4431pub enum ResolvedPath {
4432 ProjectPath(ProjectPath),
4433 AbsPath(PathBuf),
4434}
4435
4436impl ResolvedPath {
4437 pub fn abs_path(&self) -> Option<&Path> {
4438 match self {
4439 Self::AbsPath(path) => Some(path.as_path()),
4440 _ => None,
4441 }
4442 }
4443
4444 pub fn project_path(&self) -> Option<&ProjectPath> {
4445 match self {
4446 Self::ProjectPath(path) => Some(&path),
4447 _ => None,
4448 }
4449 }
4450}
4451
4452impl Item for Buffer {
4453 fn try_open(
4454 project: &Model<Project>,
4455 path: &ProjectPath,
4456 cx: &mut AppContext,
4457 ) -> Option<Task<Result<Model<Self>>>> {
4458 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4459 }
4460
4461 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
4462 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4463 }
4464
4465 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
4466 File::from_dyn(self.file()).map(|file| ProjectPath {
4467 worktree_id: file.worktree_id(cx),
4468 path: file.path().clone(),
4469 })
4470 }
4471}
4472
4473impl Completion {
4474 /// A key that can be used to sort completions when displaying
4475 /// them to the user.
4476 pub fn sort_key(&self) -> (usize, &str) {
4477 let kind_key = match self.lsp_completion.kind {
4478 Some(lsp::CompletionItemKind::KEYWORD) => 0,
4479 Some(lsp::CompletionItemKind::VARIABLE) => 1,
4480 _ => 2,
4481 };
4482 (kind_key, &self.label.text[self.label.filter_range.clone()])
4483 }
4484
4485 /// Whether this completion is a snippet.
4486 pub fn is_snippet(&self) -> bool {
4487 self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
4488 }
4489
4490 /// Returns the corresponding color for this completion.
4491 ///
4492 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
4493 pub fn color(&self) -> Option<Hsla> {
4494 match self.lsp_completion.kind {
4495 Some(CompletionItemKind::COLOR) => color_extractor::extract_color(&self.lsp_completion),
4496 _ => None,
4497 }
4498 }
4499}
4500
4501#[derive(Debug)]
4502pub struct NoRepositoryError {}
4503
4504impl std::fmt::Display for NoRepositoryError {
4505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4506 write!(f, "no git repository for worktree found")
4507 }
4508}
4509
4510impl std::error::Error for NoRepositoryError {}
4511
4512fn serialize_location(location: &Location, cx: &AppContext) -> proto::Location {
4513 proto::Location {
4514 buffer_id: location.buffer.read(cx).remote_id().into(),
4515 start: Some(serialize_anchor(&location.range.start)),
4516 end: Some(serialize_anchor(&location.range.end)),
4517 }
4518}
4519
4520fn deserialize_location(
4521 project: &Model<Project>,
4522 location: proto::Location,
4523 cx: &mut AppContext,
4524) -> Task<Result<Location>> {
4525 let buffer_id = match BufferId::new(location.buffer_id) {
4526 Ok(id) => id,
4527 Err(e) => return Task::ready(Err(e)),
4528 };
4529 let buffer_task = project.update(cx, |project, cx| {
4530 project.wait_for_remote_buffer(buffer_id, cx)
4531 });
4532 cx.spawn(|_| async move {
4533 let buffer = buffer_task.await?;
4534 let start = location
4535 .start
4536 .and_then(deserialize_anchor)
4537 .context("missing task context location start")?;
4538 let end = location
4539 .end
4540 .and_then(deserialize_anchor)
4541 .context("missing task context location end")?;
4542 Ok(Location {
4543 buffer,
4544 range: start..end,
4545 })
4546 })
4547}
4548
4549pub fn sort_worktree_entries(entries: &mut [Entry]) {
4550 entries.sort_by(|entry_a, entry_b| {
4551 compare_paths(
4552 (&entry_a.path, entry_a.is_file()),
4553 (&entry_b.path, entry_b.is_file()),
4554 )
4555 });
4556}