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