editor: Add access method for `project` (#36266)

Finn Evers created

This resolves a `TODO` that I've stumbled upon too many times whilst
looking at the editor code.

Release Notes:

- N/A

Change summary

crates/diagnostics/src/diagnostics_tests.rs    | 10 ++--
crates/editor/src/editor.rs                    | 36 ++++++++++---------
crates/editor/src/editor_tests.rs              |  2 
crates/editor/src/hover_popover.rs             |  2 
crates/editor/src/items.rs                     |  2 
crates/editor/src/linked_editing_ranges.rs     |  2 
crates/editor/src/signature_help.rs            |  2 
crates/editor/src/test/editor_test_context.rs  | 20 ++++------
crates/git_ui/src/conflict_view.rs             |  4 +-
crates/vim/src/command.rs                      |  4 +-
crates/zed/src/zed/edit_prediction_registry.rs |  3 -
11 files changed, 42 insertions(+), 45 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics_tests.rs πŸ”—

@@ -971,7 +971,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
 
     let mut cx = EditorTestContext::new(cx).await;
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
 
     cx.set_state(indoc! {"
         Λ‡fn func(abc def: i32) -> u32 {
@@ -1065,7 +1065,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     let mut cx = EditorTestContext::new(cx).await;
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
 
     cx.set_state(indoc! {"
         Λ‡fn func(abc def: i32) -> u32 {
@@ -1239,7 +1239,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
         }
     "});
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
 
     cx.update(|_, cx| {
         lsp_store.update(cx, |lsp_store, cx| {
@@ -1293,7 +1293,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext)
         fn Β«testΒ»() { println!(); }
     "});
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
     cx.update(|_, cx| {
         lsp_store.update(cx, |lsp_store, cx| {
             lsp_store.update_diagnostics(
@@ -1450,7 +1450,7 @@ async fn go_to_diagnostic_with_severity(cx: &mut TestAppContext) {
 
     let mut cx = EditorTestContext::new(cx).await;
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
 
     cx.set_state(indoc! {"error warning info hiˇnt"});
 

crates/editor/src/editor.rs πŸ”—

@@ -1039,9 +1039,7 @@ pub struct Editor {
     inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
     soft_wrap_mode_override: Option<language_settings::SoftWrap>,
     hard_wrap: Option<usize>,
-
-    // TODO: make this a access method
-    pub project: Option<Entity<Project>>,
+    project: Option<Entity<Project>>,
     semantics_provider: Option<Rc<dyn SemanticsProvider>>,
     completion_provider: Option<Rc<dyn CompletionProvider>>,
     collaboration_hub: Option<Box<dyn CollaborationHub>>,
@@ -2326,7 +2324,7 @@ impl Editor {
             editor.go_to_active_debug_line(window, cx);
 
             if let Some(buffer) = buffer.read(cx).as_singleton() {
-                if let Some(project) = editor.project.as_ref() {
+                if let Some(project) = editor.project() {
                     let handle = project.update(cx, |project, cx| {
                         project.register_buffer_with_language_servers(&buffer, cx)
                     });
@@ -2626,6 +2624,10 @@ impl Editor {
         &self.buffer
     }
 
+    pub fn project(&self) -> Option<&Entity<Project>> {
+        self.project.as_ref()
+    }
+
     pub fn workspace(&self) -> Option<Entity<Workspace>> {
         self.workspace.as_ref()?.0.upgrade()
     }
@@ -5212,7 +5214,7 @@ impl Editor {
         restrict_to_languages: Option<&HashSet<Arc<Language>>>,
         cx: &mut Context<Editor>,
     ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
-        let Some(project) = self.project.as_ref() else {
+        let Some(project) = self.project() else {
             return HashMap::default();
         };
         let project = project.read(cx);
@@ -5294,7 +5296,7 @@ impl Editor {
             return None;
         }
 
-        let project = self.project.as_ref()?;
+        let project = self.project()?;
         let position = self.selections.newest_anchor().head();
         let (buffer, buffer_position) = self
             .buffer
@@ -6141,7 +6143,7 @@ impl Editor {
         cx: &mut App,
     ) -> Task<Vec<task::DebugScenario>> {
         maybe!({
-            let project = self.project.as_ref()?;
+            let project = self.project()?;
             let dap_store = project.read(cx).dap_store();
             let mut scenarios = vec![];
             let resolved_tasks = resolved_tasks.as_ref()?;
@@ -7907,7 +7909,7 @@ impl Editor {
         let snapshot = self.snapshot(window, cx);
 
         let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
-        let Some(project) = self.project.as_ref() else {
+        let Some(project) = self.project() else {
             return breakpoint_display_points;
         };
 
@@ -10501,7 +10503,7 @@ impl Editor {
     ) {
         if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
             let project_path = buffer.read(cx).project_path(cx)?;
-            let project = self.project.as_ref()?.read(cx);
+            let project = self.project()?.read(cx);
             let entry = project.entry_for_path(&project_path, cx)?;
             let parent = match &entry.canonical_path {
                 Some(canonical_path) => canonical_path.to_path_buf(),
@@ -14875,7 +14877,7 @@ impl Editor {
             self.clear_tasks();
             return Task::ready(());
         }
-        let project = self.project.as_ref().map(Entity::downgrade);
+        let project = self.project().map(Entity::downgrade);
         let task_sources = self.lsp_task_sources(cx);
         let multi_buffer = self.buffer.downgrade();
         cx.spawn_in(window, async move |editor, cx| {
@@ -17054,7 +17056,7 @@ impl Editor {
         if !pull_diagnostics_settings.enabled {
             return None;
         }
-        let project = self.project.as_ref()?.downgrade();
+        let project = self.project()?.downgrade();
         let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
         let mut buffers = self.buffer.read(cx).all_buffers();
         if let Some(buffer_id) = buffer_id {
@@ -18018,7 +18020,7 @@ impl Editor {
         hunks: impl Iterator<Item = MultiBufferDiffHunk>,
         cx: &mut App,
     ) -> Option<()> {
-        let project = self.project.as_ref()?;
+        let project = self.project()?;
         let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
         let diff = self.buffer.read(cx).diff_for(buffer_id)?;
         let buffer_snapshot = buffer.read(cx).snapshot();
@@ -18678,7 +18680,7 @@ impl Editor {
         self.active_excerpt(cx).and_then(|(_, buffer, _)| {
             let buffer = buffer.read(cx);
             if let Some(project_path) = buffer.project_path(cx) {
-                let project = self.project.as_ref()?.read(cx);
+                let project = self.project()?.read(cx);
                 project.absolute_path(&project_path, cx)
             } else {
                 buffer
@@ -18691,7 +18693,7 @@ impl Editor {
     fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
         self.active_excerpt(cx).and_then(|(_, buffer, _)| {
             let project_path = buffer.read(cx).project_path(cx)?;
-            let project = self.project.as_ref()?.read(cx);
+            let project = self.project()?.read(cx);
             let entry = project.entry_for_path(&project_path, cx)?;
             let path = entry.path.to_path_buf();
             Some(path)
@@ -18912,7 +18914,7 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        if let Some(project) = self.project.as_ref() {
+        if let Some(project) = self.project() {
             let Some(buffer) = self.buffer().read(cx).as_singleton() else {
                 return;
             };
@@ -19028,7 +19030,7 @@ impl Editor {
             return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
         };
 
-        let Some(project) = self.project.as_ref() else {
+        let Some(project) = self.project() else {
             return Task::ready(Err(anyhow!("editor does not have project")));
         };
 
@@ -21015,7 +21017,7 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         let workspace = self.workspace();
-        let project = self.project.as_ref();
+        let project = self.project();
         let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
             let mut tasks = Vec::new();
             for (buffer_id, changes) in revert_changes {

crates/editor/src/editor_tests.rs πŸ”—

@@ -15082,7 +15082,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
 
     let mut cx = EditorTestContext::new(cx).await;
     let lsp_store =
-        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
 
     cx.set_state(indoc! {"
         Λ‡fn func(abc def: i32) -> u32 {

crates/editor/src/hover_popover.rs πŸ”—

@@ -251,7 +251,7 @@ fn show_hover(
 
     let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;
 
-    let language_registry = editor.project.as_ref()?.read(cx).languages().clone();
+    let language_registry = editor.project()?.read(cx).languages().clone();
     let provider = editor.semantics_provider.clone()?;
 
     if !ignore_timeout {

crates/editor/src/items.rs πŸ”—

@@ -678,7 +678,7 @@ impl Item for Editor {
                     let buffer = buffer.read(cx);
                     let path = buffer.project_path(cx)?;
                     let buffer_id = buffer.remote_id();
-                    let project = self.project.as_ref()?.read(cx);
+                    let project = self.project()?.read(cx);
                     let entry = project.entry_for_path(&path, cx)?;
                     let (repo, repo_path) = project
                         .git_store()

crates/editor/src/linked_editing_ranges.rs πŸ”—

@@ -51,7 +51,7 @@ pub(super) fn refresh_linked_ranges(
     if editor.pending_rename.is_some() {
         return None;
     }
-    let project = editor.project.as_ref()?.downgrade();
+    let project = editor.project()?.downgrade();
 
     editor.linked_editing_range_task = Some(cx.spawn_in(window, async move |editor, cx| {
         cx.background_executor().timer(UPDATE_DEBOUNCE).await;

crates/editor/src/signature_help.rs πŸ”—

@@ -169,7 +169,7 @@ impl Editor {
         else {
             return;
         };
-        let Some(lsp_store) = self.project.as_ref().map(|p| p.read(cx).lsp_store()) else {
+        let Some(lsp_store) = self.project().map(|p| p.read(cx).lsp_store()) else {
             return;
         };
         let task = lsp_store.update(cx, |lsp_store, cx| {

crates/editor/src/test/editor_test_context.rs πŸ”—

@@ -297,9 +297,8 @@ impl EditorTestContext {
 
     pub fn set_head_text(&mut self, diff_base: &str) {
         self.cx.run_until_parked();
-        let fs = self.update_editor(|editor, _, cx| {
-            editor.project.as_ref().unwrap().read(cx).fs().as_fake()
-        });
+        let fs =
+            self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
         let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
         fs.set_head_for_repo(
             &Self::root_path().join(".git"),
@@ -311,18 +310,16 @@ impl EditorTestContext {
 
     pub fn clear_index_text(&mut self) {
         self.cx.run_until_parked();
-        let fs = self.update_editor(|editor, _, cx| {
-            editor.project.as_ref().unwrap().read(cx).fs().as_fake()
-        });
+        let fs =
+            self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
         fs.set_index_for_repo(&Self::root_path().join(".git"), &[]);
         self.cx.run_until_parked();
     }
 
     pub fn set_index_text(&mut self, diff_base: &str) {
         self.cx.run_until_parked();
-        let fs = self.update_editor(|editor, _, cx| {
-            editor.project.as_ref().unwrap().read(cx).fs().as_fake()
-        });
+        let fs =
+            self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
         let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
         fs.set_index_for_repo(
             &Self::root_path().join(".git"),
@@ -333,9 +330,8 @@ impl EditorTestContext {
 
     #[track_caller]
     pub fn assert_index_text(&mut self, expected: Option<&str>) {
-        let fs = self.update_editor(|editor, _, cx| {
-            editor.project.as_ref().unwrap().read(cx).fs().as_fake()
-        });
+        let fs =
+            self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
         let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
         let mut found = None;
         fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| {

crates/git_ui/src/conflict_view.rs πŸ”—

@@ -112,7 +112,7 @@ fn excerpt_for_buffer_updated(
 }
 
 fn buffer_added(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
-    let Some(project) = &editor.project else {
+    let Some(project) = editor.project() else {
         return;
     };
     let git_store = project.read(cx).git_store().clone();
@@ -469,7 +469,7 @@ pub(crate) fn resolve_conflict(
         let Some((workspace, project, multibuffer, buffer)) = editor
             .update(cx, |editor, cx| {
                 let workspace = editor.workspace()?;
-                let project = editor.project.clone()?;
+                let project = editor.project()?.clone();
                 let multibuffer = editor.buffer().clone();
                 let buffer_id = resolved_conflict.ours.end.buffer_id?;
                 let buffer = multibuffer.read(cx).buffer(buffer_id)?;

crates/vim/src/command.rs πŸ”—

@@ -299,7 +299,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
 
     Vim::action(editor, cx, |vim, action: &VimSave, window, cx| {
         vim.update_editor(cx, |_, editor, cx| {
-            let Some(project) = editor.project.clone() else {
+            let Some(project) = editor.project().cloned() else {
                 return;
             };
             let Some(worktree) = project.read(cx).visible_worktrees(cx).next() else {
@@ -436,7 +436,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
             let Some(workspace) = vim.workspace(window) else {
                 return;
             };
-            let Some(project) = editor.project.clone() else {
+            let Some(project) = editor.project().cloned() else {
                 return;
             };
             let Some(worktree) = project.read(cx).visible_worktrees(cx).next() else {

crates/zed/src/zed/edit_prediction_registry.rs πŸ”—

@@ -229,8 +229,7 @@ fn assign_edit_prediction_provider(
                     if let Some(file) = buffer.read(cx).file() {
                         let id = file.worktree_id(cx);
                         if let Some(inner_worktree) = editor
-                            .project
-                            .as_ref()
+                            .project()
                             .and_then(|project| project.read(cx).worktree_for_id(id, cx))
                         {
                             worktree = Some(inner_worktree);