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