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