v0.8.0 0fa6785

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

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