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