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