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