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