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