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