Sebastian 3 месяцев назад
Родитель
Сommit
0f31fd631b

+ 14 - 5
packages/opencode/src/cli/cmd/prompt-display.ts

@@ -1,11 +1,20 @@
 const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" })
 
+function promptOffsetWidth(value: string) {
+  let width = 0
+  for (const part of graphemes.segment(value)) {
+    // Textarea offsets count newlines as one position; Bun.stringWidth counts them as zero.
+    width += part.segment === "\n" ? 1 : Bun.stringWidth(part.segment)
+  }
+  return width
+}
+
 function displayOffsetIndex(value: string, offset: number) {
   if (offset <= 0) return 0
 
   let width = 0
   for (const part of graphemes.segment(value)) {
-    const next = width + Bun.stringWidth(part.segment)
+    const next = width + promptOffsetWidth(part.segment)
     if (next > offset) return part.index
     width = next
   }
@@ -13,20 +22,20 @@ function displayOffsetIndex(value: string, offset: number) {
   return value.length
 }
 
-export function displaySlice(value: string, start = 0, end = Bun.stringWidth(value)) {
+export function displaySlice(value: string, start = 0, end = promptOffsetWidth(value)) {
   return value.slice(displayOffsetIndex(value, start), displayOffsetIndex(value, end))
 }
 
 export function displayCharAt(value: string, offset: number) {
   let width = 0
   for (const part of graphemes.segment(value)) {
-    const next = width + Bun.stringWidth(part.segment)
+    const next = width + promptOffsetWidth(part.segment)
     if (offset === width || offset < next) return part.segment
     width = next
   }
 }
 
-export function mentionTriggerIndex(value: string, offset = Bun.stringWidth(value)) {
+export function mentionTriggerIndex(value: string, offset = promptOffsetWidth(value)) {
   const text = displaySlice(value, 0, offset)
   const index = text.lastIndexOf("@")
   if (index === -1) return
@@ -34,6 +43,6 @@ export function mentionTriggerIndex(value: string, offset = Bun.stringWidth(valu
   const before = index === 0 ? undefined : text[index - 1]
   const query = text.slice(index)
   if ((before === undefined || /\s/.test(before)) && !/\s/.test(query)) {
-    return Bun.stringWidth(text.slice(0, index))
+    return promptOffsetWidth(text.slice(0, index))
   }
 }

+ 6 - 0
packages/opencode/test/cli/run/prompt.shared.test.ts

@@ -126,6 +126,12 @@ describe("run prompt shared", () => {
     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("@file1\n@file2", 13)).toBe(7)
+    expect(displayCharAt("@file1\n@file2", 6)).toBe("\n")
+    expect(displaySlice("@file1\n@file2", 8, 13)).toBe("file2")
+    expect(mentionTriggerIndex("@file1\nfoo @file2", 17)).toBe(11)
+    expect(mentionTriggerIndex("中文 @one\n@two", 14)).toBe(10)
+    expect(displaySlice("中文 @one\n@two", 11, 14)).toBe("two")
     expect(mentionTriggerIndex("中文@")).toBeUndefined()
     expect(mentionTriggerIndex("こんにちは@")).toBeUndefined()
     expect(mentionTriggerIndex("한국어@")).toBeUndefined()