Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
576c72c
add setting
marcocondrache Dec 14, 2025
408989c
wip
marcocondrache Dec 14, 2025
0afdc8e
move easing function
marcocondrache Dec 14, 2025
a647739
use easing fn
marcocondrache Dec 14, 2025
2322376
make duration configurable
marcocondrache Dec 14, 2025
8efc7f5
forgot this
marcocondrache Dec 14, 2025
a905674
move duration to settings
marcocondrache Dec 14, 2025
c7c14f8
140ms is the optimal default
marcocondrache Dec 15, 2025
f6abc30
delegate to manager
marcocondrache Dec 15, 2025
5b8c5b1
revert fmt
marcocondrache Dec 15, 2025
005b280
vim: this was bypassing animation
marcocondrache Dec 15, 2025
22e01ee
vim: make smooth scrolling work
marcocondrache Dec 15, 2025
ce5c212
comment
marcocondrache Dec 15, 2025
579f8ef
avoid sync to db and events
marcocondrache Dec 15, 2025
1e88aa1
update default
marcocondrache Dec 15, 2025
bcc7db0
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Dec 15, 2025
1e0fcbc
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Dec 25, 2025
6c9895c
cleanup
marcocondrache Dec 16, 2025
1edfb8b
run animation based on delta precision
marcocondrache Dec 16, 2025
24a8804
small stuff
marcocondrache Dec 16, 2025
273bb98
vim actions now work
marcocondrache Dec 16, 2025
e4a8213
use a single method
marcocondrache Dec 16, 2025
f9b4cd7
stop animation when we set position directly
marcocondrache Dec 16, 2025
3ae7336
fix stale anchor
marcocondrache Dec 16, 2025
bd178d9
fmt
marcocondrache Dec 18, 2025
2d9ca6a
improve settings and lower default to 125ms
marcocondrache Dec 25, 2025
92b8786
make interpolation more explicit
marcocondrache Dec 25, 2025
c46a5f8
fmt
marcocondrache Jan 1, 2026
95ec7ab
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 5, 2026
d674eb0
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 6, 2026
ba0809f
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 8, 2026
81f59aa
conflicts
marcocondrache Jan 8, 2026
d825191
readd settings
marcocondrache Jan 8, 2026
1e03c97
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 11, 2026
562b234
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 13, 2026
4d33ece
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 16, 2026
354192c
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 17, 2026
29697e1
fix: todo
marcocondrache Jan 17, 2026
ea9c615
add docs
marcocondrache Jan 17, 2026
b8f89a6
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 20, 2026
477f1a6
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 21, 2026
a636a0b
revert
marcocondrache Jan 21, 2026
ffdf2f6
revert
marcocondrache Jan 21, 2026
0dd32cc
use a single method
marcocondrache Jan 21, 2026
93b5271
update callers
marcocondrache Jan 21, 2026
03b0191
this is more correct
marcocondrache Jan 21, 2026
4c680f5
revert
marcocondrache Jan 21, 2026
ee79325
use snapshot
marcocondrache Jan 21, 2026
38dd5c6
ops
marcocondrache Jan 21, 2026
eaecfe3
read docs
marcocondrache Jan 21, 2026
2b5e8e8
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 22, 2026
fb71957
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 24, 2026
ed676d1
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 28, 2026
dde4a80
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jan 28, 2026
4887c62
fix merge leftover
marcocondrache Jan 31, 2026
08f8c54
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Feb 1, 2026
f5fdfe8
cargo fmt
marcocondrache Feb 1, 2026
ddc24db
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Feb 5, 2026
efa70ff
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Feb 5, 2026
4665173
editor: Treat regular and animated scroll as one
marcocondrache Feb 5, 2026
ef1ed53
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Feb 15, 2026
037ef66
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Feb 19, 2026
2c06a7d
fix: new tests
marcocondrache Feb 20, 2026
642d169
fix: scroll screen test
marcocondrache Feb 20, 2026
3c15aa7
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Mar 1, 2026
efd3b7a
fix negative duration
marcocondrache Mar 1, 2026
3e8fd59
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Mar 2, 2026
bfe9e4f
Merge remote-tracking branch 'upstream/main' into feat/smooth-scrolling
marcocondrache Mar 2, 2026
7355d20
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Mar 10, 2026
0d8fd32
second iteration
marcocondrache Mar 10, 2026
97119a7
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Mar 12, 2026
b2d2f6a
preserve scroll velocity
marcocondrache Mar 13, 2026
3addcc6
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Apr 21, 2026
010562b
Merge branch 'main' into feat/smooth-scrolling
marcocondrache May 11, 2026
5bfe7e6
Merge branch 'main' into feat/smooth-scrolling
marcocondrache May 13, 2026
6f39444
Fix merge with upstream
marcocondrache May 13, 2026
177165a
Revert changes on /crates/vim/src/normal/scroll.rs
marcocondrache May 13, 2026
2f5e431
Merge branch 'main' into feat/smooth-scrolling
marcocondrache May 16, 2026
3cd92f7
Merge branch 'main' into feat/smooth-scrolling
marcocondrache May 31, 2026
6f7c4bf
Prepare for tests impl
marcocondrache May 31, 2026
de5494f
Add scroll animation unit tests
marcocondrache May 31, 2026
dddcd0e
Test instant request during animation
marcocondrache May 31, 2026
e78ad40
Test setting change during animation
marcocondrache May 31, 2026
dd7d7f6
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jun 1, 2026
249c69f
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jun 14, 2026
9558b63
fix: Failing test
marcocondrache Jun 14, 2026
6461d3b
fix: Vim scroll
marcocondrache Jun 14, 2026
4640666
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jun 19, 2026
af6d534
fix: Behaviour edge case and tests
marcocondrache Jun 20, 2026
210df1e
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jun 22, 2026
b51d60d
fix: Jittering
marcocondrache Jun 22, 2026
1be94ce
Merge branch 'main' into feat/smooth-scrolling
marcocondrache Jun 23, 2026
37b2cbe
fix: Scroll jump
marcocondrache Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,10 @@
// to both the horizontal and vertical delta values while scrolling. Fast scrolling
// happens when a user holds the alt or option key while scrolling.
"fast_scroll_sensitivity": 4.0,
"smooth_scroll": {
// Whether to animate scrolling with a smooth easing effect.
"enabled": false,
},
"sticky_scroll": {
// Whether to stick scopes to the top of the editor.
"enabled": false,
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9683,6 +9683,7 @@ impl Editor {
{
let editor_settings = EditorSettings::get_global(cx);
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.scroll_manager.smooth_scroll = editor_settings.smooth_scroll.enabled;
if self.breadcrumbs_visibility.settings_visibility()
!= editor_settings.toolbar.breadcrumbs
{
Expand Down
10 changes: 10 additions & 0 deletions crates/editor/src/editor_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct EditorSettings {
pub scroll_sensitivity: f32,
pub mouse_wheel_zoom: bool,
pub fast_scroll_sensitivity: f32,
pub smooth_scroll: SmoothScroll,
pub sticky_scroll: StickyScroll,
pub relative_line_numbers: RelativeLineNumbers,
pub seed_search_query_from_cursor: SeedQuerySetting,
Expand Down Expand Up @@ -82,6 +83,11 @@ pub struct StickyScroll {
pub enabled: bool,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SmoothScroll {
pub enabled: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Toolbar {
pub breadcrumbs: bool,
Expand Down Expand Up @@ -203,6 +209,7 @@ impl Settings for EditorSettings {
let search = editor.search.unwrap();
let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
let sticky_scroll = editor.sticky_scroll.unwrap();
let smooth_scroll = editor.smooth_scroll.unwrap();
Self {
cursor_blink: editor.cursor_blink.unwrap(),
cursor_shape: editor.cursor_shape.map(Into::into),
Expand Down Expand Up @@ -264,6 +271,9 @@ impl Settings for EditorSettings {
scroll_sensitivity: editor.scroll_sensitivity.unwrap(),
mouse_wheel_zoom: editor.mouse_wheel_zoom.unwrap(),
fast_scroll_sensitivity: editor.fast_scroll_sensitivity.unwrap(),
smooth_scroll: SmoothScroll {
enabled: smooth_scroll.enabled.unwrap(),
},
sticky_scroll: StickyScroll {
enabled: sticky_scroll.enabled.unwrap(),
},
Expand Down
209 changes: 199 additions & 10 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
element::{StickyHeader, header_jump_data},
linked_editing_ranges::LinkedEditingRanges,
runnables::RunnableTasks,
scroll::scroll_amount::ScrollAmount,
scroll::{ScrollBehavior, scroll_amount::ScrollAmount},
test::{
assert_text_with_selections, build_editor, editor_content_with_blocks,
editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
Expand All @@ -19,7 +19,7 @@ use collections::HashMap;
use futures::{StreamExt, channel::oneshot};
use gpui::{
BackgroundExecutor, DismissEvent, Task, TaskExt, TestAppContext, UpdateGlobal,
VisualTestContext, WindowBounds, WindowOptions, div,
VisualTestContext, WindowBounds, WindowOptions, div, point,
};
use indoc::indoc;
use language::{
Expand Down Expand Up @@ -2864,6 +2864,142 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
cx.assert_editor_state(&"ˇone\ntwo\n \nthree\nfour\nfive\n\nsix");
}

#[gpui::test]
async fn test_instant_scroll_request_during_scroll_animation(cx: &mut TestAppContext) {
init_test(cx, |_| {});
update_test_editor_settings(cx, &|settings| {
settings.smooth_scroll = Some(settings::SmoothScrollContent {
enabled: Some(true),
});
});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.update_editor(|editor, window, cx| {
editor.set_vertical_scroll_margin(0, cx);
editor
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
let window = cx.window;
cx.simulate_window_resize(window, size(px(1000.), 4. * line_height));
cx.set_state(indoc! {"
ˇone
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
"});

cx.update_editor(|editor, window, cx| {
editor.scroll(
point(0., 8.),
None,
Some(ScrollBehavior::RequestAnimation),
window,
cx,
);
assert!(
editor
.scroll_manager
.scroll_animation()
.is_some_and(|animation| animation.is_animating())
);

editor.scroll(
point(0., 2.),
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
assert!(
editor
.scroll_manager
.scroll_animation()
.is_some_and(|animation| animation.is_finished())
);

editor.flush_scroll_animation(window, cx);
assert_eq!(editor.snapshot(window, cx).scroll_position(), point(0., 2.));
assert!(editor.scroll_manager.scroll_animation().is_none());
});
}

#[gpui::test]
async fn test_smooth_scroll_setting_update_during_animation(cx: &mut TestAppContext) {
init_test(cx, |_| {});
update_test_editor_settings(cx, &|settings| {
settings.smooth_scroll = Some(settings::SmoothScrollContent {
enabled: Some(true),
});
});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.update_editor(|editor, window, cx| {
editor.set_vertical_scroll_margin(0, cx);
editor
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
let window = cx.window;
cx.simulate_window_resize(window, size(px(1000.), 4. * line_height));
cx.set_state(indoc! {"
ˇone
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
"});

cx.update_editor(|editor, window, cx| {
assert!(editor.scroll_manager.smooth_scroll);
editor.scroll(point(0., 8.), None, None, window, cx);
assert!(
editor
.scroll_manager
.scroll_animation()
.is_some_and(|animation| animation.is_animating())
);
});

cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.editor.smooth_scroll = Some(settings::SmoothScrollContent {
enabled: Some(false),
});
});
});
});
cx.run_until_parked();

cx.update_editor(|editor, window, cx| {
assert!(!editor.scroll_manager.smooth_scroll);

editor.set_scroll_top_row(DisplayRow(3), window, cx);
assert_eq!(editor.snapshot(window, cx).scroll_position(), point(0., 3.));
assert!(editor.scroll_manager.scroll_animation().is_none());

editor.set_scroll_top_row(DisplayRow(6), window, cx);
assert_eq!(editor.snapshot(window, cx).scroll_position(), point(0., 6.));
assert!(editor.scroll_manager.scroll_animation().is_none());
});
}

#[gpui::test]
async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
Expand Down Expand Up @@ -2896,28 +3032,37 @@ async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 0.)
);

editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
editor.flush_scroll_animation(window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 3.)
);

editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
editor.flush_scroll_animation(window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 6.)
);

editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
editor.flush_scroll_animation(window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 3.)
);

editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
editor.flush_scroll_animation(window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 1.)
);

editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
editor.flush_scroll_animation(window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 3.)
Expand Down Expand Up @@ -26065,7 +26210,13 @@ async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
cx.set_state("ˇnew\nsecond\nthird\n");
cx.set_head_text("old\nsecond\nthird\n");
cx.update_editor(|editor, window, cx| {
editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
editor.scroll(
gpui::Point { x: 0., y: 0. },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
executor.run_until_parked();
assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
Expand Down Expand Up @@ -33919,7 +34070,13 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) {

let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
e.scroll(
gpui::Point { x: 0., y: offset },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();
cx.update_editor(|e, window, cx| {
Expand Down Expand Up @@ -34008,7 +34165,13 @@ async fn test_sticky_scroll_with_decoration_prefix_in_item(cx: &mut TestAppConte

let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
e.scroll(
gpui::Point { x: 0., y: offset },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();
cx.update_editor(|e, window, cx| {
Expand Down Expand Up @@ -34071,7 +34234,7 @@ async fn test_sticky_scroll_anchors_multiline_c_signature_on_name_row(cx: &mut T

let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|editor, window, cx| {
editor.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
editor.scroll(gpui::Point { x: 0., y: offset }, None, None, window, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, window, cx| {
Expand Down Expand Up @@ -34154,7 +34317,13 @@ async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(

let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
e.scroll(
gpui::Point { x: 0., y: offset },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();
cx.update_editor(|e, window, cx| {
Expand Down Expand Up @@ -34210,7 +34379,13 @@ async fn test_no_duplicated_sticky_headers(cx: &mut TestAppContext) {

let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
e.scroll(
gpui::Point { x: 0., y: offset },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();
cx.update_editor(|e, window, cx| {
Expand Down Expand Up @@ -34507,9 +34682,11 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
y: scroll_offset,
},
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
e.flush_scroll_animation(window, cx);
});
cx.run_until_parked();
cx.simulate_click(
Expand Down Expand Up @@ -34598,7 +34775,13 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
// The text "impl Bar {" starts at column 0, so column 5 = 'B'.
let click_x = text_origin_x + em_width * 5.5;
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: 4.5 }, None, window, cx);
e.scroll(
gpui::Point { x: 0., y: 4.5 },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();
cx.simulate_click(
Expand Down Expand Up @@ -34672,7 +34855,13 @@ async fn test_clicking_sticky_header_sets_character_select_mode(cx: &mut TestApp
editor.end_selection(window, cx);

// Scroll down one row to make `fn foo() {` a sticky header
editor.scroll(gpui::Point { x: 0., y: 1. }, None, window, cx);
editor.scroll(
gpui::Point { x: 0., y: 1. },
None,
Some(ScrollBehavior::Instant),
window,
cx,
);
});
cx.run_until_parked();

Expand Down
8 changes: 8 additions & 0 deletions crates/editor/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7975,6 +7975,14 @@ impl Element for EditorElement {
);
editor.set_visible_column_count(f64::from(editor_width / em_advance));

if let Some(animation) = editor.scroll_manager.update_animation() {
editor.set_scroll_position(animation.position(), window, cx);

if animation.is_animating() {
window.request_animation_frame();
}
}

if matches!(
editor.mode,
EditorMode::AutoHeight { .. } | EditorMode::Minimap { .. }
Expand Down
Loading
Loading