Skip to content

Commit 1c43e4b

Browse files
authored
fix: handle final wrapped row in copy-mode selection (#90)
Co-authored-by: leohenon <[email protected]>
1 parent 668655d commit 1c43e4b

2 files changed

Lines changed: 47 additions & 6 deletions

File tree

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,17 @@ export function createCopyMode(input: {
182182
if (info?.lineSources && local < info.lineSources.length) {
183183
const src = info.lineSources[local]
184184
const text = lines[src] ?? ""
185-
const wrapped = info.lineWraps?.[local] === 1 || info.lineSources[local + 1] === src
185+
const wrapped =
186+
info.lineWraps?.[local] === 1 || info.lineSources[local - 1] === src || info.lineSources[local + 1] === src
186187
if (!wrapped) return { text, col: match.gutter }
187-
let base = info.lineStartCols[local]
188+
const lineStart = info.lineStartCols?.[local] ?? 0
189+
let base = lineStart
188190
for (let i = local - 1; i >= 0; i--) {
189-
if (info.lineSources[i] === src) base = info.lineStartCols[i]
191+
if (info.lineSources[i] === src) base = info.lineStartCols?.[i] ?? base
190192
else break
191193
}
192-
const offset = info.lineStartCols[local] - base
193-
const width = info.lineWidthCols[local]
194+
const offset = lineStart - base
195+
const width = info.lineWidthCols?.[local] ?? Bun.stringWidth(text)
194196
return { text: sliceCols(text, offset, width), col: match.gutter }
195197
}
196198
if (local >= lines.length) return { text: "", col: match.gutter }

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { describe, expect, test } from "bun:test"
22
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
33
import type { Part } from "@opencode-ai/sdk/v2"
44
import { createRoot, createSignal } from "solid-js"
5-
import { createCopyMode } from "../../../src/cli/cmd/tui/routes/session/copy-mode"
65
import { createVimHandler } from "../../../src/cli/cmd/tui/component/vim/vim-handler"
76
import { createVimState } from "../../../src/cli/cmd/tui/component/vim/vim-state"
87
import type { VimScroll } from "../../../src/cli/cmd/tui/component/vim/vim-scroll"
98
import { vimScroll } from "../../../src/cli/cmd/tui/component/vim/vim-scroll"
9+
import { createCopyMode } from "../../../src/cli/cmd/tui/routes/session/copy-mode"
1010
import type { VimJump } from "../../../src/cli/cmd/tui/component/vim/vim-motion-jump"
1111
import {
1212
copyNextParagraph,
@@ -4250,6 +4250,45 @@ describe("vim scroll mapping", () => {
42504250
})
42514251

42524252
describe("copy mode", () => {
4253+
test("highlights final wrapped row using its visual slice", () => {
4254+
const child = {
4255+
id: "text-part",
4256+
y: 0,
4257+
height: 3,
4258+
plainText: "abcdefghijklmnopqrstuvwxyz",
4259+
lineInfo: {
4260+
lineSources: [0, 0, 0],
4261+
lineStartCols: [0, 10, 20],
4262+
lineWidthCols: [10, 10, 6],
4263+
lineWraps: [1, 1, 0],
4264+
},
4265+
}
4266+
const scroll = {
4267+
y: 0,
4268+
height: 10,
4269+
width: 80,
4270+
scrollHeight: 3,
4271+
getChildren: () => [child],
4272+
scrollBy() {},
4273+
} as unknown as ScrollBoxRenderable
4274+
const cm = createCopyMode({
4275+
scroll: () => scroll,
4276+
messages: () => [{ id: "message", role: "assistant" }],
4277+
parts: () => [{ id: "part", type: "text", text: child.plainText }] as Part[],
4278+
thinking: () => false,
4279+
details: () => false,
4280+
session: () => "session",
4281+
toBottom() {},
4282+
})
4283+
4284+
cm.prompt.enter()
4285+
cm.prompt.visual("line")
4286+
cm.prompt.jump("top")
4287+
4288+
expect(cm.highlights().get("text-part")?.at(-1)).toMatchObject({ line: 2, text: "uvwxyz" })
4289+
expect(cm.prompt.yank()).toEqual({ text: "abcdefghij\nklmnopqrst\nuvwxyz", linewise: false })
4290+
})
4291+
42534292
test("copyWordNext advances to next row when next word is on following line", () => {
42544293
const next = copyWordNext([{ col: 0 }, { col: 0 }], (idx) => ["alpha", "beta gamma"][idx]!, 0, 4, false)
42554294
expect(next).toEqual({ idx: 1, col: 5 })

0 commit comments

Comments
 (0)