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