Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/opencode/src/util/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ export function truncate(str: string, len: number): string {

export function truncateLeft(str: string, len: number): string {
if (str.length <= len) return str
return "…" + str.slice(-(len - 1))
// kilocode_change start
const keep = len - 1
// `slice(-0)` is `slice(0)` and returns the whole string, so at len <= 1 the
// tail must be dropped explicitly, otherwise it leaks the entire input.
return "…" + (keep > 0 ? str.slice(-keep) : "")
// kilocode_change end
}

export function truncateMiddle(str: string, maxLength: number = 35): string {
Expand All @@ -75,7 +80,12 @@ export function truncateMiddle(str: string, maxLength: number = 35): string {
const keepStart = Math.ceil((maxLength - ellipsis.length) / 2)
const keepEnd = Math.floor((maxLength - ellipsis.length) / 2)

return str.slice(0, keepStart) + ellipsis + str.slice(-keepEnd)
// kilocode_change start
// `slice(-0)` is `slice(0)` and returns the whole string, so when keepEnd is 0
// (maxLength 1 or 2) the tail must be dropped explicitly, otherwise the result
// is longer than the input instead of truncated.
return str.slice(0, keepStart) + ellipsis + (keepEnd > 0 ? str.slice(-keepEnd) : "")
// kilocode_change end
}

export function pluralize(count: number, singular: string, plural: string): string {
Expand Down
56 changes: 56 additions & 0 deletions packages/opencode/test/kilocode/locale.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, test } from "bun:test"
import { Locale } from "../../src/util/locale"

describe("util.locale", () => {
const long = "src/components/very-long-file-name.tsx"

describe("truncateLeft", () => {
test("never returns more than maxLength characters", () => {
for (const len of [1, 2, 3, 10, 20]) {
expect(Locale.truncateLeft(long, len).length).toBeLessThanOrEqual(len)
}
})

test("collapses to a single ellipsis at length 1", () => {
// `slice(-0)` is `slice(0)`, which used to leak the whole string here.
expect(Locale.truncateLeft(long, 1)).toBe("…")
})

test("keeps the tail characters", () => {
expect(Locale.truncateLeft(long, 5)).toBe("….tsx")
})

test("returns short strings unchanged", () => {
expect(Locale.truncateLeft("abc", 10)).toBe("abc")
})
})

describe("truncateMiddle", () => {
test("never returns more than maxLength characters", () => {
for (const len of [1, 2, 3, 10, 20]) {
expect(Locale.truncateMiddle(long, len).length).toBeLessThanOrEqual(len)
}
})

test("collapses to a single ellipsis at length 1", () => {
// keepEnd is 0 here, so `slice(-0)` used to append the whole string.
expect(Locale.truncateMiddle(long, 1)).toBe("…")
})

test("drops the tail at length 2", () => {
expect(Locale.truncateMiddle(long, 2)).toBe("s…")
})

test("keeps head and tail around the ellipsis", () => {
const result = Locale.truncateMiddle(long, 11)
expect(result.length).toBe(11)
expect(result).toContain("…")
expect(result.startsWith("src")).toBe(true)
expect(result.endsWith("tsx")).toBe(true)
})

test("returns short strings unchanged", () => {
expect(Locale.truncateMiddle("abc", 10)).toBe("abc")
})
})
})