feat(tui): マウスホイールでスクロールする実装
This commit is contained in:
parent
31eeded4a6
commit
0e98d67a5f
1
TODO.md
1
TODO.md
|
|
@ -14,6 +14,7 @@
|
|||
- [ ] フルスクリーン化によるオーバーホール → [tickets/tui-fullscreen-overhaul.md](tickets/tui-fullscreen-overhaul.md)
|
||||
- [ ] Run 中の入力キューイング → [tickets/tui-input-queue.md](tickets/tui-input-queue.md)
|
||||
- [ ] ユーザーマニフェストのモデル設定 wizard → [tickets/tui-user-model-setup.md](tickets/tui-user-model-setup.md)
|
||||
- [ ] マウスホイールでのヒストリースクロール → [tickets/tui-mouse-scroll.md](tickets/tui-mouse-scroll.md)
|
||||
- [ ] サブミット入力
|
||||
- [ ] TUI 補完 + 型付き atom 化 → [tickets/submit-tui-completion.md](tickets/submit-tui-completion.md)
|
||||
- [ ] FileRef リゾルバ → [tickets/submit-file-ref-resolver.md](tickets/submit-file-ref-resolver.md)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ use std::process::ExitCode;
|
|||
use std::time::Duration;
|
||||
|
||||
use crossterm::event::{
|
||||
self, DisableBracketedPaste, EnableBracketedPaste, Event as TermEvent, KeyCode, KeyEvent,
|
||||
KeyModifiers,
|
||||
self, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
|
||||
Event as TermEvent, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
|
|
@ -170,7 +170,12 @@ async fn main() -> ExitCode {
|
|||
// shows up cleanly in scrollback rather than inside an active
|
||||
// alternate-screen buffer.
|
||||
let mut stdout = io::stdout();
|
||||
let _ = execute!(stdout, LeaveAlternateScreen, DisableBracketedPaste);
|
||||
let _ = execute!(
|
||||
stdout,
|
||||
DisableMouseCapture,
|
||||
LeaveAlternateScreen,
|
||||
DisableBracketedPaste
|
||||
);
|
||||
let _ = disable_raw_mode();
|
||||
let _ = execute!(stdout, crossterm::cursor::Show);
|
||||
|
||||
|
|
@ -229,7 +234,11 @@ async fn run_spawn(resume_from: Option<SessionId>) -> Result<(), Box<dyn std::er
|
|||
// Leave alt-screen before reaping the child so any final pod stderr
|
||||
// (drained off-line by `stderr_drain`) cannot collide with the
|
||||
// restored scrollback.
|
||||
let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
|
||||
let _ = execute!(
|
||||
terminal.backend_mut(),
|
||||
DisableMouseCapture,
|
||||
LeaveAlternateScreen
|
||||
);
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(3), child.wait()).await {
|
||||
Ok(Ok(_)) => {}
|
||||
|
|
@ -246,7 +255,7 @@ async fn run_spawn(resume_from: Option<SessionId>) -> Result<(), Box<dyn std::er
|
|||
fn enter_fullscreen() -> Result<Terminal<CrosstermBackend<io::Stdout>>, Box<dyn std::error::Error>>
|
||||
{
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
Ok(Terminal::new(backend)?)
|
||||
}
|
||||
|
|
@ -302,6 +311,9 @@ async fn run_loop(
|
|||
client.send(&method).await?;
|
||||
}
|
||||
}
|
||||
TermEvent::Mouse(mouse) => {
|
||||
handle_mouse(app, mouse);
|
||||
}
|
||||
TermEvent::Paste(s) => {
|
||||
app.insert_paste(s);
|
||||
}
|
||||
|
|
@ -345,6 +357,20 @@ fn run_disconnected(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Lines per wheel notch. Faster than Shift+↑/↓ (which is 1 line) so
|
||||
/// hand-rolling through long histories isn't tedious, but slow enough
|
||||
/// that a single notch doesn't blow past the section the user is
|
||||
/// looking for.
|
||||
const WHEEL_LINES: usize = 3;
|
||||
|
||||
fn handle_mouse(app: &mut App, mouse: MouseEvent) {
|
||||
match mouse.kind {
|
||||
MouseEventKind::ScrollUp => app.scroll.scroll_up(WHEEL_LINES),
|
||||
MouseEventKind::ScrollDown => app.scroll.scroll_down(WHEEL_LINES),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_key(app: &mut App, key: KeyEvent) -> Option<Method> {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
|
||||
|
|
|
|||
28
tickets/tui-mouse-scroll.md
Normal file
28
tickets/tui-mouse-scroll.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# TUI: マウスホイールでヒストリーをスクロール
|
||||
|
||||
## 背景
|
||||
|
||||
TUIのヒストリービューはキー操作(Shift+↑/↓、PageUp/PageDown、Ctrl+Home/End、Ctrl+[/])でスクロールできる。スクロール状態(`Scroll` 構造体・follow_tail・top_offset)も既に揃っており、ロジック的には外部から `scroll_up`/`scroll_down` を叩けば任意の手段でスクロール可能になっている。
|
||||
|
||||
一方で、現状のイベントループは `TermEvent::Key` / `Paste` / `Resize` のみを処理しており、マウスイベントは破棄される。crossterm のマウスキャプチャも有効化されていないため、ターミナル側にホイール入力すら届かない。
|
||||
|
||||
エディタ的に常駐するTUIとして、マウスホイールで直感的にヒストリーを遡れる体験を提供したい。
|
||||
|
||||
## 要件
|
||||
|
||||
- フルスクリーンモード中にマウスホイールでヒストリービューをスクロールできる。
|
||||
- ホイール上方向で過去方向(`scroll_up`)、下方向で末尾方向(`scroll_down`)に動く。1 notch あたりの行数は既存のキー操作(Shift+↑/↓)と同じ感覚で良い。
|
||||
- マウスキャプチャの有効化はalt-screenに入っているあいだに限定し、終了時に確実に解除する(picker / spawn のインラインフェーズはネイティブのマウス挙動を保つ)。
|
||||
- クリック・ドラッグ等のホイール以外のマウスイベントは現状無視で構わないが、最低限イベントループでマッチ漏れによるエラーが出ない形にする。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- フルスクリーンTUIでマウスホイールを回すと、ヒストリービューが上下に動く。
|
||||
- TUIを正常終了・異常終了どちらでも、ターミナルのマウスキャプチャ状態が残らない。
|
||||
- 既存のキー操作によるスクロール挙動に変化がない。
|
||||
|
||||
## 範囲外
|
||||
|
||||
- ホイール以外のマウス操作(クリックでフォーカス移動、ドラッグ選択、スクロールバー表示など)
|
||||
- picker / spawn のインラインフェーズでのマウス対応
|
||||
- ホイール感度の設定可能化
|
||||
Loading…
Reference in New Issue
Block a user