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