Skip to content

Commit bdb3bdc

Browse files
authored
fix: preserve scroll after copy mode copy actions (#104)
* fix: preserve scroll after copy mode copy actions * docs: document copy mode scroll behavior --------- Co-authored-by: leohenon <77656081+lhenon999@users.noreply.github.com>
1 parent daf876f commit bdb3bdc

5 files changed

Lines changed: 25 additions & 8 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ Works similarly to tmux copy mode within opencode tui.
9595
- Press `v` / `V` to start character-wise or line-wise selection.
9696
- `y/yy` yanks to the vim register.
9797
- `Enter` copies to the system clipboard.
98-
- `Escape` exits visual mode, `q` exits copy mode.
99-
- Press `i` to exit copy mode and focus the prompt input in insert mode without moving the cursor position in copy mode.
98+
- `Escape` exits visual mode, `q` exits copy mode and scrolls to the bottom.
99+
- `i` focuses the prompt input.
100100
- `z` `zt` `zz` `zb` adjust copy-mode scroll positioning.
101101
- `H` / `M` / `L` jump to the top / middle / bottom of the viewport.
102102

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export type PromptProps = {
8585
copy?: {
8686
enter: () => void
8787
exit: () => void
88+
exitPreserveScroll: () => void
8889
focusInput: () => void
8990
visual: (mode: "char" | "line") => void
9091
yank: () => { text: string; linewise: boolean } | null
@@ -641,8 +642,8 @@ export function Prompt(props: PromptProps) {
641642
copyExitVisual() {
642643
props.copy?.exitVisual()
643644
},
644-
copyExit() {
645-
props.copy?.exit()
645+
copyExitPreserveScroll() {
646+
props.copy?.exitPreserveScroll()
646647
},
647648
copyFocusInput() {
648649
props.copy?.focusInput()
@@ -1765,7 +1766,7 @@ export function Prompt(props: PromptProps) {
17651766
if (vimState.isCopy()) {
17661767
const active = vimState.isCopy()
17671768
vim.handleKey(e)
1768-
if (active && vimState.mode() === "normal") props.copy?.exit()
1769+
if (active && vimState.mode() === "normal" && props.copy?.active()) props.copy.exit()
17691770
if (!e.defaultPrevented) e.preventDefault()
17701771
return
17711772
}

packages/opencode/src/cli/cmd/tui/component/vim/vim-handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export function createVimHandler(input: {
8585
copy?: (action: VimCopyMove) => void
8686
copyVisual?: (mode: "char" | "line") => void
8787
copyExitVisual?: () => void
88-
copyExit?: () => void
88+
copyExitPreserveScroll?: () => void
8989
copyFocusInput?: () => void
9090
copyYank?: () => void
9191
copyYankLine?: () => void
@@ -1046,6 +1046,7 @@ export function createVimHandler(input: {
10461046
if (key === "y") {
10471047
if (input.copyIsVisual?.()) {
10481048
input.copyYank?.()
1049+
input.copyExitPreserveScroll?.()
10491050
input.state.setMode("normal")
10501051
event.preventDefault()
10511052
return true
@@ -1054,8 +1055,8 @@ export function createVimHandler(input: {
10541055
input.state.clearPending()
10551056
input.copyYankLine?.()
10561057
setTimeout(() => {
1058+
input.copyExitPreserveScroll?.()
10571059
input.state.setMode("normal")
1058-
input.copyExit?.()
10591060
}, 70)
10601061
event.preventDefault()
10611062
return true
@@ -1075,6 +1076,7 @@ export function createVimHandler(input: {
10751076

10761077
if (key === "return") {
10771078
input.copyCopy?.()
1079+
input.copyExitPreserveScroll?.()
10781080
input.state.setMode("normal")
10791081
event.preventDefault()
10801082
return true

packages/opencode/src/cli/cmd/tui/routes/session/copy-mode.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,14 @@ export function createCopyMode(input: {
370370
input.toBottom()
371371
}
372372

373-
function focusInput() {
373+
function exitPreserveScroll() {
374374
setState((s) => ({ ...s, active: false, visual: undefined, anchor: undefined }))
375375
}
376376

377+
function focusInput() {
378+
exitPreserveScroll()
379+
}
380+
377381
function move(action: "up" | "down" | "left" | "right") {
378382
const scroll = input.scroll()
379383
const s = state()
@@ -751,6 +755,7 @@ export function createCopyMode(input: {
751755
prompt: {
752756
enter,
753757
exit,
758+
exitPreserveScroll,
754759
focusInput,
755760
visual,
756761
yank,

packages/opencode/test/cli/tui/vim-motions.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ function createHandler(
192192
let copyYankLines = 0
193193
let copyCopies = 0
194194
let copyExitVisuals = 0
195+
let copyExitPreserveScrolls = 0
195196
let copyFocusInputs = 0
196197

197198
function clearPending() {
@@ -307,6 +308,10 @@ function createHandler(
307308
copyExitVisuals++
308309
setCopyVisual(undefined)
309310
},
311+
copyExitPreserveScroll() {
312+
copyExitPreserveScrolls++
313+
setCopyVisual(undefined)
314+
},
310315
copyFocusInput() {
311316
copyFocusInputs++
312317
},
@@ -411,6 +416,7 @@ function createHandler(
411416
copyYankLines: () => copyYankLines,
412417
copyCopies: () => copyCopies,
413418
copyExitVisuals: () => copyExitVisuals,
419+
copyExitPreserveScrolls: () => copyExitPreserveScrolls,
414420
copyFocusInputs: () => copyFocusInputs,
415421
copyCol,
416422
copyIdx,
@@ -5099,6 +5105,7 @@ describe("copy mode", () => {
50995105
expect(evt.prevented()).toBe(true)
51005106
expect(ctx.copyYanks()).toBe(1)
51015107
expect(ctx.copyCopies()).toBe(0)
5108+
expect(ctx.copyExitPreserveScrolls()).toBe(1)
51025109
expect(ctx.state.register()).toEqual({ text: "picked text", linewise: false })
51035110
expect(ctx.state.mode()).toBe("normal")
51045111
})
@@ -5122,6 +5129,7 @@ describe("copy mode", () => {
51225129
expect(ctx.state.pending()).toBe("")
51235130

51245131
await new Promise((resolve) => setTimeout(resolve, 100))
5132+
expect(ctx.copyExitPreserveScrolls()).toBe(1)
51255133
expect(ctx.state.mode()).toBe("normal")
51265134
})
51275135

@@ -5148,6 +5156,7 @@ describe("copy mode", () => {
51485156
expect(evt.prevented()).toBe(true)
51495157
expect(ctx.copyCopies()).toBe(1)
51505158
expect(ctx.copyYanks()).toBe(0)
5159+
expect(ctx.copyExitPreserveScrolls()).toBe(1)
51515160
expect(ctx.state.mode()).toBe("normal")
51525161
})
51535162

0 commit comments

Comments
 (0)