mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
refactor(cli): extract vim prompt logic into kilocode mirror
Move the vim modal editing engine and prompt integration out of shared upstream files and into src/kilocode/ mirrors per the Fork Isolation Rule: - src/kilocode/cli/cmd/tui/component/prompt/vim.ts (engine, moved) - src/kilocode/cli/cmd/tui/component/prompt/index.tsx (new mirror with useVim, VimModeIndicator, vimToggleCommand) - test/kilocode/cli/cmd/tui/prompt/vim.test.ts (moved) The shared prompt/index.tsx now calls into the mirror behind minimal kilocode_change markers instead of inlining ~200 lines of vim logic.
This commit is contained in:
@@ -67,14 +67,7 @@ import { useCommandPalette } from "../../context/command-palette"
|
||||
import { useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
// kilocode_change start - vim modal editing for the prompt
|
||||
import {
|
||||
createVimState,
|
||||
enterNormal as vimEnterNormal,
|
||||
handleNormalKey as vimHandleNormalKey,
|
||||
handleVisualKey as vimHandleVisualKey,
|
||||
type VimDoc,
|
||||
type VimKey,
|
||||
} from "./vim"
|
||||
import { useVim, VimModeIndicator, vimToggleCommand } from "@/kilocode/cli/cmd/tui/component/prompt"
|
||||
// kilocode_change end
|
||||
|
||||
export type PromptProps = {
|
||||
@@ -216,98 +209,14 @@ export function Prompt(props: PromptProps) {
|
||||
const [warpNotice, setWarpNotice] = createSignal<string>()
|
||||
const [cursorVersion, setCursorVersion] = createSignal(0)
|
||||
// kilocode_change start - vim modal editing for the prompt
|
||||
const vimState = createVimState("insert")
|
||||
const [vimMode, setVimMode] = createSignal<"insert" | "normal" | "visual" | "visual-line">("insert")
|
||||
const vimEnabled = createMemo(() => kv.get("vim_enabled", tuiConfig.vim ?? false))
|
||||
function syncVimMode() {
|
||||
if (vimState.mode !== vimMode()) setVimMode(vimState.mode)
|
||||
setCursorVersion((value) => value + 1)
|
||||
}
|
||||
function resetVim() {
|
||||
vimState.mode = "insert"
|
||||
vimState.operator = undefined
|
||||
vimState.awaitingG = false
|
||||
vimState.awaitingReplace = false
|
||||
vimState.countDigits = ""
|
||||
vimState.desiredColumn = undefined
|
||||
vimState.visualAnchor = undefined
|
||||
if (input && !input.isDestroyed) input.clearSelection()
|
||||
setVimMode("insert")
|
||||
}
|
||||
function vimDoc(): VimDoc {
|
||||
return {
|
||||
get text() {
|
||||
return input.plainText
|
||||
},
|
||||
get cursor() {
|
||||
return input.cursorOffset
|
||||
},
|
||||
setCursor(offset: number) {
|
||||
input.cursorOffset = Math.max(0, Math.min(offset, input.plainText.length))
|
||||
},
|
||||
insert(offset: number, value: string) {
|
||||
input.cursorOffset = Math.max(0, Math.min(offset, input.plainText.length))
|
||||
input.insertText(value)
|
||||
},
|
||||
remove(start: number, end: number) {
|
||||
const removed = input.plainText.slice(start, end)
|
||||
input.setSelection(start, end)
|
||||
input.deleteSelection()
|
||||
return removed
|
||||
},
|
||||
undo() {
|
||||
input.undo()
|
||||
},
|
||||
redo() {
|
||||
input.redo()
|
||||
},
|
||||
setSelection(start: number, end: number) {
|
||||
input.setSelection(start, end)
|
||||
},
|
||||
clearSelection() {
|
||||
input.clearSelection()
|
||||
},
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Intercept a key while vim mode is active. Returns true when the key was
|
||||
* consumed by the vim layer (caller must preventDefault so the textarea does
|
||||
* not also process it).
|
||||
*/
|
||||
function vimOnKey(e: KeyEvent): boolean {
|
||||
if (!vimEnabled() || props.disabled || !input || input.isDestroyed) return false
|
||||
|
||||
// INSERT mode: only Escape is special (switch to NORMAL). Everything else
|
||||
// is left to the native textarea so typing behaves normally.
|
||||
if (vimState.mode === "insert") {
|
||||
if (e.name === "escape" && !auto()?.visible) {
|
||||
vimEnterNormal(vimDoc(), vimState)
|
||||
syncVimMode()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const visual = vimState.mode === "visual" || vimState.mode === "visual-line"
|
||||
|
||||
// In NORMAL mode keep Enter (submit) and Tab (autocomplete) working rather
|
||||
// than emulating strict vim line motions for them. In VISUAL mode the user
|
||||
// is selecting, so let the engine handle those keys instead.
|
||||
if (!visual && (e.name === "return" || e.name === "enter" || e.name === "tab")) return false
|
||||
|
||||
// Let global ctrl/meta combos (e.g. ctrl+c to exit) through, except ctrl+r
|
||||
// which is vim redo.
|
||||
const ctrl = e.ctrl === true
|
||||
if ((ctrl || e.meta === true || e.super === true) && !(ctrl && e.name === "r")) return false
|
||||
|
||||
const key: VimKey = ctrl
|
||||
? { key: e.name, ctrl: true }
|
||||
: { key: e.sequence && e.sequence.length === 1 ? e.sequence : e.name }
|
||||
|
||||
const result = visual ? vimHandleVisualKey(vimDoc(), vimState, key) : vimHandleNormalKey(vimDoc(), vimState, key)
|
||||
if (result.handled) syncVimMode()
|
||||
return result.handled
|
||||
}
|
||||
const vim = useVim({
|
||||
input: () => input,
|
||||
disabled: () => props.disabled ?? false,
|
||||
autocompleteVisible: () => Boolean(auto()?.visible),
|
||||
getVimEnabled: () => Boolean(kv.get("vim_enabled", tuiConfig.vim ?? false)),
|
||||
bumpCursor: () => setCursorVersion((value) => value + 1),
|
||||
cursorVersion: () => cursorVersion(),
|
||||
})
|
||||
// kilocode_change end
|
||||
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
|
||||
const hasRightContent = createMemo(() => Boolean(props.right))
|
||||
@@ -436,22 +345,6 @@ export function Prompt(props: PromptProps) {
|
||||
if (!props.disabled) input.cursorColor = theme.text
|
||||
})
|
||||
|
||||
// kilocode_change start - vim cursor shape + reset when vim is disabled
|
||||
createEffect(() => {
|
||||
if (!vimEnabled()) {
|
||||
if (vimState.mode !== "insert") resetVim()
|
||||
// Restore the default (non-vim) cursor so a block cursor from NORMAL/VISUAL
|
||||
// mode does not linger after vim mode is turned off.
|
||||
if (input && !input.isDestroyed) input.cursorStyle = { style: "block", blinking: true }
|
||||
return
|
||||
}
|
||||
cursorVersion()
|
||||
if (!input || input.isDestroyed) return
|
||||
// Block cursor in NORMAL/VISUAL modes, bar cursor in INSERT mode (vim convention).
|
||||
input.cursorStyle = vimMode() === "insert" ? { style: "line", blinking: true } : { style: "block", blinking: false }
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
const lastUserMessage = createMemo(() => {
|
||||
if (!props.sessionID) return undefined
|
||||
const messages = sync.data.message[props.sessionID]
|
||||
@@ -708,20 +601,13 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
},
|
||||
// kilocode_change start - vim modal editing toggle (palette + /vim)
|
||||
{
|
||||
title: "Toggle vim mode",
|
||||
desc: "Enable or disable vim modal editing in the prompt input",
|
||||
name: "prompt.vim.toggle",
|
||||
category: "Prompt",
|
||||
slashName: "vim",
|
||||
run: () => {
|
||||
const next = !vimEnabled()
|
||||
kv.set("vim_enabled", next)
|
||||
resetVim()
|
||||
dialog.clear()
|
||||
toast.show({ message: next ? "Vim mode enabled" : "Vim mode disabled", variant: "info" })
|
||||
},
|
||||
},
|
||||
vimToggleCommand({
|
||||
vimEnabled: vim.vimEnabled,
|
||||
setVimEnabled: (value) => kv.set("vim_enabled", value),
|
||||
resetVim: vim.resetVim,
|
||||
clearDialog: () => dialog.clear(),
|
||||
showToast: (message) => toast.show({ message, variant: "info" }),
|
||||
}),
|
||||
// kilocode_change end
|
||||
{
|
||||
title: "Skills",
|
||||
@@ -815,7 +701,7 @@ export function Prompt(props: PromptProps) {
|
||||
parts: [],
|
||||
})
|
||||
setStore("extmarkToPartIndex", new Map())
|
||||
resetVim() // kilocode_change - return to insert mode after the prompt is cleared
|
||||
vim.resetVim() // kilocode_change - return to insert mode after the prompt is cleared
|
||||
},
|
||||
submit() {
|
||||
void submit()
|
||||
@@ -968,7 +854,7 @@ export function Prompt(props: PromptProps) {
|
||||
input.clear()
|
||||
setStore("prompt", { input: "", parts: [] })
|
||||
setStore("extmarkToPartIndex", new Map())
|
||||
resetVim() // kilocode_change
|
||||
vim.resetVim() // kilocode_change
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -984,7 +870,7 @@ export function Prompt(props: PromptProps) {
|
||||
setStore("prompt", { input: entry.input, parts: entry.parts })
|
||||
restoreExtmarksFromParts(entry.parts)
|
||||
input.gotoBufferEnd()
|
||||
resetVim() // kilocode_change
|
||||
vim.resetVim() // kilocode_change
|
||||
}
|
||||
dialog.clear()
|
||||
},
|
||||
@@ -1002,7 +888,7 @@ export function Prompt(props: PromptProps) {
|
||||
setStore("prompt", { input: entry.input, parts: entry.parts })
|
||||
restoreExtmarksFromParts(entry.parts)
|
||||
input.gotoBufferEnd()
|
||||
resetVim() // kilocode_change
|
||||
vim.resetVim() // kilocode_change
|
||||
}}
|
||||
/>
|
||||
))
|
||||
@@ -1125,7 +1011,7 @@ export function Prompt(props: PromptProps) {
|
||||
setStore("prompt", item)
|
||||
setStore("mode", item.mode ?? "normal")
|
||||
restoreExtmarksFromParts(item.parts)
|
||||
resetVim() // kilocode_change - recalled history starts in insert mode
|
||||
vim.resetVim() // kilocode_change - recalled history starts in insert mode
|
||||
input.cursorOffset = 0
|
||||
},
|
||||
},
|
||||
@@ -1162,7 +1048,7 @@ export function Prompt(props: PromptProps) {
|
||||
setStore("prompt", item)
|
||||
setStore("mode", item.mode ?? "normal")
|
||||
restoreExtmarksFromParts(item.parts)
|
||||
resetVim() // kilocode_change - recalled history starts in insert mode
|
||||
vim.resetVim() // kilocode_change - recalled history starts in insert mode
|
||||
input.cursorOffset = input.plainText.length
|
||||
},
|
||||
},
|
||||
@@ -1404,7 +1290,7 @@ export function Prompt(props: PromptProps) {
|
||||
}, 50)
|
||||
}
|
||||
input.clear()
|
||||
resetVim() // kilocode_change - drop back to insert mode after sending
|
||||
vim.resetVim() // kilocode_change - drop back to insert mode after sending
|
||||
return true
|
||||
}
|
||||
const exit = useExit()
|
||||
@@ -1565,7 +1451,7 @@ export function Prompt(props: PromptProps) {
|
||||
parts: [],
|
||||
})
|
||||
setStore("extmarkToPartIndex", new Map())
|
||||
resetVim() // kilocode_change - don't leak stale vim mode/selection into an emptied prompt
|
||||
vim.resetVim() // kilocode_change - don't leak stale vim mode/selection into an emptied prompt
|
||||
}
|
||||
|
||||
const highlight = createMemo(() => {
|
||||
@@ -1693,17 +1579,18 @@ export function Prompt(props: PromptProps) {
|
||||
setCursorVersion((value) => value + 1)
|
||||
if (store.mode === "normal") auto()?.onCursorChange()
|
||||
}}
|
||||
onKeyDown={(e: KeyEvent) => {
|
||||
/* kilocode_change - KeyEvent type for vim key routing */ onKeyDown={(e: KeyEvent) => {
|
||||
if (props.disabled) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
// kilocode_change - route keys through the vim layer when enabled
|
||||
if (vimOnKey(e)) {
|
||||
// kilocode_change start - route keys through the vim layer when enabled
|
||||
if (vim.vimOnKey(e)) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
// kilocode_change end
|
||||
}}
|
||||
onSubmit={() => {
|
||||
// IME: double-defer so the last composed character (e.g. Korean
|
||||
@@ -1767,34 +1654,16 @@ export function Prompt(props: PromptProps) {
|
||||
{/* kilocode_change end */}
|
||||
</text>
|
||||
{/* kilocode_change start - vim mode indicator */}
|
||||
<Show when={vimEnabled() && store.mode !== "shell"}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={fadeColor(theme.textMuted, agentMetaAlpha())}>·</text>
|
||||
<text>
|
||||
<span
|
||||
style={{
|
||||
fg: fadeColor(
|
||||
vimMode() === "insert"
|
||||
? theme.info
|
||||
: vimMode() === "visual" || vimMode() === "visual-line"
|
||||
? theme.warning
|
||||
: theme.success,
|
||||
agentMetaAlpha(),
|
||||
),
|
||||
bold: true,
|
||||
}}
|
||||
>
|
||||
{vimMode() === "insert"
|
||||
? "INSERT"
|
||||
: vimMode() === "visual"
|
||||
? "VISUAL"
|
||||
: vimMode() === "visual-line"
|
||||
? "V-LINE"
|
||||
: "NORMAL"}
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
</Show>
|
||||
<VimModeIndicator
|
||||
when={() => vim.vimEnabled() && store.mode !== "shell"}
|
||||
mode={vim.vimMode}
|
||||
fade={fadeColor}
|
||||
textMuted={() => theme.textMuted}
|
||||
info={() => theme.info}
|
||||
warning={() => theme.warning}
|
||||
success={() => theme.success}
|
||||
alpha={agentMetaAlpha}
|
||||
/>
|
||||
{/* kilocode_change end */}
|
||||
<Show when={store.mode === "normal"}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
|
||||
@@ -199,7 +199,7 @@ export const Definitions = {
|
||||
"prompt.autocomplete.select": keybind("return", "Select autocomplete item"),
|
||||
"prompt.autocomplete.complete": keybind("tab", "Complete autocomplete item"),
|
||||
"permission.prompt.fullscreen": keybind("ctrl+f", "Toggle permission prompt fullscreen"),
|
||||
"prompt.vim.toggle": keybind("none", "Toggle vim modal editing in the prompt input"),
|
||||
"prompt.vim.toggle": keybind("none", "Toggle vim modal editing in the prompt input"), // kilocode_change
|
||||
"plugins.toggle": keybind("space", "Toggle plugin"),
|
||||
"dialog.plugins.install": keybind("shift+i", "Install plugin from plugin dialog"),
|
||||
|
||||
|
||||
@@ -77,7 +77,9 @@ export const TuiInfo = Schema.Struct({
|
||||
scroll_acceleration: Schema.optional(ScrollAcceleration),
|
||||
diff_style: Schema.optional(DiffStyle),
|
||||
mouse: Schema.optional(Schema.Boolean).annotate({ description: "Enable or disable mouse capture (default: true)" }),
|
||||
// kilocode_change start - vim modal editing toggle
|
||||
vim: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable vim modal editing in the prompt input (default: false)",
|
||||
}),
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
import { createEffect, createMemo, createSignal, Show } from "solid-js"
|
||||
import { type KeyEvent, type RGBA, type TextareaRenderable } from "@opentui/core"
|
||||
import type { JSX } from "@opentui/solid"
|
||||
import {
|
||||
createVimState,
|
||||
enterNormal,
|
||||
handleNormalKey,
|
||||
handleVisualKey,
|
||||
type VimDoc,
|
||||
type VimKey,
|
||||
type VimMode,
|
||||
} from "./vim"
|
||||
|
||||
export interface VimDeps {
|
||||
input: () => TextareaRenderable | undefined
|
||||
disabled: () => boolean
|
||||
autocompleteVisible: () => boolean
|
||||
getVimEnabled: () => boolean
|
||||
bumpCursor: () => void
|
||||
cursorVersion: () => number
|
||||
}
|
||||
|
||||
export interface VimApi {
|
||||
vimEnabled: () => boolean
|
||||
vimMode: () => VimMode
|
||||
vimOnKey: (e: KeyEvent) => boolean
|
||||
resetVim: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up vim modal editing for the prompt textarea. Returns the reactive
|
||||
* accessors and key handler the shared `Prompt` component wires into its
|
||||
* textarea, command palette, and meta-row indicator.
|
||||
*
|
||||
* Must be called within the `Prompt` component body so the SolidJS effects
|
||||
* are owned by the component.
|
||||
*/
|
||||
export function useVim(deps: VimDeps): VimApi {
|
||||
const state = createVimState("insert")
|
||||
const [mode, setMode] = createSignal<VimMode>("insert")
|
||||
const enabled = createMemo(() => deps.getVimEnabled())
|
||||
|
||||
function sync() {
|
||||
if (state.mode !== mode()) setMode(state.mode)
|
||||
deps.bumpCursor()
|
||||
}
|
||||
|
||||
function resetVim() {
|
||||
state.mode = "insert"
|
||||
state.operator = undefined
|
||||
state.awaitingG = false
|
||||
state.awaitingReplace = false
|
||||
state.countDigits = ""
|
||||
state.desiredColumn = undefined
|
||||
state.visualAnchor = undefined
|
||||
const input = deps.input()
|
||||
if (input && !input.isDestroyed) input.clearSelection()
|
||||
setMode("insert")
|
||||
}
|
||||
|
||||
function doc(input: TextareaRenderable): VimDoc {
|
||||
return {
|
||||
get text() {
|
||||
return input.plainText
|
||||
},
|
||||
get cursor() {
|
||||
return input.cursorOffset
|
||||
},
|
||||
setCursor(offset: number) {
|
||||
input.cursorOffset = Math.max(0, Math.min(offset, input.plainText.length))
|
||||
},
|
||||
insert(offset: number, value: string) {
|
||||
input.cursorOffset = Math.max(0, Math.min(offset, input.plainText.length))
|
||||
input.insertText(value)
|
||||
},
|
||||
remove(start: number, end: number) {
|
||||
const removed = input.plainText.slice(start, end)
|
||||
input.setSelection(start, end)
|
||||
input.deleteSelection()
|
||||
return removed
|
||||
},
|
||||
undo() {
|
||||
input.undo()
|
||||
},
|
||||
redo() {
|
||||
input.redo()
|
||||
},
|
||||
setSelection(start: number, end: number) {
|
||||
input.setSelection(start, end)
|
||||
},
|
||||
clearSelection() {
|
||||
input.clearSelection()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept a key while vim mode is active. Returns true when the key was
|
||||
* consumed by the vim layer (caller must preventDefault so the textarea does
|
||||
* not also process it).
|
||||
*/
|
||||
function vimOnKey(e: KeyEvent): boolean {
|
||||
const input = deps.input()
|
||||
if (!enabled() || deps.disabled() || !input || input.isDestroyed) return false
|
||||
|
||||
// INSERT mode: only Escape is special (switch to NORMAL). Everything else
|
||||
// is left to the native textarea so typing behaves normally.
|
||||
if (state.mode === "insert") {
|
||||
if (e.name === "escape" && !deps.autocompleteVisible()) {
|
||||
enterNormal(doc(input), state)
|
||||
sync()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const visual = state.mode === "visual" || state.mode === "visual-line"
|
||||
|
||||
// In NORMAL mode keep Enter (submit) and Tab (autocomplete) working rather
|
||||
// than emulating strict vim line motions for them. In VISUAL mode the user
|
||||
// is selecting, so let the engine handle those keys instead.
|
||||
if (!visual && (e.name === "return" || e.name === "enter" || e.name === "tab")) return false
|
||||
|
||||
// Let global ctrl/meta combos (e.g. ctrl+c to exit) through, except ctrl+r
|
||||
// which is vim redo.
|
||||
const ctrl = e.ctrl === true
|
||||
if ((ctrl || e.meta === true || e.super === true) && !(ctrl && e.name === "r")) return false
|
||||
|
||||
const key: VimKey = ctrl
|
||||
? { key: e.name, ctrl: true }
|
||||
: { key: e.sequence && e.sequence.length === 1 ? e.sequence : e.name }
|
||||
|
||||
const result = visual ? handleVisualKey(doc(input), state, key) : handleNormalKey(doc(input), state, key)
|
||||
if (result.handled) sync()
|
||||
return result.handled
|
||||
}
|
||||
|
||||
// Block cursor in NORMAL/VISUAL modes, bar cursor in INSERT mode (vim
|
||||
// convention). Also restores the default cursor when vim is disabled.
|
||||
createEffect(() => {
|
||||
if (!enabled()) {
|
||||
if (state.mode !== "insert") resetVim()
|
||||
const input = deps.input()
|
||||
if (input && !input.isDestroyed) input.cursorStyle = { style: "block", blinking: true }
|
||||
return
|
||||
}
|
||||
deps.cursorVersion()
|
||||
const input = deps.input()
|
||||
if (!input || input.isDestroyed) return
|
||||
input.cursorStyle = mode() === "insert" ? { style: "line", blinking: true } : { style: "block", blinking: false }
|
||||
})
|
||||
|
||||
return { vimEnabled: enabled, vimMode: mode, vimOnKey, resetVim }
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode indicator shown in the prompt meta row. Renders nothing unless vim mode
|
||||
* is enabled and the prompt is not in shell mode.
|
||||
*/
|
||||
export function VimModeIndicator(props: {
|
||||
when: () => boolean
|
||||
mode: () => VimMode
|
||||
fade: (color: RGBA, alpha: number) => RGBA
|
||||
textMuted: () => RGBA
|
||||
info: () => RGBA
|
||||
warning: () => RGBA
|
||||
success: () => RGBA
|
||||
alpha: () => number
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Show when={props.when()}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={props.fade(props.textMuted(), props.alpha())}>·</text>
|
||||
<text>
|
||||
<span
|
||||
style={{
|
||||
fg: props.fade(
|
||||
props.mode() === "insert"
|
||||
? props.info()
|
||||
: props.mode() === "visual" || props.mode() === "visual-line"
|
||||
? props.warning()
|
||||
: props.success(),
|
||||
props.alpha(),
|
||||
),
|
||||
bold: true,
|
||||
}}
|
||||
>
|
||||
{props.mode() === "insert"
|
||||
? "INSERT"
|
||||
: props.mode() === "visual"
|
||||
? "VISUAL"
|
||||
: props.mode() === "visual-line"
|
||||
? "V-LINE"
|
||||
: "NORMAL"}
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Command-palette entry that toggles vim mode on or off.
|
||||
*/
|
||||
export function vimToggleCommand(opts: {
|
||||
vimEnabled: () => boolean
|
||||
setVimEnabled: (value: boolean) => void
|
||||
resetVim: () => void
|
||||
clearDialog: () => void
|
||||
showToast: (message: string) => void
|
||||
}) {
|
||||
return {
|
||||
title: "Toggle vim mode",
|
||||
desc: "Enable or disable vim modal editing in the prompt input",
|
||||
name: "prompt.vim.toggle",
|
||||
category: "Prompt",
|
||||
slashName: "vim",
|
||||
run: () => {
|
||||
const next = !opts.vimEnabled()
|
||||
opts.setVimEnabled(next)
|
||||
opts.resetVim()
|
||||
opts.clearDialog()
|
||||
opts.showToast(next ? "Vim mode enabled" : "Vim mode disabled")
|
||||
},
|
||||
}
|
||||
}
|
||||
-2
@@ -1,4 +1,3 @@
|
||||
// kilocode_change start - vim modal editing for the prompt input
|
||||
/**
|
||||
* A small, self-contained vim emulation engine for the prompt textarea.
|
||||
*
|
||||
@@ -883,4 +882,3 @@ export function enterInsert(state: VimState) {
|
||||
state.mode = "insert"
|
||||
resetPending(state)
|
||||
}
|
||||
// kilocode_change end
|
||||
+1
-1
@@ -9,7 +9,7 @@ import {
|
||||
clampNormal,
|
||||
type VimDoc,
|
||||
type VimKey,
|
||||
} from "@tui/component/prompt/vim"
|
||||
} from "@/kilocode/cli/cmd/tui/component/prompt/vim"
|
||||
|
||||
/** In-memory document implementing the engine's VimDoc contract. */
|
||||
class MockDoc implements VimDoc {
|
||||
Reference in New Issue
Block a user