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