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