Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions packages/tui/src/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,23 @@ export function Prompt(props: PromptProps) {
syncVimRegisterFromClipboard,
} = promptVim

function resetVimHistory() {
vimState.resetHistory()
if (vimEnabled() && store.mode === "normal" && vimState.isInsert()) vim.beginInsertEdit()
}

createEffect<boolean>((previous) => {
const enabled = vimEnabled()
if (enabled && previous === false) resetVimHistory()
return enabled
}, vimEnabled())

createEffect<"normal" | "shell">((previous) => {
const mode = store.mode
if (mode !== previous) resetVimHistory()
return mode
}, store.mode)

createEffect(
on(
() => props.sessionID,
Expand Down Expand Up @@ -702,7 +719,7 @@ export function Prompt(props: PromptProps) {
})
restoreExtmarksFromParts(updatedNonTextParts)
input.cursorOffset = Bun.stringWidth(normalized)
vimState.resetHistory()
resetVimHistory()
},
},
{
Expand Down Expand Up @@ -734,7 +751,7 @@ export function Prompt(props: PromptProps) {
parts: [],
})
input.gotoBufferEnd()
vimState.resetHistory()
resetVimHistory()
}}
/>
))
Expand Down Expand Up @@ -925,7 +942,7 @@ export function Prompt(props: PromptProps) {
setStore("prompt", prompt)
restoreExtmarksFromParts(prompt.parts)
input.gotoBufferEnd()
vimState.resetHistory()
resetVimHistory()
},
reset() {
input.clear()
Expand All @@ -935,7 +952,7 @@ export function Prompt(props: PromptProps) {
parts: [],
})
setStore("extmarkToPartIndex", new Map())
vimState.resetHistory()
resetVimHistory()
},
submit() {
void submit()
Expand All @@ -945,13 +962,13 @@ export function Prompt(props: PromptProps) {
onMount(() => {
const saved = stashed
stashed = undefined
if (store.prompt.input) return
if (saved && saved.prompt.input) {
if (!store.prompt.input && saved?.prompt.input) {
input.setText(saved.prompt.input)
setStore("prompt", saved.prompt)
restoreExtmarksFromParts(saved.prompt.parts)
input.cursorOffset = saved.cursor
}
if (vimEnabled() && vimState.isInsert()) vim.beginInsertEdit()
})

onCleanup(() => {
Expand Down Expand Up @@ -1136,6 +1153,7 @@ export function Prompt(props: PromptProps) {
input.clear()
setStore("prompt", { input: "", parts: [] })
setStore("extmarkToPartIndex", new Map())
resetVimHistory()
dialog.clear()
},
},
Expand All @@ -1151,6 +1169,7 @@ export function Prompt(props: PromptProps) {
setStore("prompt", { input: entry.input, parts: entry.parts })
restoreExtmarksFromParts(entry.parts)
input.gotoBufferEnd()
resetVimHistory()
}
dialog.clear()
},
Expand All @@ -1168,6 +1187,7 @@ export function Prompt(props: PromptProps) {
setStore("prompt", { input: entry.input, parts: entry.parts })
restoreExtmarksFromParts(entry.parts)
input.gotoBufferEnd()
resetVimHistory()
}}
/>
))
Expand Down Expand Up @@ -1271,6 +1291,7 @@ export function Prompt(props: PromptProps) {
setStore("mode", item.mode ?? "normal")
restoreExtmarksFromParts(item.parts)
input.cursorOffset = 0
resetVimHistory()
},
},
],
Expand Down Expand Up @@ -1307,6 +1328,7 @@ export function Prompt(props: PromptProps) {
setStore("mode", item.mode ?? "normal")
restoreExtmarksFromParts(item.parts)
input.cursorOffset = input.plainText.length
resetVimHistory()
},
},
],
Expand Down Expand Up @@ -1532,6 +1554,7 @@ export function Prompt(props: PromptProps) {
}, 50)
}
input.clear()
if (vimEnabled() && vimState.isInsert()) vim.beginInsertEdit()
if (finishMoveProgress) move.finishSubmit()
return true
}
Expand Down Expand Up @@ -1715,6 +1738,7 @@ export function Prompt(props: PromptProps) {
parts: [],
})
setStore("extmarkToPartIndex", new Map())
resetVimHistory()
}

const dimmed = createMemo(() => leader() || vimState.isCopy())
Expand Down
2 changes: 2 additions & 0 deletions packages/tui/src/component/prompt/vim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function usePromptVim(opts: {
popCopyMode?.()
keymap.setData(OPENCODE_VIM_MODE_KEY, undefined)
if (vimEnabled()) lastVimMode = vimState.isCopy() ? "normal" : vimState.mode()
vimState.cancelEdit()
})

createEffect(() => {
Expand Down Expand Up @@ -93,6 +94,7 @@ export function usePromptVim(opts: {
function enterCopyMode() {
const copy = opts.copy()
if (!vimEnabled() || !copy) return false
if (vimState.isInsert()) vim.finishInsertEdit()
vimState.setMode("copy")
copy.enter()
const input = opts.textarea()
Expand Down
9 changes: 9 additions & 0 deletions packages/tui/src/component/vim/vim-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,13 @@ export function createVimHandler(input: {
const edit = repeat.edit
const begin = repeat.begin

function finishInsertEdit() {
input.state.setMode("normal")
input.state.commitEdit(snapshot())
moveLeft(input.textarea())
repeat.commit(snapshot())
}

function applyOperatorYank(result: VimOperatorResult) {
if (result.register) setRegister(result.register, true)
if (result.span && result.span.end > result.span.start) input.flash?.(result.span)
Expand Down Expand Up @@ -2271,6 +2278,8 @@ export function createVimHandler(input: {
}

return {
beginInsertEdit: repeat.begin,
finishInsertEdit,
handleKey(event: VimEvent) {
if (!input.enabled()) return false

Expand Down
44 changes: 44 additions & 0 deletions packages/tui/test/cli/tui/vim-motions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8084,6 +8084,50 @@ describe("vim dot repeat", () => {
expect(ctx.textarea.insertText).toBe(insertText)
})

test("tracks implicit insert sessions", () => {
const ctx = createHandler("", { mode: "insert" })

ctx.handler.beginInsertEdit()
ctx.textarea.insertText("abc")
press(ctx, "escape")
press(ctx, "u")
expect(ctx.textarea.plainText).toBe("")

press(ctx, ".")
expect(ctx.textarea.plainText).toBe("abc")
})

test("restarts implicit insert tracking after history reset", () => {
const ctx = createHandler("draft", { mode: "insert" })

ctx.handler.beginInsertEdit()
ctx.textarea.insertText("!")
ctx.state.resetHistory()
ctx.textarea.setText("")
ctx.handler.beginInsertEdit()
ctx.textarea.insertText("abc")
press(ctx, "escape")
press(ctx, "u")
expect(ctx.textarea.plainText).toBe("")

press(ctx, ".")
expect(ctx.textarea.plainText).toBe("abc")
})

test("finishes implicit insert tracking before external mode changes", () => {
const ctx = createHandler("", { mode: "insert" })

ctx.handler.beginInsertEdit()
ctx.textarea.insertText("abc")
ctx.handler.finishInsertEdit()
expect(ctx.textarea.cursorOffset).toBe(2)
press(ctx, "u")
expect(ctx.textarea.plainText).toBe("")

press(ctx, ".")
expect(ctx.textarea.plainText).toBe("abc")
})

test("dot repeats complex insert sessions from the current text", () => {
const ctx = createHandler("abcd")
ctx.textarea.cursorOffset = 1
Expand Down
Loading