1 #include "match.h"
2 
3 #include <stdlib.h>
4 #include <wctype.h>
5 #include <assert.h>
6 
7 #define LOG_MODULE "match"
8 #define LOG_ENABLE_DBG 0
9 #include "log.h"
10 
11 #define min(x, y) ((x < y) ? (x) : (y))
12 #define max(x, y) ((x > y) ? (x) : (y))
13 
14 struct matches {
15     const struct application_list *applications;
16     struct match *matches;
17     size_t page_count;
18     size_t match_count;
19     size_t selected;
20     size_t max_matches_per_page;
21 };
22 
23 struct matches *
matches_init(const struct application_list * applications)24 matches_init(const struct application_list *applications)
25 {
26     struct matches *matches = malloc(sizeof(*matches));
27     *matches = (struct matches) {
28         .applications = applications,
29         .matches = malloc(applications->count * sizeof(matches->matches[0])),
30         .page_count = 0,
31         .match_count = 0,
32         .selected = 0,
33         .max_matches_per_page = 0,
34     };
35     return matches;
36 }
37 
38 void
matches_destroy(struct matches * matches)39 matches_destroy(struct matches *matches)
40 {
41     if (matches == NULL)
42         return;
43 
44     free(matches->matches);
45     free(matches);
46 }
47 
48 size_t
matches_max_matches_per_page(const struct matches * matches)49 matches_max_matches_per_page(const struct matches *matches)
50 {
51     return matches->max_matches_per_page;
52 }
53 
54 void
matches_max_matches_per_page_set(struct matches * matches,size_t max_matches)55 matches_max_matches_per_page_set(struct matches *matches, size_t max_matches)
56 {
57     matches->max_matches_per_page = max_matches;
58 }
59 
60 size_t
matches_get_page_count(const struct matches * matches)61 matches_get_page_count(const struct matches *matches)
62 {
63     return matches->match_count / matches->max_matches_per_page;
64 }
65 
66 size_t
matches_get_page(const struct matches * matches)67 matches_get_page(const struct matches *matches)
68 {
69     return matches->selected / matches->max_matches_per_page;
70 }
71 
72 const struct match *
matches_get(const struct matches * matches,size_t idx)73 matches_get(const struct matches *matches, size_t idx)
74 {
75     const size_t page_no = matches_get_page(matches);
76     const size_t items_on_page __attribute__((unused)) = matches_get_count(matches);
77 
78     LOG_DBG(
79         "page-count: %zu, page-no: %zu, items-on-page: %zu, idx: %zu, max: %zu, "
80         "match-count: %zu",
81         matches->page_count, page_no, items_on_page, idx,
82         matches->max_matches_per_page, matches->match_count);
83 
84     assert(idx < items_on_page);
85     idx += page_no * matches->max_matches_per_page;
86 
87     assert(idx < matches->match_count);
88     return &matches->matches[idx];
89 }
90 
91 const struct match *
matches_get_match(const struct matches * matches)92 matches_get_match(const struct matches *matches)
93 {
94     return matches->match_count > 0
95         ? &matches->matches[matches->selected]
96         : NULL;
97 }
98 
99 size_t
matches_get_count(const struct matches * matches)100 matches_get_count(const struct matches *matches)
101 {
102     const size_t total = matches->match_count;
103     const size_t page_no = matches_get_page(matches);
104 
105     if (total == 0)
106         return 0;
107     else if (page_no + 1 >= matches->page_count) {
108         size_t remainder = total % matches->max_matches_per_page;
109         return remainder == 0 ? matches->max_matches_per_page : remainder;
110     } else
111         return matches->max_matches_per_page;
112 }
113 
114 size_t
matches_get_match_index(const struct matches * matches)115 matches_get_match_index(const struct matches *matches)
116 {
117     return matches->selected % matches->max_matches_per_page;
118 }
119 
120 bool
matches_selected_prev(struct matches * matches,bool wrap)121 matches_selected_prev(struct matches *matches, bool wrap)
122 {
123     if (matches->selected > 0) {
124         matches->selected--;
125         return true;
126     } else if (wrap && matches->match_count > 1) {
127         matches->selected = 0;
128         return true;
129     }
130 
131     return false;
132 }
133 
134 bool
matches_selected_next(struct matches * matches,bool wrap)135 matches_selected_next(struct matches *matches, bool wrap)
136 {
137     if (matches->selected + 1 < matches->match_count) {
138         matches->selected++;
139         return true;
140     } else if (wrap && matches->match_count > 1) {
141         matches->selected = matches->match_count - 1;
142         return true;
143     }
144 
145     return false;
146 }
147 
148 bool
matches_selected_prev_page(struct matches * matches)149 matches_selected_prev_page(struct matches *matches)
150 {
151     const size_t page_no = matches_get_page(matches);
152     if (page_no > 0) {
153         assert(matches->selected >= matches->max_matches_per_page);
154         matches->selected -= matches->max_matches_per_page;
155         return true;
156     } else if (matches->selected > 0) {
157         matches->selected = 0;
158         return true;
159     }
160 
161     return false;
162 }
163 
164 bool
matches_selected_next_page(struct matches * matches)165 matches_selected_next_page(struct matches *matches)
166 {
167     const size_t page_no = matches_get_page(matches);
168     if (page_no + 1 < matches->page_count) {
169         matches->selected = min(
170             matches->selected + matches->max_matches_per_page,
171             matches->match_count - 1);
172         return true;
173     } else if (matches->selected < matches->match_count - 1) {
174         matches->selected = matches->match_count - 1;
175         return true;
176     }
177 
178     return false;
179 }
180 
181 static int
sort_match_by_count(const void * _a,const void * _b)182 sort_match_by_count(const void *_a, const void *_b)
183 {
184     const struct match *a = _a;
185     const struct match *b = _b;
186     return b->application->count - a->application->count;
187 }
188 
189 static wchar_t *
wcscasestr(const wchar_t * haystack,const wchar_t * needle)190 wcscasestr(const wchar_t *haystack, const wchar_t *needle)
191 {
192     const size_t hay_len = wcslen(haystack);
193     const size_t needle_len = wcslen(needle);
194 
195     if (needle_len > hay_len)
196         return NULL;
197 
198     for (size_t i = 0; i < hay_len - needle_len + 1; i++) {
199         bool matched = true;
200         for (size_t j = 0; j < needle_len; j++) {
201             if (towlower(haystack[i + j]) != towlower(needle[j])) {
202                 matched = false;
203                 break;
204             }
205         }
206 
207         if (matched)
208             return (wchar_t *)&haystack[i];
209     }
210 
211     return NULL;
212 }
213 
214 void
matches_update(struct matches * matches,const struct prompt * prompt)215 matches_update(struct matches *matches, const struct prompt *prompt)
216 {
217     assert(matches->max_matches_per_page > 0);
218 
219     const wchar_t *ptext = prompt_text(prompt);
220 
221     /* Nothing entered; all programs found matches */
222     if (wcslen(ptext) == 0) {
223 
224         for (size_t i = 0; i < matches->applications->count; i++) {
225             matches->matches[i] = (struct match){
226                 .application = &matches->applications->v[i],
227                 .start_title = -1,
228                 .start_comment = -1};
229         }
230 
231         /* Sort */
232         matches->match_count = matches->applications->count;
233         qsort(matches->matches, matches->match_count, sizeof(matches->matches[0]), &sort_match_by_count);
234 
235         if (matches->selected >= matches->match_count && matches->selected > 0)
236             matches->selected = matches->match_count - 1;
237 
238         matches->page_count = (
239             matches->match_count + (matches->max_matches_per_page - 1)) /
240             matches->max_matches_per_page;
241 
242         return;
243     }
244 
245     matches->match_count = 0;
246     for (size_t i = 0; i < matches->applications->count; i++) {
247         struct application *app = &matches->applications->v[i];
248         ssize_t start_title = -1;
249         ssize_t start_comment = -1;
250         ssize_t start_basename = -1;
251 
252         const wchar_t *m = wcscasestr(app->title, ptext);
253         if (m != NULL)
254             start_title = m - app->title;
255 
256         if (app->comment != NULL) {
257             m = wcscasestr(app->comment, ptext);
258             if (m != NULL)
259                 start_comment = m - app->comment;
260         }
261 
262         if (app->basename != NULL) {
263             m = wcscasestr(app->basename, ptext);
264             if (m != NULL)
265                 start_basename = m - app->basename;
266         }
267 
268         if (start_title < 0 && start_comment < 0 && start_basename < 0)
269             continue;
270 
271         matches->matches[matches->match_count++] = (struct match){
272             .application = app,
273             .start_title = start_title,
274             .start_comment = start_comment,
275             .start_basename = start_basename};
276     }
277 
278     /* Sort */
279     qsort(matches->matches, matches->match_count, sizeof(matches->matches[0]), &sort_match_by_count);
280 
281     matches->page_count = (
282         matches->match_count + (matches->max_matches_per_page - 1)) /
283         matches->max_matches_per_page;
284 
285     if (matches->selected >= matches->match_count && matches->selected > 0)
286         matches->selected = matches->match_count - 1;
287 }
288