prompt: fix cursor math for wide characters (#27017)
String.length counts code points, not display columns, so CJK characters and emoji that occupy two terminal cells caused misaligned cursors, broken mention triggers, and incorrect history restoration offsets. Use Bun.stringWidth for now, we need an alternative for this. Fix #26716 Close #26922
This commit is contained in:
parent
d276d96cdf
commit
8f05bbfaa6
5 changed files with 113 additions and 29 deletions
|
|
@ -1,8 +1,11 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
createPromptHistory,
|
||||
displayCharAt,
|
||||
displaySlice,
|
||||
isExitCommand,
|
||||
isNewCommand,
|
||||
mentionTriggerIndex,
|
||||
movePromptHistory,
|
||||
printableBinding,
|
||||
promptCycle,
|
||||
|
|
@ -85,6 +88,53 @@ describe("run prompt shared", () => {
|
|||
expect(draft.state.index).toBeNull()
|
||||
})
|
||||
|
||||
test("uses display-width cursors for history restoration", () => {
|
||||
const base = createPromptHistory([prompt("one"), prompt("中文")])
|
||||
|
||||
const latest = movePromptHistory(base, -1, "草稿", 0)
|
||||
expect(latest.apply).toBe(true)
|
||||
expect(latest.text).toBe("中文")
|
||||
expect(latest.cursor).toBe(0)
|
||||
|
||||
const older = movePromptHistory(latest.state, -1, "中文", 0)
|
||||
expect(older.apply).toBe(true)
|
||||
expect(older.text).toBe("one")
|
||||
expect(older.cursor).toBe(0)
|
||||
|
||||
const newer = movePromptHistory(older.state, 1, "one", Bun.stringWidth("one"))
|
||||
expect(newer.apply).toBe(true)
|
||||
expect(newer.text).toBe("中文")
|
||||
expect(newer.cursor).toBe(Bun.stringWidth("中文"))
|
||||
|
||||
const draft = movePromptHistory(newer.state, 1, "中文", Bun.stringWidth("中文"))
|
||||
expect(draft.apply).toBe(true)
|
||||
expect(draft.text).toBe("草稿")
|
||||
expect(draft.cursor).toBe(Bun.stringWidth("草稿"))
|
||||
})
|
||||
|
||||
test("uses display-width offsets for mention helpers", () => {
|
||||
expect(mentionTriggerIndex("@")).toBe(0)
|
||||
expect(mentionTriggerIndex("test @")).toBe(5)
|
||||
expect(mentionTriggerIndex("中文 @")).toBe(5)
|
||||
expect(mentionTriggerIndex("こんにちは @")).toBe(11)
|
||||
expect(mentionTriggerIndex("한국어 @")).toBe(7)
|
||||
expect(mentionTriggerIndex("🙂 @")).toBe(3)
|
||||
expect(mentionTriggerIndex("中文 @src file", Bun.stringWidth("中文 @src"))).toBe(5)
|
||||
expect(displayCharAt("中文 @src", Bun.stringWidth("中文 @"))).toBe("s")
|
||||
expect(displaySlice("中文 @src", 5, Bun.stringWidth("中文 @src"))).toBe("@src")
|
||||
expect(displaySlice("中文 @src", 6, Bun.stringWidth("中文 @src"))).toBe("src")
|
||||
expect(mentionTriggerIndex("👨👩👧👦 @src", Bun.stringWidth("👨👩👧👦 @src"))).toBe(3)
|
||||
expect(displayCharAt("👨👩👧👦 @src", Bun.stringWidth("👨👩👧👦 @"))).toBe("s")
|
||||
expect(displaySlice("👨👩👧👦 @src", 3, Bun.stringWidth("👨👩👧👦 @src"))).toBe("@src")
|
||||
expect(mentionTriggerIndex("中文@")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("こんにちは@")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("한국어@")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("🙂@")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("hello@")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("foo@bar.com")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("中文 @src file")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("handles direct and leader-based variant cycling", () => {
|
||||
const keys = promptKeys(keybinds)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue