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