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 stop_on_entry: None,
1493 };
1494 let caps = caps.unwrap_or(Capabilities {
1495 supports_step_back: Some(false),
1496 ..Default::default()
1497 });
1498 self.dap_store
1499 .update(cx, |dap_store, cx| {
1500 dap_store.new_fake_session(config, worktree, None, caps, fails, cx)
1501 })
1502 .1
1503 }
1504
1505 #[cfg(any(test, feature = "test-support"))]
1506 pub async fn example(
1507 root_paths: impl IntoIterator<Item = &Path>,
1508 cx: &mut AsyncApp,
1509 ) -> Entity<Project> {
1510 use clock::FakeSystemClock;
1511
1512 let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
1513 let languages = LanguageRegistry::test(cx.background_executor().clone());
1514 let debug_adapters = DapRegistry::default().into();
1515 let clock = Arc::new(FakeSystemClock::new());
1516 let http_client = http_client::FakeHttpClient::with_404_response();
1517 let client = cx
1518 .update(|cx| client::Client::new(clock, http_client.clone(), cx))
1519 .unwrap();
1520 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)).unwrap();
1521 let project = cx
1522 .update(|cx| {
1523 Project::local(
1524 client,
1525 node_runtime::NodeRuntime::unavailable(),
1526 user_store,
1527 Arc::new(languages),
1528 debug_adapters,
1529 fs,
1530 None,
1531 cx,
1532 )
1533 })
1534 .unwrap();
1535 for path in root_paths {
1536 let (tree, _) = project
1537 .update(cx, |project, cx| {
1538 project.find_or_create_worktree(path, true, cx)
1539 })
1540 .unwrap()
1541 .await
1542 .unwrap();
1543 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1544 .unwrap()
1545 .await;
1546 }
1547 project
1548 }
1549
1550 #[cfg(any(test, feature = "test-support"))]
1551 pub async fn test(
1552 fs: Arc<dyn Fs>,
1553 root_paths: impl IntoIterator<Item = &Path>,
1554 cx: &mut gpui::TestAppContext,
1555 ) -> Entity<Project> {
1556 use clock::FakeSystemClock;
1557
1558 let languages = LanguageRegistry::test(cx.executor());
1559 let debug_adapters = DapRegistry::fake();
1560 let clock = Arc::new(FakeSystemClock::new());
1561 let http_client = http_client::FakeHttpClient::with_404_response();
1562 let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
1563 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
1564 let project = cx.update(|cx| {
1565 Project::local(
1566 client,
1567 node_runtime::NodeRuntime::unavailable(),
1568 user_store,
1569 Arc::new(languages),
1570 Arc::new(debug_adapters),
1571 fs,
1572 None,
1573 cx,
1574 )
1575 });
1576 for path in root_paths {
1577 let (tree, _) = project
1578 .update(cx, |project, cx| {
1579 project.find_or_create_worktree(path, true, cx)
1580 })
1581 .await
1582 .unwrap();
1583
1584 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1585 .await;
1586 }
1587 project
1588 }
1589
1590 pub fn dap_store(&self) -> Entity<DapStore> {
1591 self.dap_store.clone()
1592 }
1593
1594 pub fn breakpoint_store(&self) -> Entity<BreakpointStore> {
1595 self.breakpoint_store.clone()
1596 }
1597
1598 pub fn lsp_store(&self) -> Entity<LspStore> {
1599 self.lsp_store.clone()
1600 }
1601
1602 pub fn worktree_store(&self) -> Entity<WorktreeStore> {
1603 self.worktree_store.clone()
1604 }
1605
1606 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &App) -> Option<Entity<Buffer>> {
1607 self.buffer_store.read(cx).get(remote_id)
1608 }
1609
1610 pub fn languages(&self) -> &Arc<LanguageRegistry> {
1611 &self.languages
1612 }
1613
1614 pub fn debug_adapters(&self) -> &Arc<DapRegistry> {
1615 &self.debug_adapters
1616 }
1617
1618 pub fn client(&self) -> Arc<Client> {
1619 self.client.clone()
1620 }
1621
1622 pub fn ssh_client(&self) -> Option<Entity<SshRemoteClient>> {
1623 self.ssh_client.clone()
1624 }
1625
1626 pub fn user_store(&self) -> Entity<UserStore> {
1627 self.user_store.clone()
1628 }
1629
1630 pub fn node_runtime(&self) -> Option<&NodeRuntime> {
1631 self.node.as_ref()
1632 }
1633
1634 pub fn opened_buffers(&self, cx: &App) -> Vec<Entity<Buffer>> {
1635 self.buffer_store.read(cx).buffers().collect()
1636 }
1637
1638 pub fn environment(&self) -> &Entity<ProjectEnvironment> {
1639 &self.environment
1640 }
1641
1642 pub fn cli_environment(&self, cx: &App) -> Option<HashMap<String, String>> {
1643 self.environment.read(cx).get_cli_environment()
1644 }
1645
1646 pub fn shell_environment_errors<'a>(
1647 &'a self,
1648 cx: &'a App,
1649 ) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {
1650 self.environment.read(cx).environment_errors()
1651 }
1652
1653 pub fn remove_environment_error(&mut self, abs_path: &Path, cx: &mut Context<Self>) {
1654 self.environment.update(cx, |environment, cx| {
1655 environment.remove_environment_error(abs_path, cx);
1656 });
1657 }
1658
1659 #[cfg(any(test, feature = "test-support"))]
1660 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
1661 self.buffer_store
1662 .read(cx)
1663 .get_by_path(&path.into(), cx)
1664 .is_some()
1665 }
1666
1667 pub fn fs(&self) -> &Arc<dyn Fs> {
1668 &self.fs
1669 }
1670
1671 pub fn remote_id(&self) -> Option<u64> {
1672 match self.client_state {
1673 ProjectClientState::Local => None,
1674 ProjectClientState::Shared { remote_id, .. }
1675 | ProjectClientState::Remote { remote_id, .. } => Some(remote_id),
1676 }
1677 }
1678
1679 pub fn supports_terminal(&self, _cx: &App) -> bool {
1680 if self.is_local() {
1681 return true;
1682 }
1683 if self.is_via_ssh() {
1684 return true;
1685 }
1686
1687 return false;
1688 }
1689
1690 pub fn ssh_connection_string(&self, cx: &App) -> Option<SharedString> {
1691 if let Some(ssh_state) = &self.ssh_client {
1692 return Some(ssh_state.read(cx).connection_string().into());
1693 }
1694
1695 return None;
1696 }
1697
1698 pub fn ssh_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> {
1699 self.ssh_client
1700 .as_ref()
1701 .map(|ssh| ssh.read(cx).connection_state())
1702 }
1703
1704 pub fn ssh_connection_options(&self, cx: &App) -> Option<SshConnectionOptions> {
1705 self.ssh_client
1706 .as_ref()
1707 .map(|ssh| ssh.read(cx).connection_options())
1708 }
1709
1710 pub fn replica_id(&self) -> ReplicaId {
1711 match self.client_state {
1712 ProjectClientState::Remote { replica_id, .. } => replica_id,
1713 _ => {
1714 if self.ssh_client.is_some() {
1715 1
1716 } else {
1717 0
1718 }
1719 }
1720 }
1721 }
1722
1723 pub fn task_store(&self) -> &Entity<TaskStore> {
1724 &self.task_store
1725 }
1726
1727 pub fn snippets(&self) -> &Entity<SnippetProvider> {
1728 &self.snippets
1729 }
1730
1731 pub fn search_history(&self, kind: SearchInputKind) -> &SearchHistory {
1732 match kind {
1733 SearchInputKind::Query => &self.search_history,
1734 SearchInputKind::Include => &self.search_included_history,
1735 SearchInputKind::Exclude => &self.search_excluded_history,
1736 }
1737 }
1738
1739 pub fn search_history_mut(&mut self, kind: SearchInputKind) -> &mut SearchHistory {
1740 match kind {
1741 SearchInputKind::Query => &mut self.search_history,
1742 SearchInputKind::Include => &mut self.search_included_history,
1743 SearchInputKind::Exclude => &mut self.search_excluded_history,
1744 }
1745 }
1746
1747 pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
1748 &self.collaborators
1749 }
1750
1751 pub fn host(&self) -> Option<&Collaborator> {
1752 self.collaborators.values().find(|c| c.is_host)
1753 }
1754
1755 pub fn set_worktrees_reordered(&mut self, worktrees_reordered: bool, cx: &mut App) {
1756 self.worktree_store.update(cx, |store, _| {
1757 store.set_worktrees_reordered(worktrees_reordered);
1758 });
1759 }
1760
1761 /// Collect all worktrees, including ones that don't appear in the project panel
1762 pub fn worktrees<'a>(
1763 &self,
1764 cx: &'a App,
1765 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
1766 self.worktree_store.read(cx).worktrees()
1767 }
1768
1769 /// Collect all user-visible worktrees, the ones that appear in the project panel.
1770 pub fn visible_worktrees<'a>(
1771 &'a self,
1772 cx: &'a App,
1773 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
1774 self.worktree_store.read(cx).visible_worktrees(cx)
1775 }
1776
1777 pub fn worktree_for_root_name(&self, root_name: &str, cx: &App) -> Option<Entity<Worktree>> {
1778 self.visible_worktrees(cx)
1779 .find(|tree| tree.read(cx).root_name() == root_name)
1780 }
1781
1782 pub fn worktree_root_names<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a str> {
1783 self.visible_worktrees(cx)
1784 .map(|tree| tree.read(cx).root_name())
1785 }
1786
1787 pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {
1788 self.worktree_store.read(cx).worktree_for_id(id, cx)
1789 }
1790
1791 pub fn worktree_for_entry(
1792 &self,
1793 entry_id: ProjectEntryId,
1794 cx: &App,
1795 ) -> Option<Entity<Worktree>> {
1796 self.worktree_store
1797 .read(cx)
1798 .worktree_for_entry(entry_id, cx)
1799 }
1800
1801 pub fn worktree_id_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<WorktreeId> {
1802 self.worktree_for_entry(entry_id, cx)
1803 .map(|worktree| worktree.read(cx).id())
1804 }
1805
1806 /// Checks if the entry is the root of a worktree.
1807 pub fn entry_is_worktree_root(&self, entry_id: ProjectEntryId, cx: &App) -> bool {
1808 self.worktree_for_entry(entry_id, cx)
1809 .map(|worktree| {
1810 worktree
1811 .read(cx)
1812 .root_entry()
1813 .is_some_and(|e| e.id == entry_id)
1814 })
1815 .unwrap_or(false)
1816 }
1817
1818 pub fn project_path_git_status(
1819 &self,
1820 project_path: &ProjectPath,
1821 cx: &App,
1822 ) -> Option<FileStatus> {
1823 self.git_store
1824 .read(cx)
1825 .project_path_git_status(project_path, cx)
1826 }
1827
1828 pub fn visibility_for_paths(
1829 &self,
1830 paths: &[PathBuf],
1831 metadatas: &[Metadata],
1832 exclude_sub_dirs: bool,
1833 cx: &App,
1834 ) -> Option<bool> {
1835 paths
1836 .iter()
1837 .zip(metadatas)
1838 .map(|(path, metadata)| self.visibility_for_path(path, metadata, exclude_sub_dirs, cx))
1839 .max()
1840 .flatten()
1841 }
1842
1843 pub fn visibility_for_path(
1844 &self,
1845 path: &Path,
1846 metadata: &Metadata,
1847 exclude_sub_dirs: bool,
1848 cx: &App,
1849 ) -> Option<bool> {
1850 let sanitized_path = SanitizedPath::from(path);
1851 let path = sanitized_path.as_path();
1852 self.worktrees(cx)
1853 .filter_map(|worktree| {
1854 let worktree = worktree.read(cx);
1855 let abs_path = worktree.as_local()?.abs_path();
1856 let contains = path == abs_path
1857 || (path.starts_with(abs_path) && (!exclude_sub_dirs || !metadata.is_dir));
1858 contains.then(|| worktree.is_visible())
1859 })
1860 .max()
1861 }
1862
1863 pub fn create_entry(
1864 &mut self,
1865 project_path: impl Into<ProjectPath>,
1866 is_directory: bool,
1867 cx: &mut Context<Self>,
1868 ) -> Task<Result<CreatedEntry>> {
1869 let project_path = project_path.into();
1870 let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
1871 return Task::ready(Err(anyhow!(format!(
1872 "No worktree for path {project_path:?}"
1873 ))));
1874 };
1875 worktree.update(cx, |worktree, cx| {
1876 worktree.create_entry(project_path.path, is_directory, cx)
1877 })
1878 }
1879
1880 pub fn copy_entry(
1881 &mut self,
1882 entry_id: ProjectEntryId,
1883 relative_worktree_source_path: Option<PathBuf>,
1884 new_path: impl Into<Arc<Path>>,
1885 cx: &mut Context<Self>,
1886 ) -> Task<Result<Option<Entry>>> {
1887 let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
1888 return Task::ready(Ok(None));
1889 };
1890 worktree.update(cx, |worktree, cx| {
1891 worktree.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
1892 })
1893 }
1894
1895 /// Renames the project entry with given `entry_id`.
1896 ///
1897 /// `new_path` is a relative path to worktree root.
1898 /// If root entry is renamed then its new root name is used instead.
1899 pub fn rename_entry(
1900 &mut self,
1901 entry_id: ProjectEntryId,
1902 new_path: impl Into<Arc<Path>>,
1903 cx: &mut Context<Self>,
1904 ) -> Task<Result<CreatedEntry>> {
1905 let worktree_store = self.worktree_store.read(cx);
1906 let new_path = new_path.into();
1907 let Some((worktree, old_path, is_dir)) = worktree_store
1908 .worktree_and_entry_for_id(entry_id, cx)
1909 .map(|(worktree, entry)| (worktree, entry.path.clone(), entry.is_dir()))
1910 else {
1911 return Task::ready(Err(anyhow!(format!("No worktree for entry {entry_id:?}"))));
1912 };
1913
1914 let worktree_id = worktree.read(cx).id();
1915 let is_root_entry = self.entry_is_worktree_root(entry_id, cx);
1916
1917 let lsp_store = self.lsp_store().downgrade();
1918 cx.spawn(async move |_, cx| {
1919 let (old_abs_path, new_abs_path) = {
1920 let root_path = worktree.update(cx, |this, _| this.abs_path())?;
1921 let new_abs_path = if is_root_entry {
1922 root_path.parent().unwrap().join(&new_path)
1923 } else {
1924 root_path.join(&new_path)
1925 };
1926 (root_path.join(&old_path), new_abs_path)
1927 };
1928 LspStore::will_rename_entry(
1929 lsp_store.clone(),
1930 worktree_id,
1931 &old_abs_path,
1932 &new_abs_path,
1933 is_dir,
1934 cx.clone(),
1935 )
1936 .await;
1937
1938 let entry = worktree
1939 .update(cx, |worktree, cx| {
1940 worktree.rename_entry(entry_id, new_path.clone(), cx)
1941 })?
1942 .await?;
1943
1944 lsp_store
1945 .update(cx, |this, _| {
1946 this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir);
1947 })
1948 .ok();
1949 Ok(entry)
1950 })
1951 }
1952
1953 pub fn delete_file(
1954 &mut self,
1955 path: ProjectPath,
1956 trash: bool,
1957 cx: &mut Context<Self>,
1958 ) -> Option<Task<Result<()>>> {
1959 let entry = self.entry_for_path(&path, cx)?;
1960 self.delete_entry(entry.id, trash, cx)
1961 }
1962
1963 pub fn delete_entry(
1964 &mut self,
1965 entry_id: ProjectEntryId,
1966 trash: bool,
1967 cx: &mut Context<Self>,
1968 ) -> Option<Task<Result<()>>> {
1969 let worktree = self.worktree_for_entry(entry_id, cx)?;
1970 cx.emit(Event::DeletedEntry(worktree.read(cx).id(), entry_id));
1971 worktree.update(cx, |worktree, cx| {
1972 worktree.delete_entry(entry_id, trash, cx)
1973 })
1974 }
1975
1976 pub fn expand_entry(
1977 &mut self,
1978 worktree_id: WorktreeId,
1979 entry_id: ProjectEntryId,
1980 cx: &mut Context<Self>,
1981 ) -> Option<Task<Result<()>>> {
1982 let worktree = self.worktree_for_id(worktree_id, cx)?;
1983 worktree.update(cx, |worktree, cx| worktree.expand_entry(entry_id, cx))
1984 }
1985
1986 pub fn expand_all_for_entry(
1987 &mut self,
1988 worktree_id: WorktreeId,
1989 entry_id: ProjectEntryId,
1990 cx: &mut Context<Self>,
1991 ) -> Option<Task<Result<()>>> {
1992 let worktree = self.worktree_for_id(worktree_id, cx)?;
1993 let task = worktree.update(cx, |worktree, cx| {
1994 worktree.expand_all_for_entry(entry_id, cx)
1995 });
1996 Some(cx.spawn(async move |this, cx| {
1997 task.ok_or_else(|| anyhow!("no task"))?.await?;
1998 this.update(cx, |_, cx| {
1999 cx.emit(Event::ExpandedAllForEntry(worktree_id, entry_id));
2000 })?;
2001 Ok(())
2002 }))
2003 }
2004
2005 pub fn shared(&mut self, project_id: u64, cx: &mut Context<Self>) -> Result<()> {
2006 if !matches!(self.client_state, ProjectClientState::Local) {
2007 return Err(anyhow!("project was already shared"));
2008 }
2009
2010 self.client_subscriptions.extend([
2011 self.client
2012 .subscribe_to_entity(project_id)?
2013 .set_entity(&cx.entity(), &mut cx.to_async()),
2014 self.client
2015 .subscribe_to_entity(project_id)?
2016 .set_entity(&self.worktree_store, &mut cx.to_async()),
2017 self.client
2018 .subscribe_to_entity(project_id)?
2019 .set_entity(&self.buffer_store, &mut cx.to_async()),
2020 self.client
2021 .subscribe_to_entity(project_id)?
2022 .set_entity(&self.lsp_store, &mut cx.to_async()),
2023 self.client
2024 .subscribe_to_entity(project_id)?
2025 .set_entity(&self.settings_observer, &mut cx.to_async()),
2026 self.client
2027 .subscribe_to_entity(project_id)?
2028 .set_entity(&self.dap_store, &mut cx.to_async()),
2029 self.client
2030 .subscribe_to_entity(project_id)?
2031 .set_entity(&self.breakpoint_store, &mut cx.to_async()),
2032 self.client
2033 .subscribe_to_entity(project_id)?
2034 .set_entity(&self.git_store, &mut cx.to_async()),
2035 ]);
2036
2037 self.buffer_store.update(cx, |buffer_store, cx| {
2038 buffer_store.shared(project_id, self.client.clone().into(), cx)
2039 });
2040 self.worktree_store.update(cx, |worktree_store, cx| {
2041 worktree_store.shared(project_id, self.client.clone().into(), cx);
2042 });
2043 self.lsp_store.update(cx, |lsp_store, cx| {
2044 lsp_store.shared(project_id, self.client.clone().into(), cx)
2045 });
2046 self.breakpoint_store.update(cx, |breakpoint_store, _| {
2047 breakpoint_store.shared(project_id, self.client.clone().into())
2048 });
2049 self.dap_store.update(cx, |dap_store, cx| {
2050 dap_store.shared(project_id, self.client.clone().into(), cx);
2051 });
2052 self.task_store.update(cx, |task_store, cx| {
2053 task_store.shared(project_id, self.client.clone().into(), cx);
2054 });
2055 self.settings_observer.update(cx, |settings_observer, cx| {
2056 settings_observer.shared(project_id, self.client.clone().into(), cx)
2057 });
2058 self.git_store.update(cx, |git_store, cx| {
2059 git_store.shared(project_id, self.client.clone().into(), cx)
2060 });
2061
2062 self.client_state = ProjectClientState::Shared {
2063 remote_id: project_id,
2064 };
2065
2066 cx.emit(Event::RemoteIdChanged(Some(project_id)));
2067 Ok(())
2068 }
2069
2070 pub fn reshared(
2071 &mut self,
2072 message: proto::ResharedProject,
2073 cx: &mut Context<Self>,
2074 ) -> Result<()> {
2075 self.buffer_store
2076 .update(cx, |buffer_store, _| buffer_store.forget_shared_buffers());
2077 self.set_collaborators_from_proto(message.collaborators, cx)?;
2078
2079 self.worktree_store.update(cx, |worktree_store, cx| {
2080 worktree_store.send_project_updates(cx);
2081 });
2082 if let Some(remote_id) = self.remote_id() {
2083 self.git_store.update(cx, |git_store, cx| {
2084 git_store.shared(remote_id, self.client.clone().into(), cx)
2085 });
2086 }
2087 cx.emit(Event::Reshared);
2088 Ok(())
2089 }
2090
2091 pub fn rejoined(
2092 &mut self,
2093 message: proto::RejoinedProject,
2094 message_id: u32,
2095 cx: &mut Context<Self>,
2096 ) -> Result<()> {
2097 cx.update_global::<SettingsStore, _>(|store, cx| {
2098 self.worktree_store.update(cx, |worktree_store, cx| {
2099 for worktree in worktree_store.worktrees() {
2100 store
2101 .clear_local_settings(worktree.read(cx).id(), cx)
2102 .log_err();
2103 }
2104 });
2105 });
2106
2107 self.join_project_response_message_id = message_id;
2108 self.set_worktrees_from_proto(message.worktrees, cx)?;
2109 self.set_collaborators_from_proto(message.collaborators, cx)?;
2110 self.lsp_store.update(cx, |lsp_store, _| {
2111 lsp_store.set_language_server_statuses_from_proto(message.language_servers)
2112 });
2113 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
2114 .unwrap();
2115 cx.emit(Event::Rejoined);
2116 Ok(())
2117 }
2118
2119 pub fn unshare(&mut self, cx: &mut Context<Self>) -> Result<()> {
2120 self.unshare_internal(cx)?;
2121 cx.emit(Event::RemoteIdChanged(None));
2122 Ok(())
2123 }
2124
2125 fn unshare_internal(&mut self, cx: &mut App) -> Result<()> {
2126 if self.is_via_collab() {
2127 return Err(anyhow!("attempted to unshare a remote project"));
2128 }
2129
2130 if let ProjectClientState::Shared { remote_id, .. } = self.client_state {
2131 self.client_state = ProjectClientState::Local;
2132 self.collaborators.clear();
2133 self.client_subscriptions.clear();
2134 self.worktree_store.update(cx, |store, cx| {
2135 store.unshared(cx);
2136 });
2137 self.buffer_store.update(cx, |buffer_store, cx| {
2138 buffer_store.forget_shared_buffers();
2139 buffer_store.unshared(cx)
2140 });
2141 self.task_store.update(cx, |task_store, cx| {
2142 task_store.unshared(cx);
2143 });
2144 self.breakpoint_store.update(cx, |breakpoint_store, cx| {
2145 breakpoint_store.unshared(cx);
2146 });
2147 self.dap_store.update(cx, |dap_store, cx| {
2148 dap_store.unshared(cx);
2149 });
2150 self.settings_observer.update(cx, |settings_observer, cx| {
2151 settings_observer.unshared(cx);
2152 });
2153 self.git_store.update(cx, |git_store, cx| {
2154 git_store.unshared(cx);
2155 });
2156
2157 self.client
2158 .send(proto::UnshareProject {
2159 project_id: remote_id,
2160 })
2161 .ok();
2162 Ok(())
2163 } else {
2164 Err(anyhow!("attempted to unshare an unshared project"))
2165 }
2166 }
2167
2168 pub fn disconnected_from_host(&mut self, cx: &mut Context<Self>) {
2169 if self.is_disconnected(cx) {
2170 return;
2171 }
2172 self.disconnected_from_host_internal(cx);
2173 cx.emit(Event::DisconnectedFromHost);
2174 }
2175
2176 pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut Context<Self>) {
2177 let new_capability =
2178 if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin {
2179 Capability::ReadWrite
2180 } else {
2181 Capability::ReadOnly
2182 };
2183 if let ProjectClientState::Remote { capability, .. } = &mut self.client_state {
2184 if *capability == new_capability {
2185 return;
2186 }
2187
2188 *capability = new_capability;
2189 for buffer in self.opened_buffers(cx) {
2190 buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx));
2191 }
2192 }
2193 }
2194
2195 fn disconnected_from_host_internal(&mut self, cx: &mut App) {
2196 if let ProjectClientState::Remote {
2197 sharing_has_stopped,
2198 ..
2199 } = &mut self.client_state
2200 {
2201 *sharing_has_stopped = true;
2202 self.collaborators.clear();
2203 self.worktree_store.update(cx, |store, cx| {
2204 store.disconnected_from_host(cx);
2205 });
2206 self.buffer_store.update(cx, |buffer_store, cx| {
2207 buffer_store.disconnected_from_host(cx)
2208 });
2209 self.lsp_store
2210 .update(cx, |lsp_store, _cx| lsp_store.disconnected_from_host());
2211 }
2212 }
2213
2214 pub fn close(&mut self, cx: &mut Context<Self>) {
2215 cx.emit(Event::Closed);
2216 }
2217
2218 pub fn is_disconnected(&self, cx: &App) -> bool {
2219 match &self.client_state {
2220 ProjectClientState::Remote {
2221 sharing_has_stopped,
2222 ..
2223 } => *sharing_has_stopped,
2224 ProjectClientState::Local if self.is_via_ssh() => self.ssh_is_disconnected(cx),
2225 _ => false,
2226 }
2227 }
2228
2229 fn ssh_is_disconnected(&self, cx: &App) -> bool {
2230 self.ssh_client
2231 .as_ref()
2232 .map(|ssh| ssh.read(cx).is_disconnected())
2233 .unwrap_or(false)
2234 }
2235
2236 pub fn capability(&self) -> Capability {
2237 match &self.client_state {
2238 ProjectClientState::Remote { capability, .. } => *capability,
2239 ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite,
2240 }
2241 }
2242
2243 pub fn is_read_only(&self, cx: &App) -> bool {
2244 self.is_disconnected(cx) || self.capability() == Capability::ReadOnly
2245 }
2246
2247 pub fn is_local(&self) -> bool {
2248 match &self.client_state {
2249 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
2250 self.ssh_client.is_none()
2251 }
2252 ProjectClientState::Remote { .. } => false,
2253 }
2254 }
2255
2256 pub fn is_via_ssh(&self) -> bool {
2257 match &self.client_state {
2258 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
2259 self.ssh_client.is_some()
2260 }
2261 ProjectClientState::Remote { .. } => false,
2262 }
2263 }
2264
2265 pub fn is_via_collab(&self) -> bool {
2266 match &self.client_state {
2267 ProjectClientState::Local | ProjectClientState::Shared { .. } => false,
2268 ProjectClientState::Remote { .. } => true,
2269 }
2270 }
2271
2272 pub fn create_buffer(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
2273 self.buffer_store
2274 .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))
2275 }
2276
2277 pub fn create_local_buffer(
2278 &mut self,
2279 text: &str,
2280 language: Option<Arc<Language>>,
2281 cx: &mut Context<Self>,
2282 ) -> Entity<Buffer> {
2283 if self.is_via_collab() || self.is_via_ssh() {
2284 panic!("called create_local_buffer on a remote project")
2285 }
2286 self.buffer_store.update(cx, |buffer_store, cx| {
2287 buffer_store.create_local_buffer(text, language, cx)
2288 })
2289 }
2290
2291 pub fn open_path(
2292 &mut self,
2293 path: ProjectPath,
2294 cx: &mut Context<Self>,
2295 ) -> Task<Result<(Option<ProjectEntryId>, AnyEntity)>> {
2296 let task = self.open_buffer(path.clone(), cx);
2297 cx.spawn(async move |_project, cx| {
2298 let buffer = task.await?;
2299 let project_entry_id = buffer.read_with(cx, |buffer, cx| {
2300 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
2301 })?;
2302
2303 let buffer: &AnyEntity = &buffer;
2304 Ok((project_entry_id, buffer.clone()))
2305 })
2306 }
2307
2308 pub fn open_local_buffer(
2309 &mut self,
2310 abs_path: impl AsRef<Path>,
2311 cx: &mut Context<Self>,
2312 ) -> Task<Result<Entity<Buffer>>> {
2313 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
2314 self.open_buffer((worktree.read(cx).id(), relative_path), cx)
2315 } else {
2316 Task::ready(Err(anyhow!("no such path")))
2317 }
2318 }
2319
2320 #[cfg(any(test, feature = "test-support"))]
2321 pub fn open_local_buffer_with_lsp(
2322 &mut self,
2323 abs_path: impl AsRef<Path>,
2324 cx: &mut Context<Self>,
2325 ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
2326 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
2327 self.open_buffer_with_lsp((worktree.read(cx).id(), relative_path), cx)
2328 } else {
2329 Task::ready(Err(anyhow!("no such path")))
2330 }
2331 }
2332
2333 pub fn open_buffer(
2334 &mut self,
2335 path: impl Into<ProjectPath>,
2336 cx: &mut App,
2337 ) -> Task<Result<Entity<Buffer>>> {
2338 if self.is_disconnected(cx) {
2339 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2340 }
2341
2342 self.buffer_store.update(cx, |buffer_store, cx| {
2343 buffer_store.open_buffer(path.into(), cx)
2344 })
2345 }
2346
2347 #[cfg(any(test, feature = "test-support"))]
2348 pub fn open_buffer_with_lsp(
2349 &mut self,
2350 path: impl Into<ProjectPath>,
2351 cx: &mut Context<Self>,
2352 ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
2353 let buffer = self.open_buffer(path, cx);
2354 cx.spawn(async move |this, cx| {
2355 let buffer = buffer.await?;
2356 let handle = this.update(cx, |project, cx| {
2357 project.register_buffer_with_language_servers(&buffer, cx)
2358 })?;
2359 Ok((buffer, handle))
2360 })
2361 }
2362
2363 pub fn register_buffer_with_language_servers(
2364 &self,
2365 buffer: &Entity<Buffer>,
2366 cx: &mut App,
2367 ) -> OpenLspBufferHandle {
2368 self.lsp_store.update(cx, |lsp_store, cx| {
2369 lsp_store.register_buffer_with_language_servers(&buffer, false, cx)
2370 })
2371 }
2372
2373 pub fn open_unstaged_diff(
2374 &mut self,
2375 buffer: Entity<Buffer>,
2376 cx: &mut Context<Self>,
2377 ) -> Task<Result<Entity<BufferDiff>>> {
2378 if self.is_disconnected(cx) {
2379 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2380 }
2381 self.git_store
2382 .update(cx, |git_store, cx| git_store.open_unstaged_diff(buffer, cx))
2383 }
2384
2385 pub fn open_uncommitted_diff(
2386 &mut self,
2387 buffer: Entity<Buffer>,
2388 cx: &mut Context<Self>,
2389 ) -> Task<Result<Entity<BufferDiff>>> {
2390 if self.is_disconnected(cx) {
2391 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2392 }
2393 self.git_store.update(cx, |git_store, cx| {
2394 git_store.open_uncommitted_diff(buffer, cx)
2395 })
2396 }
2397
2398 pub fn open_buffer_by_id(
2399 &mut self,
2400 id: BufferId,
2401 cx: &mut Context<Self>,
2402 ) -> Task<Result<Entity<Buffer>>> {
2403 if let Some(buffer) = self.buffer_for_id(id, cx) {
2404 Task::ready(Ok(buffer))
2405 } else if self.is_local() || self.is_via_ssh() {
2406 Task::ready(Err(anyhow!("buffer {} does not exist", id)))
2407 } else if let Some(project_id) = self.remote_id() {
2408 let request = self.client.request(proto::OpenBufferById {
2409 project_id,
2410 id: id.into(),
2411 });
2412 cx.spawn(async move |project, cx| {
2413 let buffer_id = BufferId::new(request.await?.buffer_id)?;
2414 project
2415 .update(cx, |project, cx| {
2416 project.buffer_store.update(cx, |buffer_store, cx| {
2417 buffer_store.wait_for_remote_buffer(buffer_id, cx)
2418 })
2419 })?
2420 .await
2421 })
2422 } else {
2423 Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
2424 }
2425 }
2426
2427 pub fn save_buffers(
2428 &self,
2429 buffers: HashSet<Entity<Buffer>>,
2430 cx: &mut Context<Self>,
2431 ) -> Task<Result<()>> {
2432 cx.spawn(async move |this, cx| {
2433 let save_tasks = buffers.into_iter().filter_map(|buffer| {
2434 this.update(cx, |this, cx| this.save_buffer(buffer, cx))
2435 .ok()
2436 });
2437 try_join_all(save_tasks).await?;
2438 Ok(())
2439 })
2440 }
2441
2442 pub fn save_buffer(&self, buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Task<Result<()>> {
2443 self.buffer_store
2444 .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
2445 }
2446
2447 pub fn save_buffer_as(
2448 &mut self,
2449 buffer: Entity<Buffer>,
2450 path: ProjectPath,
2451 cx: &mut Context<Self>,
2452 ) -> Task<Result<()>> {
2453 self.buffer_store.update(cx, |buffer_store, cx| {
2454 buffer_store.save_buffer_as(buffer.clone(), path, cx)
2455 })
2456 }
2457
2458 pub fn get_open_buffer(&self, path: &ProjectPath, cx: &App) -> Option<Entity<Buffer>> {
2459 self.buffer_store.read(cx).get_by_path(path, cx)
2460 }
2461
2462 fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
2463 {
2464 let mut remotely_created_models = self.remotely_created_models.lock();
2465 if remotely_created_models.retain_count > 0 {
2466 remotely_created_models.buffers.push(buffer.clone())
2467 }
2468 }
2469
2470 self.request_buffer_diff_recalculation(buffer, cx);
2471
2472 cx.subscribe(buffer, |this, buffer, event, cx| {
2473 this.on_buffer_event(buffer, event, cx);
2474 })
2475 .detach();
2476
2477 Ok(())
2478 }
2479
2480 pub fn open_image(
2481 &mut self,
2482 path: impl Into<ProjectPath>,
2483 cx: &mut Context<Self>,
2484 ) -> Task<Result<Entity<ImageItem>>> {
2485 if self.is_disconnected(cx) {
2486 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2487 }
2488
2489 let open_image_task = self.image_store.update(cx, |image_store, cx| {
2490 image_store.open_image(path.into(), cx)
2491 });
2492
2493 let weak_project = cx.entity().downgrade();
2494 cx.spawn(async move |_, cx| {
2495 let image_item = open_image_task.await?;
2496 let project = weak_project
2497 .upgrade()
2498 .ok_or_else(|| anyhow!("Project dropped"))?;
2499
2500 let metadata = ImageItem::load_image_metadata(image_item.clone(), project, cx).await?;
2501 image_item.update(cx, |image_item, cx| {
2502 image_item.image_metadata = Some(metadata);
2503 cx.emit(ImageItemEvent::MetadataUpdated);
2504 })?;
2505
2506 Ok(image_item)
2507 })
2508 }
2509
2510 async fn send_buffer_ordered_messages(
2511 this: WeakEntity<Self>,
2512 rx: UnboundedReceiver<BufferOrderedMessage>,
2513 cx: &mut AsyncApp,
2514 ) -> Result<()> {
2515 const MAX_BATCH_SIZE: usize = 128;
2516
2517 let mut operations_by_buffer_id = HashMap::default();
2518 async fn flush_operations(
2519 this: &WeakEntity<Project>,
2520 operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
2521 needs_resync_with_host: &mut bool,
2522 is_local: bool,
2523 cx: &mut AsyncApp,
2524 ) -> Result<()> {
2525 for (buffer_id, operations) in operations_by_buffer_id.drain() {
2526 let request = this.update(cx, |this, _| {
2527 let project_id = this.remote_id()?;
2528 Some(this.client.request(proto::UpdateBuffer {
2529 buffer_id: buffer_id.into(),
2530 project_id,
2531 operations,
2532 }))
2533 })?;
2534 if let Some(request) = request {
2535 if request.await.is_err() && !is_local {
2536 *needs_resync_with_host = true;
2537 break;
2538 }
2539 }
2540 }
2541 Ok(())
2542 }
2543
2544 let mut needs_resync_with_host = false;
2545 let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
2546
2547 while let Some(changes) = changes.next().await {
2548 let is_local = this.update(cx, |this, _| this.is_local())?;
2549
2550 for change in changes {
2551 match change {
2552 BufferOrderedMessage::Operation {
2553 buffer_id,
2554 operation,
2555 } => {
2556 if needs_resync_with_host {
2557 continue;
2558 }
2559
2560 operations_by_buffer_id
2561 .entry(buffer_id)
2562 .or_insert(Vec::new())
2563 .push(operation);
2564 }
2565
2566 BufferOrderedMessage::Resync => {
2567 operations_by_buffer_id.clear();
2568 if this
2569 .update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
2570 .await
2571 .is_ok()
2572 {
2573 needs_resync_with_host = false;
2574 }
2575 }
2576
2577 BufferOrderedMessage::LanguageServerUpdate {
2578 language_server_id,
2579 message,
2580 } => {
2581 flush_operations(
2582 &this,
2583 &mut operations_by_buffer_id,
2584 &mut needs_resync_with_host,
2585 is_local,
2586 cx,
2587 )
2588 .await?;
2589
2590 this.update(cx, |this, _| {
2591 if let Some(project_id) = this.remote_id() {
2592 this.client
2593 .send(proto::UpdateLanguageServer {
2594 project_id,
2595 language_server_id: language_server_id.0 as u64,
2596 variant: Some(message),
2597 })
2598 .log_err();
2599 }
2600 })?;
2601 }
2602 }
2603 }
2604
2605 flush_operations(
2606 &this,
2607 &mut operations_by_buffer_id,
2608 &mut needs_resync_with_host,
2609 is_local,
2610 cx,
2611 )
2612 .await?;
2613 }
2614
2615 Ok(())
2616 }
2617
2618 fn on_buffer_store_event(
2619 &mut self,
2620 _: Entity<BufferStore>,
2621 event: &BufferStoreEvent,
2622 cx: &mut Context<Self>,
2623 ) {
2624 match event {
2625 BufferStoreEvent::BufferAdded(buffer) => {
2626 self.register_buffer(buffer, cx).log_err();
2627 }
2628 BufferStoreEvent::BufferDropped(buffer_id) => {
2629 if let Some(ref ssh_client) = self.ssh_client {
2630 ssh_client
2631 .read(cx)
2632 .proto_client()
2633 .send(proto::CloseBuffer {
2634 project_id: 0,
2635 buffer_id: buffer_id.to_proto(),
2636 })
2637 .log_err();
2638 }
2639 }
2640 _ => {}
2641 }
2642 }
2643
2644 fn on_image_store_event(
2645 &mut self,
2646 _: Entity<ImageStore>,
2647 event: &ImageStoreEvent,
2648 cx: &mut Context<Self>,
2649 ) {
2650 match event {
2651 ImageStoreEvent::ImageAdded(image) => {
2652 cx.subscribe(image, |this, image, event, cx| {
2653 this.on_image_event(image, event, cx);
2654 })
2655 .detach();
2656 }
2657 }
2658 }
2659
2660 fn on_dap_store_event(
2661 &mut self,
2662 _: Entity<DapStore>,
2663 event: &DapStoreEvent,
2664 cx: &mut Context<Self>,
2665 ) {
2666 match event {
2667 DapStoreEvent::Notification(message) => {
2668 cx.emit(Event::Toast {
2669 notification_id: "dap".into(),
2670 message: message.clone(),
2671 });
2672 }
2673 _ => {}
2674 }
2675 }
2676
2677 fn on_lsp_store_event(
2678 &mut self,
2679 _: Entity<LspStore>,
2680 event: &LspStoreEvent,
2681 cx: &mut Context<Self>,
2682 ) {
2683 match event {
2684 LspStoreEvent::DiagnosticsUpdated {
2685 language_server_id,
2686 path,
2687 } => cx.emit(Event::DiagnosticsUpdated {
2688 path: path.clone(),
2689 language_server_id: *language_server_id,
2690 }),
2691 LspStoreEvent::LanguageServerAdded(language_server_id, name, worktree_id) => cx.emit(
2692 Event::LanguageServerAdded(*language_server_id, name.clone(), *worktree_id),
2693 ),
2694 LspStoreEvent::LanguageServerRemoved(language_server_id) => {
2695 cx.emit(Event::LanguageServerRemoved(*language_server_id))
2696 }
2697 LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
2698 Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
2699 ),
2700 LspStoreEvent::LanguageDetected {
2701 buffer,
2702 new_language,
2703 } => {
2704 let Some(_) = new_language else {
2705 cx.emit(Event::LanguageNotFound(buffer.clone()));
2706 return;
2707 };
2708 }
2709 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
2710 LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens),
2711 LspStoreEvent::LanguageServerPrompt(prompt) => {
2712 cx.emit(Event::LanguageServerPrompt(prompt.clone()))
2713 }
2714 LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
2715 cx.emit(Event::DiskBasedDiagnosticsStarted {
2716 language_server_id: *language_server_id,
2717 });
2718 }
2719 LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
2720 cx.emit(Event::DiskBasedDiagnosticsFinished {
2721 language_server_id: *language_server_id,
2722 });
2723 }
2724 LspStoreEvent::LanguageServerUpdate {
2725 language_server_id,
2726 message,
2727 } => {
2728 if self.is_local() {
2729 self.enqueue_buffer_ordered_message(
2730 BufferOrderedMessage::LanguageServerUpdate {
2731 language_server_id: *language_server_id,
2732 message: message.clone(),
2733 },
2734 )
2735 .ok();
2736 }
2737 }
2738 LspStoreEvent::Notification(message) => cx.emit(Event::Toast {
2739 notification_id: "lsp".into(),
2740 message: message.clone(),
2741 }),
2742 LspStoreEvent::SnippetEdit {
2743 buffer_id,
2744 edits,
2745 most_recent_edit,
2746 } => {
2747 if most_recent_edit.replica_id == self.replica_id() {
2748 cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
2749 }
2750 }
2751 }
2752 }
2753
2754 fn on_ssh_event(
2755 &mut self,
2756 _: Entity<SshRemoteClient>,
2757 event: &remote::SshRemoteEvent,
2758 cx: &mut Context<Self>,
2759 ) {
2760 match event {
2761 remote::SshRemoteEvent::Disconnected => {
2762 // if self.is_via_ssh() {
2763 // self.collaborators.clear();
2764 self.worktree_store.update(cx, |store, cx| {
2765 store.disconnected_from_host(cx);
2766 });
2767 self.buffer_store.update(cx, |buffer_store, cx| {
2768 buffer_store.disconnected_from_host(cx)
2769 });
2770 self.lsp_store.update(cx, |lsp_store, _cx| {
2771 lsp_store.disconnected_from_ssh_remote()
2772 });
2773 cx.emit(Event::DisconnectedFromSshRemote);
2774 }
2775 }
2776 }
2777
2778 fn on_settings_observer_event(
2779 &mut self,
2780 _: Entity<SettingsObserver>,
2781 event: &SettingsObserverEvent,
2782 cx: &mut Context<Self>,
2783 ) {
2784 match event {
2785 SettingsObserverEvent::LocalSettingsUpdated(result) => match result {
2786 Err(InvalidSettingsError::LocalSettings { message, path }) => {
2787 let message = format!("Failed to set local settings in {path:?}:\n{message}");
2788 cx.emit(Event::Toast {
2789 notification_id: format!("local-settings-{path:?}").into(),
2790 message,
2791 });
2792 }
2793 Ok(path) => cx.emit(Event::HideToast {
2794 notification_id: format!("local-settings-{path:?}").into(),
2795 }),
2796 Err(_) => {}
2797 },
2798 SettingsObserverEvent::LocalTasksUpdated(result) => match result {
2799 Err(InvalidSettingsError::Tasks { message, path }) => {
2800 let message = format!("Failed to set local tasks in {path:?}:\n{message}");
2801 cx.emit(Event::Toast {
2802 notification_id: format!("local-tasks-{path:?}").into(),
2803 message,
2804 });
2805 }
2806 Ok(path) => cx.emit(Event::HideToast {
2807 notification_id: format!("local-tasks-{path:?}").into(),
2808 }),
2809 Err(_) => {}
2810 },
2811 }
2812 }
2813
2814 fn on_worktree_store_event(
2815 &mut self,
2816 _: Entity<WorktreeStore>,
2817 event: &WorktreeStoreEvent,
2818 cx: &mut Context<Self>,
2819 ) {
2820 match event {
2821 WorktreeStoreEvent::WorktreeAdded(worktree) => {
2822 self.on_worktree_added(worktree, cx);
2823 cx.emit(Event::WorktreeAdded(worktree.read(cx).id()));
2824 }
2825 WorktreeStoreEvent::WorktreeRemoved(_, id) => {
2826 cx.emit(Event::WorktreeRemoved(*id));
2827 }
2828 WorktreeStoreEvent::WorktreeReleased(_, id) => {
2829 self.on_worktree_released(*id, cx);
2830 }
2831 WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
2832 WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
2833 WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, changes) => {
2834 self.client()
2835 .telemetry()
2836 .report_discovered_project_events(*worktree_id, changes);
2837 cx.emit(Event::WorktreeUpdatedEntries(*worktree_id, changes.clone()))
2838 }
2839 WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, id) => {
2840 cx.emit(Event::DeletedEntry(*worktree_id, *id))
2841 }
2842 // Listen to the GitStore instead.
2843 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_, _) => {}
2844 }
2845 }
2846
2847 fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, _: &mut Context<Self>) {
2848 let mut remotely_created_models = self.remotely_created_models.lock();
2849 if remotely_created_models.retain_count > 0 {
2850 remotely_created_models.worktrees.push(worktree.clone())
2851 }
2852 }
2853
2854 fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
2855 if let Some(ssh) = &self.ssh_client {
2856 ssh.read(cx)
2857 .proto_client()
2858 .send(proto::RemoveWorktree {
2859 worktree_id: id_to_remove.to_proto(),
2860 })
2861 .log_err();
2862 }
2863 }
2864
2865 fn on_buffer_event(
2866 &mut self,
2867 buffer: Entity<Buffer>,
2868 event: &BufferEvent,
2869 cx: &mut Context<Self>,
2870 ) -> Option<()> {
2871 if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) {
2872 self.request_buffer_diff_recalculation(&buffer, cx);
2873 }
2874
2875 let buffer_id = buffer.read(cx).remote_id();
2876 match event {
2877 BufferEvent::ReloadNeeded => {
2878 if !self.is_via_collab() {
2879 self.reload_buffers([buffer.clone()].into_iter().collect(), true, cx)
2880 .detach_and_log_err(cx);
2881 }
2882 }
2883 BufferEvent::Operation {
2884 operation,
2885 is_local: true,
2886 } => {
2887 let operation = language::proto::serialize_operation(operation);
2888
2889 if let Some(ssh) = &self.ssh_client {
2890 ssh.read(cx)
2891 .proto_client()
2892 .send(proto::UpdateBuffer {
2893 project_id: 0,
2894 buffer_id: buffer_id.to_proto(),
2895 operations: vec![operation.clone()],
2896 })
2897 .ok();
2898 }
2899
2900 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
2901 buffer_id,
2902 operation,
2903 })
2904 .ok();
2905 }
2906
2907 _ => {}
2908 }
2909
2910 None
2911 }
2912
2913 fn on_image_event(
2914 &mut self,
2915 image: Entity<ImageItem>,
2916 event: &ImageItemEvent,
2917 cx: &mut Context<Self>,
2918 ) -> Option<()> {
2919 match event {
2920 ImageItemEvent::ReloadNeeded => {
2921 if !self.is_via_collab() {
2922 self.reload_images([image.clone()].into_iter().collect(), cx)
2923 .detach_and_log_err(cx);
2924 }
2925 }
2926 _ => {}
2927 }
2928
2929 None
2930 }
2931
2932 fn request_buffer_diff_recalculation(
2933 &mut self,
2934 buffer: &Entity<Buffer>,
2935 cx: &mut Context<Self>,
2936 ) {
2937 self.buffers_needing_diff.insert(buffer.downgrade());
2938 let first_insertion = self.buffers_needing_diff.len() == 1;
2939
2940 let settings = ProjectSettings::get_global(cx);
2941 let delay = if let Some(delay) = settings.git.gutter_debounce {
2942 delay
2943 } else {
2944 if first_insertion {
2945 let this = cx.weak_entity();
2946 cx.defer(move |cx| {
2947 if let Some(this) = this.upgrade() {
2948 this.update(cx, |this, cx| {
2949 this.recalculate_buffer_diffs(cx).detach();
2950 });
2951 }
2952 });
2953 }
2954 return;
2955 };
2956
2957 const MIN_DELAY: u64 = 50;
2958 let delay = delay.max(MIN_DELAY);
2959 let duration = Duration::from_millis(delay);
2960
2961 self.git_diff_debouncer
2962 .fire_new(duration, cx, move |this, cx| {
2963 this.recalculate_buffer_diffs(cx)
2964 });
2965 }
2966
2967 fn recalculate_buffer_diffs(&mut self, cx: &mut Context<Self>) -> Task<()> {
2968 cx.spawn(async move |this, cx| {
2969 loop {
2970 let task = this
2971 .update(cx, |this, cx| {
2972 let buffers = this
2973 .buffers_needing_diff
2974 .drain()
2975 .filter_map(|buffer| buffer.upgrade())
2976 .collect::<Vec<_>>();
2977 if buffers.is_empty() {
2978 None
2979 } else {
2980 Some(this.git_store.update(cx, |git_store, cx| {
2981 git_store.recalculate_buffer_diffs(buffers, cx)
2982 }))
2983 }
2984 })
2985 .ok()
2986 .flatten();
2987
2988 if let Some(task) = task {
2989 task.await;
2990 } else {
2991 break;
2992 }
2993 }
2994 })
2995 }
2996
2997 pub fn set_language_for_buffer(
2998 &mut self,
2999 buffer: &Entity<Buffer>,
3000 new_language: Arc<Language>,
3001 cx: &mut Context<Self>,
3002 ) {
3003 self.lsp_store.update(cx, |lsp_store, cx| {
3004 lsp_store.set_language_for_buffer(buffer, new_language, cx)
3005 })
3006 }
3007
3008 pub fn restart_language_servers_for_buffers(
3009 &mut self,
3010 buffers: Vec<Entity<Buffer>>,
3011 cx: &mut Context<Self>,
3012 ) {
3013 self.lsp_store.update(cx, |lsp_store, cx| {
3014 lsp_store.restart_language_servers_for_buffers(buffers, cx)
3015 })
3016 }
3017
3018 pub fn stop_language_servers_for_buffers(
3019 &mut self,
3020 buffers: Vec<Entity<Buffer>>,
3021 cx: &mut Context<Self>,
3022 ) {
3023 self.lsp_store.update(cx, |lsp_store, cx| {
3024 lsp_store.stop_language_servers_for_buffers(buffers, cx)
3025 })
3026 }
3027
3028 pub fn cancel_language_server_work_for_buffers(
3029 &mut self,
3030 buffers: impl IntoIterator<Item = Entity<Buffer>>,
3031 cx: &mut Context<Self>,
3032 ) {
3033 self.lsp_store.update(cx, |lsp_store, cx| {
3034 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
3035 })
3036 }
3037
3038 pub fn cancel_language_server_work(
3039 &mut self,
3040 server_id: LanguageServerId,
3041 token_to_cancel: Option<String>,
3042 cx: &mut Context<Self>,
3043 ) {
3044 self.lsp_store.update(cx, |lsp_store, cx| {
3045 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
3046 })
3047 }
3048
3049 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
3050 self.buffer_ordered_messages_tx
3051 .unbounded_send(message)
3052 .map_err(|e| anyhow!(e))
3053 }
3054
3055 pub fn available_toolchains(
3056 &self,
3057 path: ProjectPath,
3058 language_name: LanguageName,
3059 cx: &App,
3060 ) -> Task<Option<ToolchainList>> {
3061 if let Some(toolchain_store) = self.toolchain_store.clone() {
3062 cx.spawn(async move |cx| {
3063 cx.update(|cx| {
3064 toolchain_store
3065 .read(cx)
3066 .list_toolchains(path, language_name, cx)
3067 })
3068 .ok()?
3069 .await
3070 })
3071 } else {
3072 Task::ready(None)
3073 }
3074 }
3075
3076 pub async fn toolchain_term(
3077 languages: Arc<LanguageRegistry>,
3078 language_name: LanguageName,
3079 ) -> Option<SharedString> {
3080 languages
3081 .language_for_name(language_name.as_ref())
3082 .await
3083 .ok()?
3084 .toolchain_lister()
3085 .map(|lister| lister.term())
3086 }
3087
3088 pub fn activate_toolchain(
3089 &self,
3090 path: ProjectPath,
3091 toolchain: Toolchain,
3092 cx: &mut App,
3093 ) -> Task<Option<()>> {
3094 let Some(toolchain_store) = self.toolchain_store.clone() else {
3095 return Task::ready(None);
3096 };
3097 toolchain_store.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
3098 }
3099 pub fn active_toolchain(
3100 &self,
3101 path: ProjectPath,
3102 language_name: LanguageName,
3103 cx: &App,
3104 ) -> Task<Option<Toolchain>> {
3105 let Some(toolchain_store) = self.toolchain_store.clone() else {
3106 return Task::ready(None);
3107 };
3108 toolchain_store
3109 .read(cx)
3110 .active_toolchain(path, language_name, cx)
3111 }
3112 pub fn language_server_statuses<'a>(
3113 &'a self,
3114 cx: &'a App,
3115 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
3116 self.lsp_store.read(cx).language_server_statuses()
3117 }
3118
3119 pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
3120 self.lsp_store.read(cx).last_formatting_failure()
3121 }
3122
3123 pub fn reset_last_formatting_failure(&self, cx: &mut App) {
3124 self.lsp_store
3125 .update(cx, |store, _| store.reset_last_formatting_failure());
3126 }
3127
3128 pub fn reload_buffers(
3129 &self,
3130 buffers: HashSet<Entity<Buffer>>,
3131 push_to_history: bool,
3132 cx: &mut Context<Self>,
3133 ) -> Task<Result<ProjectTransaction>> {
3134 self.buffer_store.update(cx, |buffer_store, cx| {
3135 buffer_store.reload_buffers(buffers, push_to_history, cx)
3136 })
3137 }
3138
3139 pub fn reload_images(
3140 &self,
3141 images: HashSet<Entity<ImageItem>>,
3142 cx: &mut Context<Self>,
3143 ) -> Task<Result<()>> {
3144 self.image_store
3145 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
3146 }
3147
3148 pub fn format(
3149 &mut self,
3150 buffers: HashSet<Entity<Buffer>>,
3151 target: LspFormatTarget,
3152 push_to_history: bool,
3153 trigger: lsp_store::FormatTrigger,
3154 cx: &mut Context<Project>,
3155 ) -> Task<anyhow::Result<ProjectTransaction>> {
3156 self.lsp_store.update(cx, |lsp_store, cx| {
3157 lsp_store.format(buffers, target, push_to_history, trigger, cx)
3158 })
3159 }
3160
3161 #[inline(never)]
3162 fn definition_impl(
3163 &mut self,
3164 buffer: &Entity<Buffer>,
3165 position: PointUtf16,
3166 cx: &mut Context<Self>,
3167 ) -> Task<Result<Vec<LocationLink>>> {
3168 self.request_lsp(
3169 buffer.clone(),
3170 LanguageServerToQuery::FirstCapable,
3171 GetDefinition { position },
3172 cx,
3173 )
3174 }
3175 pub fn definition<T: ToPointUtf16>(
3176 &mut self,
3177 buffer: &Entity<Buffer>,
3178 position: T,
3179 cx: &mut Context<Self>,
3180 ) -> Task<Result<Vec<LocationLink>>> {
3181 let position = position.to_point_utf16(buffer.read(cx));
3182 self.definition_impl(buffer, position, cx)
3183 }
3184
3185 fn declaration_impl(
3186 &mut self,
3187 buffer: &Entity<Buffer>,
3188 position: PointUtf16,
3189 cx: &mut Context<Self>,
3190 ) -> Task<Result<Vec<LocationLink>>> {
3191 self.request_lsp(
3192 buffer.clone(),
3193 LanguageServerToQuery::FirstCapable,
3194 GetDeclaration { position },
3195 cx,
3196 )
3197 }
3198
3199 pub fn declaration<T: ToPointUtf16>(
3200 &mut self,
3201 buffer: &Entity<Buffer>,
3202 position: T,
3203 cx: &mut Context<Self>,
3204 ) -> Task<Result<Vec<LocationLink>>> {
3205 let position = position.to_point_utf16(buffer.read(cx));
3206 self.declaration_impl(buffer, position, cx)
3207 }
3208
3209 fn type_definition_impl(
3210 &mut self,
3211 buffer: &Entity<Buffer>,
3212 position: PointUtf16,
3213 cx: &mut Context<Self>,
3214 ) -> Task<Result<Vec<LocationLink>>> {
3215 self.request_lsp(
3216 buffer.clone(),
3217 LanguageServerToQuery::FirstCapable,
3218 GetTypeDefinition { position },
3219 cx,
3220 )
3221 }
3222
3223 pub fn type_definition<T: ToPointUtf16>(
3224 &mut self,
3225 buffer: &Entity<Buffer>,
3226 position: T,
3227 cx: &mut Context<Self>,
3228 ) -> Task<Result<Vec<LocationLink>>> {
3229 let position = position.to_point_utf16(buffer.read(cx));
3230 self.type_definition_impl(buffer, position, cx)
3231 }
3232
3233 pub fn implementation<T: ToPointUtf16>(
3234 &mut self,
3235 buffer: &Entity<Buffer>,
3236 position: T,
3237 cx: &mut Context<Self>,
3238 ) -> Task<Result<Vec<LocationLink>>> {
3239 let position = position.to_point_utf16(buffer.read(cx));
3240 self.request_lsp(
3241 buffer.clone(),
3242 LanguageServerToQuery::FirstCapable,
3243 GetImplementation { position },
3244 cx,
3245 )
3246 }
3247
3248 pub fn references<T: ToPointUtf16>(
3249 &mut self,
3250 buffer: &Entity<Buffer>,
3251 position: T,
3252 cx: &mut Context<Self>,
3253 ) -> Task<Result<Vec<Location>>> {
3254 let position = position.to_point_utf16(buffer.read(cx));
3255 self.request_lsp(
3256 buffer.clone(),
3257 LanguageServerToQuery::FirstCapable,
3258 GetReferences { position },
3259 cx,
3260 )
3261 }
3262
3263 fn document_highlights_impl(
3264 &mut self,
3265 buffer: &Entity<Buffer>,
3266 position: PointUtf16,
3267 cx: &mut Context<Self>,
3268 ) -> Task<Result<Vec<DocumentHighlight>>> {
3269 self.request_lsp(
3270 buffer.clone(),
3271 LanguageServerToQuery::FirstCapable,
3272 GetDocumentHighlights { position },
3273 cx,
3274 )
3275 }
3276
3277 pub fn document_highlights<T: ToPointUtf16>(
3278 &mut self,
3279 buffer: &Entity<Buffer>,
3280 position: T,
3281 cx: &mut Context<Self>,
3282 ) -> Task<Result<Vec<DocumentHighlight>>> {
3283 let position = position.to_point_utf16(buffer.read(cx));
3284 self.document_highlights_impl(buffer, position, cx)
3285 }
3286
3287 pub fn document_symbols(
3288 &mut self,
3289 buffer: &Entity<Buffer>,
3290 cx: &mut Context<Self>,
3291 ) -> Task<Result<Vec<DocumentSymbol>>> {
3292 self.request_lsp(
3293 buffer.clone(),
3294 LanguageServerToQuery::FirstCapable,
3295 GetDocumentSymbols,
3296 cx,
3297 )
3298 }
3299
3300 pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
3301 self.lsp_store
3302 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
3303 }
3304
3305 pub fn open_buffer_for_symbol(
3306 &mut self,
3307 symbol: &Symbol,
3308 cx: &mut Context<Self>,
3309 ) -> Task<Result<Entity<Buffer>>> {
3310 self.lsp_store.update(cx, |lsp_store, cx| {
3311 lsp_store.open_buffer_for_symbol(symbol, cx)
3312 })
3313 }
3314
3315 pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
3316 let guard = self.retain_remotely_created_models(cx);
3317 let Some(ssh_client) = self.ssh_client.as_ref() else {
3318 return Task::ready(Err(anyhow!("not an ssh project")));
3319 };
3320
3321 let proto_client = ssh_client.read(cx).proto_client();
3322
3323 cx.spawn(async move |project, cx| {
3324 let buffer = proto_client
3325 .request(proto::OpenServerSettings {
3326 project_id: SSH_PROJECT_ID,
3327 })
3328 .await?;
3329
3330 let buffer = project
3331 .update(cx, |project, cx| {
3332 project.buffer_store.update(cx, |buffer_store, cx| {
3333 anyhow::Ok(
3334 buffer_store
3335 .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
3336 )
3337 })
3338 })??
3339 .await;
3340
3341 drop(guard);
3342 buffer
3343 })
3344 }
3345
3346 pub fn open_local_buffer_via_lsp(
3347 &mut self,
3348 abs_path: lsp::Url,
3349 language_server_id: LanguageServerId,
3350 language_server_name: LanguageServerName,
3351 cx: &mut Context<Self>,
3352 ) -> Task<Result<Entity<Buffer>>> {
3353 self.lsp_store.update(cx, |lsp_store, cx| {
3354 lsp_store.open_local_buffer_via_lsp(
3355 abs_path,
3356 language_server_id,
3357 language_server_name,
3358 cx,
3359 )
3360 })
3361 }
3362
3363 pub fn signature_help<T: ToPointUtf16>(
3364 &self,
3365 buffer: &Entity<Buffer>,
3366 position: T,
3367 cx: &mut Context<Self>,
3368 ) -> Task<Vec<SignatureHelp>> {
3369 self.lsp_store.update(cx, |lsp_store, cx| {
3370 lsp_store.signature_help(buffer, position, cx)
3371 })
3372 }
3373
3374 pub fn hover<T: ToPointUtf16>(
3375 &self,
3376 buffer: &Entity<Buffer>,
3377 position: T,
3378 cx: &mut Context<Self>,
3379 ) -> Task<Vec<Hover>> {
3380 let position = position.to_point_utf16(buffer.read(cx));
3381 self.lsp_store
3382 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
3383 }
3384
3385 pub fn linked_edit(
3386 &self,
3387 buffer: &Entity<Buffer>,
3388 position: Anchor,
3389 cx: &mut Context<Self>,
3390 ) -> Task<Result<Vec<Range<Anchor>>>> {
3391 self.lsp_store.update(cx, |lsp_store, cx| {
3392 lsp_store.linked_edit(buffer, position, cx)
3393 })
3394 }
3395
3396 pub fn completions<T: ToOffset + ToPointUtf16>(
3397 &self,
3398 buffer: &Entity<Buffer>,
3399 position: T,
3400 context: CompletionContext,
3401 cx: &mut Context<Self>,
3402 ) -> Task<Result<Option<Vec<Completion>>>> {
3403 let position = position.to_point_utf16(buffer.read(cx));
3404 self.lsp_store.update(cx, |lsp_store, cx| {
3405 lsp_store.completions(buffer, position, context, cx)
3406 })
3407 }
3408
3409 pub fn code_actions<T: Clone + ToOffset>(
3410 &mut self,
3411 buffer_handle: &Entity<Buffer>,
3412 range: Range<T>,
3413 kinds: Option<Vec<CodeActionKind>>,
3414 cx: &mut Context<Self>,
3415 ) -> Task<Result<Vec<CodeAction>>> {
3416 let buffer = buffer_handle.read(cx);
3417 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3418 self.lsp_store.update(cx, |lsp_store, cx| {
3419 lsp_store.code_actions(buffer_handle, range, kinds, cx)
3420 })
3421 }
3422
3423 pub fn code_lens<T: Clone + ToOffset>(
3424 &mut self,
3425 buffer_handle: &Entity<Buffer>,
3426 range: Range<T>,
3427 cx: &mut Context<Self>,
3428 ) -> Task<Result<Vec<CodeAction>>> {
3429 let snapshot = buffer_handle.read(cx).snapshot();
3430 let range = snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end);
3431 let code_lens_actions = self
3432 .lsp_store
3433 .update(cx, |lsp_store, cx| lsp_store.code_lens(buffer_handle, cx));
3434
3435 cx.background_spawn(async move {
3436 let mut code_lens_actions = code_lens_actions.await?;
3437 code_lens_actions.retain(|code_lens_action| {
3438 range
3439 .start
3440 .cmp(&code_lens_action.range.start, &snapshot)
3441 .is_ge()
3442 && range
3443 .end
3444 .cmp(&code_lens_action.range.end, &snapshot)
3445 .is_le()
3446 });
3447 Ok(code_lens_actions)
3448 })
3449 }
3450
3451 pub fn apply_code_action(
3452 &self,
3453 buffer_handle: Entity<Buffer>,
3454 action: CodeAction,
3455 push_to_history: bool,
3456 cx: &mut Context<Self>,
3457 ) -> Task<Result<ProjectTransaction>> {
3458 self.lsp_store.update(cx, |lsp_store, cx| {
3459 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
3460 })
3461 }
3462
3463 pub fn apply_code_action_kind(
3464 &self,
3465 buffers: HashSet<Entity<Buffer>>,
3466 kind: CodeActionKind,
3467 push_to_history: bool,
3468 cx: &mut Context<Self>,
3469 ) -> Task<Result<ProjectTransaction>> {
3470 self.lsp_store.update(cx, |lsp_store, cx| {
3471 lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
3472 })
3473 }
3474
3475 fn prepare_rename_impl(
3476 &mut self,
3477 buffer: Entity<Buffer>,
3478 position: PointUtf16,
3479 cx: &mut Context<Self>,
3480 ) -> Task<Result<PrepareRenameResponse>> {
3481 self.request_lsp(
3482 buffer,
3483 LanguageServerToQuery::FirstCapable,
3484 PrepareRename { position },
3485 cx,
3486 )
3487 }
3488 pub fn prepare_rename<T: ToPointUtf16>(
3489 &mut self,
3490 buffer: Entity<Buffer>,
3491 position: T,
3492 cx: &mut Context<Self>,
3493 ) -> Task<Result<PrepareRenameResponse>> {
3494 let position = position.to_point_utf16(buffer.read(cx));
3495 self.prepare_rename_impl(buffer, position, cx)
3496 }
3497
3498 pub fn perform_rename<T: ToPointUtf16>(
3499 &mut self,
3500 buffer: Entity<Buffer>,
3501 position: T,
3502 new_name: String,
3503 cx: &mut Context<Self>,
3504 ) -> Task<Result<ProjectTransaction>> {
3505 let push_to_history = true;
3506 let position = position.to_point_utf16(buffer.read(cx));
3507 self.request_lsp(
3508 buffer,
3509 LanguageServerToQuery::FirstCapable,
3510 PerformRename {
3511 position,
3512 new_name,
3513 push_to_history,
3514 },
3515 cx,
3516 )
3517 }
3518
3519 pub fn on_type_format<T: ToPointUtf16>(
3520 &mut self,
3521 buffer: Entity<Buffer>,
3522 position: T,
3523 trigger: String,
3524 push_to_history: bool,
3525 cx: &mut Context<Self>,
3526 ) -> Task<Result<Option<Transaction>>> {
3527 self.lsp_store.update(cx, |lsp_store, cx| {
3528 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
3529 })
3530 }
3531
3532 pub fn inlay_hints<T: ToOffset>(
3533 &mut self,
3534 buffer_handle: Entity<Buffer>,
3535 range: Range<T>,
3536 cx: &mut Context<Self>,
3537 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
3538 let buffer = buffer_handle.read(cx);
3539 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3540 self.lsp_store.update(cx, |lsp_store, cx| {
3541 lsp_store.inlay_hints(buffer_handle, range, cx)
3542 })
3543 }
3544
3545 pub fn resolve_inlay_hint(
3546 &self,
3547 hint: InlayHint,
3548 buffer_handle: Entity<Buffer>,
3549 server_id: LanguageServerId,
3550 cx: &mut Context<Self>,
3551 ) -> Task<anyhow::Result<InlayHint>> {
3552 self.lsp_store.update(cx, |lsp_store, cx| {
3553 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
3554 })
3555 }
3556
3557 pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
3558 let (result_tx, result_rx) = smol::channel::unbounded();
3559
3560 let matching_buffers_rx = if query.is_opened_only() {
3561 self.sort_search_candidates(&query, cx)
3562 } else {
3563 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
3564 };
3565
3566 cx.spawn(async move |_, cx| {
3567 let mut range_count = 0;
3568 let mut buffer_count = 0;
3569 let mut limit_reached = false;
3570 let query = Arc::new(query);
3571 let mut chunks = matching_buffers_rx.ready_chunks(64);
3572
3573 // Now that we know what paths match the query, we will load at most
3574 // 64 buffers at a time to avoid overwhelming the main thread. For each
3575 // opened buffer, we will spawn a background task that retrieves all the
3576 // ranges in the buffer matched by the query.
3577 let mut chunks = pin!(chunks);
3578 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
3579 let mut chunk_results = Vec::new();
3580 for buffer in matching_buffer_chunk {
3581 let buffer = buffer.clone();
3582 let query = query.clone();
3583 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
3584 chunk_results.push(cx.background_spawn(async move {
3585 let ranges = query
3586 .search(&snapshot, None)
3587 .await
3588 .iter()
3589 .map(|range| {
3590 snapshot.anchor_before(range.start)
3591 ..snapshot.anchor_after(range.end)
3592 })
3593 .collect::<Vec<_>>();
3594 anyhow::Ok((buffer, ranges))
3595 }));
3596 }
3597
3598 let chunk_results = futures::future::join_all(chunk_results).await;
3599 for result in chunk_results {
3600 if let Some((buffer, ranges)) = result.log_err() {
3601 range_count += ranges.len();
3602 buffer_count += 1;
3603 result_tx
3604 .send(SearchResult::Buffer { buffer, ranges })
3605 .await?;
3606 if buffer_count > MAX_SEARCH_RESULT_FILES
3607 || range_count > MAX_SEARCH_RESULT_RANGES
3608 {
3609 limit_reached = true;
3610 break 'outer;
3611 }
3612 }
3613 }
3614 }
3615
3616 if limit_reached {
3617 result_tx.send(SearchResult::LimitReached).await?;
3618 }
3619
3620 anyhow::Ok(())
3621 })
3622 .detach();
3623
3624 result_rx
3625 }
3626
3627 fn find_search_candidate_buffers(
3628 &mut self,
3629 query: &SearchQuery,
3630 limit: usize,
3631 cx: &mut Context<Project>,
3632 ) -> Receiver<Entity<Buffer>> {
3633 if self.is_local() {
3634 let fs = self.fs.clone();
3635 self.buffer_store.update(cx, |buffer_store, cx| {
3636 buffer_store.find_search_candidates(query, limit, fs, cx)
3637 })
3638 } else {
3639 self.find_search_candidates_remote(query, limit, cx)
3640 }
3641 }
3642
3643 fn sort_search_candidates(
3644 &mut self,
3645 search_query: &SearchQuery,
3646 cx: &mut Context<Project>,
3647 ) -> Receiver<Entity<Buffer>> {
3648 let worktree_store = self.worktree_store.read(cx);
3649 let mut buffers = search_query
3650 .buffers()
3651 .into_iter()
3652 .flatten()
3653 .filter(|buffer| {
3654 let b = buffer.read(cx);
3655 if let Some(file) = b.file() {
3656 if !search_query.file_matches(file.path()) {
3657 return false;
3658 }
3659 if let Some(entry) = b
3660 .entry_id(cx)
3661 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
3662 {
3663 if entry.is_ignored && !search_query.include_ignored() {
3664 return false;
3665 }
3666 }
3667 }
3668 true
3669 })
3670 .collect::<Vec<_>>();
3671 let (tx, rx) = smol::channel::unbounded();
3672 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
3673 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
3674 (None, Some(_)) => std::cmp::Ordering::Less,
3675 (Some(_), None) => std::cmp::Ordering::Greater,
3676 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
3677 });
3678 for buffer in buffers {
3679 tx.send_blocking(buffer.clone()).unwrap()
3680 }
3681
3682 rx
3683 }
3684
3685 fn find_search_candidates_remote(
3686 &mut self,
3687 query: &SearchQuery,
3688 limit: usize,
3689 cx: &mut Context<Project>,
3690 ) -> Receiver<Entity<Buffer>> {
3691 let (tx, rx) = smol::channel::unbounded();
3692
3693 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
3694 (ssh_client.read(cx).proto_client(), 0)
3695 } else if let Some(remote_id) = self.remote_id() {
3696 (self.client.clone().into(), remote_id)
3697 } else {
3698 return rx;
3699 };
3700
3701 let request = client.request(proto::FindSearchCandidates {
3702 project_id: remote_id,
3703 query: Some(query.to_proto()),
3704 limit: limit as _,
3705 });
3706 let guard = self.retain_remotely_created_models(cx);
3707
3708 cx.spawn(async move |project, cx| {
3709 let response = request.await?;
3710 for buffer_id in response.buffer_ids {
3711 let buffer_id = BufferId::new(buffer_id)?;
3712 let buffer = project
3713 .update(cx, |project, cx| {
3714 project.buffer_store.update(cx, |buffer_store, cx| {
3715 buffer_store.wait_for_remote_buffer(buffer_id, cx)
3716 })
3717 })?
3718 .await?;
3719 let _ = tx.send(buffer).await;
3720 }
3721
3722 drop(guard);
3723 anyhow::Ok(())
3724 })
3725 .detach_and_log_err(cx);
3726 rx
3727 }
3728
3729 pub fn request_lsp<R: LspCommand>(
3730 &mut self,
3731 buffer_handle: Entity<Buffer>,
3732 server: LanguageServerToQuery,
3733 request: R,
3734 cx: &mut Context<Self>,
3735 ) -> Task<Result<R::Response>>
3736 where
3737 <R::LspRequest as lsp::request::Request>::Result: Send,
3738 <R::LspRequest as lsp::request::Request>::Params: Send,
3739 {
3740 let guard = self.retain_remotely_created_models(cx);
3741 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3742 lsp_store.request_lsp(buffer_handle, server, request, cx)
3743 });
3744 cx.spawn(async move |_, _| {
3745 let result = task.await;
3746 drop(guard);
3747 result
3748 })
3749 }
3750
3751 /// Move a worktree to a new position in the worktree order.
3752 ///
3753 /// The worktree will moved to the opposite side of the destination worktree.
3754 ///
3755 /// # Example
3756 ///
3757 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
3758 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
3759 ///
3760 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
3761 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
3762 ///
3763 /// # Errors
3764 ///
3765 /// An error will be returned if the worktree or destination worktree are not found.
3766 pub fn move_worktree(
3767 &mut self,
3768 source: WorktreeId,
3769 destination: WorktreeId,
3770 cx: &mut Context<Self>,
3771 ) -> Result<()> {
3772 self.worktree_store.update(cx, |worktree_store, cx| {
3773 worktree_store.move_worktree(source, destination, cx)
3774 })
3775 }
3776
3777 pub fn find_or_create_worktree(
3778 &mut self,
3779 abs_path: impl AsRef<Path>,
3780 visible: bool,
3781 cx: &mut Context<Self>,
3782 ) -> Task<Result<(Entity<Worktree>, PathBuf)>> {
3783 self.worktree_store.update(cx, |worktree_store, cx| {
3784 worktree_store.find_or_create_worktree(abs_path, visible, cx)
3785 })
3786 }
3787
3788 pub fn find_worktree(&self, abs_path: &Path, cx: &App) -> Option<(Entity<Worktree>, PathBuf)> {
3789 self.worktree_store.read_with(cx, |worktree_store, cx| {
3790 worktree_store.find_worktree(abs_path, cx)
3791 })
3792 }
3793
3794 pub fn is_shared(&self) -> bool {
3795 match &self.client_state {
3796 ProjectClientState::Shared { .. } => true,
3797 ProjectClientState::Local => false,
3798 ProjectClientState::Remote { .. } => true,
3799 }
3800 }
3801
3802 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
3803 pub fn resolve_path_in_buffer(
3804 &self,
3805 path: &str,
3806 buffer: &Entity<Buffer>,
3807 cx: &mut Context<Self>,
3808 ) -> Task<Option<ResolvedPath>> {
3809 let path_buf = PathBuf::from(path);
3810 if path_buf.is_absolute() || path.starts_with("~") {
3811 self.resolve_abs_path(path, cx)
3812 } else {
3813 self.resolve_path_in_worktrees(path_buf, buffer, cx)
3814 }
3815 }
3816
3817 pub fn resolve_abs_file_path(
3818 &self,
3819 path: &str,
3820 cx: &mut Context<Self>,
3821 ) -> Task<Option<ResolvedPath>> {
3822 let resolve_task = self.resolve_abs_path(path, cx);
3823 cx.background_spawn(async move {
3824 let resolved_path = resolve_task.await;
3825 resolved_path.filter(|path| path.is_file())
3826 })
3827 }
3828
3829 pub fn resolve_abs_path(
3830 &self,
3831 path: &str,
3832 cx: &mut Context<Self>,
3833 ) -> Task<Option<ResolvedPath>> {
3834 if self.is_local() {
3835 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3836 let fs = self.fs.clone();
3837 cx.background_spawn(async move {
3838 let path = expanded.as_path();
3839 let metadata = fs.metadata(path).await.ok().flatten();
3840
3841 metadata.map(|metadata| ResolvedPath::AbsPath {
3842 path: expanded,
3843 is_dir: metadata.is_dir,
3844 })
3845 })
3846 } else if let Some(ssh_client) = self.ssh_client.as_ref() {
3847 let request_path = Path::new(path);
3848 let request = ssh_client
3849 .read(cx)
3850 .proto_client()
3851 .request(proto::GetPathMetadata {
3852 project_id: SSH_PROJECT_ID,
3853 path: request_path.to_proto(),
3854 });
3855 cx.background_spawn(async move {
3856 let response = request.await.log_err()?;
3857 if response.exists {
3858 Some(ResolvedPath::AbsPath {
3859 path: PathBuf::from_proto(response.path),
3860 is_dir: response.is_dir,
3861 })
3862 } else {
3863 None
3864 }
3865 })
3866 } else {
3867 return Task::ready(None);
3868 }
3869 }
3870
3871 fn resolve_path_in_worktrees(
3872 &self,
3873 path: PathBuf,
3874 buffer: &Entity<Buffer>,
3875 cx: &mut Context<Self>,
3876 ) -> Task<Option<ResolvedPath>> {
3877 let mut candidates = vec![path.clone()];
3878
3879 if let Some(file) = buffer.read(cx).file() {
3880 if let Some(dir) = file.path().parent() {
3881 let joined = dir.to_path_buf().join(path);
3882 candidates.push(joined);
3883 }
3884 }
3885
3886 let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
3887 let worktrees_with_ids: Vec<_> = self
3888 .worktrees(cx)
3889 .map(|worktree| {
3890 let id = worktree.read(cx).id();
3891 (worktree, id)
3892 })
3893 .collect();
3894
3895 cx.spawn(async move |_, mut cx| {
3896 if let Some(buffer_worktree_id) = buffer_worktree_id {
3897 if let Some((worktree, _)) = worktrees_with_ids
3898 .iter()
3899 .find(|(_, id)| *id == buffer_worktree_id)
3900 {
3901 for candidate in candidates.iter() {
3902 if let Some(path) =
3903 Self::resolve_path_in_worktree(&worktree, candidate, &mut cx)
3904 {
3905 return Some(path);
3906 }
3907 }
3908 }
3909 }
3910 for (worktree, id) in worktrees_with_ids {
3911 if Some(id) == buffer_worktree_id {
3912 continue;
3913 }
3914 for candidate in candidates.iter() {
3915 if let Some(path) =
3916 Self::resolve_path_in_worktree(&worktree, candidate, &mut cx)
3917 {
3918 return Some(path);
3919 }
3920 }
3921 }
3922 None
3923 })
3924 }
3925
3926 fn resolve_path_in_worktree(
3927 worktree: &Entity<Worktree>,
3928 path: &PathBuf,
3929 cx: &mut AsyncApp,
3930 ) -> Option<ResolvedPath> {
3931 worktree
3932 .update(cx, |worktree, _| {
3933 let root_entry_path = &worktree.root_entry()?.path;
3934 let resolved = resolve_path(root_entry_path, path);
3935 let stripped = resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3936 worktree.entry_for_path(stripped).map(|entry| {
3937 let project_path = ProjectPath {
3938 worktree_id: worktree.id(),
3939 path: entry.path.clone(),
3940 };
3941 ResolvedPath::ProjectPath {
3942 project_path,
3943 is_dir: entry.is_dir(),
3944 }
3945 })
3946 })
3947 .ok()?
3948 }
3949
3950 pub fn list_directory(
3951 &self,
3952 query: String,
3953 cx: &mut Context<Self>,
3954 ) -> Task<Result<Vec<DirectoryItem>>> {
3955 if self.is_local() {
3956 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3957 } else if let Some(session) = self.ssh_client.as_ref() {
3958 let path_buf = PathBuf::from(query);
3959 let request = proto::ListRemoteDirectory {
3960 dev_server_id: SSH_PROJECT_ID,
3961 path: path_buf.to_proto(),
3962 config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
3963 };
3964
3965 let response = session.read(cx).proto_client().request(request);
3966 cx.background_spawn(async move {
3967 let proto::ListRemoteDirectoryResponse {
3968 entries,
3969 entry_info,
3970 } = response.await?;
3971 Ok(entries
3972 .into_iter()
3973 .zip(entry_info)
3974 .map(|(entry, info)| DirectoryItem {
3975 path: PathBuf::from(entry),
3976 is_dir: info.is_dir,
3977 })
3978 .collect())
3979 })
3980 } else {
3981 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3982 }
3983 }
3984
3985 pub fn create_worktree(
3986 &mut self,
3987 abs_path: impl AsRef<Path>,
3988 visible: bool,
3989 cx: &mut Context<Self>,
3990 ) -> Task<Result<Entity<Worktree>>> {
3991 self.worktree_store.update(cx, |worktree_store, cx| {
3992 worktree_store.create_worktree(abs_path, visible, cx)
3993 })
3994 }
3995
3996 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
3997 self.worktree_store.update(cx, |worktree_store, cx| {
3998 worktree_store.remove_worktree(id_to_remove, cx);
3999 });
4000 }
4001
4002 fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
4003 self.worktree_store.update(cx, |worktree_store, cx| {
4004 worktree_store.add(worktree, cx);
4005 });
4006 }
4007
4008 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
4009 let new_active_entry = entry.and_then(|project_path| {
4010 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
4011 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
4012 Some(entry.id)
4013 });
4014 if new_active_entry != self.active_entry {
4015 self.active_entry = new_active_entry;
4016 self.lsp_store.update(cx, |lsp_store, _| {
4017 lsp_store.set_active_entry(new_active_entry);
4018 });
4019 cx.emit(Event::ActiveEntryChanged(new_active_entry));
4020 }
4021 }
4022
4023 pub fn language_servers_running_disk_based_diagnostics<'a>(
4024 &'a self,
4025 cx: &'a App,
4026 ) -> impl Iterator<Item = LanguageServerId> + 'a {
4027 self.lsp_store
4028 .read(cx)
4029 .language_servers_running_disk_based_diagnostics()
4030 }
4031
4032 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
4033 self.lsp_store
4034 .read(cx)
4035 .diagnostic_summary(include_ignored, cx)
4036 }
4037
4038 pub fn diagnostic_summaries<'a>(
4039 &'a self,
4040 include_ignored: bool,
4041 cx: &'a App,
4042 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
4043 self.lsp_store
4044 .read(cx)
4045 .diagnostic_summaries(include_ignored, cx)
4046 }
4047
4048 pub fn active_entry(&self) -> Option<ProjectEntryId> {
4049 self.active_entry
4050 }
4051
4052 pub fn entry_for_path(&self, path: &ProjectPath, cx: &App) -> Option<Entry> {
4053 self.worktree_store.read(cx).entry_for_path(path, cx)
4054 }
4055
4056 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
4057 let worktree = self.worktree_for_entry(entry_id, cx)?;
4058 let worktree = worktree.read(cx);
4059 let worktree_id = worktree.id();
4060 let path = worktree.entry_for_id(entry_id)?.path.clone();
4061 Some(ProjectPath { worktree_id, path })
4062 }
4063
4064 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4065 self.worktree_for_id(project_path.worktree_id, cx)?
4066 .read(cx)
4067 .absolutize(&project_path.path)
4068 .ok()
4069 }
4070
4071 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
4072 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
4073 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
4074 /// the first visible worktree that has an entry for that relative path.
4075 ///
4076 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
4077 /// root name from paths.
4078 ///
4079 /// # Arguments
4080 ///
4081 /// * `path` - A full path that starts with a worktree root name, or alternatively a
4082 /// relative path within a visible worktree.
4083 /// * `cx` - A reference to the `AppContext`.
4084 ///
4085 /// # Returns
4086 ///
4087 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
4088 pub fn find_project_path(&self, path: impl AsRef<Path>, cx: &App) -> Option<ProjectPath> {
4089 let path = path.as_ref();
4090 let worktree_store = self.worktree_store.read(cx);
4091
4092 for worktree in worktree_store.visible_worktrees(cx) {
4093 let worktree_root_name = worktree.read(cx).root_name();
4094 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
4095 return Some(ProjectPath {
4096 worktree_id: worktree.read(cx).id(),
4097 path: relative_path.into(),
4098 });
4099 }
4100 }
4101
4102 for worktree in worktree_store.visible_worktrees(cx) {
4103 let worktree = worktree.read(cx);
4104 if let Some(entry) = worktree.entry_for_path(path) {
4105 return Some(ProjectPath {
4106 worktree_id: worktree.id(),
4107 path: entry.path.clone(),
4108 });
4109 }
4110 }
4111
4112 None
4113 }
4114
4115 pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
4116 self.find_worktree(abs_path, cx)
4117 .map(|(worktree, relative_path)| ProjectPath {
4118 worktree_id: worktree.read(cx).id(),
4119 path: relative_path.into(),
4120 })
4121 }
4122
4123 pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4124 Some(
4125 self.worktree_for_id(project_path.worktree_id, cx)?
4126 .read(cx)
4127 .abs_path()
4128 .to_path_buf(),
4129 )
4130 }
4131
4132 pub fn blame_buffer(
4133 &self,
4134 buffer: &Entity<Buffer>,
4135 version: Option<clock::Global>,
4136 cx: &App,
4137 ) -> Task<Result<Option<Blame>>> {
4138 self.git_store.read(cx).blame_buffer(buffer, version, cx)
4139 }
4140
4141 pub fn get_permalink_to_line(
4142 &self,
4143 buffer: &Entity<Buffer>,
4144 selection: Range<u32>,
4145 cx: &App,
4146 ) -> Task<Result<url::Url>> {
4147 self.git_store
4148 .read(cx)
4149 .get_permalink_to_line(buffer, selection, cx)
4150 }
4151
4152 // RPC message handlers
4153
4154 async fn handle_unshare_project(
4155 this: Entity<Self>,
4156 _: TypedEnvelope<proto::UnshareProject>,
4157 mut cx: AsyncApp,
4158 ) -> Result<()> {
4159 this.update(&mut cx, |this, cx| {
4160 if this.is_local() || this.is_via_ssh() {
4161 this.unshare(cx)?;
4162 } else {
4163 this.disconnected_from_host(cx);
4164 }
4165 Ok(())
4166 })?
4167 }
4168
4169 async fn handle_add_collaborator(
4170 this: Entity<Self>,
4171 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
4172 mut cx: AsyncApp,
4173 ) -> Result<()> {
4174 let collaborator = envelope
4175 .payload
4176 .collaborator
4177 .take()
4178 .ok_or_else(|| anyhow!("empty collaborator"))?;
4179
4180 let collaborator = Collaborator::from_proto(collaborator)?;
4181 this.update(&mut cx, |this, cx| {
4182 this.buffer_store.update(cx, |buffer_store, _| {
4183 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
4184 });
4185 this.breakpoint_store.read(cx).broadcast();
4186 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
4187 this.collaborators
4188 .insert(collaborator.peer_id, collaborator);
4189 })?;
4190
4191 Ok(())
4192 }
4193
4194 async fn handle_update_project_collaborator(
4195 this: Entity<Self>,
4196 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
4197 mut cx: AsyncApp,
4198 ) -> Result<()> {
4199 let old_peer_id = envelope
4200 .payload
4201 .old_peer_id
4202 .ok_or_else(|| anyhow!("missing old peer id"))?;
4203 let new_peer_id = envelope
4204 .payload
4205 .new_peer_id
4206 .ok_or_else(|| anyhow!("missing new peer id"))?;
4207 this.update(&mut cx, |this, cx| {
4208 let collaborator = this
4209 .collaborators
4210 .remove(&old_peer_id)
4211 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
4212 let is_host = collaborator.is_host;
4213 this.collaborators.insert(new_peer_id, collaborator);
4214
4215 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
4216 this.buffer_store.update(cx, |buffer_store, _| {
4217 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
4218 });
4219
4220 if is_host {
4221 this.buffer_store
4222 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
4223 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
4224 .unwrap();
4225 cx.emit(Event::HostReshared);
4226 }
4227
4228 cx.emit(Event::CollaboratorUpdated {
4229 old_peer_id,
4230 new_peer_id,
4231 });
4232 Ok(())
4233 })?
4234 }
4235
4236 async fn handle_remove_collaborator(
4237 this: Entity<Self>,
4238 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
4239 mut cx: AsyncApp,
4240 ) -> Result<()> {
4241 this.update(&mut cx, |this, cx| {
4242 let peer_id = envelope
4243 .payload
4244 .peer_id
4245 .ok_or_else(|| anyhow!("invalid peer id"))?;
4246 let replica_id = this
4247 .collaborators
4248 .remove(&peer_id)
4249 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
4250 .replica_id;
4251 this.buffer_store.update(cx, |buffer_store, cx| {
4252 buffer_store.forget_shared_buffers_for(&peer_id);
4253 for buffer in buffer_store.buffers() {
4254 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
4255 }
4256 });
4257 this.git_store.update(cx, |git_store, _| {
4258 git_store.forget_shared_diffs_for(&peer_id);
4259 });
4260
4261 cx.emit(Event::CollaboratorLeft(peer_id));
4262 Ok(())
4263 })?
4264 }
4265
4266 async fn handle_update_project(
4267 this: Entity<Self>,
4268 envelope: TypedEnvelope<proto::UpdateProject>,
4269 mut cx: AsyncApp,
4270 ) -> Result<()> {
4271 this.update(&mut cx, |this, cx| {
4272 // Don't handle messages that were sent before the response to us joining the project
4273 if envelope.message_id > this.join_project_response_message_id {
4274 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
4275 }
4276 Ok(())
4277 })?
4278 }
4279
4280 async fn handle_toast(
4281 this: Entity<Self>,
4282 envelope: TypedEnvelope<proto::Toast>,
4283 mut cx: AsyncApp,
4284 ) -> Result<()> {
4285 this.update(&mut cx, |_, cx| {
4286 cx.emit(Event::Toast {
4287 notification_id: envelope.payload.notification_id.into(),
4288 message: envelope.payload.message,
4289 });
4290 Ok(())
4291 })?
4292 }
4293
4294 async fn handle_language_server_prompt_request(
4295 this: Entity<Self>,
4296 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
4297 mut cx: AsyncApp,
4298 ) -> Result<proto::LanguageServerPromptResponse> {
4299 let (tx, mut rx) = smol::channel::bounded(1);
4300 let actions: Vec<_> = envelope
4301 .payload
4302 .actions
4303 .into_iter()
4304 .map(|action| MessageActionItem {
4305 title: action,
4306 properties: Default::default(),
4307 })
4308 .collect();
4309 this.update(&mut cx, |_, cx| {
4310 cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest {
4311 level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
4312 message: envelope.payload.message,
4313 actions: actions.clone(),
4314 lsp_name: envelope.payload.lsp_name,
4315 response_channel: tx,
4316 }));
4317
4318 anyhow::Ok(())
4319 })??;
4320
4321 // We drop `this` to avoid holding a reference in this future for too
4322 // long.
4323 // If we keep the reference, we might not drop the `Project` early
4324 // enough when closing a window and it will only get releases on the
4325 // next `flush_effects()` call.
4326 drop(this);
4327
4328 let mut rx = pin!(rx);
4329 let answer = rx.next().await;
4330
4331 Ok(LanguageServerPromptResponse {
4332 action_response: answer.and_then(|answer| {
4333 actions
4334 .iter()
4335 .position(|action| *action == answer)
4336 .map(|index| index as u64)
4337 }),
4338 })
4339 }
4340
4341 async fn handle_hide_toast(
4342 this: Entity<Self>,
4343 envelope: TypedEnvelope<proto::HideToast>,
4344 mut cx: AsyncApp,
4345 ) -> Result<()> {
4346 this.update(&mut cx, |_, cx| {
4347 cx.emit(Event::HideToast {
4348 notification_id: envelope.payload.notification_id.into(),
4349 });
4350 Ok(())
4351 })?
4352 }
4353
4354 // Collab sends UpdateWorktree protos as messages
4355 async fn handle_update_worktree(
4356 this: Entity<Self>,
4357 envelope: TypedEnvelope<proto::UpdateWorktree>,
4358 mut cx: AsyncApp,
4359 ) -> Result<()> {
4360 this.update(&mut cx, |this, cx| {
4361 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4362 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4363 worktree.update(cx, |worktree, _| {
4364 let worktree = worktree.as_remote_mut().unwrap();
4365 worktree.update_from_remote(envelope.payload);
4366 });
4367 }
4368 Ok(())
4369 })?
4370 }
4371
4372 async fn handle_update_buffer_from_ssh(
4373 this: Entity<Self>,
4374 envelope: TypedEnvelope<proto::UpdateBuffer>,
4375 cx: AsyncApp,
4376 ) -> Result<proto::Ack> {
4377 let buffer_store = this.read_with(&cx, |this, cx| {
4378 if let Some(remote_id) = this.remote_id() {
4379 let mut payload = envelope.payload.clone();
4380 payload.project_id = remote_id;
4381 cx.background_spawn(this.client.request(payload))
4382 .detach_and_log_err(cx);
4383 }
4384 this.buffer_store.clone()
4385 })?;
4386 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4387 }
4388
4389 async fn handle_update_buffer(
4390 this: Entity<Self>,
4391 envelope: TypedEnvelope<proto::UpdateBuffer>,
4392 cx: AsyncApp,
4393 ) -> Result<proto::Ack> {
4394 let buffer_store = this.read_with(&cx, |this, cx| {
4395 if let Some(ssh) = &this.ssh_client {
4396 let mut payload = envelope.payload.clone();
4397 payload.project_id = SSH_PROJECT_ID;
4398 cx.background_spawn(ssh.read(cx).proto_client().request(payload))
4399 .detach_and_log_err(cx);
4400 }
4401 this.buffer_store.clone()
4402 })?;
4403 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4404 }
4405
4406 fn retain_remotely_created_models(
4407 &mut self,
4408 cx: &mut Context<Self>,
4409 ) -> RemotelyCreatedModelGuard {
4410 {
4411 let mut remotely_create_models = self.remotely_created_models.lock();
4412 if remotely_create_models.retain_count == 0 {
4413 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
4414 remotely_create_models.worktrees =
4415 self.worktree_store.read(cx).worktrees().collect();
4416 }
4417 remotely_create_models.retain_count += 1;
4418 }
4419 RemotelyCreatedModelGuard {
4420 remote_models: Arc::downgrade(&self.remotely_created_models),
4421 }
4422 }
4423
4424 async fn handle_create_buffer_for_peer(
4425 this: Entity<Self>,
4426 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
4427 mut cx: AsyncApp,
4428 ) -> Result<()> {
4429 this.update(&mut cx, |this, cx| {
4430 this.buffer_store.update(cx, |buffer_store, cx| {
4431 buffer_store.handle_create_buffer_for_peer(
4432 envelope,
4433 this.replica_id(),
4434 this.capability(),
4435 cx,
4436 )
4437 })
4438 })?
4439 }
4440
4441 async fn handle_synchronize_buffers(
4442 this: Entity<Self>,
4443 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
4444 mut cx: AsyncApp,
4445 ) -> Result<proto::SynchronizeBuffersResponse> {
4446 let response = this.update(&mut cx, |this, cx| {
4447 let client = this.client.clone();
4448 this.buffer_store.update(cx, |this, cx| {
4449 this.handle_synchronize_buffers(envelope, cx, client)
4450 })
4451 })??;
4452
4453 Ok(response)
4454 }
4455
4456 async fn handle_search_candidate_buffers(
4457 this: Entity<Self>,
4458 envelope: TypedEnvelope<proto::FindSearchCandidates>,
4459 mut cx: AsyncApp,
4460 ) -> Result<proto::FindSearchCandidatesResponse> {
4461 let peer_id = envelope.original_sender_id()?;
4462 let message = envelope.payload;
4463 let query = SearchQuery::from_proto(
4464 message
4465 .query
4466 .ok_or_else(|| anyhow!("missing query field"))?,
4467 )?;
4468 let results = this.update(&mut cx, |this, cx| {
4469 this.find_search_candidate_buffers(&query, message.limit as _, cx)
4470 })?;
4471
4472 let mut response = proto::FindSearchCandidatesResponse {
4473 buffer_ids: Vec::new(),
4474 };
4475
4476 while let Ok(buffer) = results.recv().await {
4477 this.update(&mut cx, |this, cx| {
4478 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
4479 response.buffer_ids.push(buffer_id.to_proto());
4480 })?;
4481 }
4482
4483 Ok(response)
4484 }
4485
4486 async fn handle_open_buffer_by_id(
4487 this: Entity<Self>,
4488 envelope: TypedEnvelope<proto::OpenBufferById>,
4489 mut cx: AsyncApp,
4490 ) -> Result<proto::OpenBufferResponse> {
4491 let peer_id = envelope.original_sender_id()?;
4492 let buffer_id = BufferId::new(envelope.payload.id)?;
4493 let buffer = this
4494 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
4495 .await?;
4496 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4497 }
4498
4499 async fn handle_open_buffer_by_path(
4500 this: Entity<Self>,
4501 envelope: TypedEnvelope<proto::OpenBufferByPath>,
4502 mut cx: AsyncApp,
4503 ) -> Result<proto::OpenBufferResponse> {
4504 let peer_id = envelope.original_sender_id()?;
4505 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4506 let open_buffer = this.update(&mut cx, |this, cx| {
4507 this.open_buffer(
4508 ProjectPath {
4509 worktree_id,
4510 path: Arc::<Path>::from_proto(envelope.payload.path),
4511 },
4512 cx,
4513 )
4514 })?;
4515
4516 let buffer = open_buffer.await?;
4517 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4518 }
4519
4520 async fn handle_open_new_buffer(
4521 this: Entity<Self>,
4522 envelope: TypedEnvelope<proto::OpenNewBuffer>,
4523 mut cx: AsyncApp,
4524 ) -> Result<proto::OpenBufferResponse> {
4525 let buffer = this
4526 .update(&mut cx, |this, cx| this.create_buffer(cx))?
4527 .await?;
4528 let peer_id = envelope.original_sender_id()?;
4529
4530 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4531 }
4532
4533 fn respond_to_open_buffer_request(
4534 this: Entity<Self>,
4535 buffer: Entity<Buffer>,
4536 peer_id: proto::PeerId,
4537 cx: &mut AsyncApp,
4538 ) -> Result<proto::OpenBufferResponse> {
4539 this.update(cx, |this, cx| {
4540 let is_private = buffer
4541 .read(cx)
4542 .file()
4543 .map(|f| f.is_private())
4544 .unwrap_or_default();
4545 if is_private {
4546 Err(anyhow!(ErrorCode::UnsharedItem))
4547 } else {
4548 Ok(proto::OpenBufferResponse {
4549 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
4550 })
4551 }
4552 })?
4553 }
4554
4555 fn create_buffer_for_peer(
4556 &mut self,
4557 buffer: &Entity<Buffer>,
4558 peer_id: proto::PeerId,
4559 cx: &mut App,
4560 ) -> BufferId {
4561 self.buffer_store
4562 .update(cx, |buffer_store, cx| {
4563 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
4564 })
4565 .detach_and_log_err(cx);
4566 buffer.read(cx).remote_id()
4567 }
4568
4569 fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
4570 let project_id = match self.client_state {
4571 ProjectClientState::Remote {
4572 sharing_has_stopped,
4573 remote_id,
4574 ..
4575 } => {
4576 if sharing_has_stopped {
4577 return Task::ready(Err(anyhow!(
4578 "can't synchronize remote buffers on a readonly project"
4579 )));
4580 } else {
4581 remote_id
4582 }
4583 }
4584 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
4585 return Task::ready(Err(anyhow!(
4586 "can't synchronize remote buffers on a local project"
4587 )));
4588 }
4589 };
4590
4591 let client = self.client.clone();
4592 cx.spawn(async move |this, cx| {
4593 let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
4594 this.buffer_store.read(cx).buffer_version_info(cx)
4595 })?;
4596 let response = client
4597 .request(proto::SynchronizeBuffers {
4598 project_id,
4599 buffers,
4600 })
4601 .await?;
4602
4603 let send_updates_for_buffers = this.update(cx, |this, cx| {
4604 response
4605 .buffers
4606 .into_iter()
4607 .map(|buffer| {
4608 let client = client.clone();
4609 let buffer_id = match BufferId::new(buffer.id) {
4610 Ok(id) => id,
4611 Err(e) => {
4612 return Task::ready(Err(e));
4613 }
4614 };
4615 let remote_version = language::proto::deserialize_version(&buffer.version);
4616 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
4617 let operations =
4618 buffer.read(cx).serialize_ops(Some(remote_version), cx);
4619 cx.background_spawn(async move {
4620 let operations = operations.await;
4621 for chunk in split_operations(operations) {
4622 client
4623 .request(proto::UpdateBuffer {
4624 project_id,
4625 buffer_id: buffer_id.into(),
4626 operations: chunk,
4627 })
4628 .await?;
4629 }
4630 anyhow::Ok(())
4631 })
4632 } else {
4633 Task::ready(Ok(()))
4634 }
4635 })
4636 .collect::<Vec<_>>()
4637 })?;
4638
4639 // Any incomplete buffers have open requests waiting. Request that the host sends
4640 // creates these buffers for us again to unblock any waiting futures.
4641 for id in incomplete_buffer_ids {
4642 cx.background_spawn(client.request(proto::OpenBufferById {
4643 project_id,
4644 id: id.into(),
4645 }))
4646 .detach();
4647 }
4648
4649 futures::future::join_all(send_updates_for_buffers)
4650 .await
4651 .into_iter()
4652 .collect()
4653 })
4654 }
4655
4656 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
4657 self.worktree_store.read(cx).worktree_metadata_protos(cx)
4658 }
4659
4660 /// Iterator of all open buffers that have unsaved changes
4661 pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
4662 self.buffer_store.read(cx).buffers().filter_map(|buf| {
4663 let buf = buf.read(cx);
4664 if buf.is_dirty() {
4665 buf.project_path(cx)
4666 } else {
4667 None
4668 }
4669 })
4670 }
4671
4672 fn set_worktrees_from_proto(
4673 &mut self,
4674 worktrees: Vec<proto::WorktreeMetadata>,
4675 cx: &mut Context<Project>,
4676 ) -> Result<()> {
4677 self.worktree_store.update(cx, |worktree_store, cx| {
4678 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
4679 })
4680 }
4681
4682 fn set_collaborators_from_proto(
4683 &mut self,
4684 messages: Vec<proto::Collaborator>,
4685 cx: &mut Context<Self>,
4686 ) -> Result<()> {
4687 let mut collaborators = HashMap::default();
4688 for message in messages {
4689 let collaborator = Collaborator::from_proto(message)?;
4690 collaborators.insert(collaborator.peer_id, collaborator);
4691 }
4692 for old_peer_id in self.collaborators.keys() {
4693 if !collaborators.contains_key(old_peer_id) {
4694 cx.emit(Event::CollaboratorLeft(*old_peer_id));
4695 }
4696 }
4697 self.collaborators = collaborators;
4698 Ok(())
4699 }
4700
4701 pub fn supplementary_language_servers<'a>(
4702 &'a self,
4703 cx: &'a App,
4704 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
4705 self.lsp_store.read(cx).supplementary_language_servers()
4706 }
4707
4708 pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
4709 self.lsp_store.update(cx, |this, cx| {
4710 this.language_servers_for_local_buffer(buffer, cx)
4711 .any(
4712 |(_, server)| match server.capabilities().inlay_hint_provider {
4713 Some(lsp::OneOf::Left(enabled)) => enabled,
4714 Some(lsp::OneOf::Right(_)) => true,
4715 None => false,
4716 },
4717 )
4718 })
4719 }
4720
4721 pub fn language_server_id_for_name(
4722 &self,
4723 buffer: &Buffer,
4724 name: &str,
4725 cx: &mut App,
4726 ) -> Task<Option<LanguageServerId>> {
4727 if self.is_local() {
4728 Task::ready(self.lsp_store.update(cx, |lsp_store, cx| {
4729 lsp_store
4730 .language_servers_for_local_buffer(buffer, cx)
4731 .find_map(|(adapter, server)| {
4732 if adapter.name.0 == name {
4733 Some(server.server_id())
4734 } else {
4735 None
4736 }
4737 })
4738 }))
4739 } else if let Some(project_id) = self.remote_id() {
4740 let request = self.client.request(proto::LanguageServerIdForName {
4741 project_id,
4742 buffer_id: buffer.remote_id().to_proto(),
4743 name: name.to_string(),
4744 });
4745 cx.background_spawn(async move {
4746 let response = request.await.log_err()?;
4747 response.server_id.map(LanguageServerId::from_proto)
4748 })
4749 } else {
4750 Task::ready(None)
4751 }
4752 }
4753
4754 pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
4755 self.lsp_store.update(cx, |this, cx| {
4756 this.language_servers_for_local_buffer(buffer, cx)
4757 .next()
4758 .is_some()
4759 })
4760 }
4761
4762 pub fn git_init(
4763 &self,
4764 path: Arc<Path>,
4765 fallback_branch_name: String,
4766 cx: &App,
4767 ) -> Task<Result<()>> {
4768 self.git_store
4769 .read(cx)
4770 .git_init(path, fallback_branch_name, cx)
4771 }
4772
4773 pub fn buffer_store(&self) -> &Entity<BufferStore> {
4774 &self.buffer_store
4775 }
4776
4777 pub fn git_store(&self) -> &Entity<GitStore> {
4778 &self.git_store
4779 }
4780
4781 pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
4782 self.git_store.read(cx).active_repository()
4783 }
4784
4785 pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<RepositoryId, Entity<Repository>> {
4786 self.git_store.read(cx).repositories()
4787 }
4788
4789 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
4790 self.git_store.read(cx).status_for_buffer_id(buffer_id, cx)
4791 }
4792}
4793
4794pub struct PathMatchCandidateSet {
4795 pub snapshot: Snapshot,
4796 pub include_ignored: bool,
4797 pub include_root_name: bool,
4798 pub candidates: Candidates,
4799}
4800
4801pub enum Candidates {
4802 /// Only consider directories.
4803 Directories,
4804 /// Only consider files.
4805 Files,
4806 /// Consider directories and files.
4807 Entries,
4808}
4809
4810impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4811 type Candidates = PathMatchCandidateSetIter<'a>;
4812
4813 fn id(&self) -> usize {
4814 self.snapshot.id().to_usize()
4815 }
4816
4817 fn len(&self) -> usize {
4818 match self.candidates {
4819 Candidates::Files => {
4820 if self.include_ignored {
4821 self.snapshot.file_count()
4822 } else {
4823 self.snapshot.visible_file_count()
4824 }
4825 }
4826
4827 Candidates::Directories => {
4828 if self.include_ignored {
4829 self.snapshot.dir_count()
4830 } else {
4831 self.snapshot.visible_dir_count()
4832 }
4833 }
4834
4835 Candidates::Entries => {
4836 if self.include_ignored {
4837 self.snapshot.entry_count()
4838 } else {
4839 self.snapshot.visible_entry_count()
4840 }
4841 }
4842 }
4843 }
4844
4845 fn prefix(&self) -> Arc<str> {
4846 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4847 self.snapshot.root_name().into()
4848 } else if self.include_root_name {
4849 format!("{}{}", self.snapshot.root_name(), std::path::MAIN_SEPARATOR).into()
4850 } else {
4851 Arc::default()
4852 }
4853 }
4854
4855 fn candidates(&'a self, start: usize) -> Self::Candidates {
4856 PathMatchCandidateSetIter {
4857 traversal: match self.candidates {
4858 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4859 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4860 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4861 },
4862 }
4863 }
4864}
4865
4866pub struct PathMatchCandidateSetIter<'a> {
4867 traversal: Traversal<'a>,
4868}
4869
4870impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4871 type Item = fuzzy::PathMatchCandidate<'a>;
4872
4873 fn next(&mut self) -> Option<Self::Item> {
4874 self.traversal
4875 .next()
4876 .map(|entry| fuzzy::PathMatchCandidate {
4877 is_dir: entry.kind.is_dir(),
4878 path: &entry.path,
4879 char_bag: entry.char_bag,
4880 })
4881 }
4882}
4883
4884impl EventEmitter<Event> for Project {}
4885
4886impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4887 fn from(val: &'a ProjectPath) -> Self {
4888 SettingsLocation {
4889 worktree_id: val.worktree_id,
4890 path: val.path.as_ref(),
4891 }
4892 }
4893}
4894
4895impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4896 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4897 Self {
4898 worktree_id,
4899 path: path.as_ref().into(),
4900 }
4901 }
4902}
4903
4904pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4905 let mut path_components = path.components();
4906 let mut base_components = base.components();
4907 let mut components: Vec<Component> = Vec::new();
4908 loop {
4909 match (path_components.next(), base_components.next()) {
4910 (None, None) => break,
4911 (Some(a), None) => {
4912 components.push(a);
4913 components.extend(path_components.by_ref());
4914 break;
4915 }
4916 (None, _) => components.push(Component::ParentDir),
4917 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4918 (Some(a), Some(Component::CurDir)) => components.push(a),
4919 (Some(a), Some(_)) => {
4920 components.push(Component::ParentDir);
4921 for _ in base_components {
4922 components.push(Component::ParentDir);
4923 }
4924 components.push(a);
4925 components.extend(path_components.by_ref());
4926 break;
4927 }
4928 }
4929 }
4930 components.iter().map(|c| c.as_os_str()).collect()
4931}
4932
4933fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4934 let mut result = base.to_path_buf();
4935 for component in path.components() {
4936 match component {
4937 Component::ParentDir => {
4938 result.pop();
4939 }
4940 Component::CurDir => (),
4941 _ => result.push(component),
4942 }
4943 }
4944 result
4945}
4946
4947/// ResolvedPath is a path that has been resolved to either a ProjectPath
4948/// or an AbsPath and that *exists*.
4949#[derive(Debug, Clone)]
4950pub enum ResolvedPath {
4951 ProjectPath {
4952 project_path: ProjectPath,
4953 is_dir: bool,
4954 },
4955 AbsPath {
4956 path: PathBuf,
4957 is_dir: bool,
4958 },
4959}
4960
4961impl ResolvedPath {
4962 pub fn abs_path(&self) -> Option<&Path> {
4963 match self {
4964 Self::AbsPath { path, .. } => Some(path.as_path()),
4965 _ => None,
4966 }
4967 }
4968
4969 pub fn project_path(&self) -> Option<&ProjectPath> {
4970 match self {
4971 Self::ProjectPath { project_path, .. } => Some(&project_path),
4972 _ => None,
4973 }
4974 }
4975
4976 pub fn is_file(&self) -> bool {
4977 !self.is_dir()
4978 }
4979
4980 pub fn is_dir(&self) -> bool {
4981 match self {
4982 Self::ProjectPath { is_dir, .. } => *is_dir,
4983 Self::AbsPath { is_dir, .. } => *is_dir,
4984 }
4985 }
4986}
4987
4988impl ProjectItem for Buffer {
4989 fn try_open(
4990 project: &Entity<Project>,
4991 path: &ProjectPath,
4992 cx: &mut App,
4993 ) -> Option<Task<Result<Entity<Self>>>> {
4994 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4995 }
4996
4997 fn entry_id(&self, cx: &App) -> Option<ProjectEntryId> {
4998 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4999 }
5000
5001 fn project_path(&self, cx: &App) -> Option<ProjectPath> {
5002 self.file().map(|file| ProjectPath {
5003 worktree_id: file.worktree_id(cx),
5004 path: file.path().clone(),
5005 })
5006 }
5007
5008 fn is_dirty(&self) -> bool {
5009 self.is_dirty()
5010 }
5011}
5012
5013impl Completion {
5014 /// A key that can be used to sort completions when displaying
5015 /// them to the user.
5016 pub fn sort_key(&self) -> (usize, &str) {
5017 const DEFAULT_KIND_KEY: usize = 2;
5018 let kind_key = self
5019 .source
5020 // `lsp::CompletionListItemDefaults` has no `kind` field
5021 .lsp_completion(false)
5022 .and_then(|lsp_completion| lsp_completion.kind)
5023 .and_then(|lsp_completion_kind| match lsp_completion_kind {
5024 lsp::CompletionItemKind::KEYWORD => Some(0),
5025 lsp::CompletionItemKind::VARIABLE => Some(1),
5026 _ => None,
5027 })
5028 .unwrap_or(DEFAULT_KIND_KEY);
5029 (kind_key, &self.label.text[self.label.filter_range.clone()])
5030 }
5031
5032 /// Whether this completion is a snippet.
5033 pub fn is_snippet(&self) -> bool {
5034 self.source
5035 // `lsp::CompletionListItemDefaults` has `insert_text_format` field
5036 .lsp_completion(true)
5037 .map_or(false, |lsp_completion| {
5038 lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
5039 })
5040 }
5041
5042 /// Returns the corresponding color for this completion.
5043 ///
5044 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
5045 pub fn color(&self) -> Option<Hsla> {
5046 // `lsp::CompletionListItemDefaults` has no `kind` field
5047 let lsp_completion = self.source.lsp_completion(false)?;
5048 if lsp_completion.kind? == CompletionItemKind::COLOR {
5049 return color_extractor::extract_color(&lsp_completion);
5050 }
5051 None
5052 }
5053}
5054
5055pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
5056 entries.sort_by(|entry_a, entry_b| {
5057 let entry_a = entry_a.as_ref();
5058 let entry_b = entry_b.as_ref();
5059 compare_paths(
5060 (&entry_a.path, entry_a.is_file()),
5061 (&entry_b.path, entry_b.is_file()),
5062 )
5063 });
5064}
5065
5066fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
5067 match level {
5068 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
5069 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
5070 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
5071 }
5072}