Download
86 lines 2.4 KiBTomoModified 2025-11-22 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
# An example program to pick a line of input from stdin
use ../btui.tm

_HELP := "
    A simple picker program
"

struct Picker(
    options:[Text],
    prompt=">",
    query="",
    offset:Int=0,
    max_height:Int?=20,
)
    func draw(self:Picker)
        live_options := self.live_options()
        if chosen := self.chosen()
            write("\r\033[33;1m$(self.prompt)\033[m \033[2m$(chosen.replace(self.query, "\033[0;1m$(self.query)\033[0;2m"))\033[m\033[K")
        else
            write("\r\033[33;1m$(self.prompt)\033[m \033[31m$(self.query)\033[m\033[K")

        size := get_size()
        height := (self.max_height or size.y) _min_ size.y
        shown_options := live_options.to(height-1)
        for opt in shown_options
            write("\r\n\033[K$opt")
        clear(Below)
        move_cursor(ScreenVec2(0, -shown_options.length), relative=yes)
        flush()

    func update(self:&Picker)
        key := get_key()
        when key
        is "Ctrl-c"
            write("\r")
            clear(Right)
            clear(Below)
            disable()
            exit(code=1)
        is "Enter"
            write("\r")
            clear(Right)
            clear(Below)
            disable()
            if chosen := self.chosen()
                say(chosen)
                exit(code=0)
            else
                exit(code=1)
        is "Backspace"
            self.query = self.query.to(-2)
        is "Space"
            self.query ++= " "
        is "Up", "Shift-Tab"
            self.offset -= 1
        is "Down", "Tab"
            self.offset += 1
        else if key.length == 1
            self.query ++= key

    func live_options(self:Picker -> [Text])
        return [o for o in self.options if o.has(self.query)]

    func chosen(self:Picker -> Text?)
        live_options := self.live_options()
        if live_options.length > 0
            return live_options[(1 + self.offset) mod1 live_options.length]
        else
            return none

func main(choices:Path=(/dev/stdin), prompt|p=">", query|q="", max_height|H:Int?=20)
    lines := choices.lines() or fail("No such file: $choices")
    lines = [line for line in lines if line.length > 0]
    picker := Picker(
        options=lines,
        prompt=prompt,
        query=query,
        max_height=max_height,
    )
    set_mode(Normal)
    hide_cursor()
    picker.draw()
    repeat
        picker.draw()
        picker.update()
    disable()