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