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