mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 06:12:26 +00:00
OpenTUI is here (#2685)
This commit is contained in:
41
packages/opencode/src/util/binary.ts
Normal file
41
packages/opencode/src/util/binary.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export namespace Binary {
|
||||
export function search<T>(array: T[], id: string, compare: (item: T) => string): { found: boolean; index: number } {
|
||||
let left = 0
|
||||
let right = array.length - 1
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2)
|
||||
const midId = compare(array[mid])
|
||||
|
||||
if (midId === id) {
|
||||
return { found: true, index: mid }
|
||||
} else if (midId < id) {
|
||||
left = mid + 1
|
||||
} else {
|
||||
right = mid - 1
|
||||
}
|
||||
}
|
||||
|
||||
return { found: false, index: left }
|
||||
}
|
||||
|
||||
export function insert<T>(array: T[], item: T, compare: (item: T) => string): T[] {
|
||||
const id = compare(item)
|
||||
let left = 0
|
||||
let right = array.length
|
||||
|
||||
while (left < right) {
|
||||
const mid = Math.floor((left + right) / 2)
|
||||
const midId = compare(array[mid])
|
||||
|
||||
if (midId < id) {
|
||||
left = mid + 1
|
||||
} else {
|
||||
right = mid
|
||||
}
|
||||
}
|
||||
|
||||
array.splice(left, 0, item)
|
||||
return array
|
||||
}
|
||||
}
|
||||
20
packages/opencode/src/util/eventloop.ts
Normal file
20
packages/opencode/src/util/eventloop.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Log } from "./log"
|
||||
|
||||
export namespace EventLoop {
|
||||
export async function wait() {
|
||||
return new Promise<void>((resolve) => {
|
||||
const check = () => {
|
||||
const active = [...(process as any)._getActiveHandles(), ...(process as any)._getActiveRequests()]
|
||||
Log.Default.info("eventloop", {
|
||||
active,
|
||||
})
|
||||
if ((process as any)._getActiveHandles().length === 0 && (process as any)._getActiveRequests().length === 0) {
|
||||
resolve()
|
||||
} else {
|
||||
setImmediate(check)
|
||||
}
|
||||
}
|
||||
check()
|
||||
})
|
||||
}
|
||||
}
|
||||
3
packages/opencode/src/util/iife.ts
Normal file
3
packages/opencode/src/util/iife.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function iife<T>(fn: () => T) {
|
||||
return fn()
|
||||
}
|
||||
76
packages/opencode/src/util/keybind.ts
Normal file
76
packages/opencode/src/util/keybind.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { isDeepEqual } from "remeda"
|
||||
|
||||
export namespace Keybind {
|
||||
export type Info = {
|
||||
ctrl: boolean
|
||||
meta: boolean
|
||||
shift: boolean
|
||||
leader: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
export function match(a: Info, b: Info): boolean {
|
||||
return isDeepEqual(a, b)
|
||||
}
|
||||
|
||||
export function toString(info: Info): string {
|
||||
const parts: string[] = []
|
||||
|
||||
if (info.ctrl) parts.push("ctrl")
|
||||
if (info.meta) parts.push("alt")
|
||||
if (info.shift) parts.push("shift")
|
||||
if (info.name) {
|
||||
if (info.name === "delete") parts.push("del")
|
||||
else parts.push(info.name)
|
||||
}
|
||||
|
||||
let result = parts.join("+")
|
||||
|
||||
if (info.leader) {
|
||||
result = result ? `<leader> ${result}` : `<leader>`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function parse(key: string): Info[] {
|
||||
if (key === "none") return []
|
||||
|
||||
return key.split(",").map((combo) => {
|
||||
// Handle <leader> syntax by replacing with leader+
|
||||
const normalized = combo.replace(/<leader>/g, "leader+")
|
||||
const parts = normalized.toLowerCase().split("+")
|
||||
const info: Info = {
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
leader: false,
|
||||
name: "",
|
||||
}
|
||||
|
||||
for (const part of parts) {
|
||||
switch (part) {
|
||||
case "ctrl":
|
||||
info.ctrl = true
|
||||
break
|
||||
case "alt":
|
||||
case "meta":
|
||||
case "option":
|
||||
info.meta = true
|
||||
break
|
||||
case "shift":
|
||||
info.shift = true
|
||||
break
|
||||
case "leader":
|
||||
info.leader = true
|
||||
break
|
||||
default:
|
||||
info.name = part
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
})
|
||||
}
|
||||
}
|
||||
39
packages/opencode/src/util/locale.ts
Normal file
39
packages/opencode/src/util/locale.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export namespace Locale {
|
||||
export function titlecase(str: string) {
|
||||
return str.replace(/\b\w/g, (c) => c.toUpperCase())
|
||||
}
|
||||
|
||||
export function time(input: number) {
|
||||
const date = new Date(input)
|
||||
return date.toLocaleTimeString()
|
||||
}
|
||||
|
||||
export function number(num: number): string {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + "M"
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + "K"
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
export function truncate(str: string, len: number): string {
|
||||
if (str.length <= len) return str
|
||||
return str.slice(0, len - 1) + "…"
|
||||
}
|
||||
|
||||
export function truncateMiddle(str: string, maxLength: number = 35): string {
|
||||
if (str.length <= maxLength) return str
|
||||
|
||||
const ellipsis = "…"
|
||||
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)
|
||||
}
|
||||
|
||||
export function pluralize(count: number, singular: string, plural: string): string {
|
||||
const template = count === 1 ? singular : plural
|
||||
return template.replace("{}", count.toString())
|
||||
}
|
||||
}
|
||||
42
packages/opencode/src/util/rpc.ts
Normal file
42
packages/opencode/src/util/rpc.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export namespace Rpc {
|
||||
type Definition = {
|
||||
[method: string]: (input: any) => any
|
||||
}
|
||||
|
||||
export function listen(rpc: Definition) {
|
||||
onmessage = async (evt) => {
|
||||
const parsed = JSON.parse(evt.data)
|
||||
if (parsed.type === "rpc.request") {
|
||||
const result = await rpc[parsed.method](parsed.input)
|
||||
postMessage(JSON.stringify({ type: "rpc.result", result, id: parsed.id }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function client<T extends Definition>(target: {
|
||||
postMessage: (data: string) => void | null
|
||||
onmessage: ((this: Worker, ev: MessageEvent<any>) => any) | null
|
||||
}) {
|
||||
const pending = new Map<number, (result: any) => void>()
|
||||
let id = 0
|
||||
target.onmessage = async (evt) => {
|
||||
const parsed = JSON.parse(evt.data)
|
||||
if (parsed.type === "rpc.result") {
|
||||
const resolve = pending.get(parsed.id)
|
||||
if (resolve) {
|
||||
resolve(parsed.result)
|
||||
pending.delete(parsed.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
call<Method extends keyof T>(method: Method, input: Parameters<T[Method]>[0]): Promise<ReturnType<T[Method]>> {
|
||||
const requestId = id++
|
||||
return new Promise((resolve) => {
|
||||
pending.set(requestId, resolve)
|
||||
target.postMessage(JSON.stringify({ type: "rpc.request", method, input, id: requestId }))
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
12
packages/opencode/src/util/signal.ts
Normal file
12
packages/opencode/src/util/signal.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function signal() {
|
||||
let resolve: any
|
||||
const promise = new Promise((r) => (resolve = r))
|
||||
return {
|
||||
trigger() {
|
||||
return resolve()
|
||||
},
|
||||
wait() {
|
||||
return promise
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user