mirror of
https://github.com/sbrow/envr.git
synced 2026-08-26 18:03:32 -04:00
feat: Scan results can now be filtered during selection.
This commit is contained in:
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
12. `write_flags_table` should never return false.
|
12. `write_flags_table` should never return false.
|
||||||
|
|
||||||
13. Add a text filter to the multi_select.
|
|
||||||
|
|
||||||
## Double-check AI output
|
## Double-check AI output
|
||||||
|
|
||||||
- [ ] cli.odin
|
- [ ] cli.odin
|
||||||
@@ -54,7 +52,7 @@
|
|||||||
- [ ] db_test.odin
|
- [ ] db_test.odin
|
||||||
- [ ] flags.odin
|
- [ ] flags.odin
|
||||||
- [x] main.odin
|
- [x] main.odin
|
||||||
- [x] prompt.odin
|
- [ ] prompt.odin
|
||||||
- [x] scan.odin
|
- [x] scan.odin
|
||||||
- [ ] scan_test.odin
|
- [ ] scan_test.odin
|
||||||
- [ ] sodium.odin
|
- [ ] sodium.odin
|
||||||
|
|||||||
+76
-26
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
import "core:sys/posix"
|
import "core:sys/posix"
|
||||||
import "core:terminal/ansi"
|
import "core:terminal/ansi"
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ Key :: enum {
|
|||||||
Space,
|
Space,
|
||||||
Enter,
|
Enter,
|
||||||
Escape,
|
Escape,
|
||||||
|
Backspace,
|
||||||
|
Char,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +41,17 @@ multi_select :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
selected = make([dynamic]bool, len(options))
|
selected = make([dynamic]bool, len(options))
|
||||||
|
filter: [dynamic]u8
|
||||||
|
defer delete(filter)
|
||||||
|
filtered: [dynamic]int
|
||||||
|
defer delete(filtered)
|
||||||
|
rebuild_filtered(options, filter[:], &filtered)
|
||||||
|
|
||||||
cursor: int = 0
|
cursor: int = 0
|
||||||
scroll_offset: int = 0
|
scroll_offset: int = 0
|
||||||
|
|
||||||
fmt.printf(ansi.CSI + ansi.DECTCEM_HIDE)
|
fmt.printf(ansi.CSI + ansi.DECTCEM_HIDE)
|
||||||
visible := render_options(prompt, options, selected[:], cursor, scroll_offset)
|
visible := render_options(prompt, options, selected[:], filtered[:], string(filter[:]), cursor, scroll_offset)
|
||||||
|
|
||||||
raw, ok := enable_raw_mode(posix.STDIN_FILENO)
|
raw, ok := enable_raw_mode(posix.STDIN_FILENO)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -52,19 +61,31 @@ multi_select :: proc(
|
|||||||
defer disable_raw_mode(&raw)
|
defer disable_raw_mode(&raw)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
key := read_key()
|
key, ch := read_key()
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
|
case .Char:
|
||||||
|
append(&filter, u8(ch))
|
||||||
|
rebuild_filtered(options, filter[:], &filtered)
|
||||||
|
cursor = clamp(cursor, 0, max(0, len(filtered) - 1))
|
||||||
|
case .Backspace:
|
||||||
|
if len(filter) > 0 {
|
||||||
|
pop(&filter)
|
||||||
|
rebuild_filtered(options, filter[:], &filtered)
|
||||||
|
cursor = clamp(cursor, 0, max(0, len(filtered) - 1))
|
||||||
|
}
|
||||||
case .Up:
|
case .Up:
|
||||||
if cursor > 0 {
|
if cursor > 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
case .Down:
|
case .Down:
|
||||||
if cursor < len(options) - 1 {
|
if cursor < len(filtered) - 1 {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
case .Space:
|
case .Space:
|
||||||
selected[cursor] = !selected[cursor]
|
if len(filtered) > 0 {
|
||||||
|
selected[filtered[cursor]] = !selected[filtered[cursor]]
|
||||||
|
}
|
||||||
case .Enter:
|
case .Enter:
|
||||||
fmt.printf(
|
fmt.printf(
|
||||||
ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW,
|
ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW,
|
||||||
@@ -82,9 +103,21 @@ multi_select :: proc(
|
|||||||
case .Unknown:
|
case .Unknown:
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll_offset = max(0, min(cursor - MAX_VISIBLE / 2, len(options) - MAX_VISIBLE))
|
scroll_offset = max(0, min(cursor - MAX_VISIBLE / 2, len(filtered) - MAX_VISIBLE))
|
||||||
fmt.printf(ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.RESET + ansi.ED, visible + 1)
|
fmt.printf(ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.RESET + ansi.ED, visible + 1)
|
||||||
visible = render_options(prompt, options, selected[:], cursor, scroll_offset)
|
visible = render_options(prompt, options, selected[:], filtered[:], string(filter[:]), cursor, scroll_offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild_filtered :: proc(options: []string, filter: []u8, filtered: ^[dynamic]int) {
|
||||||
|
clear(filtered)
|
||||||
|
filter_str := string(filter)
|
||||||
|
filter_lower := strings.to_lower(filter_str, context.temp_allocator)
|
||||||
|
for opt, i in options {
|
||||||
|
opt_lower := strings.to_lower(opt, context.temp_allocator)
|
||||||
|
if strings.contains(opt_lower, filter_lower) {
|
||||||
|
append(filtered, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,22 +125,34 @@ render_options :: proc(
|
|||||||
prompt: string,
|
prompt: string,
|
||||||
options: []string,
|
options: []string,
|
||||||
selected: []bool,
|
selected: []bool,
|
||||||
|
filtered: []int,
|
||||||
|
filter_text: string,
|
||||||
cursor: int,
|
cursor: int,
|
||||||
scroll_offset: int,
|
scroll_offset: int,
|
||||||
) -> int {
|
) -> int {
|
||||||
fmt.printf(
|
fmt.printf(
|
||||||
"%s (↑/↓ move, space select, enter confirm)\r\n",
|
"%s (type to filter, ↑/↓ move, space select, enter confirm)\r\n",
|
||||||
colorize(.Option_Label, prompt),
|
colorize(.Option_Label, prompt),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fmt.printf("filter: %s\r\n", filter_text)
|
||||||
|
|
||||||
|
line_count := 1
|
||||||
|
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
fmt.printf(" No matches\r\n")
|
||||||
|
return line_count + 1
|
||||||
|
}
|
||||||
|
|
||||||
end := scroll_offset + MAX_VISIBLE
|
end := scroll_offset + MAX_VISIBLE
|
||||||
if end > len(options) {
|
if end > len(filtered) {
|
||||||
end = len(options)
|
end = len(filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in scroll_offset ..< end {
|
for i in scroll_offset ..< end {
|
||||||
|
original := filtered[i]
|
||||||
checkbox := " "
|
checkbox := " "
|
||||||
if selected[i] {
|
if selected[original] {
|
||||||
checkbox = "x"
|
checkbox = "x"
|
||||||
}
|
}
|
||||||
if i == cursor {
|
if i == cursor {
|
||||||
@@ -115,14 +160,14 @@ render_options :: proc(
|
|||||||
"%s [%s] %s\r\n",
|
"%s [%s] %s\r\n",
|
||||||
colorize(.Caret, ">"),
|
colorize(.Caret, ">"),
|
||||||
colorize(.Sucess, checkbox),
|
colorize(.Sucess, checkbox),
|
||||||
options[i],
|
options[original],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
fmt.printf(" [%s] %s\r\n", colorize(.Sucess, checkbox), options[i])
|
fmt.printf(" [%s] %s\r\n", colorize(.Sucess, checkbox), options[original])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return end - scroll_offset
|
return line_count + (end - scroll_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_raw_mode :: proc(fd: posix.FD) -> (Raw_State, bool) {
|
enable_raw_mode :: proc(fd: posix.FD) -> (Raw_State, bool) {
|
||||||
@@ -152,21 +197,23 @@ disable_raw_mode :: proc(state: ^Raw_State) {
|
|||||||
posix.tcsetattr(state.fd, .TCSAFLUSH, &state.original)
|
posix.tcsetattr(state.fd, .TCSAFLUSH, &state.original)
|
||||||
}
|
}
|
||||||
|
|
||||||
read_key :: proc() -> Key {
|
read_key :: proc() -> (key: Key, ch: rune) {
|
||||||
buf: [3]u8
|
buf: [3]u8
|
||||||
|
|
||||||
n := posix.read(posix.STDIN_FILENO, &buf[0], 1)
|
n := posix.read(posix.STDIN_FILENO, &buf[0], 1)
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return .Unknown
|
return .Unknown, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
switch buf[0] {
|
switch buf[0] {
|
||||||
case ' ':
|
case ' ':
|
||||||
return .Space
|
return .Space, 0
|
||||||
case '\n', '\r':
|
case '\n', '\r':
|
||||||
return .Enter
|
return .Enter, 0
|
||||||
case 0x03:
|
case 0x03:
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
|
case 0x08, 0x7F:
|
||||||
|
return .Backspace, 0
|
||||||
case 0x1b:
|
case 0x1b:
|
||||||
tv: posix.timeval
|
tv: posix.timeval
|
||||||
tv.tv_sec = 0
|
tv.tv_sec = 0
|
||||||
@@ -178,12 +225,12 @@ read_key :: proc() -> Key {
|
|||||||
|
|
||||||
ready := posix.select(1, &set, nil, nil, &tv)
|
ready := posix.select(1, &set, nil, nil, &tv)
|
||||||
if ready <= 0 {
|
if ready <= 0 {
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
n2 := posix.read(posix.STDIN_FILENO, &buf[1], 1)
|
n2 := posix.read(posix.STDIN_FILENO, &buf[1], 1)
|
||||||
if n2 <= 0 || buf[1] != '[' {
|
if n2 <= 0 || buf[1] != '[' {
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
posix.FD_ZERO(&set)
|
posix.FD_ZERO(&set)
|
||||||
@@ -193,24 +240,27 @@ read_key :: proc() -> Key {
|
|||||||
|
|
||||||
ready = posix.select(1, &set, nil, nil, &tv)
|
ready = posix.select(1, &set, nil, nil, &tv)
|
||||||
if ready <= 0 {
|
if ready <= 0 {
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
n3 := posix.read(posix.STDIN_FILENO, &buf[2], 1)
|
n3 := posix.read(posix.STDIN_FILENO, &buf[2], 1)
|
||||||
if n3 <= 0 {
|
if n3 <= 0 {
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
switch buf[2] {
|
switch buf[2] {
|
||||||
case 'A':
|
case 'A':
|
||||||
return .Up
|
return .Up, 0
|
||||||
case 'B':
|
case 'B':
|
||||||
return .Down
|
return .Down, 0
|
||||||
case:
|
case:
|
||||||
return .Escape
|
return .Escape, 0
|
||||||
}
|
}
|
||||||
case:
|
case:
|
||||||
return .Unknown
|
if buf[0] >= 0x20 && buf[0] <= 0x7E {
|
||||||
|
return .Char, rune(buf[0])
|
||||||
|
}
|
||||||
|
return .Unknown, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user