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.update(cx, |buffer_store, cx| {
1671 buffer_store.create_buffer(
1672 if self.is_via_collab() {
1673 Some((self.client.clone().into(), self.remote_id().unwrap()))
1674 } else {
1675 None
1676 },
1677 cx,
1678 )
1679 })
1680 }
1681
1682 pub fn create_local_buffer(
1683 &mut self,
1684 text: &str,
1685 language: Option<Arc<Language>>,
1686 cx: &mut ModelContext<Self>,
1687 ) -> Model<Buffer> {
1688 if self.is_via_collab() {
1689 panic!("called create_local_buffer on a remote project")
1690 }
1691 self.buffer_store.update(cx, |buffer_store, cx| {
1692 buffer_store.create_local_buffer(text, language, cx)
1693 })
1694 }
1695
1696 pub fn open_path(
1697 &mut self,
1698 path: ProjectPath,
1699 cx: &mut ModelContext<Self>,
1700 ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
1701 let task = self.open_buffer(path.clone(), cx);
1702 cx.spawn(move |_, cx| async move {
1703 let buffer = task.await?;
1704 let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
1705 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
1706 })?;
1707
1708 let buffer: &AnyModel = &buffer;
1709 Ok((project_entry_id, buffer.clone()))
1710 })
1711 }
1712
1713 pub fn open_local_buffer(
1714 &mut self,
1715 abs_path: impl AsRef<Path>,
1716 cx: &mut ModelContext<Self>,
1717 ) -> Task<Result<Model<Buffer>>> {
1718 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
1719 self.open_buffer((worktree.read(cx).id(), relative_path), cx)
1720 } else {
1721 Task::ready(Err(anyhow!("no such path")))
1722 }
1723 }
1724
1725 pub fn open_buffer(
1726 &mut self,
1727 path: impl Into<ProjectPath>,
1728 cx: &mut ModelContext<Self>,
1729 ) -> Task<Result<Model<Buffer>>> {
1730 if self.is_via_collab() && self.is_disconnected() {
1731 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
1732 }
1733
1734 self.buffer_store.update(cx, |buffer_store, cx| {
1735 buffer_store.open_buffer(path.into(), cx)
1736 })
1737 }
1738
1739 pub fn open_buffer_by_id(
1740 &mut self,
1741 id: BufferId,
1742 cx: &mut ModelContext<Self>,
1743 ) -> Task<Result<Model<Buffer>>> {
1744 if let Some(buffer) = self.buffer_for_id(id, cx) {
1745 Task::ready(Ok(buffer))
1746 } else if self.is_local_or_ssh() {
1747 Task::ready(Err(anyhow!("buffer {} does not exist", id)))
1748 } else if let Some(project_id) = self.remote_id() {
1749 let request = self.client.request(proto::OpenBufferById {
1750 project_id,
1751 id: id.into(),
1752 });
1753 cx.spawn(move |this, mut cx| async move {
1754 let buffer_id = BufferId::new(request.await?.buffer_id)?;
1755 this.update(&mut cx, |this, cx| {
1756 this.wait_for_remote_buffer(buffer_id, cx)
1757 })?
1758 .await
1759 })
1760 } else {
1761 Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
1762 }
1763 }
1764
1765 pub fn save_buffers(
1766 &self,
1767 buffers: HashSet<Model<Buffer>>,
1768 cx: &mut ModelContext<Self>,
1769 ) -> Task<Result<()>> {
1770 cx.spawn(move |this, mut cx| async move {
1771 let save_tasks = buffers.into_iter().filter_map(|buffer| {
1772 this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
1773 .ok()
1774 });
1775 try_join_all(save_tasks).await?;
1776 Ok(())
1777 })
1778 }
1779
1780 pub fn save_buffer(
1781 &self,
1782 buffer: Model<Buffer>,
1783 cx: &mut ModelContext<Self>,
1784 ) -> Task<Result<()>> {
1785 self.buffer_store
1786 .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
1787 }
1788
1789 pub fn save_buffer_as(
1790 &mut self,
1791 buffer: Model<Buffer>,
1792 path: ProjectPath,
1793 cx: &mut ModelContext<Self>,
1794 ) -> Task<Result<()>> {
1795 self.buffer_store.update(cx, |buffer_store, cx| {
1796 buffer_store.save_buffer_as(buffer.clone(), path, cx)
1797 })
1798 }
1799
1800 pub fn get_open_buffer(
1801 &mut self,
1802 path: &ProjectPath,
1803 cx: &mut ModelContext<Self>,
1804 ) -> Option<Model<Buffer>> {
1805 self.buffer_store.read(cx).get_by_path(path, cx)
1806 }
1807
1808 fn register_buffer(
1809 &mut self,
1810 buffer: &Model<Buffer>,
1811 cx: &mut ModelContext<Self>,
1812 ) -> Result<()> {
1813 {
1814 let mut remotely_created_models = self.remotely_created_models.lock();
1815 if remotely_created_models.retain_count > 0 {
1816 remotely_created_models.buffers.push(buffer.clone())
1817 }
1818 }
1819
1820 self.request_buffer_diff_recalculation(buffer, cx);
1821
1822 cx.subscribe(buffer, |this, buffer, event, cx| {
1823 this.on_buffer_event(buffer, event, cx);
1824 })
1825 .detach();
1826
1827 Ok(())
1828 }
1829
1830 async fn send_buffer_ordered_messages(
1831 this: WeakModel<Self>,
1832 rx: UnboundedReceiver<BufferOrderedMessage>,
1833 mut cx: AsyncAppContext,
1834 ) -> Result<()> {
1835 const MAX_BATCH_SIZE: usize = 128;
1836
1837 let mut operations_by_buffer_id = HashMap::default();
1838 async fn flush_operations(
1839 this: &WeakModel<Project>,
1840 operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
1841 needs_resync_with_host: &mut bool,
1842 is_local: bool,
1843 cx: &mut AsyncAppContext,
1844 ) -> Result<()> {
1845 for (buffer_id, operations) in operations_by_buffer_id.drain() {
1846 let request = this.update(cx, |this, _| {
1847 let project_id = this.remote_id()?;
1848 Some(this.client.request(proto::UpdateBuffer {
1849 buffer_id: buffer_id.into(),
1850 project_id,
1851 operations,
1852 }))
1853 })?;
1854 if let Some(request) = request {
1855 if request.await.is_err() && !is_local {
1856 *needs_resync_with_host = true;
1857 break;
1858 }
1859 }
1860 }
1861 Ok(())
1862 }
1863
1864 let mut needs_resync_with_host = false;
1865 let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
1866
1867 while let Some(changes) = changes.next().await {
1868 let is_local = this.update(&mut cx, |this, _| this.is_local_or_ssh())?;
1869
1870 for change in changes {
1871 match change {
1872 BufferOrderedMessage::Operation {
1873 buffer_id,
1874 operation,
1875 } => {
1876 if needs_resync_with_host {
1877 continue;
1878 }
1879
1880 operations_by_buffer_id
1881 .entry(buffer_id)
1882 .or_insert(Vec::new())
1883 .push(operation);
1884 }
1885
1886 BufferOrderedMessage::Resync => {
1887 operations_by_buffer_id.clear();
1888 if this
1889 .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))?
1890 .await
1891 .is_ok()
1892 {
1893 needs_resync_with_host = false;
1894 }
1895 }
1896
1897 BufferOrderedMessage::LanguageServerUpdate {
1898 language_server_id,
1899 message,
1900 } => {
1901 flush_operations(
1902 &this,
1903 &mut operations_by_buffer_id,
1904 &mut needs_resync_with_host,
1905 is_local,
1906 &mut cx,
1907 )
1908 .await?;
1909
1910 this.update(&mut cx, |this, _| {
1911 if let Some(project_id) = this.remote_id() {
1912 this.client
1913 .send(proto::UpdateLanguageServer {
1914 project_id,
1915 language_server_id: language_server_id.0 as u64,
1916 variant: Some(message),
1917 })
1918 .log_err();
1919 }
1920 })?;
1921 }
1922 }
1923 }
1924
1925 flush_operations(
1926 &this,
1927 &mut operations_by_buffer_id,
1928 &mut needs_resync_with_host,
1929 is_local,
1930 &mut cx,
1931 )
1932 .await?;
1933 }
1934
1935 Ok(())
1936 }
1937
1938 fn on_buffer_store_event(
1939 &mut self,
1940 _: Model<BufferStore>,
1941 event: &BufferStoreEvent,
1942 cx: &mut ModelContext<Self>,
1943 ) {
1944 match event {
1945 BufferStoreEvent::BufferAdded(buffer) => {
1946 self.register_buffer(buffer, cx).log_err();
1947 }
1948 BufferStoreEvent::BufferChangedFilePath { .. } => {}
1949 BufferStoreEvent::BufferDropped(buffer_id) => {
1950 if let Some(ref ssh_session) = self.ssh_session {
1951 ssh_session
1952 .send(proto::CloseBuffer {
1953 project_id: 0,
1954 buffer_id: buffer_id.to_proto(),
1955 })
1956 .log_err();
1957 }
1958 }
1959 }
1960 }
1961
1962 fn on_lsp_store_event(
1963 &mut self,
1964 _: Model<LspStore>,
1965 event: &LspStoreEvent,
1966 cx: &mut ModelContext<Self>,
1967 ) {
1968 match event {
1969 LspStoreEvent::DiagnosticsUpdated {
1970 language_server_id,
1971 path,
1972 } => cx.emit(Event::DiagnosticsUpdated {
1973 path: path.clone(),
1974 language_server_id: *language_server_id,
1975 }),
1976 LspStoreEvent::LanguageServerAdded(language_server_id) => {
1977 cx.emit(Event::LanguageServerAdded(*language_server_id))
1978 }
1979 LspStoreEvent::LanguageServerRemoved(language_server_id) => {
1980 cx.emit(Event::LanguageServerRemoved(*language_server_id))
1981 }
1982 LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
1983 Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
1984 ),
1985 LspStoreEvent::LanguageDetected {
1986 buffer,
1987 new_language,
1988 } => {
1989 let Some(_) = new_language else {
1990 cx.emit(Event::LanguageNotFound(buffer.clone()));
1991 return;
1992 };
1993 }
1994 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
1995 LspStoreEvent::LanguageServerPrompt(prompt) => {
1996 cx.emit(Event::LanguageServerPrompt(prompt.clone()))
1997 }
1998 LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
1999 cx.emit(Event::DiskBasedDiagnosticsStarted {
2000 language_server_id: *language_server_id,
2001 });
2002 }
2003 LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
2004 cx.emit(Event::DiskBasedDiagnosticsFinished {
2005 language_server_id: *language_server_id,
2006 });
2007 }
2008 LspStoreEvent::LanguageServerUpdate {
2009 language_server_id,
2010 message,
2011 } => {
2012 if self.is_local_or_ssh() {
2013 self.enqueue_buffer_ordered_message(
2014 BufferOrderedMessage::LanguageServerUpdate {
2015 language_server_id: *language_server_id,
2016 message: message.clone(),
2017 },
2018 )
2019 .ok();
2020 }
2021 }
2022 LspStoreEvent::Notification(message) => cx.emit(Event::Notification(message.clone())),
2023 LspStoreEvent::SnippetEdit {
2024 buffer_id,
2025 edits,
2026 most_recent_edit,
2027 } => {
2028 if most_recent_edit.replica_id == self.replica_id() {
2029 cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
2030 }
2031 }
2032 }
2033 }
2034
2035 fn on_settings_observer_event(
2036 &mut self,
2037 _: Model<SettingsObserver>,
2038 event: &SettingsObserverEvent,
2039 cx: &mut ModelContext<Self>,
2040 ) {
2041 match event {
2042 SettingsObserverEvent::LocalSettingsUpdated(error) => {
2043 cx.emit(Event::LocalSettingsUpdated(error.clone()))
2044 }
2045 }
2046 }
2047
2048 fn on_worktree_store_event(
2049 &mut self,
2050 _: Model<WorktreeStore>,
2051 event: &WorktreeStoreEvent,
2052 cx: &mut ModelContext<Self>,
2053 ) {
2054 match event {
2055 WorktreeStoreEvent::WorktreeAdded(worktree) => {
2056 self.on_worktree_added(worktree, cx);
2057 cx.emit(Event::WorktreeAdded);
2058 }
2059 WorktreeStoreEvent::WorktreeRemoved(_, id) => {
2060 self.on_worktree_removed(*id, cx);
2061 cx.emit(Event::WorktreeRemoved(*id));
2062 }
2063 WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
2064 WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
2065 }
2066 }
2067
2068 fn on_worktree_added(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
2069 {
2070 let mut remotely_created_models = self.remotely_created_models.lock();
2071 if remotely_created_models.retain_count > 0 {
2072 remotely_created_models.worktrees.push(worktree.clone())
2073 }
2074 }
2075 cx.observe(worktree, |_, _, cx| cx.notify()).detach();
2076 cx.subscribe(worktree, |this, worktree, event, cx| {
2077 let is_local = worktree.read(cx).is_local();
2078 match event {
2079 worktree::Event::UpdatedEntries(changes) => {
2080 if is_local {
2081 this.update_local_worktree_settings(&worktree, changes, cx);
2082 }
2083
2084 cx.emit(Event::WorktreeUpdatedEntries(
2085 worktree.read(cx).id(),
2086 changes.clone(),
2087 ));
2088
2089 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
2090 this.client()
2091 .telemetry()
2092 .report_discovered_project_events(worktree_id, changes);
2093 }
2094 worktree::Event::UpdatedGitRepositories(_) => {
2095 cx.emit(Event::WorktreeUpdatedGitRepositories);
2096 }
2097 worktree::Event::DeletedEntry(id) => cx.emit(Event::DeletedEntry(*id)),
2098 }
2099 })
2100 .detach();
2101 cx.notify();
2102 }
2103
2104 fn on_worktree_removed(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
2105 if let Some(dev_server_project_id) = self.dev_server_project_id {
2106 let paths: Vec<String> = self
2107 .visible_worktrees(cx)
2108 .filter_map(|worktree| {
2109 if worktree.read(cx).id() == id_to_remove {
2110 None
2111 } else {
2112 Some(worktree.read(cx).abs_path().to_string_lossy().to_string())
2113 }
2114 })
2115 .collect();
2116 if !paths.is_empty() {
2117 let request = self.client.request(proto::UpdateDevServerProject {
2118 dev_server_project_id: dev_server_project_id.0,
2119 paths,
2120 });
2121 cx.background_executor()
2122 .spawn(request)
2123 .detach_and_log_err(cx);
2124 }
2125 return;
2126 }
2127
2128 self.task_inventory().update(cx, |inventory, _| {
2129 inventory.remove_worktree_sources(id_to_remove);
2130 });
2131
2132 cx.notify();
2133 }
2134
2135 fn on_buffer_event(
2136 &mut self,
2137 buffer: Model<Buffer>,
2138 event: &BufferEvent,
2139 cx: &mut ModelContext<Self>,
2140 ) -> Option<()> {
2141 if matches!(
2142 event,
2143 BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
2144 ) {
2145 self.request_buffer_diff_recalculation(&buffer, cx);
2146 }
2147
2148 let buffer_id = buffer.read(cx).remote_id();
2149 match event {
2150 BufferEvent::Operation {
2151 operation,
2152 is_local: true,
2153 } => {
2154 let operation = language::proto::serialize_operation(operation);
2155
2156 if let Some(ssh) = &self.ssh_session {
2157 ssh.send(proto::UpdateBuffer {
2158 project_id: 0,
2159 buffer_id: buffer_id.to_proto(),
2160 operations: vec![operation.clone()],
2161 })
2162 .ok();
2163 }
2164
2165 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
2166 buffer_id,
2167 operation,
2168 })
2169 .ok();
2170 }
2171
2172 _ => {}
2173 }
2174
2175 None
2176 }
2177
2178 fn request_buffer_diff_recalculation(
2179 &mut self,
2180 buffer: &Model<Buffer>,
2181 cx: &mut ModelContext<Self>,
2182 ) {
2183 self.buffers_needing_diff.insert(buffer.downgrade());
2184 let first_insertion = self.buffers_needing_diff.len() == 1;
2185
2186 let settings = ProjectSettings::get_global(cx);
2187 let delay = if let Some(delay) = settings.git.gutter_debounce {
2188 delay
2189 } else {
2190 if first_insertion {
2191 let this = cx.weak_model();
2192 cx.defer(move |cx| {
2193 if let Some(this) = this.upgrade() {
2194 this.update(cx, |this, cx| {
2195 this.recalculate_buffer_diffs(cx).detach();
2196 });
2197 }
2198 });
2199 }
2200 return;
2201 };
2202
2203 const MIN_DELAY: u64 = 50;
2204 let delay = delay.max(MIN_DELAY);
2205 let duration = Duration::from_millis(delay);
2206
2207 self.git_diff_debouncer
2208 .fire_new(duration, cx, move |this, cx| {
2209 this.recalculate_buffer_diffs(cx)
2210 });
2211 }
2212
2213 fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
2214 let buffers = self.buffers_needing_diff.drain().collect::<Vec<_>>();
2215 cx.spawn(move |this, mut cx| async move {
2216 let tasks: Vec<_> = buffers
2217 .iter()
2218 .filter_map(|buffer| {
2219 let buffer = buffer.upgrade()?;
2220 buffer
2221 .update(&mut cx, |buffer, cx| buffer.recalculate_diff(cx))
2222 .ok()
2223 .flatten()
2224 })
2225 .collect();
2226
2227 futures::future::join_all(tasks).await;
2228
2229 this.update(&mut cx, |this, cx| {
2230 if this.buffers_needing_diff.is_empty() {
2231 // TODO: Would a `ModelContext<Project>.notify()` suffice here?
2232 for buffer in buffers {
2233 if let Some(buffer) = buffer.upgrade() {
2234 buffer.update(cx, |_, cx| cx.notify());
2235 }
2236 }
2237 } else {
2238 this.recalculate_buffer_diffs(cx).detach();
2239 }
2240 })
2241 .ok();
2242 })
2243 }
2244
2245 pub fn set_language_for_buffer(
2246 &mut self,
2247 buffer: &Model<Buffer>,
2248 new_language: Arc<Language>,
2249 cx: &mut ModelContext<Self>,
2250 ) {
2251 self.lsp_store.update(cx, |lsp_store, cx| {
2252 lsp_store.set_language_for_buffer(buffer, new_language, cx)
2253 })
2254 }
2255
2256 pub fn restart_language_servers_for_buffers(
2257 &mut self,
2258 buffers: impl IntoIterator<Item = Model<Buffer>>,
2259 cx: &mut ModelContext<Self>,
2260 ) {
2261 self.lsp_store.update(cx, |lsp_store, cx| {
2262 lsp_store.restart_language_servers_for_buffers(buffers, cx)
2263 })
2264 }
2265
2266 pub fn cancel_language_server_work_for_buffers(
2267 &mut self,
2268 buffers: impl IntoIterator<Item = Model<Buffer>>,
2269 cx: &mut ModelContext<Self>,
2270 ) {
2271 self.lsp_store.update(cx, |lsp_store, cx| {
2272 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
2273 })
2274 }
2275
2276 pub fn cancel_language_server_work(
2277 &mut self,
2278 server_id: LanguageServerId,
2279 token_to_cancel: Option<String>,
2280 cx: &mut ModelContext<Self>,
2281 ) {
2282 self.lsp_store.update(cx, |lsp_store, cx| {
2283 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
2284 })
2285 }
2286
2287 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
2288 self.buffer_ordered_messages_tx
2289 .unbounded_send(message)
2290 .map_err(|e| anyhow!(e))
2291 }
2292
2293 pub fn language_server_statuses<'a>(
2294 &'a self,
2295 cx: &'a AppContext,
2296 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
2297 self.lsp_store.read(cx).language_server_statuses()
2298 }
2299
2300 pub fn last_formatting_failure<'a>(&self, cx: &'a AppContext) -> Option<&'a str> {
2301 self.lsp_store.read(cx).last_formatting_failure()
2302 }
2303
2304 pub fn update_diagnostics(
2305 &mut self,
2306 language_server_id: LanguageServerId,
2307 params: lsp::PublishDiagnosticsParams,
2308 disk_based_sources: &[String],
2309 cx: &mut ModelContext<Self>,
2310 ) -> Result<()> {
2311 self.lsp_store.update(cx, |lsp_store, cx| {
2312 lsp_store.update_diagnostics(language_server_id, params, disk_based_sources, cx)
2313 })
2314 }
2315
2316 pub fn update_diagnostic_entries(
2317 &mut self,
2318 server_id: LanguageServerId,
2319 abs_path: PathBuf,
2320 version: Option<i32>,
2321 diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
2322 cx: &mut ModelContext<Project>,
2323 ) -> Result<(), anyhow::Error> {
2324 self.lsp_store.update(cx, |lsp_store, cx| {
2325 lsp_store.update_diagnostic_entries(server_id, abs_path, version, diagnostics, cx)
2326 })
2327 }
2328
2329 pub fn reload_buffers(
2330 &self,
2331 buffers: HashSet<Model<Buffer>>,
2332 push_to_history: bool,
2333 cx: &mut ModelContext<Self>,
2334 ) -> Task<Result<ProjectTransaction>> {
2335 self.buffer_store.update(cx, |buffer_store, cx| {
2336 buffer_store.reload_buffers(buffers, push_to_history, cx)
2337 })
2338 }
2339
2340 pub fn format(
2341 &mut self,
2342 buffers: HashSet<Model<Buffer>>,
2343 push_to_history: bool,
2344 trigger: lsp_store::FormatTrigger,
2345 cx: &mut ModelContext<Project>,
2346 ) -> Task<anyhow::Result<ProjectTransaction>> {
2347 self.lsp_store.update(cx, |lsp_store, cx| {
2348 lsp_store.format(buffers, push_to_history, trigger, cx)
2349 })
2350 }
2351
2352 #[inline(never)]
2353 fn definition_impl(
2354 &mut self,
2355 buffer: &Model<Buffer>,
2356 position: PointUtf16,
2357 cx: &mut ModelContext<Self>,
2358 ) -> Task<Result<Vec<LocationLink>>> {
2359 self.request_lsp(
2360 buffer.clone(),
2361 LanguageServerToQuery::Primary,
2362 GetDefinition { position },
2363 cx,
2364 )
2365 }
2366 pub fn definition<T: ToPointUtf16>(
2367 &mut self,
2368 buffer: &Model<Buffer>,
2369 position: T,
2370 cx: &mut ModelContext<Self>,
2371 ) -> Task<Result<Vec<LocationLink>>> {
2372 let position = position.to_point_utf16(buffer.read(cx));
2373 self.definition_impl(buffer, position, cx)
2374 }
2375
2376 fn declaration_impl(
2377 &mut self,
2378 buffer: &Model<Buffer>,
2379 position: PointUtf16,
2380 cx: &mut ModelContext<Self>,
2381 ) -> Task<Result<Vec<LocationLink>>> {
2382 self.request_lsp(
2383 buffer.clone(),
2384 LanguageServerToQuery::Primary,
2385 GetDeclaration { position },
2386 cx,
2387 )
2388 }
2389
2390 pub fn declaration<T: ToPointUtf16>(
2391 &mut self,
2392 buffer: &Model<Buffer>,
2393 position: T,
2394 cx: &mut ModelContext<Self>,
2395 ) -> Task<Result<Vec<LocationLink>>> {
2396 let position = position.to_point_utf16(buffer.read(cx));
2397 self.declaration_impl(buffer, position, cx)
2398 }
2399
2400 fn type_definition_impl(
2401 &mut self,
2402 buffer: &Model<Buffer>,
2403 position: PointUtf16,
2404 cx: &mut ModelContext<Self>,
2405 ) -> Task<Result<Vec<LocationLink>>> {
2406 self.request_lsp(
2407 buffer.clone(),
2408 LanguageServerToQuery::Primary,
2409 GetTypeDefinition { position },
2410 cx,
2411 )
2412 }
2413
2414 pub fn type_definition<T: ToPointUtf16>(
2415 &mut self,
2416 buffer: &Model<Buffer>,
2417 position: T,
2418 cx: &mut ModelContext<Self>,
2419 ) -> Task<Result<Vec<LocationLink>>> {
2420 let position = position.to_point_utf16(buffer.read(cx));
2421 self.type_definition_impl(buffer, position, cx)
2422 }
2423
2424 pub fn implementation<T: ToPointUtf16>(
2425 &mut self,
2426 buffer: &Model<Buffer>,
2427 position: T,
2428 cx: &mut ModelContext<Self>,
2429 ) -> Task<Result<Vec<LocationLink>>> {
2430 let position = position.to_point_utf16(buffer.read(cx));
2431 self.request_lsp(
2432 buffer.clone(),
2433 LanguageServerToQuery::Primary,
2434 GetImplementation { position },
2435 cx,
2436 )
2437 }
2438
2439 pub fn references<T: ToPointUtf16>(
2440 &mut self,
2441 buffer: &Model<Buffer>,
2442 position: T,
2443 cx: &mut ModelContext<Self>,
2444 ) -> Task<Result<Vec<Location>>> {
2445 let position = position.to_point_utf16(buffer.read(cx));
2446 self.request_lsp(
2447 buffer.clone(),
2448 LanguageServerToQuery::Primary,
2449 GetReferences { position },
2450 cx,
2451 )
2452 }
2453
2454 fn document_highlights_impl(
2455 &mut self,
2456 buffer: &Model<Buffer>,
2457 position: PointUtf16,
2458 cx: &mut ModelContext<Self>,
2459 ) -> Task<Result<Vec<DocumentHighlight>>> {
2460 self.request_lsp(
2461 buffer.clone(),
2462 LanguageServerToQuery::Primary,
2463 GetDocumentHighlights { position },
2464 cx,
2465 )
2466 }
2467
2468 pub fn document_highlights<T: ToPointUtf16>(
2469 &mut self,
2470 buffer: &Model<Buffer>,
2471 position: T,
2472 cx: &mut ModelContext<Self>,
2473 ) -> Task<Result<Vec<DocumentHighlight>>> {
2474 let position = position.to_point_utf16(buffer.read(cx));
2475 self.document_highlights_impl(buffer, position, cx)
2476 }
2477
2478 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
2479 self.lsp_store
2480 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
2481 }
2482
2483 pub fn open_buffer_for_symbol(
2484 &mut self,
2485 symbol: &Symbol,
2486 cx: &mut ModelContext<Self>,
2487 ) -> Task<Result<Model<Buffer>>> {
2488 self.lsp_store.update(cx, |lsp_store, cx| {
2489 lsp_store.open_buffer_for_symbol(symbol, cx)
2490 })
2491 }
2492
2493 pub fn open_local_buffer_via_lsp(
2494 &mut self,
2495 abs_path: lsp::Url,
2496 language_server_id: LanguageServerId,
2497 language_server_name: LanguageServerName,
2498 cx: &mut ModelContext<Self>,
2499 ) -> Task<Result<Model<Buffer>>> {
2500 self.lsp_store.update(cx, |lsp_store, cx| {
2501 lsp_store.open_local_buffer_via_lsp(
2502 abs_path,
2503 language_server_id,
2504 language_server_name,
2505 cx,
2506 )
2507 })
2508 }
2509
2510 pub fn signature_help<T: ToPointUtf16>(
2511 &self,
2512 buffer: &Model<Buffer>,
2513 position: T,
2514 cx: &mut ModelContext<Self>,
2515 ) -> Task<Vec<SignatureHelp>> {
2516 self.lsp_store.update(cx, |lsp_store, cx| {
2517 lsp_store.signature_help(buffer, position, cx)
2518 })
2519 }
2520
2521 pub fn hover<T: ToPointUtf16>(
2522 &self,
2523 buffer: &Model<Buffer>,
2524 position: T,
2525 cx: &mut ModelContext<Self>,
2526 ) -> Task<Vec<Hover>> {
2527 let position = position.to_point_utf16(buffer.read(cx));
2528 self.lsp_store
2529 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
2530 }
2531
2532 pub fn linked_edit(
2533 &self,
2534 buffer: &Model<Buffer>,
2535 position: Anchor,
2536 cx: &mut ModelContext<Self>,
2537 ) -> Task<Result<Vec<Range<Anchor>>>> {
2538 self.lsp_store.update(cx, |lsp_store, cx| {
2539 lsp_store.linked_edit(buffer, position, cx)
2540 })
2541 }
2542
2543 pub fn completions<T: ToOffset + ToPointUtf16>(
2544 &self,
2545 buffer: &Model<Buffer>,
2546 position: T,
2547 context: CompletionContext,
2548 cx: &mut ModelContext<Self>,
2549 ) -> Task<Result<Vec<Completion>>> {
2550 let position = position.to_point_utf16(buffer.read(cx));
2551 self.lsp_store.update(cx, |lsp_store, cx| {
2552 lsp_store.completions(buffer, position, context, cx)
2553 })
2554 }
2555
2556 pub fn resolve_completions(
2557 &self,
2558 buffer: Model<Buffer>,
2559 completion_indices: Vec<usize>,
2560 completions: Arc<RwLock<Box<[Completion]>>>,
2561 cx: &mut ModelContext<Self>,
2562 ) -> Task<Result<bool>> {
2563 self.lsp_store.update(cx, |lsp_store, cx| {
2564 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
2565 })
2566 }
2567
2568 pub fn apply_additional_edits_for_completion(
2569 &self,
2570 buffer_handle: Model<Buffer>,
2571 completion: Completion,
2572 push_to_history: bool,
2573 cx: &mut ModelContext<Self>,
2574 ) -> Task<Result<Option<Transaction>>> {
2575 self.lsp_store.update(cx, |lsp_store, cx| {
2576 lsp_store.apply_additional_edits_for_completion(
2577 buffer_handle,
2578 completion,
2579 push_to_history,
2580 cx,
2581 )
2582 })
2583 }
2584
2585 pub fn code_actions<T: Clone + ToOffset>(
2586 &mut self,
2587 buffer_handle: &Model<Buffer>,
2588 range: Range<T>,
2589 cx: &mut ModelContext<Self>,
2590 ) -> Task<Result<Vec<CodeAction>>> {
2591 let buffer = buffer_handle.read(cx);
2592 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2593 self.lsp_store.update(cx, |lsp_store, cx| {
2594 lsp_store.code_actions(buffer_handle, range, cx)
2595 })
2596 }
2597
2598 pub fn apply_code_action(
2599 &self,
2600 buffer_handle: Model<Buffer>,
2601 action: CodeAction,
2602 push_to_history: bool,
2603 cx: &mut ModelContext<Self>,
2604 ) -> Task<Result<ProjectTransaction>> {
2605 self.lsp_store.update(cx, |lsp_store, cx| {
2606 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
2607 })
2608 }
2609
2610 fn prepare_rename_impl(
2611 &mut self,
2612 buffer: Model<Buffer>,
2613 position: PointUtf16,
2614 cx: &mut ModelContext<Self>,
2615 ) -> Task<Result<Option<Range<Anchor>>>> {
2616 self.request_lsp(
2617 buffer,
2618 LanguageServerToQuery::Primary,
2619 PrepareRename { position },
2620 cx,
2621 )
2622 }
2623 pub fn prepare_rename<T: ToPointUtf16>(
2624 &mut self,
2625 buffer: Model<Buffer>,
2626 position: T,
2627 cx: &mut ModelContext<Self>,
2628 ) -> Task<Result<Option<Range<Anchor>>>> {
2629 let position = position.to_point_utf16(buffer.read(cx));
2630 self.prepare_rename_impl(buffer, position, cx)
2631 }
2632
2633 fn perform_rename_impl(
2634 &mut self,
2635 buffer: Model<Buffer>,
2636 position: PointUtf16,
2637 new_name: String,
2638 push_to_history: bool,
2639 cx: &mut ModelContext<Self>,
2640 ) -> Task<Result<ProjectTransaction>> {
2641 let position = position.to_point_utf16(buffer.read(cx));
2642 self.request_lsp(
2643 buffer,
2644 LanguageServerToQuery::Primary,
2645 PerformRename {
2646 position,
2647 new_name,
2648 push_to_history,
2649 },
2650 cx,
2651 )
2652 }
2653 pub fn perform_rename<T: ToPointUtf16>(
2654 &mut self,
2655 buffer: Model<Buffer>,
2656 position: T,
2657 new_name: String,
2658 push_to_history: bool,
2659 cx: &mut ModelContext<Self>,
2660 ) -> Task<Result<ProjectTransaction>> {
2661 let position = position.to_point_utf16(buffer.read(cx));
2662 self.perform_rename_impl(buffer, position, new_name, push_to_history, cx)
2663 }
2664
2665 pub fn on_type_format<T: ToPointUtf16>(
2666 &mut self,
2667 buffer: Model<Buffer>,
2668 position: T,
2669 trigger: String,
2670 push_to_history: bool,
2671 cx: &mut ModelContext<Self>,
2672 ) -> Task<Result<Option<Transaction>>> {
2673 self.lsp_store.update(cx, |lsp_store, cx| {
2674 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
2675 })
2676 }
2677
2678 pub fn inlay_hints<T: ToOffset>(
2679 &mut self,
2680 buffer_handle: Model<Buffer>,
2681 range: Range<T>,
2682 cx: &mut ModelContext<Self>,
2683 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
2684 let buffer = buffer_handle.read(cx);
2685 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2686 self.lsp_store.update(cx, |lsp_store, cx| {
2687 lsp_store.inlay_hints(buffer_handle, range, cx)
2688 })
2689 }
2690
2691 pub fn resolve_inlay_hint(
2692 &self,
2693 hint: InlayHint,
2694 buffer_handle: Model<Buffer>,
2695 server_id: LanguageServerId,
2696 cx: &mut ModelContext<Self>,
2697 ) -> Task<anyhow::Result<InlayHint>> {
2698 self.lsp_store.update(cx, |lsp_store, cx| {
2699 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
2700 })
2701 }
2702
2703 pub fn search(
2704 &mut self,
2705 query: SearchQuery,
2706 cx: &mut ModelContext<Self>,
2707 ) -> Receiver<SearchResult> {
2708 let (result_tx, result_rx) = smol::channel::unbounded();
2709
2710 let matching_buffers_rx = if query.is_opened_only() {
2711 self.sort_candidate_buffers(&query, cx)
2712 } else {
2713 self.search_for_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
2714 };
2715
2716 cx.spawn(|_, cx| async move {
2717 let mut range_count = 0;
2718 let mut buffer_count = 0;
2719 let mut limit_reached = false;
2720 let query = Arc::new(query);
2721 let mut chunks = matching_buffers_rx.ready_chunks(64);
2722
2723 // Now that we know what paths match the query, we will load at most
2724 // 64 buffers at a time to avoid overwhelming the main thread. For each
2725 // opened buffer, we will spawn a background task that retrieves all the
2726 // ranges in the buffer matched by the query.
2727 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
2728 let mut chunk_results = Vec::new();
2729 for buffer in matching_buffer_chunk {
2730 let buffer = buffer.clone();
2731 let query = query.clone();
2732 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
2733 chunk_results.push(cx.background_executor().spawn(async move {
2734 let ranges = query
2735 .search(&snapshot, None)
2736 .await
2737 .iter()
2738 .map(|range| {
2739 snapshot.anchor_before(range.start)
2740 ..snapshot.anchor_after(range.end)
2741 })
2742 .collect::<Vec<_>>();
2743 anyhow::Ok((buffer, ranges))
2744 }));
2745 }
2746
2747 let chunk_results = futures::future::join_all(chunk_results).await;
2748 for result in chunk_results {
2749 if let Some((buffer, ranges)) = result.log_err() {
2750 range_count += ranges.len();
2751 buffer_count += 1;
2752 result_tx
2753 .send(SearchResult::Buffer { buffer, ranges })
2754 .await?;
2755 if buffer_count > MAX_SEARCH_RESULT_FILES
2756 || range_count > MAX_SEARCH_RESULT_RANGES
2757 {
2758 limit_reached = true;
2759 break 'outer;
2760 }
2761 }
2762 }
2763 }
2764
2765 if limit_reached {
2766 result_tx.send(SearchResult::LimitReached).await?;
2767 }
2768
2769 anyhow::Ok(())
2770 })
2771 .detach();
2772
2773 result_rx
2774 }
2775
2776 fn search_for_candidate_buffers(
2777 &mut self,
2778 query: &SearchQuery,
2779 limit: usize,
2780 cx: &mut ModelContext<Project>,
2781 ) -> Receiver<Model<Buffer>> {
2782 if self.is_local() {
2783 let fs = self.fs.clone();
2784 self.buffer_store.update(cx, |buffer_store, cx| {
2785 buffer_store.find_search_candidates(query, limit, fs, cx)
2786 })
2787 } else {
2788 self.search_for_candidate_buffers_remote(query, limit, cx)
2789 }
2790 }
2791
2792 fn sort_candidate_buffers(
2793 &mut self,
2794 search_query: &SearchQuery,
2795 cx: &mut ModelContext<Project>,
2796 ) -> Receiver<Model<Buffer>> {
2797 let worktree_store = self.worktree_store.read(cx);
2798 let mut buffers = search_query
2799 .buffers()
2800 .into_iter()
2801 .flatten()
2802 .filter(|buffer| {
2803 let b = buffer.read(cx);
2804 if let Some(file) = b.file() {
2805 if !search_query.file_matches(file.path()) {
2806 return false;
2807 }
2808 if let Some(entry) = b
2809 .entry_id(cx)
2810 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
2811 {
2812 if entry.is_ignored && !search_query.include_ignored() {
2813 return false;
2814 }
2815 }
2816 }
2817 true
2818 })
2819 .collect::<Vec<_>>();
2820 let (tx, rx) = smol::channel::unbounded();
2821 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
2822 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
2823 (None, Some(_)) => std::cmp::Ordering::Less,
2824 (Some(_), None) => std::cmp::Ordering::Greater,
2825 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
2826 });
2827 for buffer in buffers {
2828 tx.send_blocking(buffer.clone()).unwrap()
2829 }
2830
2831 rx
2832 }
2833
2834 fn search_for_candidate_buffers_remote(
2835 &mut self,
2836 query: &SearchQuery,
2837 limit: usize,
2838 cx: &mut ModelContext<Project>,
2839 ) -> Receiver<Model<Buffer>> {
2840 let (tx, rx) = smol::channel::unbounded();
2841
2842 let (client, remote_id): (AnyProtoClient, _) =
2843 if let Some(ssh_session) = self.ssh_session.clone() {
2844 (ssh_session.into(), 0)
2845 } else if let Some(remote_id) = self.remote_id() {
2846 (self.client.clone().into(), remote_id)
2847 } else {
2848 return rx;
2849 };
2850
2851 let request = client.request(proto::FindSearchCandidates {
2852 project_id: remote_id,
2853 query: Some(query.to_proto()),
2854 limit: limit as _,
2855 });
2856 let guard = self.retain_remotely_created_models(cx);
2857
2858 cx.spawn(move |this, mut cx| async move {
2859 let response = request.await?;
2860 for buffer_id in response.buffer_ids {
2861 let buffer_id = BufferId::new(buffer_id)?;
2862 let buffer = this
2863 .update(&mut cx, |this, cx| {
2864 this.wait_for_remote_buffer(buffer_id, cx)
2865 })?
2866 .await?;
2867 let _ = tx.send(buffer).await;
2868 }
2869
2870 drop(guard);
2871 anyhow::Ok(())
2872 })
2873 .detach_and_log_err(cx);
2874 rx
2875 }
2876
2877 pub fn request_lsp<R: LspCommand>(
2878 &mut self,
2879 buffer_handle: Model<Buffer>,
2880 server: LanguageServerToQuery,
2881 request: R,
2882 cx: &mut ModelContext<Self>,
2883 ) -> Task<Result<R::Response>>
2884 where
2885 <R::LspRequest as lsp::request::Request>::Result: Send,
2886 <R::LspRequest as lsp::request::Request>::Params: Send,
2887 {
2888 let guard = self.retain_remotely_created_models(cx);
2889 let task = self.lsp_store.update(cx, |lsp_store, cx| {
2890 lsp_store.request_lsp(buffer_handle, server, request, cx)
2891 });
2892 cx.spawn(|_, _| async move {
2893 let result = task.await;
2894 drop(guard);
2895 result
2896 })
2897 }
2898
2899 /// Move a worktree to a new position in the worktree order.
2900 ///
2901 /// The worktree will moved to the opposite side of the destination worktree.
2902 ///
2903 /// # Example
2904 ///
2905 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
2906 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
2907 ///
2908 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
2909 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
2910 ///
2911 /// # Errors
2912 ///
2913 /// An error will be returned if the worktree or destination worktree are not found.
2914 pub fn move_worktree(
2915 &mut self,
2916 source: WorktreeId,
2917 destination: WorktreeId,
2918 cx: &mut ModelContext<'_, Self>,
2919 ) -> Result<()> {
2920 self.worktree_store.update(cx, |worktree_store, cx| {
2921 worktree_store.move_worktree(source, destination, cx)
2922 })
2923 }
2924
2925 pub fn find_or_create_worktree(
2926 &mut self,
2927 abs_path: impl AsRef<Path>,
2928 visible: bool,
2929 cx: &mut ModelContext<Self>,
2930 ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
2931 let abs_path = abs_path.as_ref();
2932 if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) {
2933 Task::ready(Ok((tree, relative_path)))
2934 } else {
2935 let worktree = self.create_worktree(abs_path, visible, cx);
2936 cx.background_executor()
2937 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
2938 }
2939 }
2940
2941 pub fn find_worktree(
2942 &self,
2943 abs_path: &Path,
2944 cx: &AppContext,
2945 ) -> Option<(Model<Worktree>, PathBuf)> {
2946 self.worktree_store.read_with(cx, |worktree_store, cx| {
2947 worktree_store.find_worktree(abs_path, cx)
2948 })
2949 }
2950
2951 pub fn is_shared(&self) -> bool {
2952 match &self.client_state {
2953 ProjectClientState::Shared { .. } => true,
2954 ProjectClientState::Local => false,
2955 ProjectClientState::Remote { in_room, .. } => *in_room,
2956 }
2957 }
2958
2959 // Returns the resolved version of `path`, that was found in `buffer`, if it exists.
2960 pub fn resolve_existing_file_path(
2961 &self,
2962 path: &str,
2963 buffer: &Model<Buffer>,
2964 cx: &mut ModelContext<Self>,
2965 ) -> Task<Option<ResolvedPath>> {
2966 let path_buf = PathBuf::from(path);
2967 if path_buf.is_absolute() || path.starts_with("~") {
2968 if self.is_local() {
2969 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
2970
2971 let fs = self.fs.clone();
2972 cx.background_executor().spawn(async move {
2973 let path = expanded.as_path();
2974 let exists = fs.is_file(path).await;
2975
2976 exists.then(|| ResolvedPath::AbsPath(expanded))
2977 })
2978 } else if let Some(ssh_session) = self.ssh_session.as_ref() {
2979 let request = ssh_session.request(proto::CheckFileExists {
2980 project_id: SSH_PROJECT_ID,
2981 path: path.to_string(),
2982 });
2983 cx.background_executor().spawn(async move {
2984 let response = request.await.log_err()?;
2985 if response.exists {
2986 Some(ResolvedPath::AbsPath(PathBuf::from(response.path)))
2987 } else {
2988 None
2989 }
2990 })
2991 } else {
2992 return Task::ready(None);
2993 }
2994 } else {
2995 self.resolve_path_in_worktrees(path_buf, buffer, cx)
2996 }
2997 }
2998
2999 fn resolve_path_in_worktrees(
3000 &self,
3001 path: PathBuf,
3002 buffer: &Model<Buffer>,
3003 cx: &mut ModelContext<Self>,
3004 ) -> Task<Option<ResolvedPath>> {
3005 let mut candidates = vec![path.clone()];
3006
3007 if let Some(file) = buffer.read(cx).file() {
3008 if let Some(dir) = file.path().parent() {
3009 let joined = dir.to_path_buf().join(path);
3010 candidates.push(joined);
3011 }
3012 }
3013
3014 let worktrees = self.worktrees(cx).collect::<Vec<_>>();
3015 cx.spawn(|_, mut cx| async move {
3016 for worktree in worktrees {
3017 for candidate in candidates.iter() {
3018 let path = worktree
3019 .update(&mut cx, |worktree, _| {
3020 let root_entry_path = &worktree.root_entry()?.path;
3021
3022 let resolved = resolve_path(root_entry_path, candidate);
3023
3024 let stripped =
3025 resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3026
3027 worktree.entry_for_path(stripped).map(|entry| {
3028 ResolvedPath::ProjectPath(ProjectPath {
3029 worktree_id: worktree.id(),
3030 path: entry.path.clone(),
3031 })
3032 })
3033 })
3034 .ok()?;
3035
3036 if path.is_some() {
3037 return path;
3038 }
3039 }
3040 }
3041 None
3042 })
3043 }
3044
3045 pub fn list_directory(
3046 &self,
3047 query: String,
3048 cx: &mut ModelContext<Self>,
3049 ) -> Task<Result<Vec<PathBuf>>> {
3050 if self.is_local_or_ssh() {
3051 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3052 } else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| {
3053 dev_server_projects::Store::global(cx)
3054 .read(cx)
3055 .dev_server_for_project(id)
3056 }) {
3057 let request = proto::ListRemoteDirectory {
3058 dev_server_id: dev_server.id.0,
3059 path: query,
3060 };
3061 let response = self.client.request(request);
3062 cx.background_executor().spawn(async move {
3063 let response = response.await?;
3064 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3065 })
3066 } else {
3067 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3068 }
3069 }
3070
3071 fn create_worktree(
3072 &mut self,
3073 abs_path: impl AsRef<Path>,
3074 visible: bool,
3075 cx: &mut ModelContext<Self>,
3076 ) -> Task<Result<Model<Worktree>>> {
3077 self.worktree_store.update(cx, |worktree_store, cx| {
3078 worktree_store.create_worktree(abs_path, visible, cx)
3079 })
3080 }
3081
3082 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
3083 self.worktree_store.update(cx, |worktree_store, cx| {
3084 worktree_store.remove_worktree(id_to_remove, cx);
3085 });
3086 }
3087
3088 fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
3089 self.worktree_store.update(cx, |worktree_store, cx| {
3090 worktree_store.add(worktree, cx);
3091 });
3092 }
3093
3094 fn update_local_worktree_settings(
3095 &mut self,
3096 worktree: &Model<Worktree>,
3097 changes: &UpdatedEntriesSet,
3098 cx: &mut ModelContext<Self>,
3099 ) {
3100 if worktree.read(cx).is_remote() {
3101 return;
3102 }
3103 let remote_worktree_id = worktree.read(cx).id();
3104
3105 for (path, _, change) in changes.iter() {
3106 let removed = change == &PathChange::Removed;
3107 let abs_path = match worktree.read(cx).absolutize(path) {
3108 Ok(abs_path) => abs_path,
3109 Err(e) => {
3110 log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");
3111 continue;
3112 }
3113 };
3114
3115 if path.ends_with(local_tasks_file_relative_path()) {
3116 self.task_inventory().update(cx, |task_inventory, cx| {
3117 if removed {
3118 task_inventory.remove_local_static_source(&abs_path);
3119 } else {
3120 let fs = self.fs.clone();
3121 let task_abs_path = abs_path.clone();
3122 let tasks_file_rx =
3123 watch_config_file(cx.background_executor(), fs, task_abs_path);
3124 task_inventory.add_source(
3125 TaskSourceKind::Worktree {
3126 id: remote_worktree_id,
3127 abs_path,
3128 id_base: "local_tasks_for_worktree".into(),
3129 },
3130 |tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
3131 cx,
3132 );
3133 }
3134 })
3135 } else if path.ends_with(local_vscode_tasks_file_relative_path()) {
3136 self.task_inventory().update(cx, |task_inventory, cx| {
3137 if removed {
3138 task_inventory.remove_local_static_source(&abs_path);
3139 } else {
3140 let fs = self.fs.clone();
3141 let task_abs_path = abs_path.clone();
3142 let tasks_file_rx =
3143 watch_config_file(cx.background_executor(), fs, task_abs_path);
3144 task_inventory.add_source(
3145 TaskSourceKind::Worktree {
3146 id: remote_worktree_id,
3147 abs_path,
3148 id_base: "local_vscode_tasks_for_worktree".into(),
3149 },
3150 |tx, cx| {
3151 StaticSource::new(TrackedFile::new_convertible::<
3152 task::VsCodeTaskFile,
3153 >(
3154 tasks_file_rx, tx, cx
3155 ))
3156 },
3157 cx,
3158 );
3159 }
3160 })
3161 }
3162 }
3163 }
3164
3165 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
3166 let new_active_entry = entry.and_then(|project_path| {
3167 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
3168 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
3169 Some(entry.id)
3170 });
3171 if new_active_entry != self.active_entry {
3172 self.active_entry = new_active_entry;
3173 self.lsp_store.update(cx, |lsp_store, _| {
3174 lsp_store.set_active_entry(new_active_entry);
3175 });
3176 cx.emit(Event::ActiveEntryChanged(new_active_entry));
3177 }
3178 }
3179
3180 pub fn language_servers_running_disk_based_diagnostics<'a>(
3181 &'a self,
3182 cx: &'a AppContext,
3183 ) -> impl Iterator<Item = LanguageServerId> + 'a {
3184 self.lsp_store
3185 .read(cx)
3186 .language_servers_running_disk_based_diagnostics()
3187 }
3188
3189 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
3190 let mut summary = DiagnosticSummary::default();
3191 for (_, _, path_summary) in self.diagnostic_summaries(include_ignored, cx) {
3192 summary.error_count += path_summary.error_count;
3193 summary.warning_count += path_summary.warning_count;
3194 }
3195 summary
3196 }
3197
3198 pub fn diagnostic_summaries<'a>(
3199 &'a self,
3200 include_ignored: bool,
3201 cx: &'a AppContext,
3202 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
3203 self.lsp_store
3204 .read(cx)
3205 .diagnostic_summaries(include_ignored, cx)
3206 }
3207
3208 pub fn active_entry(&self) -> Option<ProjectEntryId> {
3209 self.active_entry
3210 }
3211
3212 pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
3213 self.worktree_store.read(cx).entry_for_path(path, cx)
3214 }
3215
3216 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
3217 let worktree = self.worktree_for_entry(entry_id, cx)?;
3218 let worktree = worktree.read(cx);
3219 let worktree_id = worktree.id();
3220 let path = worktree.entry_for_id(entry_id)?.path.clone();
3221 Some(ProjectPath { worktree_id, path })
3222 }
3223
3224 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
3225 let workspace_root = self
3226 .worktree_for_id(project_path.worktree_id, cx)?
3227 .read(cx)
3228 .abs_path();
3229 let project_path = project_path.path.as_ref();
3230
3231 Some(if project_path == Path::new("") {
3232 workspace_root.to_path_buf()
3233 } else {
3234 workspace_root.join(project_path)
3235 })
3236 }
3237
3238 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
3239 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
3240 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
3241 /// the first visible worktree that has an entry for that relative path.
3242 ///
3243 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
3244 /// root name from paths.
3245 ///
3246 /// # Arguments
3247 ///
3248 /// * `path` - A full path that starts with a worktree root name, or alternatively a
3249 /// relative path within a visible worktree.
3250 /// * `cx` - A reference to the `AppContext`.
3251 ///
3252 /// # Returns
3253 ///
3254 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
3255 pub fn find_project_path(&self, path: &Path, cx: &AppContext) -> Option<ProjectPath> {
3256 let worktree_store = self.worktree_store.read(cx);
3257
3258 for worktree in worktree_store.visible_worktrees(cx) {
3259 let worktree_root_name = worktree.read(cx).root_name();
3260 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
3261 return Some(ProjectPath {
3262 worktree_id: worktree.read(cx).id(),
3263 path: relative_path.into(),
3264 });
3265 }
3266 }
3267
3268 for worktree in worktree_store.visible_worktrees(cx) {
3269 let worktree = worktree.read(cx);
3270 if let Some(entry) = worktree.entry_for_path(path) {
3271 return Some(ProjectPath {
3272 worktree_id: worktree.id(),
3273 path: entry.path.clone(),
3274 });
3275 }
3276 }
3277
3278 None
3279 }
3280
3281 pub fn get_workspace_root(
3282 &self,
3283 project_path: &ProjectPath,
3284 cx: &AppContext,
3285 ) -> Option<PathBuf> {
3286 Some(
3287 self.worktree_for_id(project_path.worktree_id, cx)?
3288 .read(cx)
3289 .abs_path()
3290 .to_path_buf(),
3291 )
3292 }
3293
3294 pub fn get_repo(
3295 &self,
3296 project_path: &ProjectPath,
3297 cx: &AppContext,
3298 ) -> Option<Arc<dyn GitRepository>> {
3299 self.worktree_for_id(project_path.worktree_id, cx)?
3300 .read(cx)
3301 .as_local()?
3302 .local_git_repo(&project_path.path)
3303 }
3304
3305 pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
3306 let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
3307 let root_entry = worktree.root_git_entry()?;
3308 worktree.get_local_repo(&root_entry)?.repo().clone().into()
3309 }
3310
3311 pub fn blame_buffer(
3312 &self,
3313 buffer: &Model<Buffer>,
3314 version: Option<clock::Global>,
3315 cx: &AppContext,
3316 ) -> Task<Result<Blame>> {
3317 self.buffer_store.read(cx).blame_buffer(buffer, version, cx)
3318 }
3319
3320 // RPC message handlers
3321
3322 async fn handle_unshare_project(
3323 this: Model<Self>,
3324 _: TypedEnvelope<proto::UnshareProject>,
3325 mut cx: AsyncAppContext,
3326 ) -> Result<()> {
3327 this.update(&mut cx, |this, cx| {
3328 if this.is_local_or_ssh() {
3329 this.unshare(cx)?;
3330 } else {
3331 this.disconnected_from_host(cx);
3332 }
3333 Ok(())
3334 })?
3335 }
3336
3337 async fn handle_add_collaborator(
3338 this: Model<Self>,
3339 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
3340 mut cx: AsyncAppContext,
3341 ) -> Result<()> {
3342 let collaborator = envelope
3343 .payload
3344 .collaborator
3345 .take()
3346 .ok_or_else(|| anyhow!("empty collaborator"))?;
3347
3348 let collaborator = Collaborator::from_proto(collaborator)?;
3349 this.update(&mut cx, |this, cx| {
3350 this.buffer_store.update(cx, |buffer_store, _| {
3351 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
3352 });
3353 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
3354 this.collaborators
3355 .insert(collaborator.peer_id, collaborator);
3356 cx.notify();
3357 })?;
3358
3359 Ok(())
3360 }
3361
3362 async fn handle_update_project_collaborator(
3363 this: Model<Self>,
3364 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
3365 mut cx: AsyncAppContext,
3366 ) -> Result<()> {
3367 let old_peer_id = envelope
3368 .payload
3369 .old_peer_id
3370 .ok_or_else(|| anyhow!("missing old peer id"))?;
3371 let new_peer_id = envelope
3372 .payload
3373 .new_peer_id
3374 .ok_or_else(|| anyhow!("missing new peer id"))?;
3375 this.update(&mut cx, |this, cx| {
3376 let collaborator = this
3377 .collaborators
3378 .remove(&old_peer_id)
3379 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
3380 let is_host = collaborator.replica_id == 0;
3381 this.collaborators.insert(new_peer_id, collaborator);
3382
3383 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
3384 this.buffer_store.update(cx, |buffer_store, _| {
3385 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
3386 });
3387
3388 if is_host {
3389 this.buffer_store
3390 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
3391 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
3392 .unwrap();
3393 cx.emit(Event::HostReshared);
3394 }
3395
3396 cx.emit(Event::CollaboratorUpdated {
3397 old_peer_id,
3398 new_peer_id,
3399 });
3400 cx.notify();
3401 Ok(())
3402 })?
3403 }
3404
3405 async fn handle_remove_collaborator(
3406 this: Model<Self>,
3407 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
3408 mut cx: AsyncAppContext,
3409 ) -> Result<()> {
3410 this.update(&mut cx, |this, cx| {
3411 let peer_id = envelope
3412 .payload
3413 .peer_id
3414 .ok_or_else(|| anyhow!("invalid peer id"))?;
3415 let replica_id = this
3416 .collaborators
3417 .remove(&peer_id)
3418 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
3419 .replica_id;
3420 this.buffer_store.update(cx, |buffer_store, cx| {
3421 buffer_store.forget_shared_buffers_for(&peer_id);
3422 for buffer in buffer_store.buffers() {
3423 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
3424 }
3425 });
3426
3427 cx.emit(Event::CollaboratorLeft(peer_id));
3428 cx.notify();
3429 Ok(())
3430 })?
3431 }
3432
3433 async fn handle_update_project(
3434 this: Model<Self>,
3435 envelope: TypedEnvelope<proto::UpdateProject>,
3436 mut cx: AsyncAppContext,
3437 ) -> Result<()> {
3438 this.update(&mut cx, |this, cx| {
3439 // Don't handle messages that were sent before the response to us joining the project
3440 if envelope.message_id > this.join_project_response_message_id {
3441 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
3442 }
3443 Ok(())
3444 })?
3445 }
3446
3447 // Collab sends UpdateWorktree protos as messages
3448 async fn handle_update_worktree(
3449 this: Model<Self>,
3450 envelope: TypedEnvelope<proto::UpdateWorktree>,
3451 mut cx: AsyncAppContext,
3452 ) -> Result<()> {
3453 this.update(&mut cx, |this, cx| {
3454 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3455 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
3456 worktree.update(cx, |worktree, _| {
3457 let worktree = worktree.as_remote_mut().unwrap();
3458 worktree.update_from_remote(envelope.payload);
3459 });
3460 }
3461 Ok(())
3462 })?
3463 }
3464
3465 async fn handle_update_buffer(
3466 this: Model<Self>,
3467 envelope: TypedEnvelope<proto::UpdateBuffer>,
3468 cx: AsyncAppContext,
3469 ) -> Result<proto::Ack> {
3470 let buffer_store = this.read_with(&cx, |this, cx| {
3471 if let Some(ssh) = &this.ssh_session {
3472 let mut payload = envelope.payload.clone();
3473 payload.project_id = 0;
3474 cx.background_executor()
3475 .spawn(ssh.request(payload))
3476 .detach_and_log_err(cx);
3477 }
3478 this.buffer_store.clone()
3479 })?;
3480 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3481 }
3482
3483 fn retain_remotely_created_models(
3484 &mut self,
3485 cx: &mut ModelContext<Self>,
3486 ) -> RemotelyCreatedModelGuard {
3487 {
3488 let mut remotely_create_models = self.remotely_created_models.lock();
3489 if remotely_create_models.retain_count == 0 {
3490 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
3491 remotely_create_models.worktrees =
3492 self.worktree_store.read(cx).worktrees().collect();
3493 }
3494 remotely_create_models.retain_count += 1;
3495 }
3496 RemotelyCreatedModelGuard {
3497 remote_models: Arc::downgrade(&self.remotely_created_models),
3498 }
3499 }
3500
3501 async fn handle_create_buffer_for_peer(
3502 this: Model<Self>,
3503 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
3504 mut cx: AsyncAppContext,
3505 ) -> Result<()> {
3506 this.update(&mut cx, |this, cx| {
3507 this.buffer_store.update(cx, |buffer_store, cx| {
3508 buffer_store.handle_create_buffer_for_peer(
3509 envelope,
3510 this.replica_id(),
3511 this.capability(),
3512 cx,
3513 )
3514 })
3515 })?
3516 }
3517
3518 async fn handle_synchronize_buffers(
3519 this: Model<Self>,
3520 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
3521 mut cx: AsyncAppContext,
3522 ) -> Result<proto::SynchronizeBuffersResponse> {
3523 let response = this.update(&mut cx, |this, cx| {
3524 let client = this.client.clone();
3525 this.buffer_store.update(cx, |this, cx| {
3526 this.handle_synchronize_buffers(envelope, cx, client)
3527 })
3528 })??;
3529
3530 Ok(response)
3531 }
3532
3533 async fn handle_task_context_for_location(
3534 project: Model<Self>,
3535 envelope: TypedEnvelope<proto::TaskContextForLocation>,
3536 mut cx: AsyncAppContext,
3537 ) -> Result<proto::TaskContext> {
3538 let location = envelope
3539 .payload
3540 .location
3541 .context("no location given for task context handling")?;
3542 let location = cx
3543 .update(|cx| deserialize_location(&project, location, cx))?
3544 .await?;
3545 let context_task = project.update(&mut cx, |project, cx| {
3546 let captured_variables = {
3547 let mut variables = TaskVariables::default();
3548 for range in location
3549 .buffer
3550 .read(cx)
3551 .snapshot()
3552 .runnable_ranges(location.range.clone())
3553 {
3554 for (capture_name, value) in range.extra_captures {
3555 variables.insert(VariableName::Custom(capture_name.into()), value);
3556 }
3557 }
3558 variables
3559 };
3560 project.task_context_for_location(captured_variables, location, cx)
3561 })?;
3562 let task_context = context_task.await.unwrap_or_default();
3563 Ok(proto::TaskContext {
3564 project_env: task_context.project_env.into_iter().collect(),
3565 cwd: task_context
3566 .cwd
3567 .map(|cwd| cwd.to_string_lossy().to_string()),
3568 task_variables: task_context
3569 .task_variables
3570 .into_iter()
3571 .map(|(variable_name, variable_value)| (variable_name.to_string(), variable_value))
3572 .collect(),
3573 })
3574 }
3575
3576 async fn handle_task_templates(
3577 project: Model<Self>,
3578 envelope: TypedEnvelope<proto::TaskTemplates>,
3579 mut cx: AsyncAppContext,
3580 ) -> Result<proto::TaskTemplatesResponse> {
3581 let worktree = envelope.payload.worktree_id.map(WorktreeId::from_proto);
3582 let location = match envelope.payload.location {
3583 Some(location) => Some(
3584 cx.update(|cx| deserialize_location(&project, location, cx))?
3585 .await
3586 .context("task templates request location deserializing")?,
3587 ),
3588 None => None,
3589 };
3590
3591 let templates = project
3592 .update(&mut cx, |project, cx| {
3593 project.task_templates(worktree, location, cx)
3594 })?
3595 .await
3596 .context("receiving task templates")?
3597 .into_iter()
3598 .map(|(kind, template)| {
3599 let kind = Some(match kind {
3600 TaskSourceKind::UserInput => proto::task_source_kind::Kind::UserInput(
3601 proto::task_source_kind::UserInput {},
3602 ),
3603 TaskSourceKind::Worktree {
3604 id,
3605 abs_path,
3606 id_base,
3607 } => {
3608 proto::task_source_kind::Kind::Worktree(proto::task_source_kind::Worktree {
3609 id: id.to_proto(),
3610 abs_path: abs_path.to_string_lossy().to_string(),
3611 id_base: id_base.to_string(),
3612 })
3613 }
3614 TaskSourceKind::AbsPath { id_base, abs_path } => {
3615 proto::task_source_kind::Kind::AbsPath(proto::task_source_kind::AbsPath {
3616 abs_path: abs_path.to_string_lossy().to_string(),
3617 id_base: id_base.to_string(),
3618 })
3619 }
3620 TaskSourceKind::Language { name } => {
3621 proto::task_source_kind::Kind::Language(proto::task_source_kind::Language {
3622 name: name.to_string(),
3623 })
3624 }
3625 });
3626 let kind = Some(proto::TaskSourceKind { kind });
3627 let template = Some(proto::TaskTemplate {
3628 label: template.label,
3629 command: template.command,
3630 args: template.args,
3631 env: template.env.into_iter().collect(),
3632 cwd: template.cwd,
3633 use_new_terminal: template.use_new_terminal,
3634 allow_concurrent_runs: template.allow_concurrent_runs,
3635 reveal: match template.reveal {
3636 RevealStrategy::Always => proto::RevealStrategy::RevealAlways as i32,
3637 RevealStrategy::Never => proto::RevealStrategy::RevealNever as i32,
3638 },
3639 hide: match template.hide {
3640 HideStrategy::Always => proto::HideStrategy::HideAlways as i32,
3641 HideStrategy::Never => proto::HideStrategy::HideNever as i32,
3642 HideStrategy::OnSuccess => proto::HideStrategy::HideOnSuccess as i32,
3643 },
3644 shell: Some(proto::Shell {
3645 shell_type: Some(match template.shell {
3646 Shell::System => proto::shell::ShellType::System(proto::System {}),
3647 Shell::Program(program) => proto::shell::ShellType::Program(program),
3648 Shell::WithArguments { program, args } => {
3649 proto::shell::ShellType::WithArguments(
3650 proto::shell::WithArguments { program, args },
3651 )
3652 }
3653 }),
3654 }),
3655 tags: template.tags,
3656 });
3657 proto::TemplatePair { kind, template }
3658 })
3659 .collect();
3660
3661 Ok(proto::TaskTemplatesResponse { templates })
3662 }
3663
3664 async fn handle_search_project(
3665 this: Model<Self>,
3666 envelope: TypedEnvelope<proto::SearchProject>,
3667 mut cx: AsyncAppContext,
3668 ) -> Result<proto::SearchProjectResponse> {
3669 let peer_id = envelope.original_sender_id()?;
3670 let query = SearchQuery::from_proto_v1(envelope.payload)?;
3671 let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
3672
3673 cx.spawn(move |mut cx| async move {
3674 let mut locations = Vec::new();
3675 let mut limit_reached = false;
3676 while let Some(result) = result.next().await {
3677 match result {
3678 SearchResult::Buffer { buffer, ranges } => {
3679 for range in ranges {
3680 let start = serialize_anchor(&range.start);
3681 let end = serialize_anchor(&range.end);
3682 let buffer_id = this.update(&mut cx, |this, cx| {
3683 this.create_buffer_for_peer(&buffer, peer_id, cx).into()
3684 })?;
3685 locations.push(proto::Location {
3686 buffer_id,
3687 start: Some(start),
3688 end: Some(end),
3689 });
3690 }
3691 }
3692 SearchResult::LimitReached => limit_reached = true,
3693 }
3694 }
3695 Ok(proto::SearchProjectResponse {
3696 locations,
3697 limit_reached,
3698 // will restart
3699 })
3700 })
3701 .await
3702 }
3703
3704 async fn handle_search_candidate_buffers(
3705 this: Model<Self>,
3706 envelope: TypedEnvelope<proto::FindSearchCandidates>,
3707 mut cx: AsyncAppContext,
3708 ) -> Result<proto::FindSearchCandidatesResponse> {
3709 let peer_id = envelope.original_sender_id()?;
3710 let message = envelope.payload;
3711 let query = SearchQuery::from_proto(
3712 message
3713 .query
3714 .ok_or_else(|| anyhow!("missing query field"))?,
3715 )?;
3716 let mut results = this.update(&mut cx, |this, cx| {
3717 this.search_for_candidate_buffers(&query, message.limit as _, cx)
3718 })?;
3719
3720 let mut response = proto::FindSearchCandidatesResponse {
3721 buffer_ids: Vec::new(),
3722 };
3723
3724 while let Some(buffer) = results.next().await {
3725 this.update(&mut cx, |this, cx| {
3726 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
3727 response.buffer_ids.push(buffer_id.to_proto());
3728 })?;
3729 }
3730
3731 Ok(response)
3732 }
3733
3734 async fn handle_open_buffer_by_id(
3735 this: Model<Self>,
3736 envelope: TypedEnvelope<proto::OpenBufferById>,
3737 mut cx: AsyncAppContext,
3738 ) -> Result<proto::OpenBufferResponse> {
3739 let peer_id = envelope.original_sender_id()?;
3740 let buffer_id = BufferId::new(envelope.payload.id)?;
3741 let buffer = this
3742 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
3743 .await?;
3744 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3745 }
3746
3747 async fn handle_open_buffer_by_path(
3748 this: Model<Self>,
3749 envelope: TypedEnvelope<proto::OpenBufferByPath>,
3750 mut cx: AsyncAppContext,
3751 ) -> Result<proto::OpenBufferResponse> {
3752 let peer_id = envelope.original_sender_id()?;
3753 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3754 let open_buffer = this.update(&mut cx, |this, cx| {
3755 this.open_buffer(
3756 ProjectPath {
3757 worktree_id,
3758 path: PathBuf::from(envelope.payload.path).into(),
3759 },
3760 cx,
3761 )
3762 })?;
3763
3764 let buffer = open_buffer.await?;
3765 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3766 }
3767
3768 async fn handle_open_new_buffer(
3769 this: Model<Self>,
3770 envelope: TypedEnvelope<proto::OpenNewBuffer>,
3771 mut cx: AsyncAppContext,
3772 ) -> Result<proto::OpenBufferResponse> {
3773 let buffer = this.update(&mut cx, |this, cx| this.create_local_buffer("", None, cx))?;
3774 let peer_id = envelope.original_sender_id()?;
3775
3776 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3777 }
3778
3779 fn respond_to_open_buffer_request(
3780 this: Model<Self>,
3781 buffer: Model<Buffer>,
3782 peer_id: proto::PeerId,
3783 cx: &mut AsyncAppContext,
3784 ) -> Result<proto::OpenBufferResponse> {
3785 this.update(cx, |this, cx| {
3786 let is_private = buffer
3787 .read(cx)
3788 .file()
3789 .map(|f| f.is_private())
3790 .unwrap_or_default();
3791 if is_private {
3792 Err(anyhow!(ErrorCode::UnsharedItem))
3793 } else {
3794 Ok(proto::OpenBufferResponse {
3795 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
3796 })
3797 }
3798 })?
3799 }
3800
3801 fn create_buffer_for_peer(
3802 &mut self,
3803 buffer: &Model<Buffer>,
3804 peer_id: proto::PeerId,
3805 cx: &mut AppContext,
3806 ) -> BufferId {
3807 self.buffer_store
3808 .update(cx, |buffer_store, cx| {
3809 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
3810 })
3811 .detach_and_log_err(cx);
3812 buffer.read(cx).remote_id()
3813 }
3814
3815 fn wait_for_remote_buffer(
3816 &mut self,
3817 id: BufferId,
3818 cx: &mut ModelContext<Self>,
3819 ) -> Task<Result<Model<Buffer>>> {
3820 self.buffer_store.update(cx, |buffer_store, cx| {
3821 buffer_store.wait_for_remote_buffer(id, cx)
3822 })
3823 }
3824
3825 fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
3826 let project_id = match self.client_state {
3827 ProjectClientState::Remote {
3828 sharing_has_stopped,
3829 remote_id,
3830 ..
3831 } => {
3832 if sharing_has_stopped {
3833 return Task::ready(Err(anyhow!(
3834 "can't synchronize remote buffers on a readonly project"
3835 )));
3836 } else {
3837 remote_id
3838 }
3839 }
3840 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
3841 return Task::ready(Err(anyhow!(
3842 "can't synchronize remote buffers on a local project"
3843 )))
3844 }
3845 };
3846
3847 let client = self.client.clone();
3848 cx.spawn(move |this, mut cx| async move {
3849 let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
3850 this.buffer_store.read(cx).buffer_version_info(cx)
3851 })?;
3852 let response = client
3853 .request(proto::SynchronizeBuffers {
3854 project_id,
3855 buffers,
3856 })
3857 .await?;
3858
3859 let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
3860 response
3861 .buffers
3862 .into_iter()
3863 .map(|buffer| {
3864 let client = client.clone();
3865 let buffer_id = match BufferId::new(buffer.id) {
3866 Ok(id) => id,
3867 Err(e) => {
3868 return Task::ready(Err(e));
3869 }
3870 };
3871 let remote_version = language::proto::deserialize_version(&buffer.version);
3872 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
3873 let operations =
3874 buffer.read(cx).serialize_ops(Some(remote_version), cx);
3875 cx.background_executor().spawn(async move {
3876 let operations = operations.await;
3877 for chunk in split_operations(operations) {
3878 client
3879 .request(proto::UpdateBuffer {
3880 project_id,
3881 buffer_id: buffer_id.into(),
3882 operations: chunk,
3883 })
3884 .await?;
3885 }
3886 anyhow::Ok(())
3887 })
3888 } else {
3889 Task::ready(Ok(()))
3890 }
3891 })
3892 .collect::<Vec<_>>()
3893 })?;
3894
3895 // Any incomplete buffers have open requests waiting. Request that the host sends
3896 // creates these buffers for us again to unblock any waiting futures.
3897 for id in incomplete_buffer_ids {
3898 cx.background_executor()
3899 .spawn(client.request(proto::OpenBufferById {
3900 project_id,
3901 id: id.into(),
3902 }))
3903 .detach();
3904 }
3905
3906 futures::future::join_all(send_updates_for_buffers)
3907 .await
3908 .into_iter()
3909 .collect()
3910 })
3911 }
3912
3913 pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
3914 self.worktree_store.read(cx).worktree_metadata_protos(cx)
3915 }
3916
3917 fn set_worktrees_from_proto(
3918 &mut self,
3919 worktrees: Vec<proto::WorktreeMetadata>,
3920 cx: &mut ModelContext<Project>,
3921 ) -> Result<()> {
3922 cx.notify();
3923 self.worktree_store.update(cx, |worktree_store, cx| {
3924 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
3925 })
3926 }
3927
3928 fn set_collaborators_from_proto(
3929 &mut self,
3930 messages: Vec<proto::Collaborator>,
3931 cx: &mut ModelContext<Self>,
3932 ) -> Result<()> {
3933 let mut collaborators = HashMap::default();
3934 for message in messages {
3935 let collaborator = Collaborator::from_proto(message)?;
3936 collaborators.insert(collaborator.peer_id, collaborator);
3937 }
3938 for old_peer_id in self.collaborators.keys() {
3939 if !collaborators.contains_key(old_peer_id) {
3940 cx.emit(Event::CollaboratorLeft(*old_peer_id));
3941 }
3942 }
3943 self.collaborators = collaborators;
3944 Ok(())
3945 }
3946
3947 pub fn language_servers<'a>(
3948 &'a self,
3949 cx: &'a AppContext,
3950 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
3951 self.lsp_store.read(cx).language_servers()
3952 }
3953
3954 pub fn supplementary_language_servers<'a>(
3955 &'a self,
3956 cx: &'a AppContext,
3957 ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
3958 self.lsp_store.read(cx).supplementary_language_servers()
3959 }
3960
3961 pub fn language_server_adapter_for_id(
3962 &self,
3963 id: LanguageServerId,
3964 cx: &AppContext,
3965 ) -> Option<Arc<CachedLspAdapter>> {
3966 self.lsp_store.read(cx).language_server_adapter_for_id(id)
3967 }
3968
3969 pub fn language_server_for_id(
3970 &self,
3971 id: LanguageServerId,
3972 cx: &AppContext,
3973 ) -> Option<Arc<LanguageServer>> {
3974 self.lsp_store.read(cx).language_server_for_id(id)
3975 }
3976
3977 pub fn language_servers_for_buffer<'a>(
3978 &'a self,
3979 buffer: &'a Buffer,
3980 cx: &'a AppContext,
3981 ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
3982 self.lsp_store
3983 .read(cx)
3984 .language_servers_for_buffer(buffer, cx)
3985 }
3986
3987 pub fn language_server_for_buffer<'a>(
3988 &'a self,
3989 buffer: &'a Buffer,
3990 server_id: LanguageServerId,
3991 cx: &'a AppContext,
3992 ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
3993 self.lsp_store
3994 .read(cx)
3995 .language_server_for_buffer(buffer, server_id, cx)
3996 }
3997
3998 pub fn task_context_for_location(
3999 &self,
4000 captured_variables: TaskVariables,
4001 location: Location,
4002 cx: &mut ModelContext<'_, Project>,
4003 ) -> Task<Option<TaskContext>> {
4004 if self.is_local_or_ssh() {
4005 let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) {
4006 (
4007 Some(worktree.read(cx).id()),
4008 Some(worktree.read(cx).abs_path()),
4009 )
4010 } else {
4011 (None, None)
4012 };
4013
4014 cx.spawn(|project, mut cx| async move {
4015 let project_env = project
4016 .update(&mut cx, |project, cx| {
4017 let worktree_abs_path = worktree_abs_path.clone();
4018 project.environment.update(cx, |environment, cx| {
4019 environment.get_environment(worktree_id, worktree_abs_path, cx)
4020 })
4021 })
4022 .ok()?
4023 .await;
4024
4025 let mut task_variables = cx
4026 .update(|cx| {
4027 combine_task_variables(
4028 captured_variables,
4029 location,
4030 project_env.as_ref(),
4031 BasicContextProvider::new(project.upgrade()?),
4032 cx,
4033 )
4034 .log_err()
4035 })
4036 .ok()
4037 .flatten()?;
4038 // Remove all custom entries starting with _, as they're not intended for use by the end user.
4039 task_variables.sweep();
4040
4041 Some(TaskContext {
4042 project_env: project_env.unwrap_or_default(),
4043 cwd: worktree_abs_path.map(|p| p.to_path_buf()),
4044 task_variables,
4045 })
4046 })
4047 } else if let Some(project_id) = self
4048 .remote_id()
4049 .filter(|_| self.ssh_connection_string(cx).is_some())
4050 {
4051 let task_context = self.client().request(proto::TaskContextForLocation {
4052 project_id,
4053 location: Some(proto::Location {
4054 buffer_id: location.buffer.read(cx).remote_id().into(),
4055 start: Some(serialize_anchor(&location.range.start)),
4056 end: Some(serialize_anchor(&location.range.end)),
4057 }),
4058 });
4059 cx.background_executor().spawn(async move {
4060 let task_context = task_context.await.log_err()?;
4061 Some(TaskContext {
4062 project_env: task_context.project_env.into_iter().collect(),
4063 cwd: task_context.cwd.map(PathBuf::from),
4064 task_variables: task_context
4065 .task_variables
4066 .into_iter()
4067 .filter_map(
4068 |(variable_name, variable_value)| match variable_name.parse() {
4069 Ok(variable_name) => Some((variable_name, variable_value)),
4070 Err(()) => {
4071 log::error!("Unknown variable name: {variable_name}");
4072 None
4073 }
4074 },
4075 )
4076 .collect(),
4077 })
4078 })
4079 } else {
4080 Task::ready(None)
4081 }
4082 }
4083
4084 pub fn task_templates(
4085 &self,
4086 worktree: Option<WorktreeId>,
4087 location: Option<Location>,
4088 cx: &mut ModelContext<Self>,
4089 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4090 if self.is_local_or_ssh() {
4091 let (file, language) = location
4092 .map(|location| {
4093 let buffer = location.buffer.read(cx);
4094 (
4095 buffer.file().cloned(),
4096 buffer.language_at(location.range.start),
4097 )
4098 })
4099 .unwrap_or_default();
4100 Task::ready(Ok(self
4101 .task_inventory()
4102 .read(cx)
4103 .list_tasks(file, language, worktree, cx)))
4104 } else if let Some(project_id) = self
4105 .remote_id()
4106 .filter(|_| self.ssh_connection_string(cx).is_some())
4107 {
4108 let remote_templates =
4109 self.query_remote_task_templates(project_id, worktree, location.as_ref(), cx);
4110 cx.background_executor().spawn(remote_templates)
4111 } else {
4112 Task::ready(Ok(Vec::new()))
4113 }
4114 }
4115
4116 pub fn query_remote_task_templates(
4117 &self,
4118 project_id: u64,
4119 worktree: Option<WorktreeId>,
4120 location: Option<&Location>,
4121 cx: &AppContext,
4122 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4123 let client = self.client();
4124 let location = location.map(|location| serialize_location(location, cx));
4125 cx.spawn(|_| async move {
4126 let response = client
4127 .request(proto::TaskTemplates {
4128 project_id,
4129 worktree_id: worktree.map(|id| id.to_proto()),
4130 location,
4131 })
4132 .await?;
4133
4134 Ok(response
4135 .templates
4136 .into_iter()
4137 .filter_map(|template_pair| {
4138 let task_source_kind = match template_pair.kind?.kind? {
4139 proto::task_source_kind::Kind::UserInput(_) => TaskSourceKind::UserInput,
4140 proto::task_source_kind::Kind::Worktree(worktree) => {
4141 TaskSourceKind::Worktree {
4142 id: WorktreeId::from_proto(worktree.id),
4143 abs_path: PathBuf::from(worktree.abs_path),
4144 id_base: Cow::Owned(worktree.id_base),
4145 }
4146 }
4147 proto::task_source_kind::Kind::AbsPath(abs_path) => {
4148 TaskSourceKind::AbsPath {
4149 id_base: Cow::Owned(abs_path.id_base),
4150 abs_path: PathBuf::from(abs_path.abs_path),
4151 }
4152 }
4153 proto::task_source_kind::Kind::Language(language) => {
4154 TaskSourceKind::Language {
4155 name: language.name.into(),
4156 }
4157 }
4158 };
4159
4160 let proto_template = template_pair.template?;
4161 let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
4162 .unwrap_or(proto::RevealStrategy::RevealAlways)
4163 {
4164 proto::RevealStrategy::RevealAlways => RevealStrategy::Always,
4165 proto::RevealStrategy::RevealNever => RevealStrategy::Never,
4166 };
4167 let hide = match proto::HideStrategy::from_i32(proto_template.hide)
4168 .unwrap_or(proto::HideStrategy::HideNever)
4169 {
4170 proto::HideStrategy::HideAlways => HideStrategy::Always,
4171 proto::HideStrategy::HideNever => HideStrategy::Never,
4172 proto::HideStrategy::HideOnSuccess => HideStrategy::OnSuccess,
4173 };
4174 let shell = match proto_template
4175 .shell
4176 .and_then(|shell| shell.shell_type)
4177 .unwrap_or(proto::shell::ShellType::System(proto::System {}))
4178 {
4179 proto::shell::ShellType::System(_) => Shell::System,
4180 proto::shell::ShellType::Program(program) => Shell::Program(program),
4181 proto::shell::ShellType::WithArguments(with_arguments) => {
4182 Shell::WithArguments {
4183 program: with_arguments.program,
4184 args: with_arguments.args,
4185 }
4186 }
4187 };
4188 let task_template = TaskTemplate {
4189 label: proto_template.label,
4190 command: proto_template.command,
4191 args: proto_template.args,
4192 env: proto_template.env.into_iter().collect(),
4193 cwd: proto_template.cwd,
4194 use_new_terminal: proto_template.use_new_terminal,
4195 allow_concurrent_runs: proto_template.allow_concurrent_runs,
4196 reveal,
4197 hide,
4198 shell,
4199 tags: proto_template.tags,
4200 };
4201 Some((task_source_kind, task_template))
4202 })
4203 .collect())
4204 })
4205 }
4206
4207 fn task_worktree(&self, cx: &AppContext) -> Option<Model<Worktree>> {
4208 let available_worktrees = self
4209 .worktrees(cx)
4210 .filter(|worktree| {
4211 let worktree = worktree.read(cx);
4212 worktree.is_visible()
4213 && worktree.is_local()
4214 && worktree.root_entry().map_or(false, |e| e.is_dir())
4215 })
4216 .collect::<Vec<_>>();
4217
4218 match available_worktrees.len() {
4219 0 => None,
4220 1 => Some(available_worktrees[0].clone()),
4221 _ => self.active_entry().and_then(|entry_id| {
4222 available_worktrees.into_iter().find_map(|worktree| {
4223 if worktree.read(cx).contains_entry(entry_id) {
4224 Some(worktree)
4225 } else {
4226 None
4227 }
4228 })
4229 }),
4230 }
4231 }
4232}
4233
4234fn combine_task_variables(
4235 mut captured_variables: TaskVariables,
4236 location: Location,
4237 project_env: Option<&HashMap<String, String>>,
4238 baseline: BasicContextProvider,
4239 cx: &mut AppContext,
4240) -> anyhow::Result<TaskVariables> {
4241 let language_context_provider = location
4242 .buffer
4243 .read(cx)
4244 .language()
4245 .and_then(|language| language.context_provider());
4246 let baseline = baseline
4247 .build_context(&captured_variables, &location, project_env, cx)
4248 .context("building basic default context")?;
4249 captured_variables.extend(baseline);
4250 if let Some(provider) = language_context_provider {
4251 captured_variables.extend(
4252 provider
4253 .build_context(&captured_variables, &location, project_env, cx)
4254 .context("building provider context")?,
4255 );
4256 }
4257 Ok(captured_variables)
4258}
4259
4260fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
4261 code_actions
4262 .iter()
4263 .flat_map(|(kind, enabled)| {
4264 if *enabled {
4265 Some(kind.clone().into())
4266 } else {
4267 None
4268 }
4269 })
4270 .collect()
4271}
4272
4273pub struct PathMatchCandidateSet {
4274 pub snapshot: Snapshot,
4275 pub include_ignored: bool,
4276 pub include_root_name: bool,
4277 pub candidates: Candidates,
4278}
4279
4280pub enum Candidates {
4281 /// Only consider directories.
4282 Directories,
4283 /// Only consider files.
4284 Files,
4285 /// Consider directories and files.
4286 Entries,
4287}
4288
4289impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4290 type Candidates = PathMatchCandidateSetIter<'a>;
4291
4292 fn id(&self) -> usize {
4293 self.snapshot.id().to_usize()
4294 }
4295
4296 fn len(&self) -> usize {
4297 match self.candidates {
4298 Candidates::Files => {
4299 if self.include_ignored {
4300 self.snapshot.file_count()
4301 } else {
4302 self.snapshot.visible_file_count()
4303 }
4304 }
4305
4306 Candidates::Directories => {
4307 if self.include_ignored {
4308 self.snapshot.dir_count()
4309 } else {
4310 self.snapshot.visible_dir_count()
4311 }
4312 }
4313
4314 Candidates::Entries => {
4315 if self.include_ignored {
4316 self.snapshot.entry_count()
4317 } else {
4318 self.snapshot.visible_entry_count()
4319 }
4320 }
4321 }
4322 }
4323
4324 fn prefix(&self) -> Arc<str> {
4325 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4326 self.snapshot.root_name().into()
4327 } else if self.include_root_name {
4328 format!("{}/", self.snapshot.root_name()).into()
4329 } else {
4330 Arc::default()
4331 }
4332 }
4333
4334 fn candidates(&'a self, start: usize) -> Self::Candidates {
4335 PathMatchCandidateSetIter {
4336 traversal: match self.candidates {
4337 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4338 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4339 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4340 },
4341 }
4342 }
4343}
4344
4345pub struct PathMatchCandidateSetIter<'a> {
4346 traversal: Traversal<'a>,
4347}
4348
4349impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4350 type Item = fuzzy::PathMatchCandidate<'a>;
4351
4352 fn next(&mut self) -> Option<Self::Item> {
4353 self.traversal
4354 .next()
4355 .map(|entry| fuzzy::PathMatchCandidate {
4356 is_dir: entry.kind.is_dir(),
4357 path: &entry.path,
4358 char_bag: entry.char_bag,
4359 })
4360 }
4361}
4362
4363impl EventEmitter<Event> for Project {}
4364
4365impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4366 fn from(val: &'a ProjectPath) -> Self {
4367 SettingsLocation {
4368 worktree_id: val.worktree_id,
4369 path: val.path.as_ref(),
4370 }
4371 }
4372}
4373
4374impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4375 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4376 Self {
4377 worktree_id,
4378 path: path.as_ref().into(),
4379 }
4380 }
4381}
4382
4383pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4384 let mut path_components = path.components();
4385 let mut base_components = base.components();
4386 let mut components: Vec<Component> = Vec::new();
4387 loop {
4388 match (path_components.next(), base_components.next()) {
4389 (None, None) => break,
4390 (Some(a), None) => {
4391 components.push(a);
4392 components.extend(path_components.by_ref());
4393 break;
4394 }
4395 (None, _) => components.push(Component::ParentDir),
4396 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4397 (Some(a), Some(Component::CurDir)) => components.push(a),
4398 (Some(a), Some(_)) => {
4399 components.push(Component::ParentDir);
4400 for _ in base_components {
4401 components.push(Component::ParentDir);
4402 }
4403 components.push(a);
4404 components.extend(path_components.by_ref());
4405 break;
4406 }
4407 }
4408 }
4409 components.iter().map(|c| c.as_os_str()).collect()
4410}
4411
4412fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4413 let mut result = base.to_path_buf();
4414 for component in path.components() {
4415 match component {
4416 Component::ParentDir => {
4417 result.pop();
4418 }
4419 Component::CurDir => (),
4420 _ => result.push(component),
4421 }
4422 }
4423 result
4424}
4425
4426/// ResolvedPath is a path that has been resolved to either a ProjectPath
4427/// or an AbsPath and that *exists*.
4428#[derive(Debug, Clone)]
4429pub enum ResolvedPath {
4430 ProjectPath(ProjectPath),
4431 AbsPath(PathBuf),
4432}
4433
4434impl ResolvedPath {
4435 pub fn abs_path(&self) -> Option<&Path> {
4436 match self {
4437 Self::AbsPath(path) => Some(path.as_path()),
4438 _ => None,
4439 }
4440 }
4441
4442 pub fn project_path(&self) -> Option<&ProjectPath> {
4443 match self {
4444 Self::ProjectPath(path) => Some(&path),
4445 _ => None,
4446 }
4447 }
4448}
4449
4450impl Item for Buffer {
4451 fn try_open(
4452 project: &Model<Project>,
4453 path: &ProjectPath,
4454 cx: &mut AppContext,
4455 ) -> Option<Task<Result<Model<Self>>>> {
4456 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4457 }
4458
4459 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
4460 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4461 }
4462
4463 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
4464 File::from_dyn(self.file()).map(|file| ProjectPath {
4465 worktree_id: file.worktree_id(cx),
4466 path: file.path().clone(),
4467 })
4468 }
4469}
4470
4471impl Completion {
4472 /// A key that can be used to sort completions when displaying
4473 /// them to the user.
4474 pub fn sort_key(&self) -> (usize, &str) {
4475 let kind_key = match self.lsp_completion.kind {
4476 Some(lsp::CompletionItemKind::KEYWORD) => 0,
4477 Some(lsp::CompletionItemKind::VARIABLE) => 1,
4478 _ => 2,
4479 };
4480 (kind_key, &self.label.text[self.label.filter_range.clone()])
4481 }
4482
4483 /// Whether this completion is a snippet.
4484 pub fn is_snippet(&self) -> bool {
4485 self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
4486 }
4487}
4488
4489#[derive(Debug)]
4490pub struct NoRepositoryError {}
4491
4492impl std::fmt::Display for NoRepositoryError {
4493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4494 write!(f, "no git repository for worktree found")
4495 }
4496}
4497
4498impl std::error::Error for NoRepositoryError {}
4499
4500fn serialize_location(location: &Location, cx: &AppContext) -> proto::Location {
4501 proto::Location {
4502 buffer_id: location.buffer.read(cx).remote_id().into(),
4503 start: Some(serialize_anchor(&location.range.start)),
4504 end: Some(serialize_anchor(&location.range.end)),
4505 }
4506}
4507
4508fn deserialize_location(
4509 project: &Model<Project>,
4510 location: proto::Location,
4511 cx: &mut AppContext,
4512) -> Task<Result<Location>> {
4513 let buffer_id = match BufferId::new(location.buffer_id) {
4514 Ok(id) => id,
4515 Err(e) => return Task::ready(Err(e)),
4516 };
4517 let buffer_task = project.update(cx, |project, cx| {
4518 project.wait_for_remote_buffer(buffer_id, cx)
4519 });
4520 cx.spawn(|_| async move {
4521 let buffer = buffer_task.await?;
4522 let start = location
4523 .start
4524 .and_then(deserialize_anchor)
4525 .context("missing task context location start")?;
4526 let end = location
4527 .end
4528 .and_then(deserialize_anchor)
4529 .context("missing task context location end")?;
4530 Ok(Location {
4531 buffer,
4532 range: start..end,
4533 })
4534 })
4535}
4536
4537pub fn sort_worktree_entries(entries: &mut [Entry]) {
4538 entries.sort_by(|entry_a, entry_b| {
4539 compare_paths(
4540 (&entry_a.path, entry_a.is_file()),
4541 (&entry_b.path, entry_b.is_file()),
4542 )
4543 });
4544}