Fix scrolling in collab panel (#4105)
When the `List` element's state is `ListState::reset()`, it eagerly trashes it's cached element heights in anticipation of a prompt render. But, due to the recent `display_layer` changes, that re-render is not always forthcoming. This is a problem for `ListState::scroll()`, which depends on these cached elements to correctly calculate the new logical scroll offset. Solutions we attempted: - Cache the element heights and continue the scroll calculation - This was conceptually incorrect, reset should only be called when the underlying data has been changed, making any calculation with the old results meaningless. - Lazily re-compute the element heights in scroll - Beyond being a non-trivial refactor, this would probably also cause us to double-render the list in a single frame, which is bad. - Cache the scroll offset and only calculate it in paint - This solution felt awkward to implement and meant we can't supply synchronous list scroll events. - Delay resetting until paint - This means that all of the other APIs that `ListState` supplies would give temporarily incorrect results, worsening the problem Given these issues, we settled on the solution with the least compromises: drop scroll events if the state has been `reset()` between `paint()` and `scroll()`. This shifts the responsibility for the problem out of the List element and into consumers of `List`, if you want perfectly smooth scrolling then you need to use `reset()` judiciously and prefer `splice()`. That said, I tested this by aggressively scrolling the Collab panel, and it seems to work as well as it did before. This PR also includes some initial testing infrastructure for working with input from the platform and rendered elements. Release Notes: - N/A
This commit is contained in:
@@ -185,31 +185,27 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let window_a = cx_a.add_empty_window();
|
||||
let editor_a =
|
||||
window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
|
||||
|
||||
let mut editor_cx_a = EditorTestContext {
|
||||
cx: VisualTestContext::from_window(window_a, cx_a),
|
||||
window: window_a.into(),
|
||||
cx: cx_a.clone(),
|
||||
window: cx_a.handle(),
|
||||
editor: editor_a,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
};
|
||||
|
||||
let window_b = cx_b.add_empty_window();
|
||||
let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
|
||||
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
.update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let editor_b = window_b.build_view(&mut cx_b, |cx| {
|
||||
Editor::for_buffer(buffer_b, Some(project_b), cx)
|
||||
});
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
|
||||
|
||||
let mut editor_cx_b = EditorTestContext {
|
||||
cx: cx_b,
|
||||
window: window_b.into(),
|
||||
cx: cx_b.clone(),
|
||||
window: cx_b.handle(),
|
||||
editor: editor_b,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
};
|
||||
@@ -311,10 +307,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let window_b = cx_b.add_empty_window();
|
||||
let editor_b = window_b.build_view(cx_b, |cx| {
|
||||
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
|
||||
});
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let editor_b =
|
||||
cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.background_executor.run_until_parked();
|
||||
@@ -323,10 +318,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
assert!(!buffer.completion_triggers().is_empty())
|
||||
});
|
||||
|
||||
let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
|
||||
|
||||
// Type a completion trigger character as the guest.
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(".", cx);
|
||||
});
|
||||
@@ -392,8 +385,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
});
|
||||
|
||||
// Confirm a completion on the guest.
|
||||
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
|
||||
@@ -431,7 +423,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
);
|
||||
});
|
||||
|
||||
buffer_b.read_with(&mut cx_b, |buffer, _| {
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"use d::SomeTrait;\nfn main() { a.first_method() }"
|
||||
@@ -960,7 +952,7 @@ async fn test_share_project(
|
||||
cx_c: &mut TestAppContext,
|
||||
) {
|
||||
let executor = cx_a.executor();
|
||||
let window_b = cx_b.add_empty_window();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -1075,7 +1067,7 @@ async fn test_share_project(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
|
||||
|
||||
// Client A sees client B's selection
|
||||
executor.run_until_parked();
|
||||
@@ -1089,8 +1081,7 @@ async fn test_share_project(
|
||||
});
|
||||
|
||||
// Edit the buffer as client B and see that edit as client A.
|
||||
let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
|
||||
editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx));
|
||||
editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
@@ -1099,7 +1090,7 @@ async fn test_share_project(
|
||||
|
||||
// Client B can invite client C on a project shared by client A.
|
||||
active_call_b
|
||||
.update(&mut cx_b, |call, cx| {
|
||||
.update(cx_b, |call, cx| {
|
||||
call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
|
||||
})
|
||||
.await
|
||||
@@ -1190,12 +1181,8 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let window_a = cx_a.add_empty_window();
|
||||
let editor_a = window_a
|
||||
.update(cx_a, |_, cx| {
|
||||
cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
|
||||
})
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
@@ -1226,10 +1213,9 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut cx_a = VisualTestContext::from_window(window_a, cx_a);
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_a.focus_view(&editor_a);
|
||||
editor_a.update(&mut cx_a, |editor, cx| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(">", cx);
|
||||
});
|
||||
@@ -1241,7 +1227,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_a.update(&mut cx_a, |editor, cx| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a>~< }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
@@ -1252,7 +1238,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
assert_eq!(buffer.text(), "fn main() { a> }")
|
||||
});
|
||||
|
||||
editor_a.update(&mut cx_a, |editor, cx| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
@@ -1323,17 +1309,15 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let window_b = cx_b.add_empty_window();
|
||||
let editor_b = window_b.build_view(cx_b, |cx| {
|
||||
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
|
||||
});
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
|
||||
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_b.focus_view(&editor_b);
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(":", cx);
|
||||
});
|
||||
@@ -1374,7 +1358,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a:~: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
@@ -1385,7 +1369,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
assert_eq!(buffer.text(), "fn main() { a: }")
|
||||
});
|
||||
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
|
||||
Reference in New Issue
Block a user