summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-07 09:09:31 +0300
committerPaul Buetow <paul@buetow.org>2026-04-07 09:24:18 +0300
commit5e94ba617d19767098bd59506ba1ebfc58c7fd2a (patch)
treefb8dc198f3c687514e248ed27dc62cb5bfbac36a
parentf2b75267a6085f28a415bc8464cf2a70b650e65f (diff)
ui: make ultra mode search live and regex-aware
Replace the Enter-to-apply search with live filtering: every keystroke recompiles the regex and rebuilds ultraFiltered immediately, so results update as the user types. Behaviour summary: - Typing: regex compiled and filter applied on each character. - Invalid regex mid-type: previous filter kept unchanged (no flicker). - Enter: confirm and close the input (empty Enter clears the filter). - Esc: cancel, clear the filter, close the input. - Regexes are always supported (was already the case via compileAndCacheRegex, now usable interactively as you type). Extracted ultraApplySearch() to centralise the compile+filter logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/ui/ultra.go64
1 files changed, 48 insertions, 16 deletions
diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go
index e2ad3e9..16c1a2c 100644
--- a/internal/ui/ultra.go
+++ b/internal/ui/ultra.go
@@ -460,34 +460,66 @@ func (m *Model) ultraFilteredIndexes(re *regexp.Regexp) []int {
return indexes
}
+// handleUltraSearchMode handles keystrokes while the ultra search input is open.
+// Results are filtered live on every keystroke; Enter confirms (keeps the
+// filter, closes the input); Esc cancels (clears the filter, closes the input).
+// The search term is treated as a regular expression.
func (m *Model) handleUltraSearchMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
- onEnter := func(value string) error {
- if value == "" {
+ switch msg.String() {
+ case "enter":
+ // Empty input clears the filter; non-empty confirms and keeps it.
+ if strings.TrimSpace(m.ultraSearchInput.Value()) == "" {
m.ultraSearchRegex = nil
m.ultraFiltered = nil
m.ultraCursor = 0
m.ultraOffset = 0
- return nil
}
+ m.ultraSearching = false
+ m.ultraSearchInput.Blur()
+ return m, nil
+ case "esc":
+ // Cancel: clear the filter and close the search input.
+ m.ultraSearching = false
+ m.ultraSearchInput.SetValue("")
+ m.ultraSearchInput.Blur()
+ m.ultraSearchRegex = nil
+ m.ultraFiltered = nil
+ m.ultraCursor = 0
+ m.ultraOffset = 0
+ return m, nil
+ }
- re, err := compileAndCacheRegex(value)
- if err != nil {
- return err
- }
+ // Forward the keystroke to the text input widget.
+ var cmd tea.Cmd
+ m.ultraSearchInput, cmd = m.ultraSearchInput.Update(msg)
+
+ // Recompile and refilter on every change for live results.
+ m.ultraApplySearch(m.ultraSearchInput.Value())
+ return m, cmd
+}
- m.ultraSearchRegex = re
- m.ultraFiltered = m.ultraFilteredIndexes(re)
+// ultraApplySearch compiles value as a regex and updates the filtered task
+// index. If value is empty the filter is cleared. If the regex is invalid
+// (e.g. mid-typing an incomplete pattern) the existing filter is left
+// unchanged so the display does not flicker.
+func (m *Model) ultraApplySearch(value string) {
+ value = strings.TrimSpace(value)
+ if value == "" {
+ m.ultraSearchRegex = nil
+ m.ultraFiltered = nil
m.ultraCursor = 0
m.ultraOffset = 0
- return nil
+ return
}
-
- onExit := func() {
- m.ultraSearching = false
- m.ultraSearchInput.SetValue("")
+ re, err := compileAndCacheRegex(value)
+ if err != nil {
+ // Keep the previous filter while the regex is incomplete.
+ return
}
-
- return m.handleTextInput(msg, &m.ultraSearchInput, onEnter, onExit)
+ m.ultraSearchRegex = re
+ m.ultraFiltered = m.ultraFilteredIndexes(re)
+ m.ultraCursor = 0
+ m.ultraOffset = 0
}
func (m *Model) ultraMoveSearchMatch(delta int) {