opencode-vim
Advanced tools
+18
-15
@@ -8,4 +8,5 @@ { | ||
| "dependencies": { | ||
| "@opentui/core": "^0.2.1", | ||
| "@opentui/solid": "^0.2.1", | ||
| "@opentui/core": "^0.2.6", | ||
| "@opentui/keymap": "^0.2.6", | ||
| "@opentui/solid": "^0.2.6", | ||
| "@vimee/core": "^0.3.0", | ||
@@ -15,3 +16,3 @@ "solid-js": "^1.9.12", | ||
| "devDependencies": { | ||
| "@opencode-ai/plugin": "1.14.30", | ||
| "@opencode-ai/plugin": "1.14.44", | ||
| }, | ||
@@ -99,22 +100,24 @@ }, | ||
| "@opencode-ai/plugin": ["@opencode-ai/plugin@1.14.30", "", { "dependencies": { "@opencode-ai/sdk": "1.14.30", "effect": "4.0.0-beta.57", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.1.105", "@opentui/solid": ">=0.1.105" }, "optionalPeers": ["@opentui/core", "@opentui/solid"] }, "sha512-O1y6qR349R5XJtB76Vx4TO/uvLkqNvdsgxtj2ZpWoygb3rtrkuVMiBsrcL2WfuyyaLJnPmbnkeigcdm42r09Hg=="], | ||
| "@opencode-ai/plugin": ["@opencode-ai/plugin@1.14.44", "", { "dependencies": { "@opencode-ai/sdk": "1.14.44", "effect": "4.0.0-beta.59", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.6", "@opentui/keymap": ">=0.2.6", "@opentui/solid": ">=0.2.6" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-7uqSJWaDuP9GNvdUJ57E8AX0WNVP4C8PyCIJ2qvjtucjw6DFT/ErjXDXUrRiqwK3omImXRlTDb32xP9IMfk0dw=="], | ||
| "@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.30", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-OgPEDvALekHZIjByo/okJ699aLPn+XtsVxgZxUqE8TlzAG7TtskMGFl0fro8O0T2p+nkOT/LstnKGbECvc0+YA=="], | ||
| "@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.44", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-hpMCYhwEWk2B1b9THHYy5GRlrCJ9g67pvSyLt9O2b2pYj0HE6v8MeVOhg6ucn2T4QBIoVJ3ZDWnh7ApaeunNWg=="], | ||
| "@opentui/core": ["@opentui/core@0.2.1", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.1", "@opentui/core-darwin-x64": "0.2.1", "@opentui/core-linux-arm64": "0.2.1", "@opentui/core-linux-x64": "0.2.1", "@opentui/core-win32-arm64": "0.2.1", "@opentui/core-win32-x64": "0.2.1" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-tXFD2NeznXzZa02yOsQ/D0o/06GBqVrPR7VdmgqI+ZChcS7NY/QgNQGgAZUditW2vezFcxlgTqoCgQaZFqRfGQ=="], | ||
| "@opentui/core": ["@opentui/core@0.2.6", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.6", "@opentui/core-darwin-x64": "0.2.6", "@opentui/core-linux-arm64": "0.2.6", "@opentui/core-linux-x64": "0.2.6", "@opentui/core-win32-arm64": "0.2.6", "@opentui/core-win32-x64": "0.2.6" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dBpMaWVM7wtW2/2TlGPrkPjg6gOL3MVU/5XXk+U1LDJB8L4q4NeYWVdzfAVNcEvgmuuCy/cVqdY2D4ei+e7MMg=="], | ||
| "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vT8rgeNdkzD84wCcS5zmrSVFgR/SjeVr+2FNM/icTjwKh9Jq3cY6UY7/EFck3PRjrmanNu4zw7W5lFKSpMxnPQ=="], | ||
| "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hR5nsxNj+059utzenTCF0kealUlibON6fLuebFUCGM/5kJnqa+shIh0XbUDFm0+F47vqVUgZufBdUuieQZIbvQ=="], | ||
| "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HAH1KeTejs0Z+RChNsNImI1eOlYJabKeAEvLtnn1aiHxvJFpzdAi6d/VrG5WFcxAem6TVRtnKN46gXff26lkig=="], | ||
| "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-pJ/bH4WC/mbBaakM1YdH6TVo67jhy0KPd61bCz97w0I/PJGr8fmNKvhmMt/AwyFgOQi3FYZiEKLMpGdvUcSsrQ=="], | ||
| "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-447FI6xolsKbG1eH3hpNGQhQ/8M6FDJIgpiJhX9XXvZGO7vEyN6efyqqIzRF+quafqF69+rT3qK70S1svjwVKA=="], | ||
| "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Pnd3kOxig8ii+/IqYheOPEgferylsQA0L6tKBnHQ9jRlCJOcu0Rv65Jepueh212vevdV9DzPURJnhejG06J6g=="], | ||
| "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Xdds+HnAbFiylTdxyYhRxPFPnrHm75KTi7QCA1JHU3lb8Sfwrxv3nlYtFhEV798y4osafsghL20Nq5mqVPjizQ=="], | ||
| "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-458Mx9tBzEPzfft8cSt5ZaIpEepoxBXBOL6AUVmDTKWaZ3uouraPcEKraGAyvOTDQp2XDI3R8c/2GdaR77FaUQ=="], | ||
| "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-VEuqX9OSFpw960mibe4OXVmnE43V/VYwB61zoxD5uw7L3jgCiOfrf+W2TuFbcRAgtTD35MypJ53fYxmUcVZc8g=="], | ||
| "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-BDUrdrT1RCcVnQoHJmUut4y811jDBAEtc6GJFB4Gs265Be8SrTjVCus6p2fSQ7j9sZQ1OcjO+5+4NkheSZICDQ=="], | ||
| "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ywwRbyjPPEbNe3da+ez4NxIaJWQ79sA2gdepuHKUVdmdxsevdRpAxOHkEpJNtiVvphPc/On9EsP25Hmn5gjYxg=="], | ||
| "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-SUYAzRJ9TSoD2Qt8kn6FJz6dbTrFEPVig5mScB4zFGgGQO/Bbod2/Q31vLS/IQrX+FDb67WaErD+kuMCnMPPLA=="], | ||
| "@opentui/solid": ["@opentui/solid@0.2.1", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.1", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-/vlW9SmcqkarbMwB4JaIwwS3ovo42DSwAnyM04pjJVhG7DzKKwdUzozQMRgvEDZZ7oGYQRvHyVAfL8GCLh0AZw=="], | ||
| "@opentui/keymap": ["@opentui/keymap@0.2.6", "", { "dependencies": { "@opentui/core": "0.2.6" }, "peerDependencies": { "@opentui/react": "0.2.6", "@opentui/solid": "0.2.6", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-+6OYuedrFCKVo4ryGFNwws++2VOmPcXU3PwpY0mP47gYQY2nvQ+etWIs2Y7r5eMIqUfxVCldkKsrzcEcA4tb/A=="], | ||
| "@opentui/solid": ["@opentui/solid@0.2.6", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.6", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2y225WlOGi/fCaajkxBmLyVW8Cr+OmhowHdvrYcz5w2kBD15sKbJLIYu1G9DxceirT1uIyasGy2TGzRRcVkTDg=="], | ||
| "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], | ||
@@ -140,3 +143,3 @@ | ||
| "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], | ||
| "bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="], | ||
@@ -157,3 +160,3 @@ "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], | ||
| "effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="], | ||
| "effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="], | ||
@@ -160,0 +163,0 @@ "electron-to-chromium": ["electron-to-chromium@1.5.345", "", {}, "sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg=="], |
+5
-4
| { | ||
| "name": "opencode-vim", | ||
| "version": "0.0.11", | ||
| "version": "0.0.12", | ||
| "exports": { | ||
@@ -10,4 +10,5 @@ "./tui": { | ||
| "dependencies": { | ||
| "@opentui/core": "^0.2.1", | ||
| "@opentui/solid": "^0.2.1", | ||
| "@opentui/core": "^0.2.6", | ||
| "@opentui/keymap": "^0.2.6", | ||
| "@opentui/solid": "^0.2.6", | ||
| "solid-js": "^1.9.12", | ||
@@ -17,4 +18,4 @@ "@vimee/core": "^0.3.0" | ||
| "devDependencies": { | ||
| "@opencode-ai/plugin": "1.14.30" | ||
| "@opencode-ai/plugin": "1.14.44" | ||
| } | ||
| } |
@@ -6,5 +6,3 @@ /** @jsxImportSource @opentui/solid */ | ||
| export function createSnippetsModule(): PromptModule { | ||
| const controller: SnippetController = {} | ||
| export function createSnippetsModule(controller: SnippetController): PromptModule { | ||
| return { | ||
@@ -11,0 +9,0 @@ id: "snippets", |
@@ -49,2 +49,3 @@ export type SnippetSource = "global" | "project" | ||
| insertTrigger?: () => void | ||
| navigate?: (delta: number) => boolean | ||
| } |
@@ -103,2 +103,3 @@ /** @jsxImportSource @opentui/solid */ | ||
| props.controller.insertTrigger = undefined | ||
| props.controller.navigate = undefined | ||
| }) | ||
@@ -160,2 +161,23 @@ | ||
| const navigate = (delta: number) => { | ||
| const ref = props.ctx.prompt() | ||
| if (!ref || !ref.focused || dialogBlockingInput()) return false | ||
| const current = findTrailingHashtagTrigger(ref.current.input) | ||
| if (!current || dismissed() === current.token) return false | ||
| const value = current.query.trim() | ||
| const total = optionsForQuery(value).length | ||
| if (total === 0 && !canCreateForQuery(current.query)) return false | ||
| if (pendingPromptSync) clearTimeout(pendingPromptSync) | ||
| pendingPromptSync = undefined | ||
| setInput(ref.current.input) | ||
| setSyncingPrompt(false) | ||
| lockKeyboardSelection() | ||
| setSelected((current) => stepSelection(current, total || 1, delta)) | ||
| props.ctx.requestRender() | ||
| return true | ||
| } | ||
| const accept = () => { | ||
@@ -210,2 +232,10 @@ const ref = props.ctx.prompt() | ||
| const canAcceptSubmit = (ref: TuiPromptRef) => { | ||
| if (isReloadCommand(ref.current.input)) return true | ||
| if (dialogBlockingInput()) return true | ||
| const current = findTrailingHashtagTrigger(ref.current.input) | ||
| return !!current && dismissed() !== current.token | ||
| } | ||
| props.controller.accept = accept | ||
@@ -222,2 +252,3 @@ props.controller.reload = () => { | ||
| } | ||
| props.controller.navigate = navigate | ||
@@ -236,36 +267,41 @@ createEffect(() => { | ||
| commandDispose = props.ctx.api.command.register(() => [ | ||
| { | ||
| title: "Reload snippets", | ||
| value: "snippets.reload", | ||
| description: "Reload snippet files from disk", | ||
| category: "Prompt", | ||
| slash: { name: "snippets:reload" }, | ||
| onSelect() { | ||
| void executeReloadInPrompt(ref) | ||
| commandDispose = props.ctx.api.keymap.registerLayer({ | ||
| commands: [ | ||
| { | ||
| namespace: "palette", | ||
| name: "snippets.reload", | ||
| title: "Reload snippets", | ||
| desc: "Reload snippet files from disk", | ||
| category: "Prompt", | ||
| slashName: "snippets:reload", | ||
| run() { | ||
| void executeReloadInPrompt(ref) | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| title: "Insert snippet", | ||
| value: "snippets.insert", | ||
| description: "Insert a snippet trigger into the prompt", | ||
| category: "Prompt", | ||
| onSelect() { | ||
| syncPromptInput(ref, insertSnippetTrigger(ref.current.input)) | ||
| ref.focus() | ||
| { | ||
| namespace: "palette", | ||
| name: "snippets.insert", | ||
| title: "Insert snippet", | ||
| desc: "Insert a snippet trigger into the prompt", | ||
| category: "Prompt", | ||
| run() { | ||
| syncPromptInput(ref, insertSnippetTrigger(ref.current.input)) | ||
| ref.focus() | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| title: "Accept snippet autocomplete", | ||
| value: "snippets.accept", | ||
| keybind: "input_submit", | ||
| category: "Prompt", | ||
| hidden: true, | ||
| enabled: ref.focused, | ||
| onSelect() { | ||
| if (accept()) return | ||
| ref.submit() | ||
| { | ||
| namespace: "palette", | ||
| name: "snippets.accept", | ||
| title: "Accept snippet autocomplete", | ||
| category: "Prompt", | ||
| hidden: true, | ||
| enabled: () => ref.focused && canAcceptSubmit(ref), | ||
| run() { | ||
| if (accept()) return | ||
| ref.submit() | ||
| }, | ||
| }, | ||
| }, | ||
| ]) | ||
| ], | ||
| bindings: acceptSnippetBindings(props.ctx.api), | ||
| }) | ||
| }, 0) | ||
@@ -288,8 +324,3 @@ }) | ||
| const total = options().length | ||
| const actionable = total > 0 || canCreate() | ||
| if ((name === "up" || name === "down") && actionable) { | ||
| lockKeyboardSelection() | ||
| setSelected((current) => stepSelection(current, total || 1, name === "up" ? -1 : 1)) | ||
| if ((name === "up" || name === "down") && navigate(name === "up" ? -1 : 1)) { | ||
| event.preventDefault() | ||
@@ -307,4 +338,4 @@ event.stopPropagation() | ||
| if (name === "tab" && actionable) { | ||
| if (total > 0) choose() | ||
| if (name === "tab" && (options().length > 0 || canCreate())) { | ||
| if (options().length > 0) choose() | ||
| else void createSnippetDraft() | ||
@@ -569,2 +600,17 @@ event.preventDefault() | ||
| function acceptSnippetBindings(api: TuiPluginApi) { | ||
| const command = "snippets.accept" | ||
| const title = "Accept snippet autocomplete" | ||
| if (!api.tuiConfig.keybinds.has("input.submit")) { | ||
| return [{ key: "input_submit", cmd: command, desc: title }] | ||
| } | ||
| return api.tuiConfig.keybinds.get("input.submit").map((binding) => ({ | ||
| ...binding, | ||
| cmd: command, | ||
| desc: binding.desc ?? title, | ||
| })) | ||
| } | ||
| function selectedText(theme: PromptContext["api"]["theme"]["current"]) { | ||
@@ -571,0 +617,0 @@ if (theme.background.a !== 0) return theme.background |
@@ -7,2 +7,3 @@ /** @jsxImportSource @opentui/solid */ | ||
| import type { PromptContext, PromptModule } from "../../prompt/types" | ||
| import type { SnippetController } from "../snippets/types" | ||
| import { applyVimCursorStyle, focusedInput } from "./actions" | ||
@@ -18,3 +19,3 @@ import type { VimConfig } from "./config" | ||
| export function createVimModule(options?: unknown, enabled: Accessor<boolean> = () => true): PromptModule { | ||
| export function createVimModule(options?: unknown, enabled: Accessor<boolean> = () => true, snippets?: SnippetController): PromptModule { | ||
| const config = createVimConfig(options) | ||
@@ -32,3 +33,3 @@ const log = createVimLog(config) | ||
| renderAbove(ctx) { | ||
| return <VimKeyboard ctx={ctx} config={config} state={state} enabled={enabled} log={log} /> | ||
| return <VimKeyboard ctx={ctx} config={config} state={state} snippets={snippets} enabled={enabled} log={log} /> | ||
| }, | ||
@@ -41,8 +42,15 @@ renderRight(ctx) { | ||
| function VimKeyboard(props: { ctx: PromptContext; config: VimConfig; state: ReturnType<typeof createVimState>; enabled: Accessor<boolean>; log: VimLog }) { | ||
| function VimKeyboard(props: { ctx: PromptContext; config: VimConfig; state: ReturnType<typeof createVimState>; snippets?: SnippetController; enabled: Accessor<boolean>; log: VimLog }) { | ||
| let cursorStyleMode = "" | ||
| const vimee = createVimeeAdapter(props.state, props.config, props.log) | ||
| const preparedEvents = new WeakSet<KeyEvent>() | ||
| props.log("keyboard.mount", { kind: props.ctx.kind }) | ||
| const cursorStyleTimer = setInterval(syncCursorStyle, 50) | ||
| const offKeyIntercept = props.ctx.api.keymap.intercept("key", ({ event }) => { | ||
| if (!props.enabled() || props.ctx.disabled || props.ctx.visible === false) return | ||
| const key = keyNotation(event) | ||
| if (!key || !passThroughKey(event, key, props.state.mode())) return | ||
| if (preparePassThroughKey(props.ctx, key, props.state.mode())) preparedEvents.add(event) | ||
| }, { priority: 100 }) | ||
@@ -73,3 +81,3 @@ useKeyboard((event) => { | ||
| if (passThroughKey(event, key, props.state.mode())) { | ||
| preparePassThroughKey(props.ctx, key, props.state.mode()) | ||
| if (!preparedEvents.delete(event)) preparePassThroughKey(props.ctx, key, props.state.mode()) | ||
| syncCursorStyle(true) | ||
@@ -80,3 +88,3 @@ props.ctx.requestRender() | ||
| if (sendNavigationKey(event, props.ctx, key, props.state.mode())) { | ||
| if (sendNavigationKey(event, props.ctx, key, props.state.mode(), props.snippets)) { | ||
| syncCursorStyle(true) | ||
@@ -98,2 +106,3 @@ props.ctx.requestRender() | ||
| props.log("keyboard.cleanup", { kind: props.ctx.kind }) | ||
| offKeyIntercept() | ||
| vimee.cleanup() | ||
@@ -126,10 +135,18 @@ clearInterval(cursorStyleTimer) | ||
| function preparePassThroughKey(ctx: PromptContext, key: string, mode: string) { | ||
| if (mode !== "normal" || key !== "<CR>") return | ||
| if (mode !== "normal" || key !== "<CR>") return false | ||
| const input = focusedInput(ctx) | ||
| if (!input?.plainText || input.cursorOffset === undefined) return | ||
| if (!input?.plainText || input.cursorOffset === undefined) return false | ||
| input.cursorOffset = Math.min(input.cursorOffset + 1, input.plainText.length) | ||
| return true | ||
| } | ||
| function sendNavigationKey(event: KeyEvent, ctx: PromptContext, key: string, mode: string) { | ||
| function sendNavigationKey(event: KeyEvent, ctx: PromptContext, key: string, mode: string, snippets?: SnippetController) { | ||
| if (mode !== "normal") return false | ||
| const delta = snippetNavigationDelta(key) | ||
| if (delta !== 0 && snippets?.navigate?.(delta)) { | ||
| event.preventDefault() | ||
| event.stopPropagation() | ||
| return true | ||
| } | ||
| const forwarded = commandNavigationKey(ctx, key) | ||
@@ -144,2 +161,8 @@ if (!forwarded) return false | ||
| function snippetNavigationDelta(key: string) { | ||
| if (key === "j") return 1 | ||
| if (key === "k") return -1 | ||
| return 0 | ||
| } | ||
| function commandNavigationKey(ctx: PromptContext, key: string): ParsedKey | undefined { | ||
@@ -146,0 +169,0 @@ if (!isPromptFocused(ctx)) return navigationKey(key) |
+22
-15
@@ -8,2 +8,3 @@ /** @jsxImportSource @opentui/solid */ | ||
| import { createSnippetsModule } from "./modules/snippets" | ||
| import type { SnippetController } from "./modules/snippets/types" | ||
| import { checkAutoUpdate } from "./update" | ||
@@ -15,15 +16,20 @@ | ||
| api.command.register(() => [{ | ||
| title: vimEnabled() ? "Disable Vim Mode" : "Enable Vim Mode", | ||
| value: "opencode-vim.toggle", | ||
| description: vimEnabled() ? "Turn off Vim key handling" : "Turn on Vim key handling", | ||
| category: "Vim", | ||
| slash: { name: "vim" }, | ||
| onSelect() { | ||
| const next = !vimEnabled() | ||
| setVimEnabled(next) | ||
| api.kv.set(vimEnabledKey, next) | ||
| api.ui.toast({ variant: "info", message: `Vim mode ${next ? "enabled" : "disabled"}` }) | ||
| }, | ||
| }]) | ||
| api.keymap.registerLayer({ | ||
| commands: [ | ||
| { | ||
| namespace: "palette", | ||
| name: "opencode-vim.toggle", | ||
| title: "Toggle Vim Mode", | ||
| desc: "Enable or disable Vim key handling", | ||
| category: "Vim", | ||
| slashName: "vim", | ||
| run() { | ||
| const next = !vimEnabled() | ||
| setVimEnabled(next) | ||
| api.kv.set(vimEnabledKey, next) | ||
| api.ui.toast({ variant: "info", message: `Vim mode ${next ? "enabled" : "disabled"}` }) | ||
| }, | ||
| }, | ||
| ], | ||
| }) | ||
@@ -51,4 +57,5 @@ if (readAutoUpdate(options)) { | ||
| const modules: PromptModule[] = [] | ||
| if (hasSnippetsPlugin(api)) modules.push(createSnippetsModule()) | ||
| modules.push(createVimModule(options, vimEnabled)) | ||
| const snippets: SnippetController = {} | ||
| if (hasSnippetsPlugin(api)) modules.push(createSnippetsModule(snippets)) | ||
| modules.push(createVimModule(options, vimEnabled, snippets)) | ||
| moduleCache.set(key, modules) | ||
@@ -55,0 +62,0 @@ return modules |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1147005
0.31%2743
2.47%5
25%+ Added
+ Added
Updated
Updated