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