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(operation) => {
2186 let operation = language::proto::serialize_operation(operation);
2187
2188 if let Some(ssh) = &self.ssh_session {
2189 ssh.send(proto::UpdateBuffer {
2190 project_id: 0,
2191 buffer_id: buffer_id.to_proto(),
2192 operations: vec![operation.clone()],
2193 })
2194 .ok();
2195 }
2196
2197 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
2198 buffer_id,
2199 operation,
2200 })
2201 .ok();
2202 }
2203
2204 BufferEvent::Reloaded => {
2205 if self.is_local_or_ssh() {
2206 if let Some(project_id) = self.remote_id() {
2207 let buffer = buffer.read(cx);
2208 self.client
2209 .send(proto::BufferReloaded {
2210 project_id,
2211 buffer_id: buffer.remote_id().to_proto(),
2212 version: serialize_version(&buffer.version()),
2213 mtime: buffer.saved_mtime().map(|t| t.into()),
2214 line_ending: serialize_line_ending(buffer.line_ending()) as i32,
2215 })
2216 .log_err();
2217 }
2218 }
2219 }
2220
2221 _ => {}
2222 }
2223
2224 None
2225 }
2226
2227 fn request_buffer_diff_recalculation(
2228 &mut self,
2229 buffer: &Model<Buffer>,
2230 cx: &mut ModelContext<Self>,
2231 ) {
2232 self.buffers_needing_diff.insert(buffer.downgrade());
2233 let first_insertion = self.buffers_needing_diff.len() == 1;
2234
2235 let settings = ProjectSettings::get_global(cx);
2236 let delay = if let Some(delay) = settings.git.gutter_debounce {
2237 delay
2238 } else {
2239 if first_insertion {
2240 let this = cx.weak_model();
2241 cx.defer(move |cx| {
2242 if let Some(this) = this.upgrade() {
2243 this.update(cx, |this, cx| {
2244 this.recalculate_buffer_diffs(cx).detach();
2245 });
2246 }
2247 });
2248 }
2249 return;
2250 };
2251
2252 const MIN_DELAY: u64 = 50;
2253 let delay = delay.max(MIN_DELAY);
2254 let duration = Duration::from_millis(delay);
2255
2256 self.git_diff_debouncer
2257 .fire_new(duration, cx, move |this, cx| {
2258 this.recalculate_buffer_diffs(cx)
2259 });
2260 }
2261
2262 fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
2263 let buffers = self.buffers_needing_diff.drain().collect::<Vec<_>>();
2264 cx.spawn(move |this, mut cx| async move {
2265 let tasks: Vec<_> = buffers
2266 .iter()
2267 .filter_map(|buffer| {
2268 let buffer = buffer.upgrade()?;
2269 buffer
2270 .update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx))
2271 .ok()
2272 .flatten()
2273 })
2274 .collect();
2275
2276 futures::future::join_all(tasks).await;
2277
2278 this.update(&mut cx, |this, cx| {
2279 if this.buffers_needing_diff.is_empty() {
2280 // TODO: Would a `ModelContext<Project>.notify()` suffice here?
2281 for buffer in buffers {
2282 if let Some(buffer) = buffer.upgrade() {
2283 buffer.update(cx, |_, cx| cx.notify());
2284 }
2285 }
2286 } else {
2287 this.recalculate_buffer_diffs(cx).detach();
2288 }
2289 })
2290 .ok();
2291 })
2292 }
2293
2294 pub fn set_language_for_buffer(
2295 &mut self,
2296 buffer: &Model<Buffer>,
2297 new_language: Arc<Language>,
2298 cx: &mut ModelContext<Self>,
2299 ) {
2300 self.lsp_store.update(cx, |lsp_store, cx| {
2301 lsp_store.set_language_for_buffer(buffer, new_language, cx)
2302 })
2303 }
2304
2305 pub fn restart_language_servers_for_buffers(
2306 &mut self,
2307 buffers: impl IntoIterator<Item = Model<Buffer>>,
2308 cx: &mut ModelContext<Self>,
2309 ) {
2310 self.lsp_store.update(cx, |lsp_store, cx| {
2311 lsp_store.restart_language_servers_for_buffers(buffers, cx)
2312 })
2313 }
2314
2315 pub fn cancel_language_server_work_for_buffers(
2316 &mut self,
2317 buffers: impl IntoIterator<Item = Model<Buffer>>,
2318 cx: &mut ModelContext<Self>,
2319 ) {
2320 self.lsp_store.update(cx, |lsp_store, cx| {
2321 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
2322 })
2323 }
2324
2325 pub fn cancel_language_server_work(
2326 &mut self,
2327 server_id: LanguageServerId,
2328 token_to_cancel: Option<String>,
2329 cx: &mut ModelContext<Self>,
2330 ) {
2331 self.lsp_store.update(cx, |lsp_store, cx| {
2332 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
2333 })
2334 }
2335
2336 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
2337 self.buffer_ordered_messages_tx
2338 .unbounded_send(message)
2339 .map_err(|e| anyhow!(e))
2340 }
2341
2342 pub fn language_server_statuses<'a>(
2343 &'a self,
2344 cx: &'a AppContext,
2345 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
2346 self.lsp_store.read(cx).language_server_statuses()
2347 }
2348
2349 pub fn last_formatting_failure(&self) -> Option<&str> {
2350 self.last_formatting_failure.as_deref()
2351 }
2352
2353 pub fn update_diagnostics(
2354 &mut self,
2355 language_server_id: LanguageServerId,
2356 params: lsp::PublishDiagnosticsParams,
2357 disk_based_sources: &[String],
2358 cx: &mut ModelContext<Self>,
2359 ) -> Result<()> {
2360 self.lsp_store.update(cx, |lsp_store, cx| {
2361 lsp_store.update_diagnostics(language_server_id, params, disk_based_sources, cx)
2362 })
2363 }
2364
2365 pub fn update_diagnostic_entries(
2366 &mut self,
2367 server_id: LanguageServerId,
2368 abs_path: PathBuf,
2369 version: Option<i32>,
2370 diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
2371 cx: &mut ModelContext<Project>,
2372 ) -> Result<(), anyhow::Error> {
2373 self.lsp_store.update(cx, |lsp_store, cx| {
2374 lsp_store.update_diagnostic_entries(server_id, abs_path, version, diagnostics, cx)
2375 })
2376 }
2377
2378 pub fn reload_buffers(
2379 &self,
2380 buffers: HashSet<Model<Buffer>>,
2381 push_to_history: bool,
2382 cx: &mut ModelContext<Self>,
2383 ) -> Task<Result<ProjectTransaction>> {
2384 let mut local_buffers = Vec::new();
2385 let mut remote_buffers = None;
2386 for buffer_handle in buffers {
2387 let buffer = buffer_handle.read(cx);
2388 if buffer.is_dirty() {
2389 if let Some(file) = File::from_dyn(buffer.file()) {
2390 if file.is_local() {
2391 local_buffers.push(buffer_handle);
2392 } else {
2393 remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
2394 }
2395 }
2396 }
2397 }
2398
2399 let remote_buffers = self.remote_id().zip(remote_buffers);
2400 let client = self.client.clone();
2401
2402 cx.spawn(move |this, mut cx| async move {
2403 let mut project_transaction = ProjectTransaction::default();
2404
2405 if let Some((project_id, remote_buffers)) = remote_buffers {
2406 let response = client
2407 .request(proto::ReloadBuffers {
2408 project_id,
2409 buffer_ids: remote_buffers
2410 .iter()
2411 .filter_map(|buffer| {
2412 buffer
2413 .update(&mut cx, |buffer, _| buffer.remote_id().into())
2414 .ok()
2415 })
2416 .collect(),
2417 })
2418 .await?
2419 .transaction
2420 .ok_or_else(|| anyhow!("missing transaction"))?;
2421 BufferStore::deserialize_project_transaction(
2422 this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
2423 response,
2424 push_to_history,
2425 cx.clone(),
2426 )
2427 .await?;
2428 }
2429
2430 for buffer in local_buffers {
2431 let transaction = buffer
2432 .update(&mut cx, |buffer, cx| buffer.reload(cx))?
2433 .await?;
2434 buffer.update(&mut cx, |buffer, cx| {
2435 if let Some(transaction) = transaction {
2436 if !push_to_history {
2437 buffer.forget_transaction(transaction.id);
2438 }
2439 project_transaction.0.insert(cx.handle(), transaction);
2440 }
2441 })?;
2442 }
2443
2444 Ok(project_transaction)
2445 })
2446 }
2447
2448 pub fn format(
2449 &mut self,
2450 buffers: HashSet<Model<Buffer>>,
2451 push_to_history: bool,
2452 trigger: FormatTrigger,
2453 cx: &mut ModelContext<Project>,
2454 ) -> Task<anyhow::Result<ProjectTransaction>> {
2455 if self.is_local_or_ssh() {
2456 let buffers_with_paths = buffers
2457 .into_iter()
2458 .map(|buffer_handle| {
2459 let buffer = buffer_handle.read(cx);
2460 let buffer_abs_path = File::from_dyn(buffer.file())
2461 .and_then(|file| file.as_local().map(|f| f.abs_path(cx)));
2462 (buffer_handle, buffer_abs_path)
2463 })
2464 .collect::<Vec<_>>();
2465
2466 cx.spawn(move |project, mut cx| async move {
2467 let result = Self::format_locally(
2468 project.clone(),
2469 buffers_with_paths,
2470 push_to_history,
2471 trigger,
2472 cx.clone(),
2473 )
2474 .await;
2475
2476 project.update(&mut cx, |project, _| match &result {
2477 Ok(_) => project.last_formatting_failure = None,
2478 Err(error) => {
2479 project.last_formatting_failure.replace(error.to_string());
2480 }
2481 })?;
2482
2483 result
2484 })
2485 } else {
2486 let remote_id = self.remote_id();
2487 let client = self.client.clone();
2488 cx.spawn(move |this, mut cx| async move {
2489 if let Some(project_id) = remote_id {
2490 let response = client
2491 .request(proto::FormatBuffers {
2492 project_id,
2493 trigger: trigger as i32,
2494 buffer_ids: buffers
2495 .iter()
2496 .map(|buffer| {
2497 buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
2498 })
2499 .collect::<Result<_>>()?,
2500 })
2501 .await?
2502 .transaction
2503 .ok_or_else(|| anyhow!("missing transaction"))?;
2504 BufferStore::deserialize_project_transaction(
2505 this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
2506 response,
2507 push_to_history,
2508 cx,
2509 )
2510 .await
2511 } else {
2512 Ok(ProjectTransaction::default())
2513 }
2514 })
2515 }
2516 }
2517
2518 async fn format_locally(
2519 project: WeakModel<Project>,
2520 mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
2521 push_to_history: bool,
2522 trigger: FormatTrigger,
2523 mut cx: AsyncAppContext,
2524 ) -> anyhow::Result<ProjectTransaction> {
2525 // Do not allow multiple concurrent formatting requests for the
2526 // same buffer.
2527 let lsp_store = project.update(&mut cx, |this, cx| {
2528 buffers_with_paths.retain(|(buffer, _)| {
2529 this.buffers_being_formatted
2530 .insert(buffer.read(cx).remote_id())
2531 });
2532 this.lsp_store.downgrade()
2533 })?;
2534
2535 let _cleanup = defer({
2536 let this = project.clone();
2537 let mut cx = cx.clone();
2538 let buffers = &buffers_with_paths;
2539 move || {
2540 this.update(&mut cx, |this, cx| {
2541 for (buffer, _) in buffers {
2542 this.buffers_being_formatted
2543 .remove(&buffer.read(cx).remote_id());
2544 }
2545 })
2546 .ok();
2547 }
2548 });
2549
2550 let mut project_transaction = ProjectTransaction::default();
2551 for (buffer, buffer_abs_path) in &buffers_with_paths {
2552 let (primary_adapter_and_server, adapters_and_servers) =
2553 project.update(&mut cx, |project, cx| {
2554 let buffer = buffer.read(cx);
2555
2556 let adapters_and_servers = project
2557 .language_servers_for_buffer(buffer, cx)
2558 .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
2559 .collect::<Vec<_>>();
2560
2561 let primary_adapter = project
2562 .lsp_store
2563 .read(cx)
2564 .primary_language_server_for_buffer(buffer, cx)
2565 .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()));
2566
2567 (primary_adapter, adapters_and_servers)
2568 })?;
2569
2570 let settings = buffer.update(&mut cx, |buffer, cx| {
2571 language_settings(buffer.language(), buffer.file(), cx).clone()
2572 })?;
2573
2574 let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
2575 let ensure_final_newline = settings.ensure_final_newline_on_save;
2576
2577 // First, format buffer's whitespace according to the settings.
2578 let trailing_whitespace_diff = if remove_trailing_whitespace {
2579 Some(
2580 buffer
2581 .update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
2582 .await,
2583 )
2584 } else {
2585 None
2586 };
2587 let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
2588 buffer.finalize_last_transaction();
2589 buffer.start_transaction();
2590 if let Some(diff) = trailing_whitespace_diff {
2591 buffer.apply_diff(diff, cx);
2592 }
2593 if ensure_final_newline {
2594 buffer.ensure_final_newline(cx);
2595 }
2596 buffer.end_transaction(cx)
2597 })?;
2598
2599 // Apply the `code_actions_on_format` before we run the formatter.
2600 let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
2601 #[allow(clippy::nonminimal_bool)]
2602 if !code_actions.is_empty()
2603 && !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
2604 {
2605 LspStore::execute_code_actions_on_servers(
2606 &lsp_store,
2607 &adapters_and_servers,
2608 code_actions,
2609 buffer,
2610 push_to_history,
2611 &mut project_transaction,
2612 &mut cx,
2613 )
2614 .await?;
2615 }
2616
2617 // Apply language-specific formatting using either the primary language server
2618 // or external command.
2619 // Except for code actions, which are applied with all connected language servers.
2620 let primary_language_server =
2621 primary_adapter_and_server.map(|(_adapter, server)| server.clone());
2622 let server_and_buffer = primary_language_server
2623 .as_ref()
2624 .zip(buffer_abs_path.as_ref());
2625
2626 let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
2627 language_settings(buffer.language(), buffer.file(), cx)
2628 .prettier
2629 .clone()
2630 })?;
2631
2632 let mut format_operations: Vec<FormatOperation> = vec![];
2633 {
2634 match trigger {
2635 FormatTrigger::Save => {
2636 match &settings.format_on_save {
2637 FormatOnSave::Off => {
2638 // nothing
2639 }
2640 FormatOnSave::On => {
2641 match &settings.formatter {
2642 SelectedFormatter::Auto => {
2643 // do the auto-format: prefer prettier, fallback to primary language server
2644 let diff = {
2645 if prettier_settings.allowed {
2646 Self::perform_format(
2647 &Formatter::Prettier,
2648 server_and_buffer,
2649 project.clone(),
2650 buffer,
2651 buffer_abs_path,
2652 &settings,
2653 &adapters_and_servers,
2654 push_to_history,
2655 &mut project_transaction,
2656 &mut cx,
2657 )
2658 .await
2659 } else {
2660 Self::perform_format(
2661 &Formatter::LanguageServer { name: None },
2662 server_and_buffer,
2663 project.clone(),
2664 buffer,
2665 buffer_abs_path,
2666 &settings,
2667 &adapters_and_servers,
2668 push_to_history,
2669 &mut project_transaction,
2670 &mut cx,
2671 )
2672 .await
2673 }
2674 }
2675 .log_err()
2676 .flatten();
2677 if let Some(op) = diff {
2678 format_operations.push(op);
2679 }
2680 }
2681 SelectedFormatter::List(formatters) => {
2682 for formatter in formatters.as_ref() {
2683 let diff = Self::perform_format(
2684 formatter,
2685 server_and_buffer,
2686 project.clone(),
2687 buffer,
2688 buffer_abs_path,
2689 &settings,
2690 &adapters_and_servers,
2691 push_to_history,
2692 &mut project_transaction,
2693 &mut cx,
2694 )
2695 .await
2696 .log_err()
2697 .flatten();
2698 if let Some(op) = diff {
2699 format_operations.push(op);
2700 }
2701
2702 // format with formatter
2703 }
2704 }
2705 }
2706 }
2707 FormatOnSave::List(formatters) => {
2708 for formatter in formatters.as_ref() {
2709 let diff = Self::perform_format(
2710 formatter,
2711 server_and_buffer,
2712 project.clone(),
2713 buffer,
2714 buffer_abs_path,
2715 &settings,
2716 &adapters_and_servers,
2717 push_to_history,
2718 &mut project_transaction,
2719 &mut cx,
2720 )
2721 .await
2722 .log_err()
2723 .flatten();
2724 if let Some(op) = diff {
2725 format_operations.push(op);
2726 }
2727 }
2728 }
2729 }
2730 }
2731 FormatTrigger::Manual => {
2732 match &settings.formatter {
2733 SelectedFormatter::Auto => {
2734 // do the auto-format: prefer prettier, fallback to primary language server
2735 let diff = {
2736 if prettier_settings.allowed {
2737 Self::perform_format(
2738 &Formatter::Prettier,
2739 server_and_buffer,
2740 project.clone(),
2741 buffer,
2742 buffer_abs_path,
2743 &settings,
2744 &adapters_and_servers,
2745 push_to_history,
2746 &mut project_transaction,
2747 &mut cx,
2748 )
2749 .await
2750 } else {
2751 Self::perform_format(
2752 &Formatter::LanguageServer { name: None },
2753 server_and_buffer,
2754 project.clone(),
2755 buffer,
2756 buffer_abs_path,
2757 &settings,
2758 &adapters_and_servers,
2759 push_to_history,
2760 &mut project_transaction,
2761 &mut cx,
2762 )
2763 .await
2764 }
2765 }
2766 .log_err()
2767 .flatten();
2768
2769 if let Some(op) = diff {
2770 format_operations.push(op)
2771 }
2772 }
2773 SelectedFormatter::List(formatters) => {
2774 for formatter in formatters.as_ref() {
2775 // format with formatter
2776 let diff = Self::perform_format(
2777 formatter,
2778 server_and_buffer,
2779 project.clone(),
2780 buffer,
2781 buffer_abs_path,
2782 &settings,
2783 &adapters_and_servers,
2784 push_to_history,
2785 &mut project_transaction,
2786 &mut cx,
2787 )
2788 .await
2789 .log_err()
2790 .flatten();
2791 if let Some(op) = diff {
2792 format_operations.push(op);
2793 }
2794 }
2795 }
2796 }
2797 }
2798 }
2799 }
2800
2801 buffer.update(&mut cx, |b, cx| {
2802 // If the buffer had its whitespace formatted and was edited while the language-specific
2803 // formatting was being computed, avoid applying the language-specific formatting, because
2804 // it can't be grouped with the whitespace formatting in the undo history.
2805 if let Some(transaction_id) = whitespace_transaction_id {
2806 if b.peek_undo_stack()
2807 .map_or(true, |e| e.transaction_id() != transaction_id)
2808 {
2809 format_operations.clear();
2810 }
2811 }
2812
2813 // Apply any language-specific formatting, and group the two formatting operations
2814 // in the buffer's undo history.
2815 for operation in format_operations {
2816 match operation {
2817 FormatOperation::Lsp(edits) => {
2818 b.edit(edits, None, cx);
2819 }
2820 FormatOperation::External(diff) => {
2821 b.apply_diff(diff, cx);
2822 }
2823 FormatOperation::Prettier(diff) => {
2824 b.apply_diff(diff, cx);
2825 }
2826 }
2827
2828 if let Some(transaction_id) = whitespace_transaction_id {
2829 b.group_until_transaction(transaction_id);
2830 } else if let Some(transaction) = project_transaction.0.get(buffer) {
2831 b.group_until_transaction(transaction.id)
2832 }
2833 }
2834
2835 if let Some(transaction) = b.finalize_last_transaction().cloned() {
2836 if !push_to_history {
2837 b.forget_transaction(transaction.id);
2838 }
2839 project_transaction.0.insert(buffer.clone(), transaction);
2840 }
2841 })?;
2842 }
2843
2844 Ok(project_transaction)
2845 }
2846
2847 #[allow(clippy::too_many_arguments)]
2848 async fn perform_format(
2849 formatter: &Formatter,
2850 primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
2851 project: WeakModel<Project>,
2852 buffer: &Model<Buffer>,
2853 buffer_abs_path: &Option<PathBuf>,
2854 settings: &LanguageSettings,
2855 adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
2856 push_to_history: bool,
2857 transaction: &mut ProjectTransaction,
2858 cx: &mut AsyncAppContext,
2859 ) -> Result<Option<FormatOperation>, anyhow::Error> {
2860 let result = match formatter {
2861 Formatter::LanguageServer { name } => {
2862 if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
2863 let language_server = if let Some(name) = name {
2864 adapters_and_servers
2865 .iter()
2866 .find_map(|(adapter, server)| {
2867 adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
2868 })
2869 .unwrap_or(language_server)
2870 } else {
2871 language_server
2872 };
2873
2874 let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
2875 Some(FormatOperation::Lsp(
2876 LspStore::format_via_lsp(
2877 &lsp_store,
2878 buffer,
2879 buffer_abs_path,
2880 language_server,
2881 settings,
2882 cx,
2883 )
2884 .await
2885 .context("failed to format via language server")?,
2886 ))
2887 } else {
2888 None
2889 }
2890 }
2891 Formatter::Prettier => {
2892 let prettier = project.update(cx, |project, cx| {
2893 project
2894 .lsp_store
2895 .read(cx)
2896 .prettier_store()
2897 .unwrap()
2898 .downgrade()
2899 })?;
2900 prettier_store::format_with_prettier(&prettier, buffer, cx)
2901 .await
2902 .transpose()
2903 .ok()
2904 .flatten()
2905 }
2906 Formatter::External { command, arguments } => {
2907 let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
2908 Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)
2909 .await
2910 .context(format!(
2911 "failed to format via external command {:?}",
2912 command
2913 ))?
2914 .map(FormatOperation::External)
2915 }
2916 Formatter::CodeActions(code_actions) => {
2917 let code_actions = deserialize_code_actions(code_actions);
2918 let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
2919 if !code_actions.is_empty() {
2920 LspStore::execute_code_actions_on_servers(
2921 &lsp_store,
2922 adapters_and_servers,
2923 code_actions,
2924 buffer,
2925 push_to_history,
2926 transaction,
2927 cx,
2928 )
2929 .await?;
2930 }
2931 None
2932 }
2933 };
2934 anyhow::Ok(result)
2935 }
2936
2937 async fn format_via_external_command(
2938 buffer: &Model<Buffer>,
2939 buffer_abs_path: Option<&Path>,
2940 command: &str,
2941 arguments: &[String],
2942 cx: &mut AsyncAppContext,
2943 ) -> Result<Option<Diff>> {
2944 let working_dir_path = buffer.update(cx, |buffer, cx| {
2945 let file = File::from_dyn(buffer.file())?;
2946 let worktree = file.worktree.read(cx);
2947 let mut worktree_path = worktree.abs_path().to_path_buf();
2948 if worktree.root_entry()?.is_file() {
2949 worktree_path.pop();
2950 }
2951 Some(worktree_path)
2952 })?;
2953
2954 let mut child = smol::process::Command::new(command);
2955 #[cfg(target_os = "windows")]
2956 {
2957 use smol::process::windows::CommandExt;
2958 child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
2959 }
2960
2961 if let Some(working_dir_path) = working_dir_path {
2962 child.current_dir(working_dir_path);
2963 }
2964
2965 let mut child = child
2966 .args(arguments.iter().map(|arg| {
2967 if let Some(buffer_abs_path) = buffer_abs_path {
2968 arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
2969 } else {
2970 arg.replace("{buffer_path}", "Untitled")
2971 }
2972 }))
2973 .stdin(smol::process::Stdio::piped())
2974 .stdout(smol::process::Stdio::piped())
2975 .stderr(smol::process::Stdio::piped())
2976 .spawn()?;
2977
2978 let stdin = child
2979 .stdin
2980 .as_mut()
2981 .ok_or_else(|| anyhow!("failed to acquire stdin"))?;
2982 let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
2983 for chunk in text.chunks() {
2984 stdin.write_all(chunk.as_bytes()).await?;
2985 }
2986 stdin.flush().await?;
2987
2988 let output = child.output().await?;
2989 if !output.status.success() {
2990 return Err(anyhow!(
2991 "command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
2992 output.status.code(),
2993 String::from_utf8_lossy(&output.stdout),
2994 String::from_utf8_lossy(&output.stderr),
2995 ));
2996 }
2997
2998 let stdout = String::from_utf8(output.stdout)?;
2999 Ok(Some(
3000 buffer
3001 .update(cx, |buffer, cx| buffer.diff(stdout, cx))?
3002 .await,
3003 ))
3004 }
3005
3006 #[inline(never)]
3007 fn definition_impl(
3008 &mut self,
3009 buffer: &Model<Buffer>,
3010 position: PointUtf16,
3011 cx: &mut ModelContext<Self>,
3012 ) -> Task<Result<Vec<LocationLink>>> {
3013 self.request_lsp(
3014 buffer.clone(),
3015 LanguageServerToQuery::Primary,
3016 GetDefinition { position },
3017 cx,
3018 )
3019 }
3020 pub fn definition<T: ToPointUtf16>(
3021 &mut self,
3022 buffer: &Model<Buffer>,
3023 position: T,
3024 cx: &mut ModelContext<Self>,
3025 ) -> Task<Result<Vec<LocationLink>>> {
3026 let position = position.to_point_utf16(buffer.read(cx));
3027 self.definition_impl(buffer, position, cx)
3028 }
3029
3030 fn declaration_impl(
3031 &mut self,
3032 buffer: &Model<Buffer>,
3033 position: PointUtf16,
3034 cx: &mut ModelContext<Self>,
3035 ) -> Task<Result<Vec<LocationLink>>> {
3036 self.request_lsp(
3037 buffer.clone(),
3038 LanguageServerToQuery::Primary,
3039 GetDeclaration { position },
3040 cx,
3041 )
3042 }
3043
3044 pub fn declaration<T: ToPointUtf16>(
3045 &mut self,
3046 buffer: &Model<Buffer>,
3047 position: T,
3048 cx: &mut ModelContext<Self>,
3049 ) -> Task<Result<Vec<LocationLink>>> {
3050 let position = position.to_point_utf16(buffer.read(cx));
3051 self.declaration_impl(buffer, position, cx)
3052 }
3053
3054 fn type_definition_impl(
3055 &mut self,
3056 buffer: &Model<Buffer>,
3057 position: PointUtf16,
3058 cx: &mut ModelContext<Self>,
3059 ) -> Task<Result<Vec<LocationLink>>> {
3060 self.request_lsp(
3061 buffer.clone(),
3062 LanguageServerToQuery::Primary,
3063 GetTypeDefinition { position },
3064 cx,
3065 )
3066 }
3067
3068 pub fn type_definition<T: ToPointUtf16>(
3069 &mut self,
3070 buffer: &Model<Buffer>,
3071 position: T,
3072 cx: &mut ModelContext<Self>,
3073 ) -> Task<Result<Vec<LocationLink>>> {
3074 let position = position.to_point_utf16(buffer.read(cx));
3075 self.type_definition_impl(buffer, position, cx)
3076 }
3077
3078 pub fn implementation<T: ToPointUtf16>(
3079 &mut self,
3080 buffer: &Model<Buffer>,
3081 position: T,
3082 cx: &mut ModelContext<Self>,
3083 ) -> Task<Result<Vec<LocationLink>>> {
3084 let position = position.to_point_utf16(buffer.read(cx));
3085 self.request_lsp(
3086 buffer.clone(),
3087 LanguageServerToQuery::Primary,
3088 GetImplementation { position },
3089 cx,
3090 )
3091 }
3092
3093 pub fn references<T: ToPointUtf16>(
3094 &mut self,
3095 buffer: &Model<Buffer>,
3096 position: T,
3097 cx: &mut ModelContext<Self>,
3098 ) -> Task<Result<Vec<Location>>> {
3099 let position = position.to_point_utf16(buffer.read(cx));
3100 self.request_lsp(
3101 buffer.clone(),
3102 LanguageServerToQuery::Primary,
3103 GetReferences { position },
3104 cx,
3105 )
3106 }
3107
3108 fn document_highlights_impl(
3109 &mut self,
3110 buffer: &Model<Buffer>,
3111 position: PointUtf16,
3112 cx: &mut ModelContext<Self>,
3113 ) -> Task<Result<Vec<DocumentHighlight>>> {
3114 self.request_lsp(
3115 buffer.clone(),
3116 LanguageServerToQuery::Primary,
3117 GetDocumentHighlights { position },
3118 cx,
3119 )
3120 }
3121
3122 pub fn document_highlights<T: ToPointUtf16>(
3123 &mut self,
3124 buffer: &Model<Buffer>,
3125 position: T,
3126 cx: &mut ModelContext<Self>,
3127 ) -> Task<Result<Vec<DocumentHighlight>>> {
3128 let position = position.to_point_utf16(buffer.read(cx));
3129 self.document_highlights_impl(buffer, position, cx)
3130 }
3131
3132 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
3133 self.lsp_store
3134 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
3135 }
3136
3137 pub fn open_buffer_for_symbol(
3138 &mut self,
3139 symbol: &Symbol,
3140 cx: &mut ModelContext<Self>,
3141 ) -> Task<Result<Model<Buffer>>> {
3142 self.lsp_store.update(cx, |lsp_store, cx| {
3143 lsp_store.open_buffer_for_symbol(symbol, cx)
3144 })
3145 }
3146
3147 pub fn open_local_buffer_via_lsp(
3148 &mut self,
3149 abs_path: lsp::Url,
3150 language_server_id: LanguageServerId,
3151 language_server_name: LanguageServerName,
3152 cx: &mut ModelContext<Self>,
3153 ) -> Task<Result<Model<Buffer>>> {
3154 self.lsp_store.update(cx, |lsp_store, cx| {
3155 lsp_store.open_local_buffer_via_lsp(
3156 abs_path,
3157 language_server_id,
3158 language_server_name,
3159 cx,
3160 )
3161 })
3162 }
3163
3164 pub fn signature_help<T: ToPointUtf16>(
3165 &self,
3166 buffer: &Model<Buffer>,
3167 position: T,
3168 cx: &mut ModelContext<Self>,
3169 ) -> Task<Vec<SignatureHelp>> {
3170 self.lsp_store.update(cx, |lsp_store, cx| {
3171 lsp_store.signature_help(buffer, position, cx)
3172 })
3173 }
3174
3175 pub fn hover<T: ToPointUtf16>(
3176 &self,
3177 buffer: &Model<Buffer>,
3178 position: T,
3179 cx: &mut ModelContext<Self>,
3180 ) -> Task<Vec<Hover>> {
3181 let position = position.to_point_utf16(buffer.read(cx));
3182 self.lsp_store
3183 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
3184 }
3185
3186 pub fn linked_edit(
3187 &self,
3188 buffer: &Model<Buffer>,
3189 position: Anchor,
3190 cx: &mut ModelContext<Self>,
3191 ) -> Task<Result<Vec<Range<Anchor>>>> {
3192 self.lsp_store.update(cx, |lsp_store, cx| {
3193 lsp_store.linked_edit(buffer, position, cx)
3194 })
3195 }
3196
3197 pub fn completions<T: ToOffset + ToPointUtf16>(
3198 &self,
3199 buffer: &Model<Buffer>,
3200 position: T,
3201 context: CompletionContext,
3202 cx: &mut ModelContext<Self>,
3203 ) -> Task<Result<Vec<Completion>>> {
3204 let position = position.to_point_utf16(buffer.read(cx));
3205 self.lsp_store.update(cx, |lsp_store, cx| {
3206 lsp_store.completions(buffer, position, context, cx)
3207 })
3208 }
3209
3210 pub fn resolve_completions(
3211 &self,
3212 buffer: Model<Buffer>,
3213 completion_indices: Vec<usize>,
3214 completions: Arc<RwLock<Box<[Completion]>>>,
3215 cx: &mut ModelContext<Self>,
3216 ) -> Task<Result<bool>> {
3217 self.lsp_store.update(cx, |lsp_store, cx| {
3218 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
3219 })
3220 }
3221
3222 pub fn apply_additional_edits_for_completion(
3223 &self,
3224 buffer_handle: Model<Buffer>,
3225 completion: Completion,
3226 push_to_history: bool,
3227 cx: &mut ModelContext<Self>,
3228 ) -> Task<Result<Option<Transaction>>> {
3229 self.lsp_store.update(cx, |lsp_store, cx| {
3230 lsp_store.apply_additional_edits_for_completion(
3231 buffer_handle,
3232 completion,
3233 push_to_history,
3234 cx,
3235 )
3236 })
3237 }
3238
3239 pub fn code_actions<T: Clone + ToOffset>(
3240 &mut self,
3241 buffer_handle: &Model<Buffer>,
3242 range: Range<T>,
3243 cx: &mut ModelContext<Self>,
3244 ) -> Task<Vec<CodeAction>> {
3245 let buffer = buffer_handle.read(cx);
3246 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3247 self.lsp_store.update(cx, |lsp_store, cx| {
3248 lsp_store.code_actions(buffer_handle, range, cx)
3249 })
3250 }
3251
3252 pub fn apply_code_action(
3253 &self,
3254 buffer_handle: Model<Buffer>,
3255 action: CodeAction,
3256 push_to_history: bool,
3257 cx: &mut ModelContext<Self>,
3258 ) -> Task<Result<ProjectTransaction>> {
3259 self.lsp_store.update(cx, |lsp_store, cx| {
3260 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
3261 })
3262 }
3263
3264 fn prepare_rename_impl(
3265 &mut self,
3266 buffer: Model<Buffer>,
3267 position: PointUtf16,
3268 cx: &mut ModelContext<Self>,
3269 ) -> Task<Result<Option<Range<Anchor>>>> {
3270 self.request_lsp(
3271 buffer,
3272 LanguageServerToQuery::Primary,
3273 PrepareRename { position },
3274 cx,
3275 )
3276 }
3277 pub fn prepare_rename<T: ToPointUtf16>(
3278 &mut self,
3279 buffer: Model<Buffer>,
3280 position: T,
3281 cx: &mut ModelContext<Self>,
3282 ) -> Task<Result<Option<Range<Anchor>>>> {
3283 let position = position.to_point_utf16(buffer.read(cx));
3284 self.prepare_rename_impl(buffer, position, cx)
3285 }
3286
3287 fn perform_rename_impl(
3288 &mut self,
3289 buffer: Model<Buffer>,
3290 position: PointUtf16,
3291 new_name: String,
3292 push_to_history: bool,
3293 cx: &mut ModelContext<Self>,
3294 ) -> Task<Result<ProjectTransaction>> {
3295 let position = position.to_point_utf16(buffer.read(cx));
3296 self.request_lsp(
3297 buffer,
3298 LanguageServerToQuery::Primary,
3299 PerformRename {
3300 position,
3301 new_name,
3302 push_to_history,
3303 },
3304 cx,
3305 )
3306 }
3307 pub fn perform_rename<T: ToPointUtf16>(
3308 &mut self,
3309 buffer: Model<Buffer>,
3310 position: T,
3311 new_name: String,
3312 push_to_history: bool,
3313 cx: &mut ModelContext<Self>,
3314 ) -> Task<Result<ProjectTransaction>> {
3315 let position = position.to_point_utf16(buffer.read(cx));
3316 self.perform_rename_impl(buffer, position, new_name, push_to_history, cx)
3317 }
3318
3319 pub fn on_type_format<T: ToPointUtf16>(
3320 &mut self,
3321 buffer: Model<Buffer>,
3322 position: T,
3323 trigger: String,
3324 push_to_history: bool,
3325 cx: &mut ModelContext<Self>,
3326 ) -> Task<Result<Option<Transaction>>> {
3327 self.lsp_store.update(cx, |lsp_store, cx| {
3328 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
3329 })
3330 }
3331
3332 pub fn inlay_hints<T: ToOffset>(
3333 &mut self,
3334 buffer_handle: Model<Buffer>,
3335 range: Range<T>,
3336 cx: &mut ModelContext<Self>,
3337 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
3338 let buffer = buffer_handle.read(cx);
3339 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3340 self.lsp_store.update(cx, |lsp_store, cx| {
3341 lsp_store.inlay_hints(buffer_handle, range, cx)
3342 })
3343 }
3344
3345 pub fn resolve_inlay_hint(
3346 &self,
3347 hint: InlayHint,
3348 buffer_handle: Model<Buffer>,
3349 server_id: LanguageServerId,
3350 cx: &mut ModelContext<Self>,
3351 ) -> Task<anyhow::Result<InlayHint>> {
3352 self.lsp_store.update(cx, |lsp_store, cx| {
3353 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
3354 })
3355 }
3356
3357 pub fn search(
3358 &mut self,
3359 query: SearchQuery,
3360 cx: &mut ModelContext<Self>,
3361 ) -> Receiver<SearchResult> {
3362 let (result_tx, result_rx) = smol::channel::unbounded();
3363
3364 let matching_buffers_rx = if query.is_opened_only() {
3365 self.sort_candidate_buffers(&query, cx)
3366 } else {
3367 self.search_for_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
3368 };
3369
3370 cx.spawn(|_, cx| async move {
3371 let mut range_count = 0;
3372 let mut buffer_count = 0;
3373 let mut limit_reached = false;
3374 let query = Arc::new(query);
3375 let mut chunks = matching_buffers_rx.ready_chunks(64);
3376
3377 // Now that we know what paths match the query, we will load at most
3378 // 64 buffers at a time to avoid overwhelming the main thread. For each
3379 // opened buffer, we will spawn a background task that retrieves all the
3380 // ranges in the buffer matched by the query.
3381 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
3382 let mut chunk_results = Vec::new();
3383 for buffer in matching_buffer_chunk {
3384 let buffer = buffer.clone();
3385 let query = query.clone();
3386 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
3387 chunk_results.push(cx.background_executor().spawn(async move {
3388 let ranges = query
3389 .search(&snapshot, None)
3390 .await
3391 .iter()
3392 .map(|range| {
3393 snapshot.anchor_before(range.start)
3394 ..snapshot.anchor_after(range.end)
3395 })
3396 .collect::<Vec<_>>();
3397 anyhow::Ok((buffer, ranges))
3398 }));
3399 }
3400
3401 let chunk_results = futures::future::join_all(chunk_results).await;
3402 for result in chunk_results {
3403 if let Some((buffer, ranges)) = result.log_err() {
3404 range_count += ranges.len();
3405 buffer_count += 1;
3406 result_tx
3407 .send(SearchResult::Buffer { buffer, ranges })
3408 .await?;
3409 if buffer_count > MAX_SEARCH_RESULT_FILES
3410 || range_count > MAX_SEARCH_RESULT_RANGES
3411 {
3412 limit_reached = true;
3413 break 'outer;
3414 }
3415 }
3416 }
3417 }
3418
3419 if limit_reached {
3420 result_tx.send(SearchResult::LimitReached).await?;
3421 }
3422
3423 anyhow::Ok(())
3424 })
3425 .detach();
3426
3427 result_rx
3428 }
3429
3430 fn search_for_candidate_buffers(
3431 &mut self,
3432 query: &SearchQuery,
3433 limit: usize,
3434 cx: &mut ModelContext<Project>,
3435 ) -> Receiver<Model<Buffer>> {
3436 if self.is_local() {
3437 let fs = self.fs.clone();
3438 self.buffer_store.update(cx, |buffer_store, cx| {
3439 buffer_store.find_search_candidates(query, limit, fs, cx)
3440 })
3441 } else {
3442 self.search_for_candidate_buffers_remote(query, limit, cx)
3443 }
3444 }
3445
3446 fn sort_candidate_buffers(
3447 &mut self,
3448 search_query: &SearchQuery,
3449 cx: &mut ModelContext<Project>,
3450 ) -> Receiver<Model<Buffer>> {
3451 let worktree_store = self.worktree_store.read(cx);
3452 let mut buffers = search_query
3453 .buffers()
3454 .into_iter()
3455 .flatten()
3456 .filter(|buffer| {
3457 let b = buffer.read(cx);
3458 if let Some(file) = b.file() {
3459 if !search_query.file_matches(file.path()) {
3460 return false;
3461 }
3462 if let Some(entry) = b
3463 .entry_id(cx)
3464 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
3465 {
3466 if entry.is_ignored && !search_query.include_ignored() {
3467 return false;
3468 }
3469 }
3470 }
3471 true
3472 })
3473 .collect::<Vec<_>>();
3474 let (tx, rx) = smol::channel::unbounded();
3475 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
3476 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
3477 (None, Some(_)) => std::cmp::Ordering::Less,
3478 (Some(_), None) => std::cmp::Ordering::Greater,
3479 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
3480 });
3481 for buffer in buffers {
3482 tx.send_blocking(buffer.clone()).unwrap()
3483 }
3484
3485 rx
3486 }
3487
3488 fn search_for_candidate_buffers_remote(
3489 &mut self,
3490 query: &SearchQuery,
3491 limit: usize,
3492 cx: &mut ModelContext<Project>,
3493 ) -> Receiver<Model<Buffer>> {
3494 let (tx, rx) = smol::channel::unbounded();
3495
3496 let (client, remote_id): (AnyProtoClient, _) =
3497 if let Some(ssh_session) = self.ssh_session.clone() {
3498 (ssh_session.into(), 0)
3499 } else if let Some(remote_id) = self.remote_id() {
3500 (self.client.clone().into(), remote_id)
3501 } else {
3502 return rx;
3503 };
3504
3505 let request = client.request(proto::FindSearchCandidates {
3506 project_id: remote_id,
3507 query: Some(query.to_proto()),
3508 limit: limit as _,
3509 });
3510 let guard = self.retain_remotely_created_models(cx);
3511
3512 cx.spawn(move |this, mut cx| async move {
3513 let response = request.await?;
3514 for buffer_id in response.buffer_ids {
3515 let buffer_id = BufferId::new(buffer_id)?;
3516 let buffer = this
3517 .update(&mut cx, |this, cx| {
3518 this.wait_for_remote_buffer(buffer_id, cx)
3519 })?
3520 .await?;
3521 let _ = tx.send(buffer).await;
3522 }
3523
3524 drop(guard);
3525 anyhow::Ok(())
3526 })
3527 .detach_and_log_err(cx);
3528 rx
3529 }
3530
3531 pub fn request_lsp<R: LspCommand>(
3532 &mut self,
3533 buffer_handle: Model<Buffer>,
3534 server: LanguageServerToQuery,
3535 request: R,
3536 cx: &mut ModelContext<Self>,
3537 ) -> Task<Result<R::Response>>
3538 where
3539 <R::LspRequest as lsp::request::Request>::Result: Send,
3540 <R::LspRequest as lsp::request::Request>::Params: Send,
3541 {
3542 let guard = self.retain_remotely_created_models(cx);
3543 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3544 lsp_store.request_lsp(buffer_handle, server, request, cx)
3545 });
3546 cx.spawn(|_, _| async move {
3547 let result = task.await;
3548 drop(guard);
3549 result
3550 })
3551 }
3552
3553 /// Move a worktree to a new position in the worktree order.
3554 ///
3555 /// The worktree will moved to the opposite side of the destination worktree.
3556 ///
3557 /// # Example
3558 ///
3559 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
3560 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
3561 ///
3562 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
3563 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
3564 ///
3565 /// # Errors
3566 ///
3567 /// An error will be returned if the worktree or destination worktree are not found.
3568 pub fn move_worktree(
3569 &mut self,
3570 source: WorktreeId,
3571 destination: WorktreeId,
3572 cx: &mut ModelContext<'_, Self>,
3573 ) -> Result<()> {
3574 self.worktree_store.update(cx, |worktree_store, cx| {
3575 worktree_store.move_worktree(source, destination, cx)
3576 })
3577 }
3578
3579 pub fn find_or_create_worktree(
3580 &mut self,
3581 abs_path: impl AsRef<Path>,
3582 visible: bool,
3583 cx: &mut ModelContext<Self>,
3584 ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
3585 let abs_path = abs_path.as_ref();
3586 if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) {
3587 Task::ready(Ok((tree, relative_path)))
3588 } else {
3589 let worktree = self.create_worktree(abs_path, visible, cx);
3590 cx.background_executor()
3591 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
3592 }
3593 }
3594
3595 pub fn find_worktree(
3596 &self,
3597 abs_path: &Path,
3598 cx: &AppContext,
3599 ) -> Option<(Model<Worktree>, PathBuf)> {
3600 self.worktree_store.read_with(cx, |worktree_store, cx| {
3601 worktree_store.find_worktree(abs_path, cx)
3602 })
3603 }
3604
3605 pub fn is_shared(&self) -> bool {
3606 match &self.client_state {
3607 ProjectClientState::Shared { .. } => true,
3608 ProjectClientState::Local => false,
3609 ProjectClientState::Remote { in_room, .. } => *in_room,
3610 }
3611 }
3612
3613 // Returns the resolved version of `path`, that was found in `buffer`, if it exists.
3614 pub fn resolve_existing_file_path(
3615 &self,
3616 path: &str,
3617 buffer: &Model<Buffer>,
3618 cx: &mut ModelContext<Self>,
3619 ) -> Task<Option<ResolvedPath>> {
3620 // TODO: ssh based remoting.
3621 if self.ssh_session.is_some() {
3622 return Task::ready(None);
3623 }
3624
3625 if self.is_local_or_ssh() {
3626 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3627
3628 if expanded.is_absolute() {
3629 let fs = self.fs.clone();
3630 cx.background_executor().spawn(async move {
3631 let path = expanded.as_path();
3632 let exists = fs.is_file(path).await;
3633
3634 exists.then(|| ResolvedPath::AbsPath(expanded))
3635 })
3636 } else {
3637 self.resolve_path_in_worktrees(expanded, buffer, cx)
3638 }
3639 } else {
3640 let path = PathBuf::from(path);
3641 if path.is_absolute() || path.starts_with("~") {
3642 return Task::ready(None);
3643 }
3644
3645 self.resolve_path_in_worktrees(path, buffer, cx)
3646 }
3647 }
3648
3649 fn resolve_path_in_worktrees(
3650 &self,
3651 path: PathBuf,
3652 buffer: &Model<Buffer>,
3653 cx: &mut ModelContext<Self>,
3654 ) -> Task<Option<ResolvedPath>> {
3655 let mut candidates = vec![path.clone()];
3656
3657 if let Some(file) = buffer.read(cx).file() {
3658 if let Some(dir) = file.path().parent() {
3659 let joined = dir.to_path_buf().join(path);
3660 candidates.push(joined);
3661 }
3662 }
3663
3664 let worktrees = self.worktrees(cx).collect::<Vec<_>>();
3665 cx.spawn(|_, mut cx| async move {
3666 for worktree in worktrees {
3667 for candidate in candidates.iter() {
3668 let path = worktree
3669 .update(&mut cx, |worktree, _| {
3670 let root_entry_path = &worktree.root_entry()?.path;
3671
3672 let resolved = resolve_path(root_entry_path, candidate);
3673
3674 let stripped =
3675 resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3676
3677 worktree.entry_for_path(stripped).map(|entry| {
3678 ResolvedPath::ProjectPath(ProjectPath {
3679 worktree_id: worktree.id(),
3680 path: entry.path.clone(),
3681 })
3682 })
3683 })
3684 .ok()?;
3685
3686 if path.is_some() {
3687 return path;
3688 }
3689 }
3690 }
3691 None
3692 })
3693 }
3694
3695 pub fn list_directory(
3696 &self,
3697 query: String,
3698 cx: &mut ModelContext<Self>,
3699 ) -> Task<Result<Vec<PathBuf>>> {
3700 if self.is_local_or_ssh() {
3701 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3702 } else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| {
3703 dev_server_projects::Store::global(cx)
3704 .read(cx)
3705 .dev_server_for_project(id)
3706 }) {
3707 let request = proto::ListRemoteDirectory {
3708 dev_server_id: dev_server.id.0,
3709 path: query,
3710 };
3711 let response = self.client.request(request);
3712 cx.background_executor().spawn(async move {
3713 let response = response.await?;
3714 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3715 })
3716 } else {
3717 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3718 }
3719 }
3720
3721 fn create_worktree(
3722 &mut self,
3723 abs_path: impl AsRef<Path>,
3724 visible: bool,
3725 cx: &mut ModelContext<Self>,
3726 ) -> Task<Result<Model<Worktree>>> {
3727 self.worktree_store.update(cx, |worktree_store, cx| {
3728 worktree_store.create_worktree(abs_path, visible, cx)
3729 })
3730 }
3731
3732 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
3733 self.worktree_store.update(cx, |worktree_store, cx| {
3734 worktree_store.remove_worktree(id_to_remove, cx);
3735 });
3736 }
3737
3738 fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
3739 self.worktree_store.update(cx, |worktree_store, cx| {
3740 worktree_store.add(worktree, cx);
3741 });
3742 }
3743
3744 fn update_local_worktree_settings(
3745 &mut self,
3746 worktree: &Model<Worktree>,
3747 changes: &UpdatedEntriesSet,
3748 cx: &mut ModelContext<Self>,
3749 ) {
3750 if worktree.read(cx).is_remote() {
3751 return;
3752 }
3753 let remote_worktree_id = worktree.read(cx).id();
3754
3755 for (path, _, change) in changes.iter() {
3756 let removed = change == &PathChange::Removed;
3757 let abs_path = match worktree.read(cx).absolutize(path) {
3758 Ok(abs_path) => abs_path,
3759 Err(e) => {
3760 log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");
3761 continue;
3762 }
3763 };
3764
3765 if path.ends_with(local_tasks_file_relative_path()) {
3766 self.task_inventory().update(cx, |task_inventory, cx| {
3767 if removed {
3768 task_inventory.remove_local_static_source(&abs_path);
3769 } else {
3770 let fs = self.fs.clone();
3771 let task_abs_path = abs_path.clone();
3772 let tasks_file_rx =
3773 watch_config_file(cx.background_executor(), fs, task_abs_path);
3774 task_inventory.add_source(
3775 TaskSourceKind::Worktree {
3776 id: remote_worktree_id,
3777 abs_path,
3778 id_base: "local_tasks_for_worktree".into(),
3779 },
3780 |tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
3781 cx,
3782 );
3783 }
3784 })
3785 } else if path.ends_with(local_vscode_tasks_file_relative_path()) {
3786 self.task_inventory().update(cx, |task_inventory, cx| {
3787 if removed {
3788 task_inventory.remove_local_static_source(&abs_path);
3789 } else {
3790 let fs = self.fs.clone();
3791 let task_abs_path = abs_path.clone();
3792 let tasks_file_rx =
3793 watch_config_file(cx.background_executor(), fs, task_abs_path);
3794 task_inventory.add_source(
3795 TaskSourceKind::Worktree {
3796 id: remote_worktree_id,
3797 abs_path,
3798 id_base: "local_vscode_tasks_for_worktree".into(),
3799 },
3800 |tx, cx| {
3801 StaticSource::new(TrackedFile::new_convertible::<
3802 task::VsCodeTaskFile,
3803 >(
3804 tasks_file_rx, tx, cx
3805 ))
3806 },
3807 cx,
3808 );
3809 }
3810 })
3811 }
3812 }
3813 }
3814
3815 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
3816 let new_active_entry = entry.and_then(|project_path| {
3817 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
3818 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
3819 Some(entry.id)
3820 });
3821 if new_active_entry != self.active_entry {
3822 self.active_entry = new_active_entry;
3823 self.lsp_store.update(cx, |lsp_store, _| {
3824 lsp_store.set_active_entry(new_active_entry);
3825 });
3826 cx.emit(Event::ActiveEntryChanged(new_active_entry));
3827 }
3828 }
3829
3830 pub fn language_servers_running_disk_based_diagnostics<'a>(
3831 &'a self,
3832 cx: &'a AppContext,
3833 ) -> impl Iterator<Item = LanguageServerId> + 'a {
3834 self.lsp_store
3835 .read(cx)
3836 .language_servers_running_disk_based_diagnostics()
3837 }
3838
3839 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
3840 let mut summary = DiagnosticSummary::default();
3841 for (_, _, path_summary) in self.diagnostic_summaries(include_ignored, cx) {
3842 summary.error_count += path_summary.error_count;
3843 summary.warning_count += path_summary.warning_count;
3844 }
3845 summary
3846 }
3847
3848 pub fn diagnostic_summaries<'a>(
3849 &'a self,
3850 include_ignored: bool,
3851 cx: &'a AppContext,
3852 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
3853 self.lsp_store
3854 .read(cx)
3855 .diagnostic_summaries(include_ignored, cx)
3856 }
3857
3858 pub fn active_entry(&self) -> Option<ProjectEntryId> {
3859 self.active_entry
3860 }
3861
3862 pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
3863 self.worktree_store.read(cx).entry_for_path(path, cx)
3864 }
3865
3866 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
3867 let worktree = self.worktree_for_entry(entry_id, cx)?;
3868 let worktree = worktree.read(cx);
3869 let worktree_id = worktree.id();
3870 let path = worktree.entry_for_id(entry_id)?.path.clone();
3871 Some(ProjectPath { worktree_id, path })
3872 }
3873
3874 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
3875 let workspace_root = self
3876 .worktree_for_id(project_path.worktree_id, cx)?
3877 .read(cx)
3878 .abs_path();
3879 let project_path = project_path.path.as_ref();
3880
3881 Some(if project_path == Path::new("") {
3882 workspace_root.to_path_buf()
3883 } else {
3884 workspace_root.join(project_path)
3885 })
3886 }
3887
3888 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
3889 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
3890 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
3891 /// the first visible worktree that has an entry for that relative path.
3892 ///
3893 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
3894 /// root name from paths.
3895 ///
3896 /// # Arguments
3897 ///
3898 /// * `path` - A full path that starts with a worktree root name, or alternatively a
3899 /// relative path within a visible worktree.
3900 /// * `cx` - A reference to the `AppContext`.
3901 ///
3902 /// # Returns
3903 ///
3904 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
3905 pub fn find_project_path(&self, path: &Path, cx: &AppContext) -> Option<ProjectPath> {
3906 let worktree_store = self.worktree_store.read(cx);
3907
3908 for worktree in worktree_store.visible_worktrees(cx) {
3909 let worktree_root_name = worktree.read(cx).root_name();
3910 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
3911 return Some(ProjectPath {
3912 worktree_id: worktree.read(cx).id(),
3913 path: relative_path.into(),
3914 });
3915 }
3916 }
3917
3918 for worktree in worktree_store.visible_worktrees(cx) {
3919 let worktree = worktree.read(cx);
3920 if let Some(entry) = worktree.entry_for_path(path) {
3921 return Some(ProjectPath {
3922 worktree_id: worktree.id(),
3923 path: entry.path.clone(),
3924 });
3925 }
3926 }
3927
3928 None
3929 }
3930
3931 pub fn get_workspace_root(
3932 &self,
3933 project_path: &ProjectPath,
3934 cx: &AppContext,
3935 ) -> Option<PathBuf> {
3936 Some(
3937 self.worktree_for_id(project_path.worktree_id, cx)?
3938 .read(cx)
3939 .abs_path()
3940 .to_path_buf(),
3941 )
3942 }
3943
3944 pub fn get_repo(
3945 &self,
3946 project_path: &ProjectPath,
3947 cx: &AppContext,
3948 ) -> Option<Arc<dyn GitRepository>> {
3949 self.worktree_for_id(project_path.worktree_id, cx)?
3950 .read(cx)
3951 .as_local()?
3952 .local_git_repo(&project_path.path)
3953 }
3954
3955 pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
3956 let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
3957 let root_entry = worktree.root_git_entry()?;
3958 worktree.get_local_repo(&root_entry)?.repo().clone().into()
3959 }
3960
3961 pub fn blame_buffer(
3962 &self,
3963 buffer: &Model<Buffer>,
3964 version: Option<clock::Global>,
3965 cx: &AppContext,
3966 ) -> Task<Result<Blame>> {
3967 self.buffer_store.read(cx).blame_buffer(buffer, version, cx)
3968 }
3969
3970 // RPC message handlers
3971
3972 async fn handle_unshare_project(
3973 this: Model<Self>,
3974 _: TypedEnvelope<proto::UnshareProject>,
3975 mut cx: AsyncAppContext,
3976 ) -> Result<()> {
3977 this.update(&mut cx, |this, cx| {
3978 if this.is_local_or_ssh() {
3979 this.unshare(cx)?;
3980 } else {
3981 this.disconnected_from_host(cx);
3982 }
3983 Ok(())
3984 })?
3985 }
3986
3987 async fn handle_add_collaborator(
3988 this: Model<Self>,
3989 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
3990 mut cx: AsyncAppContext,
3991 ) -> Result<()> {
3992 let collaborator = envelope
3993 .payload
3994 .collaborator
3995 .take()
3996 .ok_or_else(|| anyhow!("empty collaborator"))?;
3997
3998 let collaborator = Collaborator::from_proto(collaborator)?;
3999 this.update(&mut cx, |this, cx| {
4000 this.buffer_store.update(cx, |buffer_store, _| {
4001 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
4002 });
4003 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
4004 this.collaborators
4005 .insert(collaborator.peer_id, collaborator);
4006 cx.notify();
4007 })?;
4008
4009 Ok(())
4010 }
4011
4012 async fn handle_update_project_collaborator(
4013 this: Model<Self>,
4014 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
4015 mut cx: AsyncAppContext,
4016 ) -> Result<()> {
4017 let old_peer_id = envelope
4018 .payload
4019 .old_peer_id
4020 .ok_or_else(|| anyhow!("missing old peer id"))?;
4021 let new_peer_id = envelope
4022 .payload
4023 .new_peer_id
4024 .ok_or_else(|| anyhow!("missing new peer id"))?;
4025 this.update(&mut cx, |this, cx| {
4026 let collaborator = this
4027 .collaborators
4028 .remove(&old_peer_id)
4029 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
4030 let is_host = collaborator.replica_id == 0;
4031 this.collaborators.insert(new_peer_id, collaborator);
4032
4033 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
4034 this.buffer_store.update(cx, |buffer_store, _| {
4035 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
4036 });
4037
4038 if is_host {
4039 this.buffer_store
4040 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
4041 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
4042 .unwrap();
4043 cx.emit(Event::HostReshared);
4044 }
4045
4046 cx.emit(Event::CollaboratorUpdated {
4047 old_peer_id,
4048 new_peer_id,
4049 });
4050 cx.notify();
4051 Ok(())
4052 })?
4053 }
4054
4055 async fn handle_remove_collaborator(
4056 this: Model<Self>,
4057 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
4058 mut cx: AsyncAppContext,
4059 ) -> Result<()> {
4060 this.update(&mut cx, |this, cx| {
4061 let peer_id = envelope
4062 .payload
4063 .peer_id
4064 .ok_or_else(|| anyhow!("invalid peer id"))?;
4065 let replica_id = this
4066 .collaborators
4067 .remove(&peer_id)
4068 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
4069 .replica_id;
4070 this.buffer_store.update(cx, |buffer_store, cx| {
4071 buffer_store.forget_shared_buffers_for(&peer_id);
4072 for buffer in buffer_store.buffers() {
4073 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
4074 }
4075 });
4076
4077 cx.emit(Event::CollaboratorLeft(peer_id));
4078 cx.notify();
4079 Ok(())
4080 })?
4081 }
4082
4083 async fn handle_update_project(
4084 this: Model<Self>,
4085 envelope: TypedEnvelope<proto::UpdateProject>,
4086 mut cx: AsyncAppContext,
4087 ) -> Result<()> {
4088 this.update(&mut cx, |this, cx| {
4089 // Don't handle messages that were sent before the response to us joining the project
4090 if envelope.message_id > this.join_project_response_message_id {
4091 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
4092 }
4093 Ok(())
4094 })?
4095 }
4096
4097 // Collab sends UpdateWorktree protos as messages
4098 async fn handle_update_worktree(
4099 this: Model<Self>,
4100 envelope: TypedEnvelope<proto::UpdateWorktree>,
4101 mut cx: AsyncAppContext,
4102 ) -> Result<()> {
4103 this.update(&mut cx, |this, cx| {
4104 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4105 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4106 worktree.update(cx, |worktree, _| {
4107 let worktree = worktree.as_remote_mut().unwrap();
4108 worktree.update_from_remote(envelope.payload);
4109 });
4110 }
4111 Ok(())
4112 })?
4113 }
4114
4115 async fn handle_update_buffer(
4116 this: Model<Self>,
4117 envelope: TypedEnvelope<proto::UpdateBuffer>,
4118 cx: AsyncAppContext,
4119 ) -> Result<proto::Ack> {
4120 let buffer_store = this.read_with(&cx, |this, cx| {
4121 if let Some(ssh) = &this.ssh_session {
4122 let mut payload = envelope.payload.clone();
4123 payload.project_id = 0;
4124 cx.background_executor()
4125 .spawn(ssh.request(payload))
4126 .detach_and_log_err(cx);
4127 }
4128 this.buffer_store.clone()
4129 })?;
4130 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4131 }
4132
4133 fn retain_remotely_created_models(
4134 &mut self,
4135 cx: &mut ModelContext<Self>,
4136 ) -> RemotelyCreatedModelGuard {
4137 {
4138 let mut remotely_create_models = self.remotely_created_models.lock();
4139 if remotely_create_models.retain_count == 0 {
4140 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
4141 remotely_create_models.worktrees =
4142 self.worktree_store.read(cx).worktrees().collect();
4143 }
4144 remotely_create_models.retain_count += 1;
4145 }
4146 RemotelyCreatedModelGuard {
4147 remote_models: Arc::downgrade(&self.remotely_created_models),
4148 }
4149 }
4150
4151 async fn handle_create_buffer_for_peer(
4152 this: Model<Self>,
4153 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
4154 mut cx: AsyncAppContext,
4155 ) -> Result<()> {
4156 this.update(&mut cx, |this, cx| {
4157 this.buffer_store.update(cx, |buffer_store, cx| {
4158 buffer_store.handle_create_buffer_for_peer(
4159 envelope,
4160 this.replica_id(),
4161 this.capability(),
4162 cx,
4163 )
4164 })
4165 })?
4166 }
4167
4168 async fn handle_reload_buffers(
4169 this: Model<Self>,
4170 envelope: TypedEnvelope<proto::ReloadBuffers>,
4171 mut cx: AsyncAppContext,
4172 ) -> Result<proto::ReloadBuffersResponse> {
4173 let sender_id = envelope.original_sender_id()?;
4174 let reload = this.update(&mut cx, |this, cx| {
4175 let mut buffers = HashSet::default();
4176 for buffer_id in &envelope.payload.buffer_ids {
4177 let buffer_id = BufferId::new(*buffer_id)?;
4178 buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
4179 }
4180 Ok::<_, anyhow::Error>(this.reload_buffers(buffers, false, cx))
4181 })??;
4182
4183 let project_transaction = reload.await?;
4184 let project_transaction = this.update(&mut cx, |this, cx| {
4185 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
4186 })?;
4187 Ok(proto::ReloadBuffersResponse {
4188 transaction: Some(project_transaction),
4189 })
4190 }
4191
4192 async fn handle_synchronize_buffers(
4193 this: Model<Self>,
4194 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
4195 mut cx: AsyncAppContext,
4196 ) -> Result<proto::SynchronizeBuffersResponse> {
4197 let response = this.update(&mut cx, |this, cx| {
4198 let client = this.client.clone();
4199 this.buffer_store.update(cx, |this, cx| {
4200 this.handle_synchronize_buffers(envelope, cx, client)
4201 })
4202 })??;
4203
4204 Ok(response)
4205 }
4206
4207 async fn handle_format_buffers(
4208 this: Model<Self>,
4209 envelope: TypedEnvelope<proto::FormatBuffers>,
4210 mut cx: AsyncAppContext,
4211 ) -> Result<proto::FormatBuffersResponse> {
4212 let sender_id = envelope.original_sender_id()?;
4213 let format = this.update(&mut cx, |this, cx| {
4214 let mut buffers = HashSet::default();
4215 for buffer_id in &envelope.payload.buffer_ids {
4216 let buffer_id = BufferId::new(*buffer_id)?;
4217 buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
4218 }
4219 let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
4220 Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
4221 })??;
4222
4223 let project_transaction = format.await?;
4224 let project_transaction = this.update(&mut cx, |this, cx| {
4225 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
4226 })?;
4227 Ok(proto::FormatBuffersResponse {
4228 transaction: Some(project_transaction),
4229 })
4230 }
4231
4232 async fn handle_task_context_for_location(
4233 project: Model<Self>,
4234 envelope: TypedEnvelope<proto::TaskContextForLocation>,
4235 mut cx: AsyncAppContext,
4236 ) -> Result<proto::TaskContext> {
4237 let location = envelope
4238 .payload
4239 .location
4240 .context("no location given for task context handling")?;
4241 let location = cx
4242 .update(|cx| deserialize_location(&project, location, cx))?
4243 .await?;
4244 let context_task = project.update(&mut cx, |project, cx| {
4245 let captured_variables = {
4246 let mut variables = TaskVariables::default();
4247 for range in location
4248 .buffer
4249 .read(cx)
4250 .snapshot()
4251 .runnable_ranges(location.range.clone())
4252 {
4253 for (capture_name, value) in range.extra_captures {
4254 variables.insert(VariableName::Custom(capture_name.into()), value);
4255 }
4256 }
4257 variables
4258 };
4259 project.task_context_for_location(captured_variables, location, cx)
4260 })?;
4261 let task_context = context_task.await.unwrap_or_default();
4262 Ok(proto::TaskContext {
4263 project_env: task_context.project_env.into_iter().collect(),
4264 cwd: task_context
4265 .cwd
4266 .map(|cwd| cwd.to_string_lossy().to_string()),
4267 task_variables: task_context
4268 .task_variables
4269 .into_iter()
4270 .map(|(variable_name, variable_value)| (variable_name.to_string(), variable_value))
4271 .collect(),
4272 })
4273 }
4274
4275 async fn handle_task_templates(
4276 project: Model<Self>,
4277 envelope: TypedEnvelope<proto::TaskTemplates>,
4278 mut cx: AsyncAppContext,
4279 ) -> Result<proto::TaskTemplatesResponse> {
4280 let worktree = envelope.payload.worktree_id.map(WorktreeId::from_proto);
4281 let location = match envelope.payload.location {
4282 Some(location) => Some(
4283 cx.update(|cx| deserialize_location(&project, location, cx))?
4284 .await
4285 .context("task templates request location deserializing")?,
4286 ),
4287 None => None,
4288 };
4289
4290 let templates = project
4291 .update(&mut cx, |project, cx| {
4292 project.task_templates(worktree, location, cx)
4293 })?
4294 .await
4295 .context("receiving task templates")?
4296 .into_iter()
4297 .map(|(kind, template)| {
4298 let kind = Some(match kind {
4299 TaskSourceKind::UserInput => proto::task_source_kind::Kind::UserInput(
4300 proto::task_source_kind::UserInput {},
4301 ),
4302 TaskSourceKind::Worktree {
4303 id,
4304 abs_path,
4305 id_base,
4306 } => {
4307 proto::task_source_kind::Kind::Worktree(proto::task_source_kind::Worktree {
4308 id: id.to_proto(),
4309 abs_path: abs_path.to_string_lossy().to_string(),
4310 id_base: id_base.to_string(),
4311 })
4312 }
4313 TaskSourceKind::AbsPath { id_base, abs_path } => {
4314 proto::task_source_kind::Kind::AbsPath(proto::task_source_kind::AbsPath {
4315 abs_path: abs_path.to_string_lossy().to_string(),
4316 id_base: id_base.to_string(),
4317 })
4318 }
4319 TaskSourceKind::Language { name } => {
4320 proto::task_source_kind::Kind::Language(proto::task_source_kind::Language {
4321 name: name.to_string(),
4322 })
4323 }
4324 });
4325 let kind = Some(proto::TaskSourceKind { kind });
4326 let template = Some(proto::TaskTemplate {
4327 label: template.label,
4328 command: template.command,
4329 args: template.args,
4330 env: template.env.into_iter().collect(),
4331 cwd: template.cwd,
4332 use_new_terminal: template.use_new_terminal,
4333 allow_concurrent_runs: template.allow_concurrent_runs,
4334 reveal: match template.reveal {
4335 RevealStrategy::Always => proto::RevealStrategy::RevealAlways as i32,
4336 RevealStrategy::Never => proto::RevealStrategy::RevealNever as i32,
4337 },
4338 hide: match template.hide {
4339 HideStrategy::Always => proto::HideStrategy::HideAlways as i32,
4340 HideStrategy::Never => proto::HideStrategy::HideNever as i32,
4341 HideStrategy::OnSuccess => proto::HideStrategy::HideOnSuccess as i32,
4342 },
4343 shell: Some(proto::Shell {
4344 shell_type: Some(match template.shell {
4345 Shell::System => proto::shell::ShellType::System(proto::System {}),
4346 Shell::Program(program) => proto::shell::ShellType::Program(program),
4347 Shell::WithArguments { program, args } => {
4348 proto::shell::ShellType::WithArguments(
4349 proto::shell::WithArguments { program, args },
4350 )
4351 }
4352 }),
4353 }),
4354 tags: template.tags,
4355 });
4356 proto::TemplatePair { kind, template }
4357 })
4358 .collect();
4359
4360 Ok(proto::TaskTemplatesResponse { templates })
4361 }
4362
4363 async fn handle_search_project(
4364 this: Model<Self>,
4365 envelope: TypedEnvelope<proto::SearchProject>,
4366 mut cx: AsyncAppContext,
4367 ) -> Result<proto::SearchProjectResponse> {
4368 let peer_id = envelope.original_sender_id()?;
4369 let query = SearchQuery::from_proto_v1(envelope.payload)?;
4370 let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
4371
4372 cx.spawn(move |mut cx| async move {
4373 let mut locations = Vec::new();
4374 let mut limit_reached = false;
4375 while let Some(result) = result.next().await {
4376 match result {
4377 SearchResult::Buffer { buffer, ranges } => {
4378 for range in ranges {
4379 let start = serialize_anchor(&range.start);
4380 let end = serialize_anchor(&range.end);
4381 let buffer_id = this.update(&mut cx, |this, cx| {
4382 this.create_buffer_for_peer(&buffer, peer_id, cx).into()
4383 })?;
4384 locations.push(proto::Location {
4385 buffer_id,
4386 start: Some(start),
4387 end: Some(end),
4388 });
4389 }
4390 }
4391 SearchResult::LimitReached => limit_reached = true,
4392 }
4393 }
4394 Ok(proto::SearchProjectResponse {
4395 locations,
4396 limit_reached,
4397 // will restart
4398 })
4399 })
4400 .await
4401 }
4402
4403 async fn handle_search_candidate_buffers(
4404 this: Model<Self>,
4405 envelope: TypedEnvelope<proto::FindSearchCandidates>,
4406 mut cx: AsyncAppContext,
4407 ) -> Result<proto::FindSearchCandidatesResponse> {
4408 let peer_id = envelope.original_sender_id()?;
4409 let message = envelope.payload;
4410 let query = SearchQuery::from_proto(
4411 message
4412 .query
4413 .ok_or_else(|| anyhow!("missing query field"))?,
4414 )?;
4415 let mut results = this.update(&mut cx, |this, cx| {
4416 this.search_for_candidate_buffers(&query, message.limit as _, cx)
4417 })?;
4418
4419 let mut response = proto::FindSearchCandidatesResponse {
4420 buffer_ids: Vec::new(),
4421 };
4422
4423 while let Some(buffer) = results.next().await {
4424 this.update(&mut cx, |this, cx| {
4425 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
4426 response.buffer_ids.push(buffer_id.to_proto());
4427 })?;
4428 }
4429
4430 Ok(response)
4431 }
4432
4433 async fn handle_open_buffer_by_id(
4434 this: Model<Self>,
4435 envelope: TypedEnvelope<proto::OpenBufferById>,
4436 mut cx: AsyncAppContext,
4437 ) -> Result<proto::OpenBufferResponse> {
4438 let peer_id = envelope.original_sender_id()?;
4439 let buffer_id = BufferId::new(envelope.payload.id)?;
4440 let buffer = this
4441 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
4442 .await?;
4443 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4444 }
4445
4446 async fn handle_open_buffer_by_path(
4447 this: Model<Self>,
4448 envelope: TypedEnvelope<proto::OpenBufferByPath>,
4449 mut cx: AsyncAppContext,
4450 ) -> Result<proto::OpenBufferResponse> {
4451 let peer_id = envelope.original_sender_id()?;
4452 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4453 let open_buffer = this.update(&mut cx, |this, cx| {
4454 this.open_buffer(
4455 ProjectPath {
4456 worktree_id,
4457 path: PathBuf::from(envelope.payload.path).into(),
4458 },
4459 cx,
4460 )
4461 })?;
4462
4463 let buffer = open_buffer.await?;
4464 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4465 }
4466
4467 async fn handle_open_new_buffer(
4468 this: Model<Self>,
4469 envelope: TypedEnvelope<proto::OpenNewBuffer>,
4470 mut cx: AsyncAppContext,
4471 ) -> Result<proto::OpenBufferResponse> {
4472 let buffer = this.update(&mut cx, |this, cx| this.create_local_buffer("", None, cx))?;
4473 let peer_id = envelope.original_sender_id()?;
4474
4475 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4476 }
4477
4478 fn respond_to_open_buffer_request(
4479 this: Model<Self>,
4480 buffer: Model<Buffer>,
4481 peer_id: proto::PeerId,
4482 cx: &mut AsyncAppContext,
4483 ) -> Result<proto::OpenBufferResponse> {
4484 this.update(cx, |this, cx| {
4485 let is_private = buffer
4486 .read(cx)
4487 .file()
4488 .map(|f| f.is_private())
4489 .unwrap_or_default();
4490 if is_private {
4491 Err(anyhow!(ErrorCode::UnsharedItem))
4492 } else {
4493 Ok(proto::OpenBufferResponse {
4494 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
4495 })
4496 }
4497 })?
4498 }
4499
4500 fn serialize_project_transaction_for_peer(
4501 &mut self,
4502 project_transaction: ProjectTransaction,
4503 peer_id: proto::PeerId,
4504 cx: &mut AppContext,
4505 ) -> proto::ProjectTransaction {
4506 self.buffer_store.update(cx, |buffer_store, cx| {
4507 buffer_store.serialize_project_transaction_for_peer(project_transaction, peer_id, cx)
4508 })
4509 }
4510
4511 fn create_buffer_for_peer(
4512 &mut self,
4513 buffer: &Model<Buffer>,
4514 peer_id: proto::PeerId,
4515 cx: &mut AppContext,
4516 ) -> BufferId {
4517 self.buffer_store
4518 .update(cx, |buffer_store, cx| {
4519 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
4520 })
4521 .detach_and_log_err(cx);
4522 buffer.read(cx).remote_id()
4523 }
4524
4525 fn wait_for_remote_buffer(
4526 &mut self,
4527 id: BufferId,
4528 cx: &mut ModelContext<Self>,
4529 ) -> Task<Result<Model<Buffer>>> {
4530 self.buffer_store.update(cx, |buffer_store, cx| {
4531 buffer_store.wait_for_remote_buffer(id, cx)
4532 })
4533 }
4534
4535 fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
4536 let project_id = match self.client_state {
4537 ProjectClientState::Remote {
4538 sharing_has_stopped,
4539 remote_id,
4540 ..
4541 } => {
4542 if sharing_has_stopped {
4543 return Task::ready(Err(anyhow!(
4544 "can't synchronize remote buffers on a readonly project"
4545 )));
4546 } else {
4547 remote_id
4548 }
4549 }
4550 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
4551 return Task::ready(Err(anyhow!(
4552 "can't synchronize remote buffers on a local project"
4553 )))
4554 }
4555 };
4556
4557 let client = self.client.clone();
4558 cx.spawn(move |this, mut cx| async move {
4559 let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
4560 this.buffer_store.read(cx).buffer_version_info(cx)
4561 })?;
4562 let response = client
4563 .request(proto::SynchronizeBuffers {
4564 project_id,
4565 buffers,
4566 })
4567 .await?;
4568
4569 let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
4570 response
4571 .buffers
4572 .into_iter()
4573 .map(|buffer| {
4574 let client = client.clone();
4575 let buffer_id = match BufferId::new(buffer.id) {
4576 Ok(id) => id,
4577 Err(e) => {
4578 return Task::ready(Err(e));
4579 }
4580 };
4581 let remote_version = language::proto::deserialize_version(&buffer.version);
4582 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
4583 let operations =
4584 buffer.read(cx).serialize_ops(Some(remote_version), cx);
4585 cx.background_executor().spawn(async move {
4586 let operations = operations.await;
4587 for chunk in split_operations(operations) {
4588 client
4589 .request(proto::UpdateBuffer {
4590 project_id,
4591 buffer_id: buffer_id.into(),
4592 operations: chunk,
4593 })
4594 .await?;
4595 }
4596 anyhow::Ok(())
4597 })
4598 } else {
4599 Task::ready(Ok(()))
4600 }
4601 })
4602 .collect::<Vec<_>>()
4603 })?;
4604
4605 // Any incomplete buffers have open requests waiting. Request that the host sends
4606 // creates these buffers for us again to unblock any waiting futures.
4607 for id in incomplete_buffer_ids {
4608 cx.background_executor()
4609 .spawn(client.request(proto::OpenBufferById {
4610 project_id,
4611 id: id.into(),
4612 }))
4613 .detach();
4614 }
4615
4616 futures::future::join_all(send_updates_for_buffers)
4617 .await
4618 .into_iter()
4619 .collect()
4620 })
4621 }
4622
4623 pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
4624 self.worktrees(cx)
4625 .map(|worktree| {
4626 let worktree = worktree.read(cx);
4627 proto::WorktreeMetadata {
4628 id: worktree.id().to_proto(),
4629 root_name: worktree.root_name().into(),
4630 visible: worktree.is_visible(),
4631 abs_path: worktree.abs_path().to_string_lossy().into(),
4632 }
4633 })
4634 .collect()
4635 }
4636
4637 fn set_worktrees_from_proto(
4638 &mut self,
4639 worktrees: Vec<proto::WorktreeMetadata>,
4640 cx: &mut ModelContext<Project>,
4641 ) -> Result<()> {
4642 cx.notify();
4643 let result = self.worktree_store.update(cx, |worktree_store, cx| {
4644 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
4645 });
4646 result
4647 }
4648
4649 fn set_collaborators_from_proto(
4650 &mut self,
4651 messages: Vec<proto::Collaborator>,
4652 cx: &mut ModelContext<Self>,
4653 ) -> Result<()> {
4654 let mut collaborators = HashMap::default();
4655 for message in messages {
4656 let collaborator = Collaborator::from_proto(message)?;
4657 collaborators.insert(collaborator.peer_id, collaborator);
4658 }
4659 for old_peer_id in self.collaborators.keys() {
4660 if !collaborators.contains_key(old_peer_id) {
4661 cx.emit(Event::CollaboratorLeft(*old_peer_id));
4662 }
4663 }
4664 self.collaborators = collaborators;
4665 Ok(())
4666 }
4667
4668 pub fn language_servers<'a>(
4669 &'a self,
4670 cx: &'a AppContext,
4671 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
4672 self.lsp_store.read(cx).language_servers()
4673 }
4674
4675 pub fn supplementary_language_servers<'a>(
4676 &'a self,
4677 cx: &'a AppContext,
4678 ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
4679 self.lsp_store.read(cx).supplementary_language_servers()
4680 }
4681
4682 pub fn language_server_adapter_for_id(
4683 &self,
4684 id: LanguageServerId,
4685 cx: &AppContext,
4686 ) -> Option<Arc<CachedLspAdapter>> {
4687 self.lsp_store.read(cx).language_server_adapter_for_id(id)
4688 }
4689
4690 pub fn language_server_for_id(
4691 &self,
4692 id: LanguageServerId,
4693 cx: &AppContext,
4694 ) -> Option<Arc<LanguageServer>> {
4695 self.lsp_store.read(cx).language_server_for_id(id)
4696 }
4697
4698 pub fn language_servers_for_buffer<'a>(
4699 &'a self,
4700 buffer: &'a Buffer,
4701 cx: &'a AppContext,
4702 ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
4703 self.lsp_store
4704 .read(cx)
4705 .language_servers_for_buffer(buffer, cx)
4706 }
4707
4708 pub fn language_server_for_buffer<'a>(
4709 &'a self,
4710 buffer: &'a Buffer,
4711 server_id: LanguageServerId,
4712 cx: &'a AppContext,
4713 ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
4714 self.lsp_store
4715 .read(cx)
4716 .language_server_for_buffer(buffer, server_id, cx)
4717 }
4718
4719 pub fn task_context_for_location(
4720 &self,
4721 captured_variables: TaskVariables,
4722 location: Location,
4723 cx: &mut ModelContext<'_, Project>,
4724 ) -> Task<Option<TaskContext>> {
4725 if self.is_local_or_ssh() {
4726 let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) {
4727 (
4728 Some(worktree.read(cx).id()),
4729 Some(worktree.read(cx).abs_path()),
4730 )
4731 } else {
4732 (None, None)
4733 };
4734
4735 cx.spawn(|project, mut cx| async move {
4736 let project_env = project
4737 .update(&mut cx, |project, cx| {
4738 let worktree_abs_path = worktree_abs_path.clone();
4739 project.environment.update(cx, |environment, cx| {
4740 environment.get_environment(worktree_id, worktree_abs_path, cx)
4741 })
4742 })
4743 .ok()?
4744 .await;
4745
4746 let mut task_variables = cx
4747 .update(|cx| {
4748 combine_task_variables(
4749 captured_variables,
4750 location,
4751 project_env.as_ref(),
4752 BasicContextProvider::new(project.upgrade()?),
4753 cx,
4754 )
4755 .log_err()
4756 })
4757 .ok()
4758 .flatten()?;
4759 // Remove all custom entries starting with _, as they're not intended for use by the end user.
4760 task_variables.sweep();
4761
4762 Some(TaskContext {
4763 project_env: project_env.unwrap_or_default(),
4764 cwd: worktree_abs_path.map(|p| p.to_path_buf()),
4765 task_variables,
4766 })
4767 })
4768 } else if let Some(project_id) = self
4769 .remote_id()
4770 .filter(|_| self.ssh_connection_string(cx).is_some())
4771 {
4772 let task_context = self.client().request(proto::TaskContextForLocation {
4773 project_id,
4774 location: Some(proto::Location {
4775 buffer_id: location.buffer.read(cx).remote_id().into(),
4776 start: Some(serialize_anchor(&location.range.start)),
4777 end: Some(serialize_anchor(&location.range.end)),
4778 }),
4779 });
4780 cx.background_executor().spawn(async move {
4781 let task_context = task_context.await.log_err()?;
4782 Some(TaskContext {
4783 project_env: task_context.project_env.into_iter().collect(),
4784 cwd: task_context.cwd.map(PathBuf::from),
4785 task_variables: task_context
4786 .task_variables
4787 .into_iter()
4788 .filter_map(
4789 |(variable_name, variable_value)| match variable_name.parse() {
4790 Ok(variable_name) => Some((variable_name, variable_value)),
4791 Err(()) => {
4792 log::error!("Unknown variable name: {variable_name}");
4793 None
4794 }
4795 },
4796 )
4797 .collect(),
4798 })
4799 })
4800 } else {
4801 Task::ready(None)
4802 }
4803 }
4804
4805 pub fn task_templates(
4806 &self,
4807 worktree: Option<WorktreeId>,
4808 location: Option<Location>,
4809 cx: &mut ModelContext<Self>,
4810 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4811 if self.is_local_or_ssh() {
4812 let (file, language) = location
4813 .map(|location| {
4814 let buffer = location.buffer.read(cx);
4815 (
4816 buffer.file().cloned(),
4817 buffer.language_at(location.range.start),
4818 )
4819 })
4820 .unwrap_or_default();
4821 Task::ready(Ok(self
4822 .task_inventory()
4823 .read(cx)
4824 .list_tasks(file, language, worktree, cx)))
4825 } else if let Some(project_id) = self
4826 .remote_id()
4827 .filter(|_| self.ssh_connection_string(cx).is_some())
4828 {
4829 let remote_templates =
4830 self.query_remote_task_templates(project_id, worktree, location.as_ref(), cx);
4831 cx.background_executor().spawn(remote_templates)
4832 } else {
4833 Task::ready(Ok(Vec::new()))
4834 }
4835 }
4836
4837 pub fn query_remote_task_templates(
4838 &self,
4839 project_id: u64,
4840 worktree: Option<WorktreeId>,
4841 location: Option<&Location>,
4842 cx: &AppContext,
4843 ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
4844 let client = self.client();
4845 let location = location.map(|location| serialize_location(location, cx));
4846 cx.spawn(|_| async move {
4847 let response = client
4848 .request(proto::TaskTemplates {
4849 project_id,
4850 worktree_id: worktree.map(|id| id.to_proto()),
4851 location,
4852 })
4853 .await?;
4854
4855 Ok(response
4856 .templates
4857 .into_iter()
4858 .filter_map(|template_pair| {
4859 let task_source_kind = match template_pair.kind?.kind? {
4860 proto::task_source_kind::Kind::UserInput(_) => TaskSourceKind::UserInput,
4861 proto::task_source_kind::Kind::Worktree(worktree) => {
4862 TaskSourceKind::Worktree {
4863 id: WorktreeId::from_proto(worktree.id),
4864 abs_path: PathBuf::from(worktree.abs_path),
4865 id_base: Cow::Owned(worktree.id_base),
4866 }
4867 }
4868 proto::task_source_kind::Kind::AbsPath(abs_path) => {
4869 TaskSourceKind::AbsPath {
4870 id_base: Cow::Owned(abs_path.id_base),
4871 abs_path: PathBuf::from(abs_path.abs_path),
4872 }
4873 }
4874 proto::task_source_kind::Kind::Language(language) => {
4875 TaskSourceKind::Language {
4876 name: language.name.into(),
4877 }
4878 }
4879 };
4880
4881 let proto_template = template_pair.template?;
4882 let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
4883 .unwrap_or(proto::RevealStrategy::RevealAlways)
4884 {
4885 proto::RevealStrategy::RevealAlways => RevealStrategy::Always,
4886 proto::RevealStrategy::RevealNever => RevealStrategy::Never,
4887 };
4888 let hide = match proto::HideStrategy::from_i32(proto_template.hide)
4889 .unwrap_or(proto::HideStrategy::HideNever)
4890 {
4891 proto::HideStrategy::HideAlways => HideStrategy::Always,
4892 proto::HideStrategy::HideNever => HideStrategy::Never,
4893 proto::HideStrategy::HideOnSuccess => HideStrategy::OnSuccess,
4894 };
4895 let shell = match proto_template
4896 .shell
4897 .and_then(|shell| shell.shell_type)
4898 .unwrap_or(proto::shell::ShellType::System(proto::System {}))
4899 {
4900 proto::shell::ShellType::System(_) => Shell::System,
4901 proto::shell::ShellType::Program(program) => Shell::Program(program),
4902 proto::shell::ShellType::WithArguments(with_arguments) => {
4903 Shell::WithArguments {
4904 program: with_arguments.program,
4905 args: with_arguments.args,
4906 }
4907 }
4908 };
4909 let task_template = TaskTemplate {
4910 label: proto_template.label,
4911 command: proto_template.command,
4912 args: proto_template.args,
4913 env: proto_template.env.into_iter().collect(),
4914 cwd: proto_template.cwd,
4915 use_new_terminal: proto_template.use_new_terminal,
4916 allow_concurrent_runs: proto_template.allow_concurrent_runs,
4917 reveal,
4918 hide,
4919 shell,
4920 tags: proto_template.tags,
4921 };
4922 Some((task_source_kind, task_template))
4923 })
4924 .collect())
4925 })
4926 }
4927
4928 fn task_worktree(&self, cx: &AppContext) -> Option<Model<Worktree>> {
4929 let available_worktrees = self
4930 .worktrees(cx)
4931 .filter(|worktree| {
4932 let worktree = worktree.read(cx);
4933 worktree.is_visible()
4934 && worktree.is_local()
4935 && worktree.root_entry().map_or(false, |e| e.is_dir())
4936 })
4937 .collect::<Vec<_>>();
4938
4939 match available_worktrees.len() {
4940 0 => None,
4941 1 => Some(available_worktrees[0].clone()),
4942 _ => self.active_entry().and_then(|entry_id| {
4943 available_worktrees.into_iter().find_map(|worktree| {
4944 if worktree.read(cx).contains_entry(entry_id) {
4945 Some(worktree)
4946 } else {
4947 None
4948 }
4949 })
4950 }),
4951 }
4952 }
4953}
4954
4955fn combine_task_variables(
4956 mut captured_variables: TaskVariables,
4957 location: Location,
4958 project_env: Option<&HashMap<String, String>>,
4959 baseline: BasicContextProvider,
4960 cx: &mut AppContext,
4961) -> anyhow::Result<TaskVariables> {
4962 let language_context_provider = location
4963 .buffer
4964 .read(cx)
4965 .language()
4966 .and_then(|language| language.context_provider());
4967 let baseline = baseline
4968 .build_context(&captured_variables, &location, project_env, cx)
4969 .context("building basic default context")?;
4970 captured_variables.extend(baseline);
4971 if let Some(provider) = language_context_provider {
4972 captured_variables.extend(
4973 provider
4974 .build_context(&captured_variables, &location, project_env, cx)
4975 .context("building provider context")?,
4976 );
4977 }
4978 Ok(captured_variables)
4979}
4980
4981fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
4982 code_actions
4983 .iter()
4984 .flat_map(|(kind, enabled)| {
4985 if *enabled {
4986 Some(kind.clone().into())
4987 } else {
4988 None
4989 }
4990 })
4991 .collect()
4992}
4993
4994pub struct PathMatchCandidateSet {
4995 pub snapshot: Snapshot,
4996 pub include_ignored: bool,
4997 pub include_root_name: bool,
4998 pub candidates: Candidates,
4999}
5000
5001pub enum Candidates {
5002 /// Only consider directories.
5003 Directories,
5004 /// Only consider files.
5005 Files,
5006 /// Consider directories and files.
5007 Entries,
5008}
5009
5010impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
5011 type Candidates = PathMatchCandidateSetIter<'a>;
5012
5013 fn id(&self) -> usize {
5014 self.snapshot.id().to_usize()
5015 }
5016
5017 fn len(&self) -> usize {
5018 match self.candidates {
5019 Candidates::Files => {
5020 if self.include_ignored {
5021 self.snapshot.file_count()
5022 } else {
5023 self.snapshot.visible_file_count()
5024 }
5025 }
5026
5027 Candidates::Directories => {
5028 if self.include_ignored {
5029 self.snapshot.dir_count()
5030 } else {
5031 self.snapshot.visible_dir_count()
5032 }
5033 }
5034
5035 Candidates::Entries => {
5036 if self.include_ignored {
5037 self.snapshot.entry_count()
5038 } else {
5039 self.snapshot.visible_entry_count()
5040 }
5041 }
5042 }
5043 }
5044
5045 fn prefix(&self) -> Arc<str> {
5046 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
5047 self.snapshot.root_name().into()
5048 } else if self.include_root_name {
5049 format!("{}/", self.snapshot.root_name()).into()
5050 } else {
5051 Arc::default()
5052 }
5053 }
5054
5055 fn candidates(&'a self, start: usize) -> Self::Candidates {
5056 PathMatchCandidateSetIter {
5057 traversal: match self.candidates {
5058 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
5059 Candidates::Files => self.snapshot.files(self.include_ignored, start),
5060 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
5061 },
5062 }
5063 }
5064}
5065
5066pub struct PathMatchCandidateSetIter<'a> {
5067 traversal: Traversal<'a>,
5068}
5069
5070impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
5071 type Item = fuzzy::PathMatchCandidate<'a>;
5072
5073 fn next(&mut self) -> Option<Self::Item> {
5074 self.traversal
5075 .next()
5076 .map(|entry| fuzzy::PathMatchCandidate {
5077 is_dir: entry.kind.is_dir(),
5078 path: &entry.path,
5079 char_bag: entry.char_bag,
5080 })
5081 }
5082}
5083
5084impl EventEmitter<Event> for Project {}
5085
5086impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
5087 fn from(val: &'a ProjectPath) -> Self {
5088 SettingsLocation {
5089 worktree_id: val.worktree_id,
5090 path: val.path.as_ref(),
5091 }
5092 }
5093}
5094
5095impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
5096 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
5097 Self {
5098 worktree_id,
5099 path: path.as_ref().into(),
5100 }
5101 }
5102}
5103
5104pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
5105 let mut path_components = path.components();
5106 let mut base_components = base.components();
5107 let mut components: Vec<Component> = Vec::new();
5108 loop {
5109 match (path_components.next(), base_components.next()) {
5110 (None, None) => break,
5111 (Some(a), None) => {
5112 components.push(a);
5113 components.extend(path_components.by_ref());
5114 break;
5115 }
5116 (None, _) => components.push(Component::ParentDir),
5117 (Some(a), Some(b)) if components.is_empty() && a == b => (),
5118 (Some(a), Some(Component::CurDir)) => components.push(a),
5119 (Some(a), Some(_)) => {
5120 components.push(Component::ParentDir);
5121 for _ in base_components {
5122 components.push(Component::ParentDir);
5123 }
5124 components.push(a);
5125 components.extend(path_components.by_ref());
5126 break;
5127 }
5128 }
5129 }
5130 components.iter().map(|c| c.as_os_str()).collect()
5131}
5132
5133fn resolve_path(base: &Path, path: &Path) -> PathBuf {
5134 let mut result = base.to_path_buf();
5135 for component in path.components() {
5136 match component {
5137 Component::ParentDir => {
5138 result.pop();
5139 }
5140 Component::CurDir => (),
5141 _ => result.push(component),
5142 }
5143 }
5144 result
5145}
5146
5147/// ResolvedPath is a path that has been resolved to either a ProjectPath
5148/// or an AbsPath and that *exists*.
5149#[derive(Debug, Clone)]
5150pub enum ResolvedPath {
5151 ProjectPath(ProjectPath),
5152 AbsPath(PathBuf),
5153}
5154
5155impl Item for Buffer {
5156 fn try_open(
5157 project: &Model<Project>,
5158 path: &ProjectPath,
5159 cx: &mut AppContext,
5160 ) -> Option<Task<Result<Model<Self>>>> {
5161 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
5162 }
5163
5164 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
5165 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
5166 }
5167
5168 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
5169 File::from_dyn(self.file()).map(|file| ProjectPath {
5170 worktree_id: file.worktree_id(cx),
5171 path: file.path().clone(),
5172 })
5173 }
5174}
5175
5176impl Completion {
5177 /// A key that can be used to sort completions when displaying
5178 /// them to the user.
5179 pub fn sort_key(&self) -> (usize, &str) {
5180 let kind_key = match self.lsp_completion.kind {
5181 Some(lsp::CompletionItemKind::KEYWORD) => 0,
5182 Some(lsp::CompletionItemKind::VARIABLE) => 1,
5183 _ => 2,
5184 };
5185 (kind_key, &self.label.text[self.label.filter_range.clone()])
5186 }
5187
5188 /// Whether this completion is a snippet.
5189 pub fn is_snippet(&self) -> bool {
5190 self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
5191 }
5192}
5193
5194#[derive(Debug)]
5195pub struct NoRepositoryError {}
5196
5197impl std::fmt::Display for NoRepositoryError {
5198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5199 write!(f, "no git repository for worktree found")
5200 }
5201}
5202
5203impl std::error::Error for NoRepositoryError {}
5204
5205fn serialize_location(location: &Location, cx: &AppContext) -> proto::Location {
5206 proto::Location {
5207 buffer_id: location.buffer.read(cx).remote_id().into(),
5208 start: Some(serialize_anchor(&location.range.start)),
5209 end: Some(serialize_anchor(&location.range.end)),
5210 }
5211}
5212
5213fn deserialize_location(
5214 project: &Model<Project>,
5215 location: proto::Location,
5216 cx: &mut AppContext,
5217) -> Task<Result<Location>> {
5218 let buffer_id = match BufferId::new(location.buffer_id) {
5219 Ok(id) => id,
5220 Err(e) => return Task::ready(Err(e)),
5221 };
5222 let buffer_task = project.update(cx, |project, cx| {
5223 project.wait_for_remote_buffer(buffer_id, cx)
5224 });
5225 cx.spawn(|_| async move {
5226 let buffer = buffer_task.await?;
5227 let start = location
5228 .start
5229 .and_then(deserialize_anchor)
5230 .context("missing task context location start")?;
5231 let end = location
5232 .end
5233 .and_then(deserialize_anchor)
5234 .context("missing task context location end")?;
5235 Ok(Location {
5236 buffer,
5237 range: start..end,
5238 })
5239 })
5240}
5241
5242pub fn sort_worktree_entries(entries: &mut [Entry]) {
5243 entries.sort_by(|entry_a, entry_b| {
5244 compare_paths(
5245 (&entry_a.path, entry_a.is_file()),
5246 (&entry_b.path, entry_b.is_file()),
5247 )
5248 });
5249}