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