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