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