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