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