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
2468 pub async fn toolchain_term(
2469 languages: Arc<LanguageRegistry>,
2470 language_name: LanguageName,
2471 ) -> Option<SharedString> {
2472 languages
2473 .language_for_name(&language_name.0)
2474 .await
2475 .ok()?
2476 .toolchain_lister()
2477 .map(|lister| lister.term())
2478 }
2479
2480 pub fn activate_toolchain(
2481 &self,
2482 worktree_id: WorktreeId,
2483 toolchain: Toolchain,
2484 cx: &mut AppContext,
2485 ) -> Task<Option<()>> {
2486 let Some(toolchain_store) = self.toolchain_store.clone() else {
2487 return Task::ready(None);
2488 };
2489 toolchain_store.update(cx, |this, cx| {
2490 this.activate_toolchain(worktree_id, toolchain, cx)
2491 })
2492 }
2493 pub fn active_toolchain(
2494 &self,
2495 worktree_id: WorktreeId,
2496 language_name: LanguageName,
2497 cx: &AppContext,
2498 ) -> Task<Option<Toolchain>> {
2499 let Some(toolchain_store) = self.toolchain_store.clone() else {
2500 return Task::ready(None);
2501 };
2502 toolchain_store
2503 .read(cx)
2504 .active_toolchain(worktree_id, language_name, cx)
2505 }
2506 pub fn language_server_statuses<'a>(
2507 &'a self,
2508 cx: &'a AppContext,
2509 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
2510 self.lsp_store.read(cx).language_server_statuses()
2511 }
2512
2513 pub fn last_formatting_failure<'a>(&self, cx: &'a AppContext) -> Option<&'a str> {
2514 self.lsp_store.read(cx).last_formatting_failure()
2515 }
2516
2517 pub fn reset_last_formatting_failure(&self, cx: &mut AppContext) {
2518 self.lsp_store
2519 .update(cx, |store, _| store.reset_last_formatting_failure());
2520 }
2521
2522 pub fn update_diagnostics(
2523 &mut self,
2524 language_server_id: LanguageServerId,
2525 params: lsp::PublishDiagnosticsParams,
2526 disk_based_sources: &[String],
2527 cx: &mut ModelContext<Self>,
2528 ) -> Result<()> {
2529 self.lsp_store.update(cx, |lsp_store, cx| {
2530 lsp_store.update_diagnostics(language_server_id, params, disk_based_sources, cx)
2531 })
2532 }
2533
2534 pub fn update_diagnostic_entries(
2535 &mut self,
2536 server_id: LanguageServerId,
2537 abs_path: PathBuf,
2538 version: Option<i32>,
2539 diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
2540 cx: &mut ModelContext<Project>,
2541 ) -> Result<(), anyhow::Error> {
2542 self.lsp_store.update(cx, |lsp_store, cx| {
2543 lsp_store.update_diagnostic_entries(server_id, abs_path, version, diagnostics, cx)
2544 })
2545 }
2546
2547 pub fn reload_buffers(
2548 &self,
2549 buffers: HashSet<Model<Buffer>>,
2550 push_to_history: bool,
2551 cx: &mut ModelContext<Self>,
2552 ) -> Task<Result<ProjectTransaction>> {
2553 self.buffer_store.update(cx, |buffer_store, cx| {
2554 buffer_store.reload_buffers(buffers, push_to_history, cx)
2555 })
2556 }
2557
2558 pub fn reload_images(
2559 &self,
2560 images: HashSet<Model<ImageItem>>,
2561 cx: &mut ModelContext<Self>,
2562 ) -> Task<Result<()>> {
2563 self.image_store
2564 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
2565 }
2566
2567 pub fn format(
2568 &mut self,
2569 buffers: HashSet<Model<Buffer>>,
2570 push_to_history: bool,
2571 trigger: lsp_store::FormatTrigger,
2572 target: lsp_store::FormatTarget,
2573 cx: &mut ModelContext<Project>,
2574 ) -> Task<anyhow::Result<ProjectTransaction>> {
2575 self.lsp_store.update(cx, |lsp_store, cx| {
2576 lsp_store.format(buffers, push_to_history, trigger, target, cx)
2577 })
2578 }
2579
2580 #[inline(never)]
2581 fn definition_impl(
2582 &mut self,
2583 buffer: &Model<Buffer>,
2584 position: PointUtf16,
2585 cx: &mut ModelContext<Self>,
2586 ) -> Task<Result<Vec<LocationLink>>> {
2587 self.request_lsp(
2588 buffer.clone(),
2589 LanguageServerToQuery::Primary,
2590 GetDefinition { position },
2591 cx,
2592 )
2593 }
2594 pub fn definition<T: ToPointUtf16>(
2595 &mut self,
2596 buffer: &Model<Buffer>,
2597 position: T,
2598 cx: &mut ModelContext<Self>,
2599 ) -> Task<Result<Vec<LocationLink>>> {
2600 let position = position.to_point_utf16(buffer.read(cx));
2601 self.definition_impl(buffer, position, cx)
2602 }
2603
2604 fn declaration_impl(
2605 &mut self,
2606 buffer: &Model<Buffer>,
2607 position: PointUtf16,
2608 cx: &mut ModelContext<Self>,
2609 ) -> Task<Result<Vec<LocationLink>>> {
2610 self.request_lsp(
2611 buffer.clone(),
2612 LanguageServerToQuery::Primary,
2613 GetDeclaration { position },
2614 cx,
2615 )
2616 }
2617
2618 pub fn declaration<T: ToPointUtf16>(
2619 &mut self,
2620 buffer: &Model<Buffer>,
2621 position: T,
2622 cx: &mut ModelContext<Self>,
2623 ) -> Task<Result<Vec<LocationLink>>> {
2624 let position = position.to_point_utf16(buffer.read(cx));
2625 self.declaration_impl(buffer, position, cx)
2626 }
2627
2628 fn type_definition_impl(
2629 &mut self,
2630 buffer: &Model<Buffer>,
2631 position: PointUtf16,
2632 cx: &mut ModelContext<Self>,
2633 ) -> Task<Result<Vec<LocationLink>>> {
2634 self.request_lsp(
2635 buffer.clone(),
2636 LanguageServerToQuery::Primary,
2637 GetTypeDefinition { position },
2638 cx,
2639 )
2640 }
2641
2642 pub fn type_definition<T: ToPointUtf16>(
2643 &mut self,
2644 buffer: &Model<Buffer>,
2645 position: T,
2646 cx: &mut ModelContext<Self>,
2647 ) -> Task<Result<Vec<LocationLink>>> {
2648 let position = position.to_point_utf16(buffer.read(cx));
2649 self.type_definition_impl(buffer, position, cx)
2650 }
2651
2652 pub fn implementation<T: ToPointUtf16>(
2653 &mut self,
2654 buffer: &Model<Buffer>,
2655 position: T,
2656 cx: &mut ModelContext<Self>,
2657 ) -> Task<Result<Vec<LocationLink>>> {
2658 let position = position.to_point_utf16(buffer.read(cx));
2659 self.request_lsp(
2660 buffer.clone(),
2661 LanguageServerToQuery::Primary,
2662 GetImplementation { position },
2663 cx,
2664 )
2665 }
2666
2667 pub fn references<T: ToPointUtf16>(
2668 &mut self,
2669 buffer: &Model<Buffer>,
2670 position: T,
2671 cx: &mut ModelContext<Self>,
2672 ) -> Task<Result<Vec<Location>>> {
2673 let position = position.to_point_utf16(buffer.read(cx));
2674 self.request_lsp(
2675 buffer.clone(),
2676 LanguageServerToQuery::Primary,
2677 GetReferences { position },
2678 cx,
2679 )
2680 }
2681
2682 fn document_highlights_impl(
2683 &mut self,
2684 buffer: &Model<Buffer>,
2685 position: PointUtf16,
2686 cx: &mut ModelContext<Self>,
2687 ) -> Task<Result<Vec<DocumentHighlight>>> {
2688 self.request_lsp(
2689 buffer.clone(),
2690 LanguageServerToQuery::Primary,
2691 GetDocumentHighlights { position },
2692 cx,
2693 )
2694 }
2695
2696 pub fn document_highlights<T: ToPointUtf16>(
2697 &mut self,
2698 buffer: &Model<Buffer>,
2699 position: T,
2700 cx: &mut ModelContext<Self>,
2701 ) -> Task<Result<Vec<DocumentHighlight>>> {
2702 let position = position.to_point_utf16(buffer.read(cx));
2703 self.document_highlights_impl(buffer, position, cx)
2704 }
2705
2706 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
2707 self.lsp_store
2708 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
2709 }
2710
2711 pub fn open_buffer_for_symbol(
2712 &mut self,
2713 symbol: &Symbol,
2714 cx: &mut ModelContext<Self>,
2715 ) -> Task<Result<Model<Buffer>>> {
2716 self.lsp_store.update(cx, |lsp_store, cx| {
2717 lsp_store.open_buffer_for_symbol(symbol, cx)
2718 })
2719 }
2720
2721 pub fn open_server_settings(
2722 &mut self,
2723 cx: &mut ModelContext<Self>,
2724 ) -> Task<Result<Model<Buffer>>> {
2725 let guard = self.retain_remotely_created_models(cx);
2726 let Some(ssh_client) = self.ssh_client.as_ref() else {
2727 return Task::ready(Err(anyhow!("not an ssh project")));
2728 };
2729
2730 let proto_client = ssh_client.read(cx).proto_client();
2731
2732 cx.spawn(|this, mut cx| async move {
2733 let buffer = proto_client
2734 .request(proto::OpenServerSettings {
2735 project_id: SSH_PROJECT_ID,
2736 })
2737 .await?;
2738
2739 let buffer = this
2740 .update(&mut cx, |this, cx| {
2741 anyhow::Ok(this.wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx))
2742 })??
2743 .await;
2744
2745 drop(guard);
2746 buffer
2747 })
2748 }
2749
2750 pub fn open_local_buffer_via_lsp(
2751 &mut self,
2752 abs_path: lsp::Url,
2753 language_server_id: LanguageServerId,
2754 language_server_name: LanguageServerName,
2755 cx: &mut ModelContext<Self>,
2756 ) -> Task<Result<Model<Buffer>>> {
2757 self.lsp_store.update(cx, |lsp_store, cx| {
2758 lsp_store.open_local_buffer_via_lsp(
2759 abs_path,
2760 language_server_id,
2761 language_server_name,
2762 cx,
2763 )
2764 })
2765 }
2766
2767 pub fn signature_help<T: ToPointUtf16>(
2768 &self,
2769 buffer: &Model<Buffer>,
2770 position: T,
2771 cx: &mut ModelContext<Self>,
2772 ) -> Task<Vec<SignatureHelp>> {
2773 self.lsp_store.update(cx, |lsp_store, cx| {
2774 lsp_store.signature_help(buffer, position, cx)
2775 })
2776 }
2777
2778 pub fn hover<T: ToPointUtf16>(
2779 &self,
2780 buffer: &Model<Buffer>,
2781 position: T,
2782 cx: &mut ModelContext<Self>,
2783 ) -> Task<Vec<Hover>> {
2784 let position = position.to_point_utf16(buffer.read(cx));
2785 self.lsp_store
2786 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
2787 }
2788
2789 pub fn linked_edit(
2790 &self,
2791 buffer: &Model<Buffer>,
2792 position: Anchor,
2793 cx: &mut ModelContext<Self>,
2794 ) -> Task<Result<Vec<Range<Anchor>>>> {
2795 self.lsp_store.update(cx, |lsp_store, cx| {
2796 lsp_store.linked_edit(buffer, position, cx)
2797 })
2798 }
2799
2800 pub fn completions<T: ToOffset + ToPointUtf16>(
2801 &self,
2802 buffer: &Model<Buffer>,
2803 position: T,
2804 context: CompletionContext,
2805 cx: &mut ModelContext<Self>,
2806 ) -> Task<Result<Vec<Completion>>> {
2807 let position = position.to_point_utf16(buffer.read(cx));
2808 self.lsp_store.update(cx, |lsp_store, cx| {
2809 lsp_store.completions(buffer, position, context, cx)
2810 })
2811 }
2812
2813 pub fn resolve_completions(
2814 &self,
2815 buffer: Model<Buffer>,
2816 completion_indices: Vec<usize>,
2817 completions: Arc<RwLock<Box<[Completion]>>>,
2818 cx: &mut ModelContext<Self>,
2819 ) -> Task<Result<bool>> {
2820 self.lsp_store.update(cx, |lsp_store, cx| {
2821 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
2822 })
2823 }
2824
2825 pub fn apply_additional_edits_for_completion(
2826 &self,
2827 buffer_handle: Model<Buffer>,
2828 completion: Completion,
2829 push_to_history: bool,
2830 cx: &mut ModelContext<Self>,
2831 ) -> Task<Result<Option<Transaction>>> {
2832 self.lsp_store.update(cx, |lsp_store, cx| {
2833 lsp_store.apply_additional_edits_for_completion(
2834 buffer_handle,
2835 completion,
2836 push_to_history,
2837 cx,
2838 )
2839 })
2840 }
2841
2842 pub fn code_actions<T: Clone + ToOffset>(
2843 &mut self,
2844 buffer_handle: &Model<Buffer>,
2845 range: Range<T>,
2846 cx: &mut ModelContext<Self>,
2847 ) -> Task<Result<Vec<CodeAction>>> {
2848 let buffer = buffer_handle.read(cx);
2849 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2850 self.lsp_store.update(cx, |lsp_store, cx| {
2851 lsp_store.code_actions(buffer_handle, range, cx)
2852 })
2853 }
2854
2855 pub fn apply_code_action(
2856 &self,
2857 buffer_handle: Model<Buffer>,
2858 action: CodeAction,
2859 push_to_history: bool,
2860 cx: &mut ModelContext<Self>,
2861 ) -> Task<Result<ProjectTransaction>> {
2862 self.lsp_store.update(cx, |lsp_store, cx| {
2863 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
2864 })
2865 }
2866
2867 fn prepare_rename_impl(
2868 &mut self,
2869 buffer: Model<Buffer>,
2870 position: PointUtf16,
2871 cx: &mut ModelContext<Self>,
2872 ) -> Task<Result<Option<Range<Anchor>>>> {
2873 self.request_lsp(
2874 buffer,
2875 LanguageServerToQuery::Primary,
2876 PrepareRename { position },
2877 cx,
2878 )
2879 }
2880 pub fn prepare_rename<T: ToPointUtf16>(
2881 &mut self,
2882 buffer: Model<Buffer>,
2883 position: T,
2884 cx: &mut ModelContext<Self>,
2885 ) -> Task<Result<Option<Range<Anchor>>>> {
2886 let position = position.to_point_utf16(buffer.read(cx));
2887 self.prepare_rename_impl(buffer, position, cx)
2888 }
2889
2890 fn perform_rename_impl(
2891 &mut self,
2892 buffer: Model<Buffer>,
2893 position: PointUtf16,
2894 new_name: String,
2895 push_to_history: bool,
2896 cx: &mut ModelContext<Self>,
2897 ) -> Task<Result<ProjectTransaction>> {
2898 let position = position.to_point_utf16(buffer.read(cx));
2899 self.request_lsp(
2900 buffer,
2901 LanguageServerToQuery::Primary,
2902 PerformRename {
2903 position,
2904 new_name,
2905 push_to_history,
2906 },
2907 cx,
2908 )
2909 }
2910
2911 pub fn perform_rename<T: ToPointUtf16>(
2912 &mut self,
2913 buffer: Model<Buffer>,
2914 position: T,
2915 new_name: String,
2916 cx: &mut ModelContext<Self>,
2917 ) -> Task<Result<ProjectTransaction>> {
2918 let position = position.to_point_utf16(buffer.read(cx));
2919 self.perform_rename_impl(buffer, position, new_name, true, cx)
2920 }
2921
2922 pub fn on_type_format<T: ToPointUtf16>(
2923 &mut self,
2924 buffer: Model<Buffer>,
2925 position: T,
2926 trigger: String,
2927 push_to_history: bool,
2928 cx: &mut ModelContext<Self>,
2929 ) -> Task<Result<Option<Transaction>>> {
2930 self.lsp_store.update(cx, |lsp_store, cx| {
2931 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
2932 })
2933 }
2934
2935 pub fn inlay_hints<T: ToOffset>(
2936 &mut self,
2937 buffer_handle: Model<Buffer>,
2938 range: Range<T>,
2939 cx: &mut ModelContext<Self>,
2940 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
2941 let buffer = buffer_handle.read(cx);
2942 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
2943 self.lsp_store.update(cx, |lsp_store, cx| {
2944 lsp_store.inlay_hints(buffer_handle, range, cx)
2945 })
2946 }
2947
2948 pub fn resolve_inlay_hint(
2949 &self,
2950 hint: InlayHint,
2951 buffer_handle: Model<Buffer>,
2952 server_id: LanguageServerId,
2953 cx: &mut ModelContext<Self>,
2954 ) -> Task<anyhow::Result<InlayHint>> {
2955 self.lsp_store.update(cx, |lsp_store, cx| {
2956 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
2957 })
2958 }
2959
2960 pub fn search(
2961 &mut self,
2962 query: SearchQuery,
2963 cx: &mut ModelContext<Self>,
2964 ) -> Receiver<SearchResult> {
2965 let (result_tx, result_rx) = smol::channel::unbounded();
2966
2967 let matching_buffers_rx = if query.is_opened_only() {
2968 self.sort_search_candidates(&query, cx)
2969 } else {
2970 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
2971 };
2972
2973 cx.spawn(|_, cx| async move {
2974 let mut range_count = 0;
2975 let mut buffer_count = 0;
2976 let mut limit_reached = false;
2977 let query = Arc::new(query);
2978 let mut chunks = matching_buffers_rx.ready_chunks(64);
2979
2980 // Now that we know what paths match the query, we will load at most
2981 // 64 buffers at a time to avoid overwhelming the main thread. For each
2982 // opened buffer, we will spawn a background task that retrieves all the
2983 // ranges in the buffer matched by the query.
2984 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
2985 let mut chunk_results = Vec::new();
2986 for buffer in matching_buffer_chunk {
2987 let buffer = buffer.clone();
2988 let query = query.clone();
2989 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
2990 chunk_results.push(cx.background_executor().spawn(async move {
2991 let ranges = query
2992 .search(&snapshot, None)
2993 .await
2994 .iter()
2995 .map(|range| {
2996 snapshot.anchor_before(range.start)
2997 ..snapshot.anchor_after(range.end)
2998 })
2999 .collect::<Vec<_>>();
3000 anyhow::Ok((buffer, ranges))
3001 }));
3002 }
3003
3004 let chunk_results = futures::future::join_all(chunk_results).await;
3005 for result in chunk_results {
3006 if let Some((buffer, ranges)) = result.log_err() {
3007 range_count += ranges.len();
3008 buffer_count += 1;
3009 result_tx
3010 .send(SearchResult::Buffer { buffer, ranges })
3011 .await?;
3012 if buffer_count > MAX_SEARCH_RESULT_FILES
3013 || range_count > MAX_SEARCH_RESULT_RANGES
3014 {
3015 limit_reached = true;
3016 break 'outer;
3017 }
3018 }
3019 }
3020 }
3021
3022 if limit_reached {
3023 result_tx.send(SearchResult::LimitReached).await?;
3024 }
3025
3026 anyhow::Ok(())
3027 })
3028 .detach();
3029
3030 result_rx
3031 }
3032
3033 fn find_search_candidate_buffers(
3034 &mut self,
3035 query: &SearchQuery,
3036 limit: usize,
3037 cx: &mut ModelContext<Project>,
3038 ) -> Receiver<Model<Buffer>> {
3039 if self.is_local() {
3040 let fs = self.fs.clone();
3041 self.buffer_store.update(cx, |buffer_store, cx| {
3042 buffer_store.find_search_candidates(query, limit, fs, cx)
3043 })
3044 } else {
3045 self.find_search_candidates_remote(query, limit, cx)
3046 }
3047 }
3048
3049 fn sort_search_candidates(
3050 &mut self,
3051 search_query: &SearchQuery,
3052 cx: &mut ModelContext<Project>,
3053 ) -> Receiver<Model<Buffer>> {
3054 let worktree_store = self.worktree_store.read(cx);
3055 let mut buffers = search_query
3056 .buffers()
3057 .into_iter()
3058 .flatten()
3059 .filter(|buffer| {
3060 let b = buffer.read(cx);
3061 if let Some(file) = b.file() {
3062 if !search_query.file_matches(file.path()) {
3063 return false;
3064 }
3065 if let Some(entry) = b
3066 .entry_id(cx)
3067 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
3068 {
3069 if entry.is_ignored && !search_query.include_ignored() {
3070 return false;
3071 }
3072 }
3073 }
3074 true
3075 })
3076 .collect::<Vec<_>>();
3077 let (tx, rx) = smol::channel::unbounded();
3078 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
3079 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
3080 (None, Some(_)) => std::cmp::Ordering::Less,
3081 (Some(_), None) => std::cmp::Ordering::Greater,
3082 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
3083 });
3084 for buffer in buffers {
3085 tx.send_blocking(buffer.clone()).unwrap()
3086 }
3087
3088 rx
3089 }
3090
3091 fn find_search_candidates_remote(
3092 &mut self,
3093 query: &SearchQuery,
3094 limit: usize,
3095 cx: &mut ModelContext<Project>,
3096 ) -> Receiver<Model<Buffer>> {
3097 let (tx, rx) = smol::channel::unbounded();
3098
3099 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
3100 (ssh_client.read(cx).proto_client(), 0)
3101 } else if let Some(remote_id) = self.remote_id() {
3102 (self.client.clone().into(), remote_id)
3103 } else {
3104 return rx;
3105 };
3106
3107 let request = client.request(proto::FindSearchCandidates {
3108 project_id: remote_id,
3109 query: Some(query.to_proto()),
3110 limit: limit as _,
3111 });
3112 let guard = self.retain_remotely_created_models(cx);
3113
3114 cx.spawn(move |this, mut cx| async move {
3115 let response = request.await?;
3116 for buffer_id in response.buffer_ids {
3117 let buffer_id = BufferId::new(buffer_id)?;
3118 let buffer = this
3119 .update(&mut cx, |this, cx| {
3120 this.wait_for_remote_buffer(buffer_id, cx)
3121 })?
3122 .await?;
3123 let _ = tx.send(buffer).await;
3124 }
3125
3126 drop(guard);
3127 anyhow::Ok(())
3128 })
3129 .detach_and_log_err(cx);
3130 rx
3131 }
3132
3133 pub fn request_lsp<R: LspCommand>(
3134 &mut self,
3135 buffer_handle: Model<Buffer>,
3136 server: LanguageServerToQuery,
3137 request: R,
3138 cx: &mut ModelContext<Self>,
3139 ) -> Task<Result<R::Response>>
3140 where
3141 <R::LspRequest as lsp::request::Request>::Result: Send,
3142 <R::LspRequest as lsp::request::Request>::Params: Send,
3143 {
3144 let guard = self.retain_remotely_created_models(cx);
3145 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3146 lsp_store.request_lsp(buffer_handle, server, request, cx)
3147 });
3148 cx.spawn(|_, _| async move {
3149 let result = task.await;
3150 drop(guard);
3151 result
3152 })
3153 }
3154
3155 /// Move a worktree to a new position in the worktree order.
3156 ///
3157 /// The worktree will moved to the opposite side of the destination worktree.
3158 ///
3159 /// # Example
3160 ///
3161 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
3162 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
3163 ///
3164 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
3165 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
3166 ///
3167 /// # Errors
3168 ///
3169 /// An error will be returned if the worktree or destination worktree are not found.
3170 pub fn move_worktree(
3171 &mut self,
3172 source: WorktreeId,
3173 destination: WorktreeId,
3174 cx: &mut ModelContext<'_, Self>,
3175 ) -> Result<()> {
3176 self.worktree_store.update(cx, |worktree_store, cx| {
3177 worktree_store.move_worktree(source, destination, cx)
3178 })
3179 }
3180
3181 pub fn find_or_create_worktree(
3182 &mut self,
3183 abs_path: impl AsRef<Path>,
3184 visible: bool,
3185 cx: &mut ModelContext<Self>,
3186 ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
3187 self.worktree_store.update(cx, |worktree_store, cx| {
3188 worktree_store.find_or_create_worktree(abs_path, visible, cx)
3189 })
3190 }
3191
3192 pub fn find_worktree(
3193 &self,
3194 abs_path: &Path,
3195 cx: &AppContext,
3196 ) -> Option<(Model<Worktree>, PathBuf)> {
3197 self.worktree_store.read_with(cx, |worktree_store, cx| {
3198 worktree_store.find_worktree(abs_path, cx)
3199 })
3200 }
3201
3202 pub fn is_shared(&self) -> bool {
3203 match &self.client_state {
3204 ProjectClientState::Shared { .. } => true,
3205 ProjectClientState::Local => false,
3206 ProjectClientState::Remote { .. } => true,
3207 }
3208 }
3209
3210 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
3211 pub fn resolve_path_in_buffer(
3212 &self,
3213 path: &str,
3214 buffer: &Model<Buffer>,
3215 cx: &mut ModelContext<Self>,
3216 ) -> Task<Option<ResolvedPath>> {
3217 let path_buf = PathBuf::from(path);
3218 if path_buf.is_absolute() || path.starts_with("~") {
3219 self.resolve_abs_path(path, cx)
3220 } else {
3221 self.resolve_path_in_worktrees(path_buf, buffer, cx)
3222 }
3223 }
3224
3225 pub fn resolve_abs_file_path(
3226 &self,
3227 path: &str,
3228 cx: &mut ModelContext<Self>,
3229 ) -> Task<Option<ResolvedPath>> {
3230 let resolve_task = self.resolve_abs_path(path, cx);
3231 cx.background_executor().spawn(async move {
3232 let resolved_path = resolve_task.await;
3233 resolved_path.filter(|path| path.is_file())
3234 })
3235 }
3236
3237 pub fn resolve_abs_path(
3238 &self,
3239 path: &str,
3240 cx: &mut ModelContext<Self>,
3241 ) -> Task<Option<ResolvedPath>> {
3242 if self.is_local() {
3243 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3244 let fs = self.fs.clone();
3245 cx.background_executor().spawn(async move {
3246 let path = expanded.as_path();
3247 let metadata = fs.metadata(path).await.ok().flatten();
3248
3249 metadata.map(|metadata| ResolvedPath::AbsPath {
3250 path: expanded,
3251 is_dir: metadata.is_dir,
3252 })
3253 })
3254 } else if let Some(ssh_client) = self.ssh_client.as_ref() {
3255 let request = ssh_client
3256 .read(cx)
3257 .proto_client()
3258 .request(proto::GetPathMetadata {
3259 project_id: SSH_PROJECT_ID,
3260 path: path.to_string(),
3261 });
3262 cx.background_executor().spawn(async move {
3263 let response = request.await.log_err()?;
3264 if response.exists {
3265 Some(ResolvedPath::AbsPath {
3266 path: PathBuf::from(response.path),
3267 is_dir: response.is_dir,
3268 })
3269 } else {
3270 None
3271 }
3272 })
3273 } else {
3274 return Task::ready(None);
3275 }
3276 }
3277
3278 fn resolve_path_in_worktrees(
3279 &self,
3280 path: PathBuf,
3281 buffer: &Model<Buffer>,
3282 cx: &mut ModelContext<Self>,
3283 ) -> Task<Option<ResolvedPath>> {
3284 let mut candidates = vec![path.clone()];
3285
3286 if let Some(file) = buffer.read(cx).file() {
3287 if let Some(dir) = file.path().parent() {
3288 let joined = dir.to_path_buf().join(path);
3289 candidates.push(joined);
3290 }
3291 }
3292
3293 let worktrees = self.worktrees(cx).collect::<Vec<_>>();
3294 cx.spawn(|_, mut cx| async move {
3295 for worktree in worktrees {
3296 for candidate in candidates.iter() {
3297 let path = worktree
3298 .update(&mut cx, |worktree, _| {
3299 let root_entry_path = &worktree.root_entry()?.path;
3300
3301 let resolved = resolve_path(root_entry_path, candidate);
3302
3303 let stripped =
3304 resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3305
3306 worktree.entry_for_path(stripped).map(|entry| {
3307 let project_path = ProjectPath {
3308 worktree_id: worktree.id(),
3309 path: entry.path.clone(),
3310 };
3311 ResolvedPath::ProjectPath {
3312 project_path,
3313 is_dir: entry.is_dir(),
3314 }
3315 })
3316 })
3317 .ok()?;
3318
3319 if path.is_some() {
3320 return path;
3321 }
3322 }
3323 }
3324 None
3325 })
3326 }
3327
3328 pub fn list_directory(
3329 &self,
3330 query: String,
3331 cx: &mut ModelContext<Self>,
3332 ) -> Task<Result<Vec<PathBuf>>> {
3333 if self.is_local() {
3334 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3335 } else if let Some(session) = self.ssh_client.as_ref() {
3336 let request = proto::ListRemoteDirectory {
3337 dev_server_id: SSH_PROJECT_ID,
3338 path: query,
3339 };
3340
3341 let response = session.read(cx).proto_client().request(request);
3342 cx.background_executor().spawn(async move {
3343 let response = response.await?;
3344 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3345 })
3346 } else {
3347 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3348 }
3349 }
3350
3351 pub fn create_worktree(
3352 &mut self,
3353 abs_path: impl AsRef<Path>,
3354 visible: bool,
3355 cx: &mut ModelContext<Self>,
3356 ) -> Task<Result<Model<Worktree>>> {
3357 self.worktree_store.update(cx, |worktree_store, cx| {
3358 worktree_store.create_worktree(abs_path, visible, cx)
3359 })
3360 }
3361
3362 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
3363 self.worktree_store.update(cx, |worktree_store, cx| {
3364 worktree_store.remove_worktree(id_to_remove, cx);
3365 });
3366 }
3367
3368 fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
3369 self.worktree_store.update(cx, |worktree_store, cx| {
3370 worktree_store.add(worktree, cx);
3371 });
3372 }
3373
3374 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
3375 let new_active_entry = entry.and_then(|project_path| {
3376 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
3377 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
3378 Some(entry.id)
3379 });
3380 if new_active_entry != self.active_entry {
3381 self.active_entry = new_active_entry;
3382 self.lsp_store.update(cx, |lsp_store, _| {
3383 lsp_store.set_active_entry(new_active_entry);
3384 });
3385 cx.emit(Event::ActiveEntryChanged(new_active_entry));
3386 }
3387 }
3388
3389 pub fn language_servers_running_disk_based_diagnostics<'a>(
3390 &'a self,
3391 cx: &'a AppContext,
3392 ) -> impl Iterator<Item = LanguageServerId> + 'a {
3393 self.lsp_store
3394 .read(cx)
3395 .language_servers_running_disk_based_diagnostics()
3396 }
3397
3398 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
3399 let mut summary = DiagnosticSummary::default();
3400 for (_, _, path_summary) in self.diagnostic_summaries(include_ignored, cx) {
3401 summary.error_count += path_summary.error_count;
3402 summary.warning_count += path_summary.warning_count;
3403 }
3404 summary
3405 }
3406
3407 pub fn diagnostic_summaries<'a>(
3408 &'a self,
3409 include_ignored: bool,
3410 cx: &'a AppContext,
3411 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
3412 self.lsp_store
3413 .read(cx)
3414 .diagnostic_summaries(include_ignored, cx)
3415 }
3416
3417 pub fn active_entry(&self) -> Option<ProjectEntryId> {
3418 self.active_entry
3419 }
3420
3421 pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
3422 self.worktree_store.read(cx).entry_for_path(path, cx)
3423 }
3424
3425 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
3426 let worktree = self.worktree_for_entry(entry_id, cx)?;
3427 let worktree = worktree.read(cx);
3428 let worktree_id = worktree.id();
3429 let path = worktree.entry_for_id(entry_id)?.path.clone();
3430 Some(ProjectPath { worktree_id, path })
3431 }
3432
3433 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
3434 self.worktree_for_id(project_path.worktree_id, cx)?
3435 .read(cx)
3436 .absolutize(&project_path.path)
3437 .ok()
3438 }
3439
3440 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
3441 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
3442 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
3443 /// the first visible worktree that has an entry for that relative path.
3444 ///
3445 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
3446 /// root name from paths.
3447 ///
3448 /// # Arguments
3449 ///
3450 /// * `path` - A full path that starts with a worktree root name, or alternatively a
3451 /// relative path within a visible worktree.
3452 /// * `cx` - A reference to the `AppContext`.
3453 ///
3454 /// # Returns
3455 ///
3456 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
3457 pub fn find_project_path(&self, path: &Path, cx: &AppContext) -> Option<ProjectPath> {
3458 let worktree_store = self.worktree_store.read(cx);
3459
3460 for worktree in worktree_store.visible_worktrees(cx) {
3461 let worktree_root_name = worktree.read(cx).root_name();
3462 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
3463 return Some(ProjectPath {
3464 worktree_id: worktree.read(cx).id(),
3465 path: relative_path.into(),
3466 });
3467 }
3468 }
3469
3470 for worktree in worktree_store.visible_worktrees(cx) {
3471 let worktree = worktree.read(cx);
3472 if let Some(entry) = worktree.entry_for_path(path) {
3473 return Some(ProjectPath {
3474 worktree_id: worktree.id(),
3475 path: entry.path.clone(),
3476 });
3477 }
3478 }
3479
3480 None
3481 }
3482
3483 pub fn get_workspace_root(
3484 &self,
3485 project_path: &ProjectPath,
3486 cx: &AppContext,
3487 ) -> Option<PathBuf> {
3488 Some(
3489 self.worktree_for_id(project_path.worktree_id, cx)?
3490 .read(cx)
3491 .abs_path()
3492 .to_path_buf(),
3493 )
3494 }
3495
3496 pub fn get_repo(
3497 &self,
3498 project_path: &ProjectPath,
3499 cx: &AppContext,
3500 ) -> Option<Arc<dyn GitRepository>> {
3501 self.worktree_for_id(project_path.worktree_id, cx)?
3502 .read(cx)
3503 .as_local()?
3504 .local_git_repo(&project_path.path)
3505 }
3506
3507 pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
3508 let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
3509 let root_entry = worktree.root_git_entry()?;
3510 worktree.get_local_repo(&root_entry)?.repo().clone().into()
3511 }
3512
3513 pub fn branches(
3514 &self,
3515 project_path: ProjectPath,
3516 cx: &AppContext,
3517 ) -> Task<Result<Vec<git::repository::Branch>>> {
3518 self.worktree_store().read(cx).branches(project_path, cx)
3519 }
3520
3521 pub fn update_or_create_branch(
3522 &self,
3523 repository: ProjectPath,
3524 new_branch: String,
3525 cx: &AppContext,
3526 ) -> Task<Result<()>> {
3527 self.worktree_store()
3528 .read(cx)
3529 .update_or_create_branch(repository, new_branch, cx)
3530 }
3531
3532 pub fn blame_buffer(
3533 &self,
3534 buffer: &Model<Buffer>,
3535 version: Option<clock::Global>,
3536 cx: &AppContext,
3537 ) -> Task<Result<Option<Blame>>> {
3538 self.buffer_store.read(cx).blame_buffer(buffer, version, cx)
3539 }
3540
3541 pub fn get_permalink_to_line(
3542 &self,
3543 buffer: &Model<Buffer>,
3544 selection: Range<u32>,
3545 cx: &AppContext,
3546 ) -> Task<Result<url::Url>> {
3547 self.buffer_store
3548 .read(cx)
3549 .get_permalink_to_line(buffer, selection, cx)
3550 }
3551
3552 // RPC message handlers
3553
3554 async fn handle_unshare_project(
3555 this: Model<Self>,
3556 _: TypedEnvelope<proto::UnshareProject>,
3557 mut cx: AsyncAppContext,
3558 ) -> Result<()> {
3559 this.update(&mut cx, |this, cx| {
3560 if this.is_local() || this.is_via_ssh() {
3561 this.unshare(cx)?;
3562 } else {
3563 this.disconnected_from_host(cx);
3564 }
3565 Ok(())
3566 })?
3567 }
3568
3569 async fn handle_add_collaborator(
3570 this: Model<Self>,
3571 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
3572 mut cx: AsyncAppContext,
3573 ) -> Result<()> {
3574 let collaborator = envelope
3575 .payload
3576 .collaborator
3577 .take()
3578 .ok_or_else(|| anyhow!("empty collaborator"))?;
3579
3580 let collaborator = Collaborator::from_proto(collaborator)?;
3581 this.update(&mut cx, |this, cx| {
3582 this.buffer_store.update(cx, |buffer_store, _| {
3583 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
3584 });
3585 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
3586 this.collaborators
3587 .insert(collaborator.peer_id, collaborator);
3588 cx.notify();
3589 })?;
3590
3591 Ok(())
3592 }
3593
3594 async fn handle_update_project_collaborator(
3595 this: Model<Self>,
3596 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
3597 mut cx: AsyncAppContext,
3598 ) -> Result<()> {
3599 let old_peer_id = envelope
3600 .payload
3601 .old_peer_id
3602 .ok_or_else(|| anyhow!("missing old peer id"))?;
3603 let new_peer_id = envelope
3604 .payload
3605 .new_peer_id
3606 .ok_or_else(|| anyhow!("missing new peer id"))?;
3607 this.update(&mut cx, |this, cx| {
3608 let collaborator = this
3609 .collaborators
3610 .remove(&old_peer_id)
3611 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
3612 let is_host = collaborator.is_host;
3613 this.collaborators.insert(new_peer_id, collaborator);
3614
3615 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
3616 this.buffer_store.update(cx, |buffer_store, _| {
3617 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
3618 });
3619
3620 if is_host {
3621 this.buffer_store
3622 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
3623 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
3624 .unwrap();
3625 cx.emit(Event::HostReshared);
3626 }
3627
3628 cx.emit(Event::CollaboratorUpdated {
3629 old_peer_id,
3630 new_peer_id,
3631 });
3632 cx.notify();
3633 Ok(())
3634 })?
3635 }
3636
3637 async fn handle_remove_collaborator(
3638 this: Model<Self>,
3639 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
3640 mut cx: AsyncAppContext,
3641 ) -> Result<()> {
3642 this.update(&mut cx, |this, cx| {
3643 let peer_id = envelope
3644 .payload
3645 .peer_id
3646 .ok_or_else(|| anyhow!("invalid peer id"))?;
3647 let replica_id = this
3648 .collaborators
3649 .remove(&peer_id)
3650 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
3651 .replica_id;
3652 this.buffer_store.update(cx, |buffer_store, cx| {
3653 buffer_store.forget_shared_buffers_for(&peer_id);
3654 for buffer in buffer_store.buffers() {
3655 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
3656 }
3657 });
3658
3659 cx.emit(Event::CollaboratorLeft(peer_id));
3660 cx.notify();
3661 Ok(())
3662 })?
3663 }
3664
3665 async fn handle_update_project(
3666 this: Model<Self>,
3667 envelope: TypedEnvelope<proto::UpdateProject>,
3668 mut cx: AsyncAppContext,
3669 ) -> Result<()> {
3670 this.update(&mut cx, |this, cx| {
3671 // Don't handle messages that were sent before the response to us joining the project
3672 if envelope.message_id > this.join_project_response_message_id {
3673 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
3674 }
3675 Ok(())
3676 })?
3677 }
3678
3679 async fn handle_toast(
3680 this: Model<Self>,
3681 envelope: TypedEnvelope<proto::Toast>,
3682 mut cx: AsyncAppContext,
3683 ) -> Result<()> {
3684 this.update(&mut cx, |_, cx| {
3685 cx.emit(Event::Toast {
3686 notification_id: envelope.payload.notification_id.into(),
3687 message: envelope.payload.message,
3688 });
3689 Ok(())
3690 })?
3691 }
3692
3693 async fn handle_language_server_prompt_request(
3694 this: Model<Self>,
3695 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
3696 mut cx: AsyncAppContext,
3697 ) -> Result<proto::LanguageServerPromptResponse> {
3698 let (tx, mut rx) = smol::channel::bounded(1);
3699 let actions: Vec<_> = envelope
3700 .payload
3701 .actions
3702 .into_iter()
3703 .map(|action| MessageActionItem {
3704 title: action,
3705 properties: Default::default(),
3706 })
3707 .collect();
3708 this.update(&mut cx, |_, cx| {
3709 cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest {
3710 level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
3711 message: envelope.payload.message,
3712 actions: actions.clone(),
3713 lsp_name: envelope.payload.lsp_name,
3714 response_channel: tx,
3715 }));
3716
3717 anyhow::Ok(())
3718 })??;
3719
3720 // We drop `this` to avoid holding a reference in this future for too
3721 // long.
3722 // If we keep the reference, we might not drop the `Project` early
3723 // enough when closing a window and it will only get releases on the
3724 // next `flush_effects()` call.
3725 drop(this);
3726
3727 let answer = rx.next().await;
3728
3729 Ok(LanguageServerPromptResponse {
3730 action_response: answer.and_then(|answer| {
3731 actions
3732 .iter()
3733 .position(|action| *action == answer)
3734 .map(|index| index as u64)
3735 }),
3736 })
3737 }
3738
3739 async fn handle_hide_toast(
3740 this: Model<Self>,
3741 envelope: TypedEnvelope<proto::HideToast>,
3742 mut cx: AsyncAppContext,
3743 ) -> Result<()> {
3744 this.update(&mut cx, |_, cx| {
3745 cx.emit(Event::HideToast {
3746 notification_id: envelope.payload.notification_id.into(),
3747 });
3748 Ok(())
3749 })?
3750 }
3751
3752 // Collab sends UpdateWorktree protos as messages
3753 async fn handle_update_worktree(
3754 this: Model<Self>,
3755 envelope: TypedEnvelope<proto::UpdateWorktree>,
3756 mut cx: AsyncAppContext,
3757 ) -> Result<()> {
3758 this.update(&mut cx, |this, cx| {
3759 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3760 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
3761 worktree.update(cx, |worktree, _| {
3762 let worktree = worktree.as_remote_mut().unwrap();
3763 worktree.update_from_remote(envelope.payload);
3764 });
3765 }
3766 Ok(())
3767 })?
3768 }
3769
3770 async fn handle_update_buffer_from_ssh(
3771 this: Model<Self>,
3772 envelope: TypedEnvelope<proto::UpdateBuffer>,
3773 cx: AsyncAppContext,
3774 ) -> Result<proto::Ack> {
3775 let buffer_store = this.read_with(&cx, |this, cx| {
3776 if let Some(remote_id) = this.remote_id() {
3777 let mut payload = envelope.payload.clone();
3778 payload.project_id = remote_id;
3779 cx.background_executor()
3780 .spawn(this.client.request(payload))
3781 .detach_and_log_err(cx);
3782 }
3783 this.buffer_store.clone()
3784 })?;
3785 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3786 }
3787
3788 async fn handle_update_buffer(
3789 this: Model<Self>,
3790 envelope: TypedEnvelope<proto::UpdateBuffer>,
3791 cx: AsyncAppContext,
3792 ) -> Result<proto::Ack> {
3793 let buffer_store = this.read_with(&cx, |this, cx| {
3794 if let Some(ssh) = &this.ssh_client {
3795 let mut payload = envelope.payload.clone();
3796 payload.project_id = SSH_PROJECT_ID;
3797 cx.background_executor()
3798 .spawn(ssh.read(cx).proto_client().request(payload))
3799 .detach_and_log_err(cx);
3800 }
3801 this.buffer_store.clone()
3802 })?;
3803 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3804 }
3805
3806 fn retain_remotely_created_models(
3807 &mut self,
3808 cx: &mut ModelContext<Self>,
3809 ) -> RemotelyCreatedModelGuard {
3810 {
3811 let mut remotely_create_models = self.remotely_created_models.lock();
3812 if remotely_create_models.retain_count == 0 {
3813 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
3814 remotely_create_models.worktrees =
3815 self.worktree_store.read(cx).worktrees().collect();
3816 }
3817 remotely_create_models.retain_count += 1;
3818 }
3819 RemotelyCreatedModelGuard {
3820 remote_models: Arc::downgrade(&self.remotely_created_models),
3821 }
3822 }
3823
3824 async fn handle_create_buffer_for_peer(
3825 this: Model<Self>,
3826 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
3827 mut cx: AsyncAppContext,
3828 ) -> Result<()> {
3829 this.update(&mut cx, |this, cx| {
3830 this.buffer_store.update(cx, |buffer_store, cx| {
3831 buffer_store.handle_create_buffer_for_peer(
3832 envelope,
3833 this.replica_id(),
3834 this.capability(),
3835 cx,
3836 )
3837 })
3838 })?
3839 }
3840
3841 async fn handle_synchronize_buffers(
3842 this: Model<Self>,
3843 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
3844 mut cx: AsyncAppContext,
3845 ) -> Result<proto::SynchronizeBuffersResponse> {
3846 let response = this.update(&mut cx, |this, cx| {
3847 let client = this.client.clone();
3848 this.buffer_store.update(cx, |this, cx| {
3849 this.handle_synchronize_buffers(envelope, cx, client)
3850 })
3851 })??;
3852
3853 Ok(response)
3854 }
3855
3856 async fn handle_search_candidate_buffers(
3857 this: Model<Self>,
3858 envelope: TypedEnvelope<proto::FindSearchCandidates>,
3859 mut cx: AsyncAppContext,
3860 ) -> Result<proto::FindSearchCandidatesResponse> {
3861 let peer_id = envelope.original_sender_id()?;
3862 let message = envelope.payload;
3863 let query = SearchQuery::from_proto(
3864 message
3865 .query
3866 .ok_or_else(|| anyhow!("missing query field"))?,
3867 )?;
3868 let mut results = this.update(&mut cx, |this, cx| {
3869 this.find_search_candidate_buffers(&query, message.limit as _, cx)
3870 })?;
3871
3872 let mut response = proto::FindSearchCandidatesResponse {
3873 buffer_ids: Vec::new(),
3874 };
3875
3876 while let Some(buffer) = results.next().await {
3877 this.update(&mut cx, |this, cx| {
3878 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
3879 response.buffer_ids.push(buffer_id.to_proto());
3880 })?;
3881 }
3882
3883 Ok(response)
3884 }
3885
3886 async fn handle_open_buffer_by_id(
3887 this: Model<Self>,
3888 envelope: TypedEnvelope<proto::OpenBufferById>,
3889 mut cx: AsyncAppContext,
3890 ) -> Result<proto::OpenBufferResponse> {
3891 let peer_id = envelope.original_sender_id()?;
3892 let buffer_id = BufferId::new(envelope.payload.id)?;
3893 let buffer = this
3894 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
3895 .await?;
3896 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3897 }
3898
3899 async fn handle_open_buffer_by_path(
3900 this: Model<Self>,
3901 envelope: TypedEnvelope<proto::OpenBufferByPath>,
3902 mut cx: AsyncAppContext,
3903 ) -> Result<proto::OpenBufferResponse> {
3904 let peer_id = envelope.original_sender_id()?;
3905 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3906 let open_buffer = this.update(&mut cx, |this, cx| {
3907 this.open_buffer(
3908 ProjectPath {
3909 worktree_id,
3910 path: PathBuf::from(envelope.payload.path).into(),
3911 },
3912 cx,
3913 )
3914 })?;
3915
3916 let buffer = open_buffer.await?;
3917 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3918 }
3919
3920 async fn handle_open_new_buffer(
3921 this: Model<Self>,
3922 envelope: TypedEnvelope<proto::OpenNewBuffer>,
3923 mut cx: AsyncAppContext,
3924 ) -> Result<proto::OpenBufferResponse> {
3925 let buffer = this
3926 .update(&mut cx, |this, cx| this.create_buffer(cx))?
3927 .await?;
3928 let peer_id = envelope.original_sender_id()?;
3929
3930 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
3931 }
3932
3933 fn respond_to_open_buffer_request(
3934 this: Model<Self>,
3935 buffer: Model<Buffer>,
3936 peer_id: proto::PeerId,
3937 cx: &mut AsyncAppContext,
3938 ) -> Result<proto::OpenBufferResponse> {
3939 this.update(cx, |this, cx| {
3940 let is_private = buffer
3941 .read(cx)
3942 .file()
3943 .map(|f| f.is_private())
3944 .unwrap_or_default();
3945 if is_private {
3946 Err(anyhow!(ErrorCode::UnsharedItem))
3947 } else {
3948 Ok(proto::OpenBufferResponse {
3949 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
3950 })
3951 }
3952 })?
3953 }
3954
3955 fn create_buffer_for_peer(
3956 &mut self,
3957 buffer: &Model<Buffer>,
3958 peer_id: proto::PeerId,
3959 cx: &mut AppContext,
3960 ) -> BufferId {
3961 self.buffer_store
3962 .update(cx, |buffer_store, cx| {
3963 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
3964 })
3965 .detach_and_log_err(cx);
3966 buffer.read(cx).remote_id()
3967 }
3968
3969 fn wait_for_remote_buffer(
3970 &mut self,
3971 id: BufferId,
3972 cx: &mut ModelContext<Self>,
3973 ) -> Task<Result<Model<Buffer>>> {
3974 self.buffer_store.update(cx, |buffer_store, cx| {
3975 buffer_store.wait_for_remote_buffer(id, cx)
3976 })
3977 }
3978
3979 fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
3980 let project_id = match self.client_state {
3981 ProjectClientState::Remote {
3982 sharing_has_stopped,
3983 remote_id,
3984 ..
3985 } => {
3986 if sharing_has_stopped {
3987 return Task::ready(Err(anyhow!(
3988 "can't synchronize remote buffers on a readonly project"
3989 )));
3990 } else {
3991 remote_id
3992 }
3993 }
3994 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
3995 return Task::ready(Err(anyhow!(
3996 "can't synchronize remote buffers on a local project"
3997 )))
3998 }
3999 };
4000
4001 let client = self.client.clone();
4002 cx.spawn(move |this, mut cx| async move {
4003 let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
4004 this.buffer_store.read(cx).buffer_version_info(cx)
4005 })?;
4006 let response = client
4007 .request(proto::SynchronizeBuffers {
4008 project_id,
4009 buffers,
4010 })
4011 .await?;
4012
4013 let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
4014 response
4015 .buffers
4016 .into_iter()
4017 .map(|buffer| {
4018 let client = client.clone();
4019 let buffer_id = match BufferId::new(buffer.id) {
4020 Ok(id) => id,
4021 Err(e) => {
4022 return Task::ready(Err(e));
4023 }
4024 };
4025 let remote_version = language::proto::deserialize_version(&buffer.version);
4026 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
4027 let operations =
4028 buffer.read(cx).serialize_ops(Some(remote_version), cx);
4029 cx.background_executor().spawn(async move {
4030 let operations = operations.await;
4031 for chunk in split_operations(operations) {
4032 client
4033 .request(proto::UpdateBuffer {
4034 project_id,
4035 buffer_id: buffer_id.into(),
4036 operations: chunk,
4037 })
4038 .await?;
4039 }
4040 anyhow::Ok(())
4041 })
4042 } else {
4043 Task::ready(Ok(()))
4044 }
4045 })
4046 .collect::<Vec<_>>()
4047 })?;
4048
4049 // Any incomplete buffers have open requests waiting. Request that the host sends
4050 // creates these buffers for us again to unblock any waiting futures.
4051 for id in incomplete_buffer_ids {
4052 cx.background_executor()
4053 .spawn(client.request(proto::OpenBufferById {
4054 project_id,
4055 id: id.into(),
4056 }))
4057 .detach();
4058 }
4059
4060 futures::future::join_all(send_updates_for_buffers)
4061 .await
4062 .into_iter()
4063 .collect()
4064 })
4065 }
4066
4067 pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
4068 self.worktree_store.read(cx).worktree_metadata_protos(cx)
4069 }
4070
4071 /// Iterator of all open buffers that have unsaved changes
4072 pub fn dirty_buffers<'a>(
4073 &'a self,
4074 cx: &'a AppContext,
4075 ) -> impl Iterator<Item = ProjectPath> + 'a {
4076 self.buffer_store.read(cx).buffers().filter_map(|buf| {
4077 let buf = buf.read(cx);
4078 if buf.is_dirty() {
4079 buf.project_path(cx)
4080 } else {
4081 None
4082 }
4083 })
4084 }
4085
4086 fn set_worktrees_from_proto(
4087 &mut self,
4088 worktrees: Vec<proto::WorktreeMetadata>,
4089 cx: &mut ModelContext<Project>,
4090 ) -> Result<()> {
4091 cx.notify();
4092 self.worktree_store.update(cx, |worktree_store, cx| {
4093 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
4094 })
4095 }
4096
4097 fn set_collaborators_from_proto(
4098 &mut self,
4099 messages: Vec<proto::Collaborator>,
4100 cx: &mut ModelContext<Self>,
4101 ) -> Result<()> {
4102 let mut collaborators = HashMap::default();
4103 for message in messages {
4104 let collaborator = Collaborator::from_proto(message)?;
4105 collaborators.insert(collaborator.peer_id, collaborator);
4106 }
4107 for old_peer_id in self.collaborators.keys() {
4108 if !collaborators.contains_key(old_peer_id) {
4109 cx.emit(Event::CollaboratorLeft(*old_peer_id));
4110 }
4111 }
4112 self.collaborators = collaborators;
4113 Ok(())
4114 }
4115
4116 pub fn language_servers<'a>(
4117 &'a self,
4118 cx: &'a AppContext,
4119 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
4120 self.lsp_store.read(cx).language_servers()
4121 }
4122
4123 pub fn supplementary_language_servers<'a>(
4124 &'a self,
4125 cx: &'a AppContext,
4126 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
4127 self.lsp_store.read(cx).supplementary_language_servers()
4128 }
4129
4130 pub fn language_server_for_id(
4131 &self,
4132 id: LanguageServerId,
4133 cx: &AppContext,
4134 ) -> Option<Arc<LanguageServer>> {
4135 self.lsp_store.read(cx).language_server_for_id(id)
4136 }
4137
4138 pub fn language_servers_for_buffer<'a>(
4139 &'a self,
4140 buffer: &'a Buffer,
4141 cx: &'a AppContext,
4142 ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
4143 self.lsp_store
4144 .read(cx)
4145 .language_servers_for_buffer(buffer, cx)
4146 }
4147}
4148
4149fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
4150 code_actions
4151 .iter()
4152 .flat_map(|(kind, enabled)| {
4153 if *enabled {
4154 Some(kind.clone().into())
4155 } else {
4156 None
4157 }
4158 })
4159 .collect()
4160}
4161
4162pub struct PathMatchCandidateSet {
4163 pub snapshot: Snapshot,
4164 pub include_ignored: bool,
4165 pub include_root_name: bool,
4166 pub candidates: Candidates,
4167}
4168
4169pub enum Candidates {
4170 /// Only consider directories.
4171 Directories,
4172 /// Only consider files.
4173 Files,
4174 /// Consider directories and files.
4175 Entries,
4176}
4177
4178impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4179 type Candidates = PathMatchCandidateSetIter<'a>;
4180
4181 fn id(&self) -> usize {
4182 self.snapshot.id().to_usize()
4183 }
4184
4185 fn len(&self) -> usize {
4186 match self.candidates {
4187 Candidates::Files => {
4188 if self.include_ignored {
4189 self.snapshot.file_count()
4190 } else {
4191 self.snapshot.visible_file_count()
4192 }
4193 }
4194
4195 Candidates::Directories => {
4196 if self.include_ignored {
4197 self.snapshot.dir_count()
4198 } else {
4199 self.snapshot.visible_dir_count()
4200 }
4201 }
4202
4203 Candidates::Entries => {
4204 if self.include_ignored {
4205 self.snapshot.entry_count()
4206 } else {
4207 self.snapshot.visible_entry_count()
4208 }
4209 }
4210 }
4211 }
4212
4213 fn prefix(&self) -> Arc<str> {
4214 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4215 self.snapshot.root_name().into()
4216 } else if self.include_root_name {
4217 format!("{}{}", self.snapshot.root_name(), std::path::MAIN_SEPARATOR).into()
4218 } else {
4219 Arc::default()
4220 }
4221 }
4222
4223 fn candidates(&'a self, start: usize) -> Self::Candidates {
4224 PathMatchCandidateSetIter {
4225 traversal: match self.candidates {
4226 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4227 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4228 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4229 },
4230 }
4231 }
4232}
4233
4234pub struct PathMatchCandidateSetIter<'a> {
4235 traversal: Traversal<'a>,
4236}
4237
4238impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4239 type Item = fuzzy::PathMatchCandidate<'a>;
4240
4241 fn next(&mut self) -> Option<Self::Item> {
4242 self.traversal
4243 .next()
4244 .map(|entry| fuzzy::PathMatchCandidate {
4245 is_dir: entry.kind.is_dir(),
4246 path: &entry.path,
4247 char_bag: entry.char_bag,
4248 })
4249 }
4250}
4251
4252impl EventEmitter<Event> for Project {}
4253
4254impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4255 fn from(val: &'a ProjectPath) -> Self {
4256 SettingsLocation {
4257 worktree_id: val.worktree_id,
4258 path: val.path.as_ref(),
4259 }
4260 }
4261}
4262
4263impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4264 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4265 Self {
4266 worktree_id,
4267 path: path.as_ref().into(),
4268 }
4269 }
4270}
4271
4272pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4273 let mut path_components = path.components();
4274 let mut base_components = base.components();
4275 let mut components: Vec<Component> = Vec::new();
4276 loop {
4277 match (path_components.next(), base_components.next()) {
4278 (None, None) => break,
4279 (Some(a), None) => {
4280 components.push(a);
4281 components.extend(path_components.by_ref());
4282 break;
4283 }
4284 (None, _) => components.push(Component::ParentDir),
4285 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4286 (Some(a), Some(Component::CurDir)) => components.push(a),
4287 (Some(a), Some(_)) => {
4288 components.push(Component::ParentDir);
4289 for _ in base_components {
4290 components.push(Component::ParentDir);
4291 }
4292 components.push(a);
4293 components.extend(path_components.by_ref());
4294 break;
4295 }
4296 }
4297 }
4298 components.iter().map(|c| c.as_os_str()).collect()
4299}
4300
4301fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4302 let mut result = base.to_path_buf();
4303 for component in path.components() {
4304 match component {
4305 Component::ParentDir => {
4306 result.pop();
4307 }
4308 Component::CurDir => (),
4309 _ => result.push(component),
4310 }
4311 }
4312 result
4313}
4314
4315/// ResolvedPath is a path that has been resolved to either a ProjectPath
4316/// or an AbsPath and that *exists*.
4317#[derive(Debug, Clone)]
4318pub enum ResolvedPath {
4319 ProjectPath {
4320 project_path: ProjectPath,
4321 is_dir: bool,
4322 },
4323 AbsPath {
4324 path: PathBuf,
4325 is_dir: bool,
4326 },
4327}
4328
4329impl ResolvedPath {
4330 pub fn abs_path(&self) -> Option<&Path> {
4331 match self {
4332 Self::AbsPath { path, .. } => Some(path.as_path()),
4333 _ => None,
4334 }
4335 }
4336
4337 pub fn project_path(&self) -> Option<&ProjectPath> {
4338 match self {
4339 Self::ProjectPath { project_path, .. } => Some(&project_path),
4340 _ => None,
4341 }
4342 }
4343
4344 pub fn is_file(&self) -> bool {
4345 !self.is_dir()
4346 }
4347
4348 pub fn is_dir(&self) -> bool {
4349 match self {
4350 Self::ProjectPath { is_dir, .. } => *is_dir,
4351 Self::AbsPath { is_dir, .. } => *is_dir,
4352 }
4353 }
4354}
4355
4356impl Item for Buffer {
4357 fn try_open(
4358 project: &Model<Project>,
4359 path: &ProjectPath,
4360 cx: &mut AppContext,
4361 ) -> Option<Task<Result<Model<Self>>>> {
4362 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4363 }
4364
4365 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
4366 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4367 }
4368
4369 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
4370 File::from_dyn(self.file()).map(|file| ProjectPath {
4371 worktree_id: file.worktree_id(cx),
4372 path: file.path().clone(),
4373 })
4374 }
4375}
4376
4377impl Completion {
4378 /// A key that can be used to sort completions when displaying
4379 /// them to the user.
4380 pub fn sort_key(&self) -> (usize, &str) {
4381 let kind_key = match self.lsp_completion.kind {
4382 Some(lsp::CompletionItemKind::KEYWORD) => 0,
4383 Some(lsp::CompletionItemKind::VARIABLE) => 1,
4384 _ => 2,
4385 };
4386 (kind_key, &self.label.text[self.label.filter_range.clone()])
4387 }
4388
4389 /// Whether this completion is a snippet.
4390 pub fn is_snippet(&self) -> bool {
4391 self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
4392 }
4393
4394 /// Returns the corresponding color for this completion.
4395 ///
4396 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
4397 pub fn color(&self) -> Option<Hsla> {
4398 match self.lsp_completion.kind {
4399 Some(CompletionItemKind::COLOR) => color_extractor::extract_color(&self.lsp_completion),
4400 _ => None,
4401 }
4402 }
4403}
4404
4405pub fn sort_worktree_entries(entries: &mut [Entry]) {
4406 entries.sort_by(|entry_a, entry_b| {
4407 compare_paths(
4408 (&entry_a.path, entry_a.is_file()),
4409 (&entry_b.path, entry_b.is_file()),
4410 )
4411 });
4412}
4413
4414fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
4415 match level {
4416 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
4417 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
4418 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
4419 }
4420}