mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Make transcript mouse scroll speed configurable (#50209)
## What changed Add `tui.mouse_scroll_speed` as a finite positive multiplier for transcript mouse wheel scrolling, including transcript overlays and session previews. Change the default from three rows to one row per wheel event; set the multiplier to `3.0` to retain the previous speed. Fractional values accumulate movement across events and discard the remainder when direction reverses. For example, `0.5` scrolls one row every two events. Following the latest output pauses only when a whole row moves. ## Testing Add tests for configuration validation, integer and fractional scroll speeds, direction changes, decimal accumulation, and following behavior. Extend local settings coverage to include a command-line override for the new setting. GitOrigin-RevId: d355e246fd943415f0ce5a6affe849f77f3615e6
This commit is contained in:
@@ -45,6 +45,7 @@ pub mod test_support;
|
||||
mod thread_config;
|
||||
mod tui_effects;
|
||||
mod tui_keymap;
|
||||
mod tui_mouse_scroll;
|
||||
mod tui_rendering;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Keep transcript mouse-scroll speed validation and its configuration schema in sync.
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::Schema;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
|
||||
pub(crate) fn schema(generator: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = Option::<f64>::json_schema(generator).into_object();
|
||||
schema.number().exclusive_minimum = Some(0.0);
|
||||
schema.into()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let speed = Option::<f64>::deserialize(deserializer)?;
|
||||
if speed.is_some_and(|speed| !speed.is_finite() || speed <= 0.0) {
|
||||
return Err(serde::de::Error::custom(
|
||||
"tui.mouse_scroll_speed must be a finite positive number",
|
||||
));
|
||||
}
|
||||
Ok(speed)
|
||||
}
|
||||
@@ -861,6 +861,12 @@ pub struct Tui {
|
||||
#[serde(default = "default_true")]
|
||||
pub fullscreen_transcript: bool,
|
||||
|
||||
/// Mouse wheel speed multiplier for transcript scrolling, based on one row per event.
|
||||
/// Defaults to `1.0`. Positive fractional values slow scrolling; values above `1.0` speed it up.
|
||||
#[serde(default, deserialize_with = "crate::tui_mouse_scroll::deserialize")]
|
||||
#[schemars(schema_with = "crate::tui_mouse_scroll::schema")]
|
||||
pub mouse_scroll_speed: Option<f64>,
|
||||
|
||||
/// Copy selected transcript text when the mouse button is released.
|
||||
/// Defaults to `auto`: enabled except in direct terminals known to forward their native
|
||||
/// copy shortcut (Ghostty 1.2+, Kitty on macOS, Windows Terminal, and VS Code on Windows).
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_speed_accepts_integer_and_fractional_multipliers() {
|
||||
for (value, expected) in [("1", 1.0), ("0.5", 0.5), ("3.0", 3.0)] {
|
||||
let tui: Tui = toml::from_str(&format!("mouse_scroll_speed = {value}")).unwrap();
|
||||
assert_eq!(tui.mouse_scroll_speed, Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_speed_rejects_nonpositive_and_nonfinite_values() {
|
||||
for value in ["0", "-0.5", "nan", "inf", "-inf"] {
|
||||
let error = toml::from_str::<Tui>(&format!("mouse_scroll_speed = {value}")).unwrap_err();
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("tui.mouse_scroll_speed must be a finite positive number"),
|
||||
"{error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_skill_config_with_name_selector() {
|
||||
let cfg: SkillConfig = toml::from_str(
|
||||
|
||||
@@ -4686,6 +4686,13 @@
|
||||
"default": {},
|
||||
"description": "Startup tooltip availability NUX state persisted by the TUI."
|
||||
},
|
||||
"mouse_scroll_speed": {
|
||||
"default": null,
|
||||
"description": "Mouse wheel speed multiplier for transcript scrolling, based on one row per event. Defaults to `1.0`. Positive fractional values slow scrolling; values above `1.0` speed it up.",
|
||||
"exclusiveMinimum": 0.0,
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"notification_condition": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -1269,6 +1269,7 @@ fn config_toml_deserializes_model_availability_nux() {
|
||||
question_esc_back: true,
|
||||
raw_output_mode: false,
|
||||
fullscreen_transcript: true,
|
||||
mouse_scroll_speed: None,
|
||||
copy_on_select: Default::default(),
|
||||
right_click_paste: Default::default(),
|
||||
alternate_screen: AltScreenMode::default(),
|
||||
@@ -4410,6 +4411,7 @@ fn tui_config_missing_notifications_field_defaults_to_enabled() {
|
||||
question_esc_back: true,
|
||||
raw_output_mode: false,
|
||||
fullscreen_transcript: true,
|
||||
mouse_scroll_speed: None,
|
||||
copy_on_select: Default::default(),
|
||||
right_click_paste: Default::default(),
|
||||
alternate_screen: AltScreenMode::Auto,
|
||||
|
||||
@@ -803,6 +803,9 @@ pub struct Config {
|
||||
/// Own the fullscreen transcript when the alternate screen is enabled.
|
||||
pub tui_fullscreen_transcript: bool,
|
||||
|
||||
/// Mouse wheel speed multiplier for transcript scrolling; defaults to one row per event.
|
||||
pub tui_mouse_scroll_speed: Option<f64>,
|
||||
|
||||
/// Override the terminal-specific default for copying transcript mouse selections.
|
||||
pub tui_copy_on_select: codex_config::types::CopyOnSelect,
|
||||
|
||||
@@ -4533,6 +4536,7 @@ impl Config {
|
||||
.tui
|
||||
.as_ref()
|
||||
.is_none_or(|tui| tui.fullscreen_transcript),
|
||||
tui_mouse_scroll_speed: cfg.tui.as_ref().and_then(|tui| tui.mouse_scroll_speed),
|
||||
tui_copy_on_select: cfg
|
||||
.tui
|
||||
.as_ref()
|
||||
|
||||
@@ -290,6 +290,7 @@ async fn new_config(
|
||||
tui_auto_recap: true,
|
||||
model_availability_nux: ModelAvailabilityNuxConfig::default(),
|
||||
tui_fullscreen_transcript: false,
|
||||
tui_mouse_scroll_speed: None,
|
||||
tui_copy_on_select: Default::default(),
|
||||
tui_right_click_paste: Default::default(),
|
||||
tui_alternate_screen: AltScreenMode::Auto,
|
||||
|
||||
@@ -42,6 +42,7 @@ impl App {
|
||||
let chat_widget = &self.chat_widget;
|
||||
let transcript_width = chat_widget.history_wrap_width(width);
|
||||
let view = &mut self.transcript_view;
|
||||
view.mouse_scroll_speed = self.local_settings.tui.mouse_scroll_speed.unwrap_or(1.0);
|
||||
view.primary_selection = self.right_click_paste_environment.primary;
|
||||
view.copy_on_select = self
|
||||
.local_settings
|
||||
|
||||
+2
-2
@@ -2,8 +2,6 @@
|
||||
source: tui/src/app/owned_transcript_input_tests.rs
|
||||
expression: scrolled
|
||||
---
|
||||
• Plan step 27
|
||||
• Plan step 28
|
||||
• Plan step 29
|
||||
• Plan step 30
|
||||
• Plan step 31
|
||||
@@ -18,6 +16,8 @@ expression: scrolled
|
||||
• Plan step 40
|
||||
|
||||
|
||||
|
||||
|
||||
Implement this plan?
|
||||
|
||||
|
||||
|
||||
@@ -9757,6 +9757,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
|
||||
app.transcript_cells.clone(),
|
||||
crate::keymap::RuntimeKeymap::defaults().pager,
|
||||
/*copy_on_select*/ false,
|
||||
/*mouse_scroll_speed*/ 1.0,
|
||||
));
|
||||
app.deferred_history_lines = vec![Line::from("stale buffered line").into()];
|
||||
app.has_emitted_history_lines = true;
|
||||
|
||||
@@ -171,6 +171,7 @@ impl App {
|
||||
self.keymap.pager.clone(),
|
||||
self.local_settings
|
||||
.copy_on_select(&codex_terminal_detection::terminal_info()),
|
||||
self.local_settings.tui.mouse_scroll_speed.unwrap_or(1.0),
|
||||
));
|
||||
if self.scrollback_has_older_history
|
||||
&& let Some(Overlay::Transcript(overlay)) = self.overlay.as_mut()
|
||||
|
||||
@@ -82,6 +82,7 @@ async fn blocked_copy_allows_overlay_exit_rejects_backlog_and_wakes_completion()
|
||||
vec![Arc::new(PlainHistoryCell::new(vec!["selected".into()]))],
|
||||
RuntimeKeymap::defaults().pager,
|
||||
/*copy_on_select*/ false,
|
||||
/*mouse_scroll_speed*/ 1.0,
|
||||
);
|
||||
overlay
|
||||
.handle_event(
|
||||
|
||||
@@ -68,6 +68,7 @@ impl LocalSettings {
|
||||
question_esc_back: config.tui_question_esc_back,
|
||||
raw_output_mode: config.tui_raw_output_mode,
|
||||
fullscreen_transcript: config.tui_fullscreen_transcript,
|
||||
mouse_scroll_speed: config.tui_mouse_scroll_speed,
|
||||
copy_on_select: config.tui_copy_on_select,
|
||||
right_click_paste: config.tui_right_click_paste,
|
||||
alternate_screen: config.tui_alternate_screen,
|
||||
|
||||
@@ -105,6 +105,7 @@ show_tooltips = false
|
||||
show_server_version_notice = false
|
||||
auto_recap = false
|
||||
fullscreen_transcript = true
|
||||
mouse_scroll_speed = 0.5
|
||||
copy_on_select = "never"
|
||||
right_click_paste = "off"
|
||||
vim_mode_default = true
|
||||
@@ -136,6 +137,7 @@ fast_default_opt_out = true
|
||||
.cli_overrides(vec![
|
||||
("tui.disable_paste_burst".into(), true.into()),
|
||||
("tui.right_click_paste".into(), "on".into()),
|
||||
("tui.mouse_scroll_speed".into(), 1.5.into()),
|
||||
// The deprecated flag must not override or migrate into the TUI preference.
|
||||
(
|
||||
"features.transcript_v2".into(),
|
||||
@@ -145,12 +147,14 @@ fast_default_opt_out = true
|
||||
.build()
|
||||
.await?;
|
||||
assert_eq!(config.startup_warnings, Vec::<String>::new());
|
||||
assert_eq!(config.tui_mouse_scroll_speed, Some(1.5));
|
||||
let bootstrap = crate::legacy_core::config::load_config_toml_with_layer_stack(
|
||||
home.path(),
|
||||
/*cwd*/ None,
|
||||
vec![
|
||||
("tui.disable_paste_burst".into(), true.into()),
|
||||
("tui.right_click_paste".into(), "on".into()),
|
||||
("tui.mouse_scroll_speed".into(), 1.5.into()),
|
||||
(
|
||||
"features.transcript_v2".into(),
|
||||
config_text.is_empty().into(),
|
||||
|
||||
@@ -52,9 +52,11 @@ impl Overlay {
|
||||
cells: Vec<Arc<dyn HistoryCell>>,
|
||||
keymap: PagerKeymap,
|
||||
copy_on_select: bool,
|
||||
mouse_scroll_speed: f64,
|
||||
) -> Self {
|
||||
let mut overlay = TranscriptOverlay::new(cells, keymap);
|
||||
overlay.view.copy_on_select = copy_on_select;
|
||||
overlay.view.mouse_scroll_speed = mouse_scroll_speed;
|
||||
overlay.view.primary_selection = crate::clipboard_copy::primary::available();
|
||||
Self::Transcript(overlay)
|
||||
}
|
||||
|
||||
@@ -336,6 +336,7 @@ struct SessionPickerViewPersistence {
|
||||
struct SessionPickerRunOptions {
|
||||
use_theme_colors: bool,
|
||||
copy_on_select: bool,
|
||||
mouse_scroll_speed: f64,
|
||||
show_all: bool,
|
||||
filter_cwd: Option<PathBuf>,
|
||||
local_filter_cwd: Option<PathBuf>,
|
||||
@@ -447,6 +448,7 @@ async fn run_resume_picker_with_launch_context(
|
||||
let options = SessionPickerRunOptions {
|
||||
use_theme_colors: local_settings.tui.status_line_use_colors,
|
||||
copy_on_select: local_settings.copy_on_select(&codex_terminal_detection::terminal_info()),
|
||||
mouse_scroll_speed: local_settings.tui.mouse_scroll_speed.unwrap_or(1.0),
|
||||
show_all,
|
||||
filter_cwd: cwd_filter,
|
||||
local_filter_cwd,
|
||||
@@ -507,6 +509,7 @@ pub async fn run_fork_picker_with_app_server(
|
||||
let options = SessionPickerRunOptions {
|
||||
use_theme_colors: local_settings.tui.status_line_use_colors,
|
||||
copy_on_select: local_settings.copy_on_select(&codex_terminal_detection::terminal_info()),
|
||||
mouse_scroll_speed: local_settings.tui.mouse_scroll_speed.unwrap_or(1.0),
|
||||
show_all,
|
||||
filter_cwd: cwd_filter,
|
||||
local_filter_cwd,
|
||||
@@ -562,6 +565,7 @@ async fn run_session_picker_with_loader(
|
||||
state.local_filter_cwd = options.local_filter_cwd;
|
||||
state.use_theme_colors = options.use_theme_colors;
|
||||
state.copy_on_select = options.copy_on_select;
|
||||
state.mouse_scroll_speed = options.mouse_scroll_speed;
|
||||
state.worktrees_enabled = options.worktrees_enabled;
|
||||
state.density = options.initial_density;
|
||||
state.view_persistence = options.view_persistence;
|
||||
@@ -835,6 +839,7 @@ struct PickerState {
|
||||
clock_format: ClockFormat,
|
||||
use_theme_colors: bool,
|
||||
copy_on_select: bool,
|
||||
mouse_scroll_speed: f64,
|
||||
// Resolve local filesystem membership once per cwd for each page-loading cycle.
|
||||
local_cwd_matches: HashMap<PathBuf, bool>,
|
||||
requester: FrameRequester,
|
||||
@@ -1036,6 +1041,7 @@ impl PickerState {
|
||||
clock_format: ClockFormat::system(),
|
||||
use_theme_colors: true,
|
||||
copy_on_select: false,
|
||||
mouse_scroll_speed: 1.0,
|
||||
requester,
|
||||
relative_time_reference: None,
|
||||
pagination: PaginationState::new(),
|
||||
@@ -1141,6 +1147,7 @@ impl PickerState {
|
||||
cells.clone(),
|
||||
self.keymap.pager.clone(),
|
||||
self.copy_on_select,
|
||||
self.mouse_scroll_speed,
|
||||
);
|
||||
if let Overlay::Transcript(view) = &mut overlay {
|
||||
view.set_keymap_bindings(&self.keymap);
|
||||
|
||||
+8
-8
@@ -2,16 +2,16 @@
|
||||
source: tui/src/transcript_view_tests.rs
|
||||
expression: "frames.join(\"\\n\\n\")"
|
||||
---
|
||||
row 15
|
||||
row 16
|
||||
row 17
|
||||
row 18
|
||||
row 19
|
||||
row 20
|
||||
row 21
|
||||
row 22
|
||||
row 23
|
||||
row 24
|
||||
|
||||
row 25
|
||||
row 26
|
||||
row 27
|
||||
row 28
|
||||
row 29
|
||||
row 30
|
||||
row 31
|
||||
row 32
|
||||
row 33
|
||||
row 34
|
||||
|
||||
@@ -85,6 +85,8 @@ struct VisibleRow {
|
||||
|
||||
/// Shared scrolling and interaction state for compact and detailed transcript presentations.
|
||||
pub(crate) struct TranscriptView {
|
||||
pub(crate) mouse_scroll_speed: f64,
|
||||
pending_mouse_scroll: f64,
|
||||
pub(crate) copy_on_select: bool,
|
||||
pub(crate) primary_selection: bool,
|
||||
position: Position,
|
||||
@@ -118,6 +120,8 @@ pub(crate) struct TranscriptView {
|
||||
impl Default for TranscriptView {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mouse_scroll_speed: 1.0,
|
||||
pending_mouse_scroll: 0.0,
|
||||
copy_on_select: false,
|
||||
primary_selection: false,
|
||||
position: Position::Latest,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Shift-click extends the existing selection from its original text unit.
|
||||
//! Optional automatic copying happens only when a nonempty mouse selection is released.
|
||||
//! Automatic copies retain the selection; explicit copies clear it after confirmed delivery.
|
||||
//! Wheel input accumulates fractional rows and discards the remainder when direction reverses.
|
||||
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -232,8 +233,27 @@ impl TranscriptView {
|
||||
selection.pointer = None;
|
||||
}
|
||||
match event.kind {
|
||||
MouseEventKind::ScrollUp => self.scroll(cells, /*rows*/ -3),
|
||||
MouseEventKind::ScrollDown => self.scroll(cells, /*rows*/ 3),
|
||||
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => {
|
||||
let direction = if event.kind == MouseEventKind::ScrollUp {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
if self.pending_mouse_scroll.signum() != direction {
|
||||
self.pending_mouse_scroll = 0.0;
|
||||
}
|
||||
self.pending_mouse_scroll += direction * self.mouse_scroll_speed;
|
||||
// Decimal speeds can land just short of a whole row after repeated addition.
|
||||
let rounded = self.pending_mouse_scroll.round();
|
||||
if rounded != 0.0 && (self.pending_mouse_scroll - rounded).abs() < 1e-9 {
|
||||
self.pending_mouse_scroll = rounded;
|
||||
}
|
||||
let rows = self.pending_mouse_scroll as isize;
|
||||
self.pending_mouse_scroll = self.pending_mouse_scroll.fract();
|
||||
if rows != 0 {
|
||||
self.scroll(cells, rows);
|
||||
}
|
||||
}
|
||||
MouseEventKind::Down(MouseButton::Right) if inside => {
|
||||
return self
|
||||
.selected_text(cells)
|
||||
|
||||
@@ -1,9 +1,115 @@
|
||||
//! Link activation and selection extension share pointer gestures without losing selection anchors.
|
||||
//! Mouse gestures preserve selection anchors and accumulate fractional transcript scrolling.
|
||||
|
||||
use super::*;
|
||||
use crate::history_cell::AgentMarkdownCell;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_speed_scales_rows_and_accumulates_fractional_movement() {
|
||||
use crate::history_cell::PlainHistoryCell;
|
||||
use crate::transcript_view::tests::text;
|
||||
|
||||
let cells: Vec<Arc<dyn HistoryCell>> = vec![Arc::new(PlainHistoryCell::new(
|
||||
(1..=24).map(|row| format!("row {row:02}").into()).collect(),
|
||||
))];
|
||||
let area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 12, /*height*/ 4,
|
||||
);
|
||||
let mut frames = Vec::new();
|
||||
for (speed, deltas) in [
|
||||
(None, vec![-1, 1]),
|
||||
(Some(0.5), vec![0, -1, 0, 0, 1]),
|
||||
(Some(1.5), vec![-1, -2, 1, 2]),
|
||||
(Some(3.0), vec![-3, 3]),
|
||||
] {
|
||||
let mut view = TranscriptView::default();
|
||||
if let Some(speed) = speed {
|
||||
view.mouse_scroll_speed = speed;
|
||||
}
|
||||
let mut buffer = Buffer::empty(area);
|
||||
view.render(area, &mut buffer, &cells);
|
||||
view.scroll(&cells, /*rows*/ -8);
|
||||
view.render(area, &mut buffer, &cells);
|
||||
let (index, mut row) = view.start(&cells);
|
||||
let up_events = deltas.len().div_ceil(2);
|
||||
for (event_index, delta) in deltas.into_iter().enumerate() {
|
||||
let kind = if event_index < up_events {
|
||||
MouseEventKind::ScrollUp
|
||||
} else {
|
||||
MouseEventKind::ScrollDown
|
||||
};
|
||||
view.handle_mouse(mouse(kind, /*column*/ 1, /*row*/ 1), &cells);
|
||||
row = row.saturating_add_signed(delta);
|
||||
assert_eq!(view.start(&cells), (index, row));
|
||||
view.render(area, &mut buffer, &cells);
|
||||
if event_index + 1 == up_events {
|
||||
frames.push(format!(
|
||||
"speed {speed:?}, {up_events} up events:\n{}",
|
||||
text(&buffer)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
insta::assert_snapshot!(frames.join("\n\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decimal_mouse_scroll_speeds_preserve_whole_rows() {
|
||||
use crate::history_cell::PlainHistoryCell;
|
||||
|
||||
let cells: Vec<Arc<dyn HistoryCell>> = vec![Arc::new(PlainHistoryCell::new(
|
||||
(1..=120)
|
||||
.map(|row| format!("row {row:03}").into())
|
||||
.collect(),
|
||||
))];
|
||||
let area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 12, /*height*/ 4,
|
||||
);
|
||||
for speed in [0.1, 0.3] {
|
||||
let mut view = TranscriptView {
|
||||
mouse_scroll_speed: speed,
|
||||
..TranscriptView::default()
|
||||
};
|
||||
view.render(area, &mut Buffer::empty(area), &cells);
|
||||
view.scroll(&cells, /*rows*/ -40);
|
||||
let (index, row) = view.start(&cells);
|
||||
for (kind, direction) in [
|
||||
(MouseEventKind::ScrollUp, -1),
|
||||
(MouseEventKind::ScrollDown, 1),
|
||||
] {
|
||||
let (_, start_row) = view.start(&cells);
|
||||
for event in 1..=100 {
|
||||
view.handle_mouse(mouse(kind, /*column*/ 1, /*row*/ 1), &cells);
|
||||
let rows = (f64::from(event) * speed).floor() as isize * direction;
|
||||
assert_eq!(
|
||||
view.start(&cells),
|
||||
(index, start_row.saturating_add_signed(rows))
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(view.start(&cells), (index, row));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fractional_mouse_scroll_does_not_pause_following_until_a_row_moves() {
|
||||
let (mut view, cells) = transcript(
|
||||
"one\n\ntwo\n\nthree\n\nfour\n\nfive\n\nsix",
|
||||
/*width*/ 12,
|
||||
);
|
||||
view.mouse_scroll_speed = 0.5;
|
||||
view.handle_mouse(
|
||||
mouse(MouseEventKind::ScrollUp, /*column*/ 4, /*row*/ 1),
|
||||
&cells,
|
||||
);
|
||||
assert!(view.is_following());
|
||||
view.handle_mouse(
|
||||
mouse(MouseEventKind::ScrollUp, /*column*/ 4, /*row*/ 1),
|
||||
&cells,
|
||||
);
|
||||
assert!(!view.is_following());
|
||||
}
|
||||
|
||||
fn transcript(markdown: &str, width: u16) -> (TranscriptView, Vec<Arc<dyn HistoryCell>>) {
|
||||
let cells: Vec<Arc<dyn HistoryCell>> = vec![Arc::new(AgentMarkdownCell::new(
|
||||
markdown.into(),
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: tui/src/transcript_view/input_tests.rs
|
||||
expression: "frames.join(\"\\n\\n\")"
|
||||
---
|
||||
speed None, 1 up events:
|
||||
row 12
|
||||
row 13
|
||||
row 14
|
||||
row 15
|
||||
|
||||
speed Some(0.5), 3 up events:
|
||||
row 12
|
||||
row 13
|
||||
row 14
|
||||
row 15
|
||||
|
||||
speed Some(1.5), 2 up events:
|
||||
row 10
|
||||
row 11
|
||||
row 12
|
||||
row 13
|
||||
|
||||
speed Some(3.0), 1 up events:
|
||||
row 10
|
||||
row 11
|
||||
row 12
|
||||
row 13
|
||||
Reference in New Issue
Block a user