diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-07 14:50:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-07 14:50:55 +0300 |
| commit | 33bf1bf183486ca9cdd84cecaf56d0b967797e74 (patch) | |
| tree | e845fbbf6e28ba2fff2806ee5d307137b8904611 | |
| parent | 0f771e9a2ac8d52071b90f0cffb675a7c941f03b (diff) | |
ui: compact ultra mode and case-insensitive search
- Remove empty lines between card sections by skipping blank separators
when no explicit blankLine is provided to ultraJoinSectionsWithBlank
- Remove 2-space leading indent from description and annotation lines
- Make ultra search case-insensitive by default (prepends (?i)); users
can override with explicit inline flags like (?-i)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | internal/ui/table_test.go | 9 | ||||
| -rw-r--r-- | internal/ui/ultra.go | 39 |
2 files changed, 26 insertions, 22 deletions
diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index a065ca7..855f7d6 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -1291,11 +1291,12 @@ func TestUltraEntryResizeAndNavigationBindings(t *testing.T) { if m.ultraCursor != 2 { t.Fatalf("u: cursor = %d, want 2", m.ultraCursor) } - if m.ultraOffset != 1 { - t.Fatalf("u: offset = %d, want 1", m.ultraOffset) + // Compact cards (3 lines each) all fit at offset 0 in a 60×16 window. + if m.ultraOffset != 0 { + t.Fatalf("u: offset = %d, want 0", m.ultraOffset) } - if got := m.ultraVisibleCount(); got != 2 { - t.Fatalf("u: visible count = %d, want 2", got) + if got := m.ultraVisibleCount(); got != 3 { + t.Fatalf("u: visible count = %d, want 3", got) } if start := m.ultraVisibleStart(len(m.ultraTaskList())); m.ultraCursor < start || m.ultraCursor >= start+m.ultraVisibleCount() { t.Fatalf("u: cursor %d not visible at offset %d", m.ultraCursor, m.ultraOffset) diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go index 16c1a2c..890fc87 100644 --- a/internal/ui/ultra.go +++ b/internal/ui/ultra.go @@ -498,10 +498,12 @@ func (m *Model) handleUltraSearchMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) return m, cmd } -// 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. +// ultraApplySearch compiles value as a case-insensitive regex and updates the +// filtered task index. The pattern is automatically wrapped with (?i) so plain +// text searches like "foo" match "Foo" and "FOO" without extra effort. Explicit +// flags (e.g. (?-i)) in the pattern override this default. 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 == "" { @@ -511,7 +513,13 @@ func (m *Model) ultraApplySearch(value string) { m.ultraOffset = 0 return } - re, err := compileAndCacheRegex(value) + // Prepend (?i) for case-insensitive matching unless the user already + // supplied inline flags (patterns starting with "(?" opt in explicitly). + pattern := value + if !strings.HasPrefix(pattern, "(?") { + pattern = "(?i)" + pattern + } + re, err := compileAndCacheRegex(pattern) if err != nil { // Keep the previous filter while the regex is incomplete. return @@ -570,11 +578,8 @@ func (m *Model) ultraDescriptionLines(t task.Task, width int) []string { text = "-" } - lines := wordWrap(text, ultraBodyWidth(width)) - for i, line := range lines { - lines[i] = " " + line - } - return lines + // No leading indent — keep description flush with the card edge for a compact layout. + return wordWrap(text, ultraBodyWidth(width)) } func (m *Model) ultraDescriptionText(t task.Task, width int) string { @@ -590,12 +595,8 @@ func (m *Model) ultraAnnotationsLines(t task.Task, width int) []string { var lines []string for _, ann := range t.Annotations { text := fmt.Sprintf("[%s] %s", m.formatTaskDate(ann.Entry), ultraOrDash(strings.TrimSpace(ann.Description))) - for i, line := range wordWrap(text, bodyWidth) { - if i > 0 { - line = " " + line - } - lines = append(lines, line) - } + // No indent on continuation lines — keep annotations flush for a compact layout. + lines = append(lines, wordWrap(text, bodyWidth)...) } return lines } @@ -888,14 +889,16 @@ func ultraJoinSections(sections ...string) string { // ultraJoinSectionsWithBlank joins non-empty sections separated by blankLine. // When blankLine is a styled empty string (e.g. with a background colour), // the inter-section gap carries that style instead of falling back to the -// terminal default. +// terminal default. A blankLine of "" means no separator — sections are joined +// with a plain newline for a compact, gap-free layout. func ultraJoinSectionsWithBlank(blankLine string, sections ...string) string { var parts []string for _, sec := range sections { if sec == "" { continue } - if len(parts) > 0 { + if len(parts) > 0 && blankLine != "" { + // Only insert the blank line when one was explicitly provided. parts = append(parts, blankLine) } parts = append(parts, sec) |
