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