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