mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 11:05:31 +08:00
test(webview-ui): centralize IME Enter guard and add unit tests
Extract isEnterKeyCommitNotIme so PromptInput, docks, selectors, and KiloClaw inputs share one definition. Bun tests cover Enter, composing, and keyCode 229.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import { isEnterKeyCommitNotIme } from "../../webview-ui/src/utils/ime-enter"
|
||||
|
||||
describe("isEnterKeyCommitNotIme", () => {
|
||||
it("is true for a normal Enter keydown", () => {
|
||||
expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: false, keyCode: 13 })).toBe(true)
|
||||
})
|
||||
|
||||
it("is false while isComposing is true", () => {
|
||||
expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: true, keyCode: 13 })).toBe(false)
|
||||
})
|
||||
|
||||
it("is false when keyCode is 229 (IME-processed key on Windows)", () => {
|
||||
expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: false, keyCode: 229 })).toBe(false)
|
||||
})
|
||||
|
||||
it("is false for non-Enter keys", () => {
|
||||
expect(isEnterKeyCommitNotIme({ key: "a", isComposing: false, keyCode: 65 })).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import { For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-j
|
||||
import { useClaw } from "../context/claw"
|
||||
import { useKiloClawLanguage } from "../context/language"
|
||||
import type { ConversationListItem } from "../lib/types"
|
||||
import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter"
|
||||
|
||||
type Group = { label: string; items: ConversationListItem[] }
|
||||
|
||||
@@ -132,7 +133,7 @@ function ConversationItem(props: { conversation: ConversationListItem }) {
|
||||
}
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e)) {
|
||||
e.preventDefault()
|
||||
commitRename()
|
||||
} else if (e.key === "Escape") {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useKiloClawLanguage } from "../context/language"
|
||||
import { MessageBubble } from "./MessageBubble"
|
||||
import { computeBotDisplay, useNowTicker } from "./botStatus"
|
||||
import type { Message } from "../lib/types"
|
||||
import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter"
|
||||
|
||||
export function MessageArea() {
|
||||
const claw = useClaw()
|
||||
@@ -150,7 +151,7 @@ export function MessageArea() {
|
||||
}
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e) && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
submit()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Markdown } from "@kilocode/kilo-ui/markdown"
|
||||
import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
import type { ContentBlock, ExecApprovalDecision, Message } from "../lib/types"
|
||||
import { useKiloClawLanguage } from "../context/language"
|
||||
import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter"
|
||||
|
||||
const ULID_TIME_LEN = 10
|
||||
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
||||
@@ -254,7 +255,7 @@ export function MessageBubble(props: MessageBubbleProps) {
|
||||
value={editText()}
|
||||
onInput={(e) => setEditText(e.currentTarget.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e) && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
saveEdit()
|
||||
} else if (e.key === "Escape") {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Show, createMemo, createSignal } from "solid-js"
|
||||
import { useClaw } from "../context/claw"
|
||||
import { useKiloClawLanguage } from "../context/language"
|
||||
import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter"
|
||||
|
||||
function dot(status: string | null | undefined): string {
|
||||
if (!status) return "kiloclaw-dot-offline"
|
||||
@@ -77,7 +78,7 @@ export function StatusSidebar() {
|
||||
}
|
||||
|
||||
const onTitleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e)) {
|
||||
e.preventDefault()
|
||||
commitTitleRename()
|
||||
} else if (e.key === "Escape") {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { PermissionDiff } from "./PermissionDiff"
|
||||
import { permissionDiffs } from "./permission-diff-utils"
|
||||
import { normalizeUrls } from "../../../../../opencode/src/kilocode/util/url"
|
||||
import type { PermissionRequest } from "../../types/messages"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
|
||||
let rulesExpandedPreference = false
|
||||
|
||||
@@ -133,7 +134,7 @@ export const PermissionDock: Component<{
|
||||
)
|
||||
|
||||
const plain = (e: KeyboardEvent) =>
|
||||
e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey && !e.isComposing && e.keyCode !== 229
|
||||
isEnterKeyCommitNotIme(e) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey
|
||||
|
||||
const skip = (e: KeyboardEvent, target: Element | undefined) => {
|
||||
const local = !!target?.closest("[data-component='permission-shortcuts']")
|
||||
|
||||
@@ -48,6 +48,7 @@ import { formatReviewCommentsMarkdown } from "../../utils/review-comment-markdow
|
||||
import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../utils/prompt-drafts"
|
||||
import { ReviewComments } from "./ReviewComments"
|
||||
import { partReview, reviewBody } from "../../../../src/shared/review-comments"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
|
||||
// Per-session input text storage (module-level so it survives remounts)
|
||||
const drafts = new Map<string, string>()
|
||||
@@ -615,7 +616,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
session.abort()
|
||||
return
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e) && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSend()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
toggleAnswer,
|
||||
tr,
|
||||
} from "./question-dock-utils"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
|
||||
export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => {
|
||||
const session = useSession()
|
||||
@@ -288,7 +289,7 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
||||
reject()
|
||||
return
|
||||
}
|
||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e) && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (store.sending) return
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { useSession } from "../../context/session"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import type { AgentInfo } from "../../types/messages"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
|
||||
/** Format an agent for display. Uses displayName if available, otherwise title-cases the slug. */
|
||||
function formatAgentLabel(agent: AgentInfo): string {
|
||||
@@ -87,7 +88,7 @@ export const ModeSwitcherBase: Component<ModeSwitcherBaseProps> = (props) => {
|
||||
} else if (e.key === "End") {
|
||||
e.preventDefault()
|
||||
focusItem(len - 1)
|
||||
} else if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) {
|
||||
} else if (e.key === " " || isEnterKeyCommitNotIme(e)) {
|
||||
e.preventDefault()
|
||||
if (cur >= 0 && cur < len) pick(props.agents[cur].name)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import type { EnrichedModel } from "../../context/provider"
|
||||
import { useSession, SessionContext } from "../../context/session"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import type { ModelSelection } from "../../types/messages"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
import {
|
||||
KILO_GATEWAY_ID,
|
||||
isSmall,
|
||||
@@ -549,7 +550,7 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) {
|
||||
if (isEnterKeyCommitNotIme(e)) {
|
||||
e.preventDefault()
|
||||
const row = rowMap().get(selectedKey())
|
||||
if (row) selectRow(row)
|
||||
|
||||
@@ -11,6 +11,7 @@ import { type Accessor, Component, createSignal, For, onCleanup, Show } from "so
|
||||
import { PopupSelector } from "./PopupSelector"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { useSession } from "../../context/session"
|
||||
import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reusable base component
|
||||
@@ -117,7 +118,7 @@ export const ThinkingSelectorBase: Component<ThinkingSelectorBaseProps> = (props
|
||||
focusItem(len - 1)
|
||||
return
|
||||
}
|
||||
if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) {
|
||||
if (e.key === " " || isEnterKeyCommitNotIme(e)) {
|
||||
e.preventDefault()
|
||||
if (cur >= 0 && cur < len) pick(items[cur])
|
||||
return
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* IME composition: Enter should confirm glyphs, not trigger chat send / shortcuts.
|
||||
* Browsers set `isComposing` while composing; on Windows, `keyCode === 229` often
|
||||
* marks IME-handled keys even when `isComposing` is false for a given keydown.
|
||||
*/
|
||||
export function isEnterKeyCommitNotIme(
|
||||
e: Pick<KeyboardEvent, "key" | "isComposing" | "keyCode">,
|
||||
): boolean {
|
||||
return e.key === "Enter" && !e.isComposing && e.keyCode !== 229
|
||||
}
|
||||
Reference in New Issue
Block a user