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