From 38b1854c12f0e43723b249ac94b2153de8ebc9e0 Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Mon, 23 Sep 2024 17:50:46 -0500 Subject: [PATCH] implement an interactive "chooser" --- config.nims | 4 +- src/hwylterm.nim | 6 +- src/hwylterm/chooser.nim | 139 +++++++++++++++++++++++++++++++++++++++ src/hwylterm/cli.nim | 4 +- tools/tools.nimble | 5 +- 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 src/hwylterm/chooser.nim diff --git a/config.nims b/config.nims index 7df5664..5cbb571 100644 --- a/config.nims +++ b/config.nims @@ -12,7 +12,8 @@ task docs, "Deploy doc html + search index to public/ directory": deployDir = getCurrentDir() / "public" pkgName = "hwylterm" gitUrl = fmt"https://github.com/daylinmorgan/{pkgName}" - selfExec fmt"doc --docRoot:{getCurrentDir()}/src/ --index:on --outdir:{deployDir} src/hwylterm/cli" + for module in ["cli", "chooser"]: + selfExec fmt"doc --docRoot:{getCurrentDir()}/src/ --index:on --outdir:{deployDir} src/hwylterm/{module}" selfExec fmt"doc --project --index:on --git.url:{gitUrl} --git.commit:main --outdir:{deployDir} --project src/{pkgName}.nim" withDir deployDir: mvFile(pkgName & ".html", "index.html") @@ -24,3 +25,4 @@ task docs, "Deploy doc html + search index to public/ directory": when withDir(thisDir(), system.dirExists("nimbledeps")): --path:"./nimbledeps/pkgs2/cligen-1.7.5-f3ffe7329c8db755677d3ca377d02ff176cec8b1" + --path:"./nimbledeps/pkgs2/illwill-0.4.1-9c58351502f89a16caf031cbd1992ad3fdfd3c67" diff --git a/src/hwylterm.nim b/src/hwylterm.nim index d60d390..5ad6cf9 100644 --- a/src/hwylterm.nim +++ b/src/hwylterm.nim @@ -1,8 +1,10 @@ ##[ # Hwylterm - [see bbansi](./hwylterm/bbansi.html) - [see cli](./hwylterm/cli.html) + see also these utility modules: + + - [cli](./hwylterm/cli.html), requires [cligen](https://github.com/c-blake/cligen) + - [chooser](./hwylterm/chooser.html), requires [illwill](https://github.com/johnnovak/illwill) ]## import hwylterm/[spin, bbansi] diff --git a/src/hwylterm/chooser.nim b/src/hwylterm/chooser.nim new file mode 100644 index 0000000..f7e0623 --- /dev/null +++ b/src/hwylterm/chooser.nim @@ -0,0 +1,139 @@ +##[ + # Hwylterm Chooser + + ```nim + import hwylterm/chooser + + let items = ["a","b","c"] + let item = choose(items) + ``` +]## + + +import std/[enumerate, os, strutils, sequtils, sets, terminal] + +template canImport(x): bool = compiles: import x +when not canImport(illwill): {.fatal: "hwylterm/choose requires illwill >= 0.4.1".} +import illwill + + +proc exitProc() {.noconv.} = + illwillDeInit() + showCursor() + +proc quitProc() {.noconv.} = + exitProc() + quit(0) + +type + State = object + lastKey: Key + buffer: string + selections: HashSet[Natural] + height, max, pos, low, high: Natural + +func newState[T](things: openArray[T], height: Natural): State = + result.max = len(things) - 1 + result.height = height + result.high = height + +func up(s: var State) = + if s.pos > 0: dec s.pos + if (s.pos < s.low): + dec s.low + dec s.high + +func down(s: var State) = + if s.pos < s.max: + inc s.pos + if ((s.pos - s.low) > s.height) and + (s.pos > s.high) and + (s.high < s.max): + inc s.low + inc s.high + +func pressed(s: var State, k: Key) = s.lastKey = k + +func select(s: var State ) = + s.selections = + symmetricDifference(s.selections, toHashSet([s.pos])) + +proc clip(s: string, length: int): string = + if s.len > length: s[0..length] + else: s + +# proc addHelp(s: var screen) = + +func addThingsWindow[T](state: var State, things: openArray[T]) = + var window: string + for i, t in enumerate(things[state.low..state.high]): + window.add ( + if (i + state.low) == state.pos: ">" + else: " " + ) + window.add ( + if (i + state.low) in state.selections: ">" + else: " " + ) + window.add $t + window.add "\n" + state.buffer.add window + + +proc draw(s: var State) = + let maxWidth = terminalWidth() + var lines= ( + s.buffer.splitLines().mapIt((" " & it).clip(maxWidth).alignLeft(maxWidth)) + ) + when defined(debugChoose): + lines = @[$s] & lines + + for l in lines: + stdout.writeLine l + + cursorUp lines.len + flushFile stdout + s.buffer = "" + +proc getSelections[T](state: State, things: openArray[T]): seq[T] = + if state.selections.len == 0: + result.add things[state.pos] + for i in state.selections: + result.add things[i] + + +proc choose*[T](things: openArray[T], height: Natural = 6): seq[T] = + illwillInit(fullscreen = false) + setControlCHook(quitProc) + hideCursor() + + var state = newState(things, height) + + while true: + var key = getKey() + pressed(state, key) + case key: + of Key.None: discard + of Key.Down, Key.J: + down state + of Key.Up, Key.K: + up state + of Key.Tab: + select state + of Key.Enter: + exitProc() + return getSelections(state, things) + else: discard + + addThingsWindow(state, things) + draw state + sleep 20 + + +when isMainModule: + let items = LowercaseLetters.toSeq() + let item = choose(items) + echo "selected: ", item + + + diff --git a/src/hwylterm/cli.nim b/src/hwylterm/cli.nim index 5e7aabe..4449922 100644 --- a/src/hwylterm/cli.nim +++ b/src/hwylterm/cli.nim @@ -4,8 +4,10 @@ Adapter to add hwylterm colors to cligen output. ]## import std/[tables] -import cligen import ./bbansi +template canImport(x): bool = compiles: import x +when not canImport(cligen): {.fatal: "hwylterm/cli requires cligen>= 1.7.5".} +import cligen type diff --git a/tools/tools.nimble b/tools/tools.nimble index e38a3df..47b42ad 100644 --- a/tools/tools.nimble +++ b/tools/tools.nimble @@ -5,7 +5,10 @@ author = "Daylin Morgan" description = "bringing some fun (hwyl) to the terminal" license = "MIT" srcDir = "../src" -namedBin = {"hwylterm/bbansi":"bbansi"}.toTable +namedBin = { + "hwylterm/bbansi" :"bbansi", + "hwylterm/chooser":"hwylchoose" +}.toTable requires "nim >= 2.0.8"