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