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