v0.4.0 6203a22

types.go RawHistoryFolder

1package cache
2
3import (
4	"sync"
5	"time"
6	"unsafe"
7
8	"github.com/bnema/dumber/internal/config"
9)
10
11// DmenuFuzzyCache provides high-performance fuzzy search for dmenu history.
12type DmenuFuzzyCache struct {
13	// Pre-computed search indices
14	trigramIndex map[string][]uint32 // 3-char sequences -> entry IDs
15	prefixTrie   *Trie               // Compressed trie for prefix matching
16	sortedIndex  []uint32            // Pre-sorted entry indices by score (highest first)
17
18	// Compact history storage
19	entries []CompactEntry // Minimal memory footprint
20
21	// Metadata
22	version      uint32 // Cache format version
23	lastModified int64  // Unix timestamp
24	entryCount   uint32 // Number of entries
25
26	// Concurrency control
27	mu   sync.RWMutex
28	data unsafe.Pointer // Atomic pointer to cache data
29}
30
31// CompactEntry represents a history entry optimized for memory usage.
32type CompactEntry struct {
33	URL        string // Original URL
34	Title      string // Page title
35	VisitCount uint16 // Max 65535 visits (sufficient for most use cases)
36	LastVisit  uint32 // Days since Unix epoch (saves 4 bytes vs time.Time)
37	Score      uint16 // Pre-computed base score (0-65535)
38}
39
40// FuzzyResult contains the results of a fuzzy search query.
41type FuzzyResult struct {
42	Matches   []FuzzyMatch  // Matching entries
43	QueryTime time.Duration // Time taken to execute query
44}
45
46// FuzzyMatch represents a single fuzzy search match.
47type FuzzyMatch struct {
48	Entry        *CompactEntry // Matched entry
49	Score        float64       // Final fuzzy match score (0.0-1.0)
50	URLScore     float64       // URL similarity score
51	TitleScore   float64       // Title similarity score
52	RecencyScore float64       // Recency boost score
53	VisitScore   float64       // Visit count boost score
54	MatchType    MatchType     // Type of match found
55}
56
57// MatchType indicates how the query matched the entry.
58type MatchType uint8
59
60const (
61	MatchTypeExact   MatchType = iota // Exact substring match
62	MatchTypePrefix                   // Prefix match
63	MatchTypeFuzzy                    // Fuzzy similarity match
64	MatchTypeTrigram                  // Trigram-based match
65)
66
67// CacheConfig holds configuration for the fuzzy cache.
68type CacheConfig struct {
69	// File paths
70	CacheFile string // Path to binary cache file
71
72	// Performance tuning
73	MaxEntries       uint32  // Maximum entries to cache
74	TrigramThreshold uint32  // Minimum trigram matches required
75	ScoreThreshold   float64 // Minimum score for results
76	MaxResults       int     // Maximum results to return
77
78	// Scoring weights
79	URLWeight     float64 // Weight for URL matches
80	TitleWeight   float64 // Weight for title matches
81	RecencyWeight float64 // Weight for recency
82	VisitWeight   float64 // Weight for visit count
83
84	// Cache behavior
85	TTL           time.Duration // Cache time-to-live
86	WarmupEnabled bool          // Enable background cache warming
87}
88
89// DefaultCacheConfig returns sensible default configuration.
90func DefaultCacheConfig() *CacheConfig {
91	return &CacheConfig{
92		MaxEntries:       10000,
93		TrigramThreshold: 2,
94		ScoreThreshold:   0.2,
95		MaxResults:       50,
96		URLWeight:        0.4,
97		TitleWeight:      0.3,
98		RecencyWeight:    0.2,
99		VisitWeight:      0.1,
100		TTL:              30 * time.Minute,
101		WarmupEnabled:    true,
102	}
103}
104
105// GetStateDir returns the state directory using the config package.
106func (c *CacheConfig) GetStateDir() (string, error) {
107	return config.GetStateDir()
108}
109
110// Trie represents a compressed prefix tree for fast prefix matching.
111type Trie struct {
112	Root *TrieNode
113}
114
115// TrieNode represents a node in the prefix trie.
116type TrieNode struct {
117	Children map[rune]*TrieNode // Child nodes
118	Entries  []uint32           // Entry IDs that end at this node
119	IsEnd    bool               // Marks end of a prefix
120}
121
122// Reset clears the FuzzyResult for reuse.
123func (r *FuzzyResult) Reset() {
124	r.Matches = r.Matches[:0]
125	r.QueryTime = 0
126}
127
128// String returns a string representation of the match type.
129func (mt MatchType) String() string {
130	switch mt {
131	case MatchTypeExact:
132		return "exact"
133	case MatchTypePrefix:
134		return "prefix"
135	case MatchTypeFuzzy:
136		return "fuzzy"
137	case MatchTypeTrigram:
138		return "trigram"
139	default:
140		return "unknown"
141	}
142}
143
144// DaysFromTime converts a time.Time to days since Unix epoch.
145func DaysFromTime(t time.Time) uint32 {
146	return uint32(t.Unix() / 86400) // 86400 seconds per day
147}
148
149// TimeFromDays converts days since Unix epoch back to time.Time.
150func TimeFromDays(days uint32) time.Time {
151	return time.Unix(int64(days)*86400, 0)
152}
153
154// NewCompactEntry creates a CompactEntry from database values.
155func NewCompactEntry(url, title string, visitCount int64, lastVisit time.Time) CompactEntry {
156	entry := CompactEntry{
157		URL:   url,
158		Title: title,
159	}
160
161	// Clamp visit count to uint16 max
162	if visitCount > 65535 {
163		entry.VisitCount = 65535
164	} else if visitCount < 0 {
165		entry.VisitCount = 0
166	} else {
167		entry.VisitCount = uint16(visitCount)
168	}
169
170	entry.LastVisit = DaysFromTime(lastVisit)
171
172	// Pre-compute a base score for sorting
173	entry.Score = entry.calculateBaseScore()
174
175	return entry
176}
177
178// calculateBaseScore computes a base score for the entry based on visits and recency.
179func (e *CompactEntry) calculateBaseScore() uint16 {
180	visitScore := float64(e.VisitCount) / 65535.0 // Normalize to 0-1
181
182	now := time.Now()
183	lastVisit := TimeFromDays(e.LastVisit)
184	daysSince := now.Sub(lastVisit).Hours() / 24
185	recencyScore := 1.0 / (1.0 + daysSince/30.0) // Decay over 30 days
186
187	// Weighted combination
188	baseScore := (visitScore*0.3 + recencyScore*0.7) * 65535.0
189
190	if baseScore > 65535 {
191		return 65535
192	}
193	return uint16(baseScore)
194}
195
196// Display returns a formatted string for dmenu display.
197func (e *CompactEntry) Display() string {
198	if e.Title != "" && e.Title != e.URL {
199		return e.Title
200	}
201	return e.URL
202}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close