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