Skip to content

Commit fda964a

Browse files
committed
💄 Improve Studio TUI scroll indicators and keyboard navigation
Add scroll position indicators showing current line, total lines, and percentage to panel titles in changelog, PR, release notes, and review modes. These indicators only appear when content exceeds visible area. Add vim-style and standard keyboard shortcuts: - Ctrl+u/Ctrl+d for page up/down in diff views - Home/End keys as aliases for g/G navigation - [/] for hunk navigation and n/p for file navigation in diffs Fix panel scroll mapping in reducer so scrolling targets the correct panel (center for content, right for diff views) across all modes. Make modal sizing responsive to terminal dimensions and content size, with proper bounds checking to prevent overflow on small terminals. Add error state to chat with visual display and proper cleanup on new user input.
1 parent f34bda3 commit fda964a

File tree

14 files changed

+421
-104
lines changed

14 files changed

+421
-104
lines changed

src/studio/handlers/changelog.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
9595
state.mark_dirty();
9696
vec![]
9797
}
98-
KeyCode::PageUp => {
98+
KeyCode::PageUp | KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
9999
state.modes.changelog.diff_view.scroll_up(20);
100100
state.mark_dirty();
101101
vec![]
102102
}
103-
KeyCode::PageDown => {
103+
KeyCode::PageDown | KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
104104
state.modes.changelog.diff_view.scroll_down(20);
105105
state.mark_dirty();
106106
vec![]
@@ -115,6 +115,28 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
115115
state.mark_dirty();
116116
vec![]
117117
}
118+
// Hunk navigation
119+
KeyCode::Char(']') => {
120+
state.modes.changelog.diff_view.next_hunk();
121+
state.mark_dirty();
122+
vec![]
123+
}
124+
KeyCode::Char('[') => {
125+
state.modes.changelog.diff_view.prev_hunk();
126+
state.mark_dirty();
127+
vec![]
128+
}
129+
// File navigation within diff
130+
KeyCode::Char('n') => {
131+
state.modes.changelog.diff_view.next_file();
132+
state.mark_dirty();
133+
vec![]
134+
}
135+
KeyCode::Char('p') => {
136+
state.modes.changelog.diff_view.prev_file();
137+
state.mark_dirty();
138+
vec![]
139+
}
118140
// Generate changelog
119141
KeyCode::Char('r') => {
120142
state.set_iris_thinking("Generating changelog...");

src/studio/handlers/commit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ fn handle_files_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
6363
state.mark_dirty();
6464
vec![]
6565
}
66-
KeyCode::Char('g') => {
66+
KeyCode::Char('g') | KeyCode::Home => {
6767
state.modes.commit.file_tree.select_first();
6868
sync_file_selection(state);
6969
state.mark_dirty();
7070
vec![]
7171
}
72-
KeyCode::Char('G') => {
72+
KeyCode::Char('G') | KeyCode::End => {
7373
state.modes.commit.file_tree.select_last();
7474
sync_file_selection(state);
7575
state.mark_dirty();

src/studio/handlers/explore.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ fn handle_file_tree_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffec
6767
state.mark_dirty();
6868
vec![]
6969
}
70-
KeyCode::Char('g') => {
70+
KeyCode::Char('g') | KeyCode::Home => {
7171
// Go to first
7272
state.modes.explore.file_tree.select_first();
7373
load_selected_file(state);
7474
state.mark_dirty();
7575
vec![]
7676
}
77-
KeyCode::Char('G') => {
77+
KeyCode::Char('G') | KeyCode::End => {
7878
// Go to last
7979
state.modes.explore.file_tree.select_last();
8080
load_selected_file(state);
@@ -177,7 +177,7 @@ fn handle_code_view_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffec
177177
state.mark_dirty();
178178
vec![]
179179
}
180-
KeyCode::Char('g') => {
180+
KeyCode::Char('g') | KeyCode::Home => {
181181
// Go to first line
182182
state.modes.explore.code_view.goto_first();
183183
state.modes.explore.current_line = 1;
@@ -187,7 +187,7 @@ fn handle_code_view_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffec
187187
state.mark_dirty();
188188
vec![]
189189
}
190-
KeyCode::Char('G') => {
190+
KeyCode::Char('G') | KeyCode::End => {
191191
// Go to last line
192192
state
193193
.modes

src/studio/handlers/pr.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
9595
state.mark_dirty();
9696
vec![]
9797
}
98-
KeyCode::PageUp => {
98+
KeyCode::PageUp | KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
9999
state.modes.pr.diff_view.scroll_up(20);
100100
state.mark_dirty();
101101
vec![]
102102
}
103-
KeyCode::PageDown => {
103+
KeyCode::PageDown | KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
104104
state.modes.pr.diff_view.scroll_down(20);
105105
state.mark_dirty();
106106
vec![]
@@ -115,6 +115,28 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
115115
state.mark_dirty();
116116
vec![]
117117
}
118+
// Hunk navigation
119+
KeyCode::Char(']') => {
120+
state.modes.pr.diff_view.next_hunk();
121+
state.mark_dirty();
122+
vec![]
123+
}
124+
KeyCode::Char('[') => {
125+
state.modes.pr.diff_view.prev_hunk();
126+
state.mark_dirty();
127+
vec![]
128+
}
129+
// File navigation within diff
130+
KeyCode::Char('n') => {
131+
state.modes.pr.diff_view.next_file();
132+
state.mark_dirty();
133+
vec![]
134+
}
135+
KeyCode::Char('p') => {
136+
state.modes.pr.diff_view.prev_file();
137+
state.mark_dirty();
138+
vec![]
139+
}
118140
// Generate PR
119141
KeyCode::Char('r') => {
120142
state.set_iris_thinking("Generating PR description...");

src/studio/handlers/release_notes.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
101101
state.mark_dirty();
102102
vec![]
103103
}
104-
KeyCode::PageUp => {
104+
KeyCode::PageUp | KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
105105
state.modes.release_notes.diff_view.scroll_up(20);
106106
state.mark_dirty();
107107
vec![]
108108
}
109-
KeyCode::PageDown => {
109+
KeyCode::PageDown | KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
110110
state.modes.release_notes.diff_view.scroll_down(20);
111111
state.mark_dirty();
112112
vec![]
@@ -121,6 +121,28 @@ fn handle_diff_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
121121
state.mark_dirty();
122122
vec![]
123123
}
124+
// Hunk navigation
125+
KeyCode::Char(']') => {
126+
state.modes.release_notes.diff_view.next_hunk();
127+
state.mark_dirty();
128+
vec![]
129+
}
130+
KeyCode::Char('[') => {
131+
state.modes.release_notes.diff_view.prev_hunk();
132+
state.mark_dirty();
133+
vec![]
134+
}
135+
// File navigation within diff
136+
KeyCode::Char('n') => {
137+
state.modes.release_notes.diff_view.next_file();
138+
state.mark_dirty();
139+
vec![]
140+
}
141+
KeyCode::Char('p') => {
142+
state.modes.release_notes.diff_view.prev_file();
143+
state.mark_dirty();
144+
vec![]
145+
}
124146
// Generate release notes
125147
KeyCode::Char('r') => {
126148
state.set_iris_thinking("Generating release notes...");

src/studio/handlers/review.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ fn handle_files_key(state: &mut StudioState, key: KeyEvent) -> Vec<SideEffect> {
4747
state.mark_dirty();
4848
vec![]
4949
}
50-
KeyCode::Char('g') => {
50+
KeyCode::Char('g') | KeyCode::Home => {
5151
state.modes.review.file_tree.select_first();
5252
sync_file_selection(state);
5353
state.mark_dirty();
5454
vec![]
5555
}
56-
KeyCode::Char('G') => {
56+
KeyCode::Char('G') | KeyCode::End => {
5757
state.modes.review.file_tree.select_last();
5858
sync_file_selection(state);
5959
state.mark_dirty();

0 commit comments

Comments
 (0)