Merge pull request #58 from bnema/fix/scrollback-status-docs
fix(copy): label passive scrollback mode
6 files · +14 -14
M README.md +3 -3
| @@ -194,9 +194,9 @@ Tabs hold pane layouts. Panes can be split left/right/up/down, stacked, focused | |||
| 194 | 194 | ||
| 195 | 195 | The top bar shows numbered tabs. The bottom bar shows the active session followed by recent sessions, fading toward older entries. Ephemeral sessions are marked with `*`, for example `0*`. | |
| 196 | 196 | ||
| 197 | - | ## Visual mode and copy | |
| 197 | + | ## Scrollback mode and copy | |
| 198 | 198 | ||
| 199 | - | Visual mode freezes a view over history while the program keeps running underneath. Enter it with the `VIS` palette command or by scrolling up with the mouse wheel. | |
| 199 | + | Scrollback mode freezes a view over history while the program keeps running underneath. Enter it with the `VIS` palette command or by scrolling up with the mouse wheel. Passive browsing is labeled `[SCROLL]`; selecting lines changes the status to `[SELECT]`. Scrolling back to the bottom exits to the live pane. | |
| 200 | 200 | ||
| 201 | 201 | | Key | Action | | |
| 202 | 202 | |---|---| | |
| @@ -207,7 +207,7 @@ Visual mode freezes a view over history while the program keeps running undernea | |||
| 207 | 207 | | y or Enter | copy selection and exit | | |
| 208 | 208 | | q, Esc, Ctrl-C | exit without copying | | |
| 209 | 209 | ||
| 210 | - | Copy uses OSC 52 clipboard sequences when supported by your terminal. | |
| 210 | + | Copy uses OSC 52 clipboard sequences when supported by your terminal. After a successful copy, the bottom bar briefly shows `copied N chars to clipboard`. | |
| 211 | 211 | ||
| 212 | 212 | ## Development | |
| 213 | 213 | ||
| @@ -194,9 +194,9 @@ Tabs hold pane layouts. Panes can be split left/right/up/down, stacked, focused | |||||
| 194 | 194 | ||||
| 195 | The top bar shows numbered tabs. The bottom bar shows the active session followed by recent sessions, fading toward older entries. Ephemeral sessions are marked with `*`, for example `0*`. | 195 | The top bar shows numbered tabs. The bottom bar shows the active session followed by recent sessions, fading toward older entries. Ephemeral sessions are marked with `*`, for example `0*`. | ||
| 196 | 196 | ||||
| 197 | - | ## Visual mode and copy | 197 | + | ## Scrollback mode and copy |
| 198 | 198 | ||||
| 199 | - | Visual mode freezes a view over history while the program keeps running underneath. Enter it with the `VIS` palette command or by scrolling up with the mouse wheel. | 199 | + | Scrollback mode freezes a view over history while the program keeps running underneath. Enter it with the `VIS` palette command or by scrolling up with the mouse wheel. Passive browsing is labeled `[SCROLL]`; selecting lines changes the status to `[SELECT]`. Scrolling back to the bottom exits to the live pane. |
| 200 | 200 | ||||
| 201 | | Key | Action | | 201 | | Key | Action | | ||
| 202 | |---|---| | 202 | |---|---| | ||
| @@ -207,7 +207,7 @@ Visual mode freezes a view over history while the program keeps running undernea | |||||
| 207 | | y or Enter | copy selection and exit | | 207 | | y or Enter | copy selection and exit | | ||
| 208 | | q, Esc, Ctrl-C | exit without copying | | 208 | | q, Esc, Ctrl-C | exit without copying | | ||
| 209 | 209 | ||||
| 210 | - | Copy uses OSC 52 clipboard sequences when supported by your terminal. | 210 | + | Copy uses OSC 52 clipboard sequences when supported by your terminal. After a successful copy, the bottom bar briefly shows `copied N chars to clipboard`. |
| 211 | 211 | ||||
| 212 | ## Development | 212 | ## Development | ||
| 213 | 213 | ||||
M internal/usecase/copy/mode.go +1 -1
| @@ -338,7 +338,7 @@ func drawCopyStatus(row []renderer.Cell, m *Mode, total int, style renderer.Styl | |||
| 338 | 338 | for i := range row { | |
| 339 | 339 | row[i] = renderer.BlankCell() | |
| 340 | 340 | } | |
| 341 | - | text := " [VISUAL] " | |
| 341 | + | text := " [SCROLL] " | |
| 342 | 342 | if m.Selecting { | |
| 343 | 343 | text = " [SELECT] " | |
| 344 | 344 | } | |
| @@ -338,7 +338,7 @@ func drawCopyStatus(row []renderer.Cell, m *Mode, total int, style renderer.Styl | |||||
| 338 | for i := range row { | 338 | for i := range row { | ||
| 339 | row[i] = renderer.BlankCell() | 339 | row[i] = renderer.BlankCell() | ||
| 340 | } | 340 | } | ||
| 341 | - | text := " [VISUAL] " | 341 | + | text := " [SCROLL] " |
| 342 | if m.Selecting { | 342 | if m.Selecting { | ||
| 343 | text = " [SELECT] " | 343 | text = " [SELECT] " | ||
| 344 | } | 344 | } | ||
M internal/usecase/copy/mode_test.go +4 -4
| @@ -213,14 +213,14 @@ func TestScrollbackModeStatusDistinguishesPassiveAndVisual(t *testing.T) { | |||
| 213 | 213 | m := NewMode(s) | |
| 214 | 214 | ||
| 215 | 215 | frame := m.Render(s) | |
| 216 | - | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[VISUAL]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") { | |
| 217 | - | t.Fatalf("passive status = %q, want [VISUAL] with N/M and no scroll/select label", got) | |
| 216 | + | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SCROLL]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[VISUAL]") || strings.Contains(got, "[SELECT]") { | |
| 217 | + | t.Fatalf("passive status = %q, want [SCROLL] with N/M and no visual/select label", got) | |
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | 220 | m.ToggleSelection() | |
| 221 | 221 | frame = m.Render(s) | |
| 222 | - | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SELECT]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[VISUAL]") { | |
| 223 | - | t.Fatalf("selection status = %q, want [SELECT] with N/M and no [VISUAL]", got) | |
| 222 | + | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SELECT]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[VISUAL]") { | |
| 223 | + | t.Fatalf("selection status = %q, want [SELECT] with N/M and no passive label", got) | |
| 224 | 224 | } | |
| 225 | 225 | } | |
| 226 | 226 | ||
| @@ -213,14 +213,14 @@ func TestScrollbackModeStatusDistinguishesPassiveAndVisual(t *testing.T) { | |||||
| 213 | m := NewMode(s) | 213 | m := NewMode(s) | ||
| 214 | 214 | ||||
| 215 | frame := m.Render(s) | 215 | frame := m.Render(s) | ||
| 216 | - | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[VISUAL]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") { | 216 | + | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SCROLL]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[VISUAL]") || strings.Contains(got, "[SELECT]") { |
| 217 | - | t.Fatalf("passive status = %q, want [VISUAL] with N/M and no scroll/select label", got) | 217 | + | t.Fatalf("passive status = %q, want [SCROLL] with N/M and no visual/select label", got) |
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | m.ToggleSelection() | 220 | m.ToggleSelection() | ||
| 221 | frame = m.Render(s) | 221 | frame = m.Render(s) | ||
| 222 | - | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SELECT]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[VISUAL]") { | 222 | + | if got := frameText(frame.Row(s.Height)); !strings.Contains(got, "[SELECT]") || !strings.Contains(got, "3/3") || strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[VISUAL]") { |
| 223 | - | t.Fatalf("selection status = %q, want [SELECT] with N/M and no [VISUAL]", got) | 223 | + | t.Fatalf("selection status = %q, want [SELECT] with N/M and no passive label", got) |
| 224 | } | 224 | } | ||
| 225 | } | 225 | } | ||
| 226 | 226 | ||||
M internal/usecase/daemon/copymode_test.go +3 -3
| @@ -105,7 +105,7 @@ func TestCopyModeFrameIncludesTopAndBottomChrome(t *testing.T) { | |||
| 105 | 105 | require.Equal(t, 12, frame.Width) | |
| 106 | 106 | require.Equal(t, 5, frame.Height) | |
| 107 | 107 | require.Equal(t, " 1 ", rowText(frame.Row(0))) | |
| 108 | - | require.Contains(t, rowText(frame.Row(4)), "[VISUAL]") | |
| 108 | + | require.Contains(t, rowText(frame.Row(4)), "[SCROLL]") | |
| 109 | 109 | require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, damage) | |
| 110 | 110 | } | |
| 111 | 111 | ||
| @@ -131,8 +131,8 @@ func TestCopyModePaletteCommandEntersAndDoesNotForward(t *testing.T) { | |||
| 131 | 131 | out := awaitFrame(t, sends, ports.MsgOutput) | |
| 132 | 132 | msg, err := ports.UnmarshalOutput(out.Payload) | |
| 133 | 133 | require.NoError(t, err) | |
| 134 | - | if got := string(msg.Data); !strings.Contains(got, "[VISUAL]") || strings.Contains(got, "[COPY]") { | |
| 135 | - | t.Fatalf("visual mode paint = %q, want [VISUAL] without [COPY]", got) | |
| 134 | + | if got := string(msg.Data); !strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") || strings.Contains(got, "[COPY]") { | |
| 135 | + | t.Fatalf("passive scrollback paint = %q, want [SCROLL] without [SELECT]/[COPY]", got) | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | 138 | d.handleInput(sess, ac, []byte(" ")) | |
| @@ -105,7 +105,7 @@ func TestCopyModeFrameIncludesTopAndBottomChrome(t *testing.T) { | |||||
| 105 | require.Equal(t, 12, frame.Width) | 105 | require.Equal(t, 12, frame.Width) | ||
| 106 | require.Equal(t, 5, frame.Height) | 106 | require.Equal(t, 5, frame.Height) | ||
| 107 | require.Equal(t, " 1 ", rowText(frame.Row(0))) | 107 | require.Equal(t, " 1 ", rowText(frame.Row(0))) | ||
| 108 | - | require.Contains(t, rowText(frame.Row(4)), "[VISUAL]") | 108 | + | require.Contains(t, rowText(frame.Row(4)), "[SCROLL]") |
| 109 | require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, damage) | 109 | require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, damage) | ||
| 110 | } | 110 | } | ||
| 111 | 111 | ||||
| @@ -131,8 +131,8 @@ func TestCopyModePaletteCommandEntersAndDoesNotForward(t *testing.T) { | |||||
| 131 | out := awaitFrame(t, sends, ports.MsgOutput) | 131 | out := awaitFrame(t, sends, ports.MsgOutput) | ||
| 132 | msg, err := ports.UnmarshalOutput(out.Payload) | 132 | msg, err := ports.UnmarshalOutput(out.Payload) | ||
| 133 | require.NoError(t, err) | 133 | require.NoError(t, err) | ||
| 134 | - | if got := string(msg.Data); !strings.Contains(got, "[VISUAL]") || strings.Contains(got, "[COPY]") { | 134 | + | if got := string(msg.Data); !strings.Contains(got, "[SCROLL]") || strings.Contains(got, "[SELECT]") || strings.Contains(got, "[COPY]") { |
| 135 | - | t.Fatalf("visual mode paint = %q, want [VISUAL] without [COPY]", got) | 135 | + | t.Fatalf("passive scrollback paint = %q, want [SCROLL] without [SELECT]/[COPY]", got) |
| 136 | } | 136 | } | ||
| 137 | 137 | ||||
| 138 | d.handleInput(sess, ac, []byte(" ")) | 138 | d.handleInput(sess, ac, []byte(" ")) | ||
M internal/usecase/daemon/input_test.go +1 -1
| @@ -652,7 +652,7 @@ func TestMouseWheelEntersScrollbackModeAndExitsAtBottom(t *testing.T) { | |||
| 652 | 652 | data := mustOutputData(t, sends) | |
| 653 | 653 | require.NotNil(t, ac.overlays.copyMode) | |
| 654 | 654 | require.Equal(t, 19, ac.overlays.copyMode.Cursor) | |
| 655 | - | require.Contains(t, string(data), "[VISUAL]") | |
| 655 | + | require.Contains(t, string(data), "[SCROLL]") | |
| 656 | 656 | ||
| 657 | 657 | d.handleInput(sess, ac, []byte("\x1b[<65;1;1M")) | |
| 658 | 658 | mustOutputData(t, sends) | |
| @@ -652,7 +652,7 @@ func TestMouseWheelEntersScrollbackModeAndExitsAtBottom(t *testing.T) { | |||||
| 652 | data := mustOutputData(t, sends) | 652 | data := mustOutputData(t, sends) | ||
| 653 | require.NotNil(t, ac.overlays.copyMode) | 653 | require.NotNil(t, ac.overlays.copyMode) | ||
| 654 | require.Equal(t, 19, ac.overlays.copyMode.Cursor) | 654 | require.Equal(t, 19, ac.overlays.copyMode.Cursor) | ||
| 655 | - | require.Contains(t, string(data), "[VISUAL]") | 655 | + | require.Contains(t, string(data), "[SCROLL]") |
| 656 | 656 | ||||
| 657 | d.handleInput(sess, ac, []byte("\x1b[<65;1;1M")) | 657 | d.handleInput(sess, ac, []byte("\x1b[<65;1;1M")) | ||
| 658 | mustOutputData(t, sends) | 658 | mustOutputData(t, sends) | ||
M internal/usecase/daemon/render_test.go +2 -2
| @@ -669,8 +669,8 @@ func TestOverlayPaintInvalidationShowsAndRestoresBaseFrame(t *testing.T) { | |||
| 669 | 669 | prepareScreen: func(sess *session) { | |
| 670 | 670 | sess.tabs[0].focusedPane().scrollback = scopy.NewScrollback(4) | |
| 671 | 671 | }, | |
| 672 | - | visible: "[VISUAL]", | |
| 673 | - | notVisible: "[VISUAL]", | |
| 672 | + | visible: "[SCROLL]", | |
| 673 | + | notVisible: "[SCROLL]", | |
| 674 | 674 | wantRestored: "live", | |
| 675 | 675 | }, | |
| 676 | 676 | } | |
| @@ -669,8 +669,8 @@ func TestOverlayPaintInvalidationShowsAndRestoresBaseFrame(t *testing.T) { | |||||
| 669 | prepareScreen: func(sess *session) { | 669 | prepareScreen: func(sess *session) { | ||
| 670 | sess.tabs[0].focusedPane().scrollback = scopy.NewScrollback(4) | 670 | sess.tabs[0].focusedPane().scrollback = scopy.NewScrollback(4) | ||
| 671 | }, | 671 | }, | ||
| 672 | - | visible: "[VISUAL]", | 672 | + | visible: "[SCROLL]", |
| 673 | - | notVisible: "[VISUAL]", | 673 | + | notVisible: "[SCROLL]", |
| 674 | wantRestored: "live", | 674 | wantRestored: "live", | ||
| 675 | }, | 675 | }, | ||
| 676 | } | 676 | } | ||