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