diff --git a/crates/editor/src/folding_ranges.rs b/crates/editor/src/folding_ranges.rs index c59a3e004a8b4f791af2d44be19878239ece1d42..6c1db5f3ee908602e11adaf57fe18847fe10db64 100644 --- a/crates/editor/src/folding_ranges.rs +++ b/crates/editor/src/folding_ranges.rs @@ -16,7 +16,7 @@ impl Editor { if !self.lsp_data_enabled() || !self.use_document_folding_ranges { return; } - let Some(project) = self.project.clone() else { + let Some(project) = self.project.as_ref().map(|p| p.downgrade()) else { return; }; @@ -43,7 +43,8 @@ impl Editor { let Some(tasks) = editor .update(cx, |_, cx| { - project.read(cx).lsp_store().update(cx, |lsp_store, cx| { + let project = project.upgrade()?; + Some(project.read(cx).lsp_store().update(cx, |lsp_store, cx| { buffers_to_query .into_iter() .map(|buffer| { @@ -52,9 +53,10 @@ impl Editor { async move { (buffer_id, task.await) } }) .collect::>() - }) + })) }) .ok() + .flatten() else { return; }; diff --git a/crates/editor/src/semantic_tokens.rs b/crates/editor/src/semantic_tokens.rs index 29c998ce976fee988e027db64ebd1f35f709f0af..23ce7c41be7550ff42d2c946e5e9a9147ee4f015 100644 --- a/crates/editor/src/semantic_tokens.rs +++ b/crates/editor/src/semantic_tokens.rs @@ -142,7 +142,10 @@ impl Editor { ); } - let Some((sema, project)) = self.semantics_provider.clone().zip(self.project.clone()) + let Some((sema, project)) = self + .semantics_provider + .clone() + .zip(self.project.as_ref().map(|p| p.downgrade())) else { return; }; @@ -283,6 +286,9 @@ impl Editor { .buffer(buffer_id) .and_then(|buf| buf.read(cx).language().map(|l| l.name())); + let Some(project) = project.upgrade() else { + return; + }; editor.display_map.update(cx, |display_map, cx| { project.read(cx).lsp_store().update(cx, |lsp_store, cx| { let mut token_highlights = Vec::new(); diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 52c16e8a2ba7106fb74ae083bc49febc644a4711..9f97f829b0cec408840070e16c87e014217376d8 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -1848,6 +1848,10 @@ impl GitStore { if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event { let buffer_id = diff.read(cx).buffer_id; if let Some(diff_state) = self.diffs.get(&buffer_id) { + let new_index_text = new_index_text.as_ref().map(|rope| rope.to_string()); + if new_index_text.as_deref() == diff_state.read(cx).index_text.as_deref() { + return; + } let hunk_staging_operation_count = diff_state.update(cx, |diff_state, _| { diff_state.hunk_staging_operation_count += 1; diff_state.hunk_staging_operation_count @@ -1857,7 +1861,7 @@ impl GitStore { log::debug!("hunks changed for {}", path.as_unix_str()); repo.spawn_set_index_text_job( path, - new_index_text.as_ref().map(|rope| rope.to_string()), + new_index_text, Some(hunk_staging_operation_count), cx, ) diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index d31403275cbb140b4f659a6ebeb3dd3405668a6f..840485e67506dc75a6a31b406f7965ba746cce54 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -2624,6 +2624,115 @@ async fn test_remote_apply_code_action_skips_unadvertised_command( assert_eq!(transaction.0.len(), 0); } +#[gpui::test] +async fn test_remote_restore_unstaged_hunk_clears_diff( + cx: &mut TestAppContext, + server_cx: &mut TestAppContext, +) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + theme_settings::init(theme::LoadThemes::JustBase, cx); + release_channel::init(semver::Version::new(0, 0, 0), cx); + editor::init(cx); + }); + + use editor::Editor; + use gpui::VisualContext; + + let base_text = " + fn one() -> usize { + 1 + } + " + .unindent(); + let modified_text = " + fn one() -> usize { + 100 + } + " + .unindent(); + + let fs = FakeFs::new(server_cx.executor()); + fs.insert_tree( + path!("/code"), + json!({ + "project1": { + ".git": {}, + "src": { + "lib.rs": modified_text + }, + }, + }), + ) + .await; + fs.set_index_for_repo( + Path::new(path!("/code/project1/.git")), + &[("src/lib.rs", base_text.clone())], + ); + fs.set_head_for_repo( + Path::new(path!("/code/project1/.git")), + &[("src/lib.rs", base_text.clone())], + "deadbeef", + ); + + let (project, _headless) = init_test(&fs, cx, server_cx).await; + let worktree_id = { + let (worktree, _) = project + .update(cx, |project, cx| { + project.find_or_create_worktree(path!("/code/project1"), true, cx) + }) + .await + .unwrap(); + cx.update(|cx| worktree.read(cx).id()) + }; + cx.executor().run_until_parked(); + + let buffer = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) + }) + .await + .unwrap(); + + let cx = cx.add_empty_window(); + let editor = cx.new_window_entity(|window, cx| { + Editor::for_buffer(buffer, Some(project.clone()), window, cx) + }); + cx.executor().run_until_parked(); + + editor.update_in(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let hunks: Vec<_> = editor + .diff_hunks_in_ranges( + &[editor::Anchor::Min..editor::Anchor::Max], + &snapshot.buffer_snapshot(), + ) + .collect(); + assert!(!hunks.is_empty(), "should have diff hunks before restore"); + }); + + cx.update_window_entity(&editor, |editor, window, cx| { + editor.select_all(&editor::actions::SelectAll, window, cx); + editor.git_restore(&git::Restore, window, cx); + }); + cx.executor().run_until_parked(); + + editor.update_in(cx, |editor, _window, cx| { + let snapshot = editor.buffer().read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + base_text, + "buffer text should match base after restoring all hunks" + ); + + let hunks: Vec<_> = editor + .diff_hunks_in_ranges(&[editor::Anchor::Min..editor::Anchor::Max], &snapshot) + .collect(); + assert!(hunks.is_empty(), "should have no diff hunks after restore"); + }); +} + pub async fn init_test( server_fs: &Arc, cx: &mut TestAppContext,