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