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