feat: add auto lookback mode (#7)
* feat: add auto lookback mode Automatically shows full terminal history after idle timeout, returns to truncated view when new output arrives. - Add auto_lookback_timeout_ms config option (default: 1000ms) - Add --auto-lookback-timeout CLI flag - Set to 0 to disable the feature * intermediate * auto lookback --------- Co-authored-by: Ilya Sinkin <ilyasinkin2002@yandex.kz>
This commit is contained in:
@@ -48,6 +48,8 @@ Press `Ctrl+6` (or your configured key) to enter lookback mode:
|
|||||||
|
|
||||||
When you exit lookback mode, any cached output is processed and the current state is displayed.
|
When you exit lookback mode, any cached output is processed and the current state is displayed.
|
||||||
|
|
||||||
|
**Auto-lookback**: After 5 seconds of idle (no new renders), the full history is automatically displayed so you can scroll back without pressing any keys.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Create `~/.config/claude-chill.toml`:
|
Create `~/.config/claude-chill.toml`:
|
||||||
|
|||||||
@@ -42,10 +42,15 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let auto_lookback_timeout_ms = cli
|
||||||
|
.auto_lookback_timeout
|
||||||
|
.unwrap_or(config.auto_lookback_timeout_ms);
|
||||||
|
|
||||||
let proxy_config = ProxyConfig {
|
let proxy_config = ProxyConfig {
|
||||||
max_history_lines: history_lines,
|
max_history_lines: history_lines,
|
||||||
lookback_key,
|
lookback_key,
|
||||||
lookback_sequence,
|
lookback_sequence,
|
||||||
|
auto_lookback_timeout_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cmd_args: Vec<&str> = cli.args.iter().map(|s| s.as_str()).collect();
|
let cmd_args: Vec<&str> = cli.args.iter().map(|s| s.as_str()).collect();
|
||||||
|
|||||||
@@ -13,4 +13,7 @@ pub struct Cli {
|
|||||||
|
|
||||||
#[arg(short = 'k', long = "lookback-key")]
|
#[arg(short = 'k', long = "lookback-key")]
|
||||||
pub lookback_key: Option<String>,
|
pub lookback_key: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "auto-lookback-timeout")]
|
||||||
|
pub auto_lookback_timeout: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
const DEFAULT_LOOKBACK_KEY: &str = "[ctrl][6]";
|
const DEFAULT_LOOKBACK_KEY: &str = "[ctrl][6]";
|
||||||
const DEFAULT_REFRESH_RATE: u64 = 20;
|
const DEFAULT_REFRESH_RATE: u64 = 20;
|
||||||
|
const DEFAULT_AUTO_LOOKBACK_TIMEOUT_MS: u64 = 5000;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -12,6 +13,7 @@ pub struct Config {
|
|||||||
pub history_lines: usize,
|
pub history_lines: usize,
|
||||||
pub lookback_key: String,
|
pub lookback_key: String,
|
||||||
pub refresh_rate: u64,
|
pub refresh_rate: u64,
|
||||||
|
pub auto_lookback_timeout_ms: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@@ -20,6 +22,7 @@ impl Default for Config {
|
|||||||
history_lines: 100_000,
|
history_lines: 100_000,
|
||||||
lookback_key: DEFAULT_LOOKBACK_KEY.to_string(),
|
lookback_key: DEFAULT_LOOKBACK_KEY.to_string(),
|
||||||
refresh_rate: DEFAULT_REFRESH_RATE,
|
refresh_rate: DEFAULT_REFRESH_RATE,
|
||||||
|
auto_lookback_timeout_ms: DEFAULT_AUTO_LOOKBACK_TIMEOUT_MS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +100,7 @@ mod tests {
|
|||||||
assert_eq!(config.lookback_key, "[ctrl][6]");
|
assert_eq!(config.lookback_key, "[ctrl][6]");
|
||||||
assert_eq!(config.refresh_rate, 20);
|
assert_eq!(config.refresh_rate, 20);
|
||||||
assert_eq!(config.redraw_throttle_ms(), 50);
|
assert_eq!(config.redraw_throttle_ms(), 50);
|
||||||
|
assert_eq!(config.auto_lookback_timeout_ms, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ pub struct ProxyConfig {
|
|||||||
pub max_history_lines: usize,
|
pub max_history_lines: usize,
|
||||||
pub lookback_key: String,
|
pub lookback_key: String,
|
||||||
pub lookback_sequence: Vec<u8>,
|
pub lookback_sequence: Vec<u8>,
|
||||||
|
pub auto_lookback_timeout_ms: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProxyConfig {
|
impl Default for ProxyConfig {
|
||||||
@@ -56,6 +57,7 @@ impl Default for ProxyConfig {
|
|||||||
max_history_lines: 100_000,
|
max_history_lines: 100_000,
|
||||||
lookback_key: "[ctrl][6]".to_string(),
|
lookback_key: "[ctrl][6]".to_string(),
|
||||||
lookback_sequence: vec![0x1E],
|
lookback_sequence: vec![0x1E],
|
||||||
|
auto_lookback_timeout_ms: 5000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,8 @@ pub struct Proxy {
|
|||||||
vt_parser: vt100::Parser,
|
vt_parser: vt100::Parser,
|
||||||
vt_prev_screen: Option<vt100::Screen>,
|
vt_prev_screen: Option<vt100::Screen>,
|
||||||
last_output_time: Option<Instant>,
|
last_output_time: Option<Instant>,
|
||||||
|
last_render_time: Option<Instant>,
|
||||||
|
auto_lookback_timeout: Duration,
|
||||||
sync_buffer: Vec<u8>,
|
sync_buffer: Vec<u8>,
|
||||||
in_sync_block: bool,
|
in_sync_block: bool,
|
||||||
in_lookback_mode: bool,
|
in_lookback_mode: bool,
|
||||||
@@ -161,6 +165,8 @@ impl Proxy {
|
|||||||
history.push_bytes(CLEAR_SCREEN);
|
history.push_bytes(CLEAR_SCREEN);
|
||||||
history.push_bytes(CURSOR_HOME);
|
history.push_bytes(CURSOR_HOME);
|
||||||
|
|
||||||
|
let auto_lookback_timeout = Duration::from_millis(config.auto_lookback_timeout_ms);
|
||||||
|
|
||||||
debug!("Proxy::spawn: command={} args={:?}", command, args);
|
debug!("Proxy::spawn: command={} args={:?}", command, args);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -172,6 +178,8 @@ impl Proxy {
|
|||||||
vt_parser,
|
vt_parser,
|
||||||
vt_prev_screen: None,
|
vt_prev_screen: None,
|
||||||
last_output_time: None,
|
last_output_time: None,
|
||||||
|
last_render_time: None,
|
||||||
|
auto_lookback_timeout,
|
||||||
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
||||||
in_sync_block: false,
|
in_sync_block: false,
|
||||||
in_lookback_mode: false,
|
in_lookback_mode: false,
|
||||||
@@ -224,6 +232,7 @@ impl Proxy {
|
|||||||
match poll(&mut poll_fds, PollTimeout::from(poll_timeout_ms)) {
|
match poll(&mut poll_fds, PollTimeout::from(poll_timeout_ms)) {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
self.flush_pending_vt_render(&stdout_fd)?;
|
self.flush_pending_vt_render(&stdout_fd)?;
|
||||||
|
self.check_auto_lookback(&stdout_fd)?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
@@ -551,6 +560,43 @@ impl Proxy {
|
|||||||
// Store current screen for next diff
|
// Store current screen for next diff
|
||||||
self.vt_prev_screen = Some(self.vt_parser.screen().clone());
|
self.vt_prev_screen = Some(self.vt_parser.screen().clone());
|
||||||
self.vt_render_pending = false;
|
self.vt_render_pending = false;
|
||||||
|
self.last_render_time = Some(Instant::now());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_auto_lookback<F: AsFd>(&mut self, stdout_fd: &F) -> Result<()> {
|
||||||
|
if self.auto_lookback_timeout.is_zero() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.in_lookback_mode || self.in_alternate_screen {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let Some(render_time) = self.last_render_time else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if render_time.elapsed() < self.auto_lookback_timeout {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.dump_history(stdout_fd)?;
|
||||||
|
self.last_render_time = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_history<F: AsFd>(&mut self, stdout_fd: &F) -> Result<()> {
|
||||||
|
debug!(
|
||||||
|
"dump_history: history_bytes={} lines={}",
|
||||||
|
self.history.total_bytes(),
|
||||||
|
self.history.line_count()
|
||||||
|
);
|
||||||
|
self.output_buffer.clear();
|
||||||
|
self.history.append_all(&mut self.output_buffer);
|
||||||
|
|
||||||
|
write_all(stdout_fd, CLEAR_SCREEN)?;
|
||||||
|
write_all(stdout_fd, CURSOR_HOME)?;
|
||||||
|
write_all(stdout_fd, &self.output_buffer)?;
|
||||||
|
|
||||||
|
// Force full VT render on next output since terminal now shows history
|
||||||
|
self.vt_prev_screen = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user