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