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