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