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