diff options
-rw-r--r-- | src/ui/lookupwidget.c | 69 | ||||
-rw-r--r-- | src/visited.c | 50 |
2 files changed, 95 insertions, 24 deletions
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index c1c52708..74f394e6 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | #include "util.h" | 27 | #include "util.h" |
28 | #include "command.h" | 28 | #include "command.h" |
29 | #include "bookmarks.h" | 29 | #include "bookmarks.h" |
30 | #include "visited.h" | ||
30 | #include "gmutil.h" | 31 | #include "gmutil.h" |
31 | #include "app.h" | 32 | #include "app.h" |
32 | 33 | ||
@@ -38,11 +39,13 @@ iDeclareType(LookupJob) | |||
38 | 39 | ||
39 | struct Impl_LookupJob { | 40 | struct Impl_LookupJob { |
40 | iRegExp *term; | 41 | iRegExp *term; |
42 | iTime now; | ||
41 | iPtrArray results; | 43 | iPtrArray results; |
42 | }; | 44 | }; |
43 | 45 | ||
44 | static void init_LookupJob(iLookupJob *d) { | 46 | static void init_LookupJob(iLookupJob *d) { |
45 | d->term = NULL; | 47 | d->term = NULL; |
48 | initCurrent_Time(&d->now); | ||
46 | init_PtrArray(&d->results); | 49 | init_PtrArray(&d->results); |
47 | } | 50 | } |
48 | 51 | ||
@@ -102,7 +105,7 @@ static void draw_LookupItem_(iLookupItem *d, iPaint *p, iRect rect, const iListW | |||
102 | const iInt2 size = measure_Text(d->font, cstr_String(&d->text)); | 105 | const iInt2 size = measure_Text(d->font, cstr_String(&d->text)); |
103 | iInt2 pos = init_I2(left_Rect(rect) + 3 * gap_UI, mid_Rect(rect).y - size.y / 2); | 106 | iInt2 pos = init_I2(left_Rect(rect) + 3 * gap_UI, mid_Rect(rect).y - size.y / 2); |
104 | if (d->listItem.isSeparator) { | 107 | if (d->listItem.isSeparator) { |
105 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font) - gap_UI; | 108 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font); |
106 | } | 109 | } |
107 | drawRange_Text(d->font, pos, fg, range_String(&d->text)); | 110 | drawRange_Text(d->font, pos, fg, range_String(&d->text)); |
108 | } | 111 | } |
@@ -147,18 +150,31 @@ static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark * | |||
147 | return h + iMax(p, t) + 2 * g; /* extra weight for tags */ | 150 | return h + iMax(p, t) + 2 * g; /* extra weight for tags */ |
148 | } | 151 | } |
149 | 152 | ||
153 | static float visitedRelevance_LookupJob_(const iLookupJob *d, const iVisitedUrl *vis) { | ||
154 | iUrl parts; | ||
155 | init_Url(&parts, &vis->url); | ||
156 | const float h = scoreMatch_(d->term, parts.host); | ||
157 | const float p = scoreMatch_(d->term, parts.path); | ||
158 | const double age = secondsSince_Time(&d->now, &vis->when) / 3600.0 / 24.0; /* days */ | ||
159 | return iMax(h, p) / (age + 1); /* extra weight for recency */ | ||
160 | } | ||
161 | |||
150 | static iBool matchBookmark_LookupJob_(void *context, const iBookmark *bm) { | 162 | static iBool matchBookmark_LookupJob_(void *context, const iBookmark *bm) { |
151 | return bookmarkRelevance_LookupJob_(context, bm) > 0; | 163 | return bookmarkRelevance_LookupJob_(context, bm) > 0; |
152 | } | 164 | } |
153 | 165 | ||
154 | static void searchBookmarks_LookupJob_(iLookupJob *d) { | 166 | static void searchBookmarks_LookupJob_(iLookupJob *d) { |
155 | /* Note: Called in a background thread. */ | 167 | /* Note: Called in a background thread. */ |
168 | /* TODO: Thread safety! What if a bookmark gets deleted while its being accessed here? */ | ||
156 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, matchBookmark_LookupJob_, d)) { | 169 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, matchBookmark_LookupJob_, d)) { |
157 | const iBookmark *bm = i.ptr; | 170 | const iBookmark *bm = i.ptr; |
158 | iLookupResult * res = new_LookupResult(); | 171 | iLookupResult * res = new_LookupResult(); |
159 | res->type = bookmark_LookupResultType; | 172 | res->type = bookmark_LookupResultType; |
160 | res->relevance = bookmarkRelevance_LookupJob_(d, bm); | 173 | res->relevance = bookmarkRelevance_LookupJob_(d, bm); |
161 | set_String(&res->label, &bm->title); | 174 | appendChar_String(&res->label, bm->icon); |
175 | appendChar_String(&res->label, ' '); | ||
176 | append_String(&res->label, &bm->title); | ||
177 | // set_String(&res->label, &bm->title); | ||
162 | // appendFormat_String(&res->label, " (%f)", res->relevance); | 178 | // appendFormat_String(&res->label, " (%f)", res->relevance); |
163 | set_String(&res->url, &bm->url); | 179 | set_String(&res->url, &bm->url); |
164 | res->when = bm->when; | 180 | res->when = bm->when; |
@@ -166,6 +182,24 @@ static void searchBookmarks_LookupJob_(iLookupJob *d) { | |||
166 | } | 182 | } |
167 | } | 183 | } |
168 | 184 | ||
185 | static void searchVisited_LookupJob_(iLookupJob *d) { | ||
186 | /* Note: Called in a background thread. */ | ||
187 | /* TODO: Thread safety! Visited URLs may be deleted while being accessed here. */ | ||
188 | iConstForEach(PtrArray, i, list_Visited(visited_App(), 0)) { | ||
189 | const iVisitedUrl *vis = i.ptr; | ||
190 | const float relevance = visitedRelevance_LookupJob_(d, vis); | ||
191 | if (relevance > 0) { | ||
192 | iLookupResult *res = new_LookupResult(); | ||
193 | res->type = history_LookupResultType; | ||
194 | res->relevance = relevance; | ||
195 | set_String(&res->label, &vis->url); | ||
196 | set_String(&res->url, &vis->url); | ||
197 | res->when = vis->when; | ||
198 | pushBack_PtrArray(&d->results, res); | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | |||
169 | static iThreadResult worker_LookupWidget_(iThread *thread) { | 203 | static iThreadResult worker_LookupWidget_(iThread *thread) { |
170 | iLookupWidget *d = userData_Thread(thread); | 204 | iLookupWidget *d = userData_Thread(thread); |
171 | printf("[LookupWidget] worker is running\n"); fflush(stdout); | 205 | printf("[LookupWidget] worker is running\n"); fflush(stdout); |
@@ -193,7 +227,6 @@ static iThreadResult worker_LookupWidget_(iThread *thread) { | |||
193 | isFirst = iFalse; | 227 | isFirst = iFalse; |
194 | } | 228 | } |
195 | iAssert(!isEmpty_String(pattern)); | 229 | iAssert(!isEmpty_String(pattern)); |
196 | // printf("{%s}\n", cstr_String(pattern)); | ||
197 | job->term = new_RegExp(cstr_String(pattern), caseInsensitive_RegExpOption); | 230 | job->term = new_RegExp(cstr_String(pattern), caseInsensitive_RegExpOption); |
198 | delete_String(pattern); | 231 | delete_String(pattern); |
199 | } | 232 | } |
@@ -201,6 +234,7 @@ static iThreadResult worker_LookupWidget_(iThread *thread) { | |||
201 | unlock_Mutex(d->mtx); | 234 | unlock_Mutex(d->mtx); |
202 | /* Do the lookup. */ { | 235 | /* Do the lookup. */ { |
203 | searchBookmarks_LookupJob_(job); | 236 | searchBookmarks_LookupJob_(job); |
237 | searchVisited_LookupJob_(job); | ||
204 | } | 238 | } |
205 | /* Submit the result. */ | 239 | /* Submit the result. */ |
206 | lock_Mutex(d->mtx); | 240 | lock_Mutex(d->mtx); |
@@ -226,7 +260,7 @@ void init_LookupWidget(iLookupWidget *d) { | |||
226 | setId_Widget(w, "lookup"); | 260 | setId_Widget(w, "lookup"); |
227 | setFlags_Widget(w, focusable_WidgetFlag | resizeChildren_WidgetFlag, iTrue); | 261 | setFlags_Widget(w, focusable_WidgetFlag | resizeChildren_WidgetFlag, iTrue); |
228 | d->list = addChild_Widget(w, iClob(new_ListWidget())); | 262 | d->list = addChild_Widget(w, iClob(new_ListWidget())); |
229 | setItemHeight_ListWidget(d->list, lineHeight_Text(default_FontId) * 2); | 263 | setItemHeight_ListWidget(d->list, lineHeight_Text(uiContent_FontId) * 1.25f); |
230 | d->cursor = iInvalidPos; | 264 | d->cursor = iInvalidPos; |
231 | d->work = new_Thread(worker_LookupWidget_); | 265 | d->work = new_Thread(worker_LookupWidget_); |
232 | setUserData_Thread(d->work, d); | 266 | setUserData_Thread(d->work, d); |
@@ -321,23 +355,34 @@ static void presentResults_LookupWidget_(iLookupWidget *d) { | |||
321 | iLookupItem *item = new_LookupItem(NULL); | 355 | iLookupItem *item = new_LookupItem(NULL); |
322 | item->listItem.isSeparator = iTrue; | 356 | item->listItem.isSeparator = iTrue; |
323 | item->fg = uiHeading_ColorId; | 357 | item->fg = uiHeading_ColorId; |
324 | item->font = default_FontId; | 358 | item->font = uiLabel_FontId; |
325 | format_String(&item->text, "%s", cstr_LookupResultType(res->type)); | 359 | format_String(&item->text, "%s", cstr_LookupResultType(res->type)); |
326 | addItem_ListWidget(d->list, item); | 360 | addItem_ListWidget(d->list, item); |
327 | iRelease(item); | 361 | iRelease(item); |
328 | lastType = res->type; | 362 | lastType = res->type; |
329 | } | 363 | } |
330 | iLookupItem *item = new_LookupItem(res); | 364 | iLookupItem *item = new_LookupItem(res); |
365 | const char *url = cstr_String(&res->url); | ||
366 | if (startsWithCase_String(&res->url, "gemini://")) { | ||
367 | url += 9; | ||
368 | } | ||
331 | switch (res->type) { | 369 | switch (res->type) { |
332 | case bookmark_LookupResultType: { | 370 | case bookmark_LookupResultType: { |
333 | item->fg = uiTextStrong_ColorId; | 371 | item->fg = uiTextStrong_ColorId; |
334 | item->font = default_FontId; | 372 | item->font = uiLabel_FontId; |
335 | const char *url = cstr_String(&res->url); | 373 | format_String(&item->text, |
336 | if (startsWithCase_String(&res->url, "gemini://")) { | 374 | "%s %s\u2014 %s", |
337 | url += 9; | 375 | cstr_String(&res->label), |
338 | } | 376 | uiText_ColorEscape, |
339 | format_String(&item->text, "%s\n%s Open %s", cstr_String(&res->label), | 377 | url); |
340 | uiText_ColorEscape, url); | 378 | format_String(&item->command, "open url:%s", cstr_String(&res->url)); |
379 | break; | ||
380 | } | ||
381 | case history_LookupResultType: { | ||
382 | item->fg = uiText_ColorId; | ||
383 | item->font = uiContent_FontId; | ||
384 | format_String(&item->text, "%s \u2014 ", url); | ||
385 | append_String(&item->text, collect_String(format_Time(&res->when, "%b %d, %Y"))); | ||
341 | format_String(&item->command, "open url:%s", cstr_String(&res->url)); | 386 | format_String(&item->command, "open url:%s", cstr_String(&res->url)); |
342 | break; | 387 | break; |
343 | } | 388 | } |
diff --git a/src/visited.c b/src/visited.c index 912a6318..b0b12beb 100644 --- a/src/visited.c +++ b/src/visited.c | |||
@@ -24,6 +24,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | #include "app.h" | 24 | #include "app.h" |
25 | 25 | ||
26 | #include <the_Foundation/file.h> | 26 | #include <the_Foundation/file.h> |
27 | #include <the_Foundation/mutex.h> | ||
27 | #include <the_Foundation/path.h> | 28 | #include <the_Foundation/path.h> |
28 | #include <the_Foundation/ptrarray.h> | 29 | #include <the_Foundation/ptrarray.h> |
29 | #include <the_Foundation/sortedarray.h> | 30 | #include <the_Foundation/sortedarray.h> |
@@ -51,24 +52,30 @@ static int cmpNewer_VisitedUrl_(const void *insert, const void *existing) { | |||
51 | /*----------------------------------------------------------------------------------------------*/ | 52 | /*----------------------------------------------------------------------------------------------*/ |
52 | 53 | ||
53 | struct Impl_Visited { | 54 | struct Impl_Visited { |
55 | iMutex *mtx; | ||
54 | iSortedArray visited; | 56 | iSortedArray visited; |
55 | }; | 57 | }; |
56 | 58 | ||
57 | iDefineTypeConstruction(Visited) | 59 | iDefineTypeConstruction(Visited) |
58 | 60 | ||
59 | void init_Visited(iVisited *d) { | 61 | void init_Visited(iVisited *d) { |
62 | d->mtx = new_Mutex(); | ||
60 | init_SortedArray(&d->visited, sizeof(iVisitedUrl), cmpUrl_VisitedUrl_); | 63 | init_SortedArray(&d->visited, sizeof(iVisitedUrl), cmpUrl_VisitedUrl_); |
61 | } | 64 | } |
62 | 65 | ||
63 | void deinit_Visited(iVisited *d) { | 66 | void deinit_Visited(iVisited *d) { |
64 | clear_Visited(d); | 67 | iGuardMutex(d->mtx, { |
65 | deinit_SortedArray(&d->visited); | 68 | clear_Visited(d); |
69 | deinit_SortedArray(&d->visited); | ||
70 | }); | ||
71 | delete_Mutex(d->mtx); | ||
66 | } | 72 | } |
67 | 73 | ||
68 | void save_Visited(const iVisited *d, const char *dirPath) { | 74 | void save_Visited(const iVisited *d, const char *dirPath) { |
69 | iString *line = new_String(); | 75 | iString *line = new_String(); |
70 | iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); | 76 | iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); |
71 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 77 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
78 | lock_Mutex(d->mtx); | ||
72 | iConstForEach(Array, i, &d->visited.values) { | 79 | iConstForEach(Array, i, &d->visited.values) { |
73 | const iVisitedUrl *item = i.value; | 80 | const iVisitedUrl *item = i.value; |
74 | iDate date; | 81 | iDate date; |
@@ -84,6 +91,7 @@ void save_Visited(const iVisited *d, const char *dirPath) { | |||
84 | cstr_String(&item->url)); | 91 | cstr_String(&item->url)); |
85 | writeData_File(f, cstr_String(line), size_String(line)); | 92 | writeData_File(f, cstr_String(line), size_String(line)); |
86 | } | 93 | } |
94 | unlock_Mutex(d->mtx); | ||
87 | } | 95 | } |
88 | iRelease(f); | 96 | iRelease(f); |
89 | delete_String(line); | 97 | delete_String(line); |
@@ -92,6 +100,7 @@ void save_Visited(const iVisited *d, const char *dirPath) { | |||
92 | void load_Visited(iVisited *d, const char *dirPath) { | 100 | void load_Visited(iVisited *d, const char *dirPath) { |
93 | iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); | 101 | iFile *f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); |
94 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | 102 | if (open_File(f, readOnly_FileMode | text_FileMode)) { |
103 | lock_Mutex(d->mtx); | ||
95 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); | 104 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); |
96 | iRangecc line = iNullRange; | 105 | iRangecc line = iNullRange; |
97 | iTime now; | 106 | iTime now; |
@@ -111,15 +120,18 @@ void load_Visited(iVisited *d, const char *dirPath) { | |||
111 | initRange_String(&item.url, (iRangecc){ line.start + 20, line.end }); | 120 | initRange_String(&item.url, (iRangecc){ line.start + 20, line.end }); |
112 | insert_SortedArray(&d->visited, &item); | 121 | insert_SortedArray(&d->visited, &item); |
113 | } | 122 | } |
123 | unlock_Mutex(d->mtx); | ||
114 | } | 124 | } |
115 | iRelease(f); | 125 | iRelease(f); |
116 | } | 126 | } |
117 | 127 | ||
118 | void clear_Visited(iVisited *d) { | 128 | void clear_Visited(iVisited *d) { |
129 | lock_Mutex(d->mtx); | ||
119 | iForEach(Array, v, &d->visited.values) { | 130 | iForEach(Array, v, &d->visited.values) { |
120 | deinit_VisitedUrl(v.value); | 131 | deinit_VisitedUrl(v.value); |
121 | } | 132 | } |
122 | clear_SortedArray(&d->visited); | 133 | clear_SortedArray(&d->visited); |
134 | unlock_Mutex(d->mtx); | ||
123 | } | 135 | } |
124 | 136 | ||
125 | static size_t find_Visited_(const iVisited *d, const iString *url) { | 137 | static size_t find_Visited_(const iVisited *d, const iString *url) { |
@@ -127,8 +139,10 @@ static size_t find_Visited_(const iVisited *d, const iString *url) { | |||
127 | init_VisitedUrl(&visit); | 139 | init_VisitedUrl(&visit); |
128 | set_String(&visit.url, url); | 140 | set_String(&visit.url, url); |
129 | size_t pos = iInvalidPos; | 141 | size_t pos = iInvalidPos; |
130 | locate_SortedArray(&d->visited, &visit, &pos); | 142 | iGuardMutex(d->mtx, { |
131 | deinit_VisitedUrl(&visit); | 143 | locate_SortedArray(&d->visited, &visit, &pos); |
144 | deinit_VisitedUrl(&visit); | ||
145 | }); | ||
132 | return pos; | 146 | return pos; |
133 | } | 147 | } |
134 | 148 | ||
@@ -137,23 +151,28 @@ void visitUrl_Visited(iVisited *d, const iString *url) { | |||
137 | init_VisitedUrl(&visit); | 151 | init_VisitedUrl(&visit); |
138 | set_String(&visit.url, url); | 152 | set_String(&visit.url, url); |
139 | size_t pos; | 153 | size_t pos; |
154 | lock_Mutex(d->mtx); | ||
140 | if (locate_SortedArray(&d->visited, &visit, &pos)) { | 155 | if (locate_SortedArray(&d->visited, &visit, &pos)) { |
141 | iVisitedUrl *old = at_SortedArray(&d->visited, pos); | 156 | iVisitedUrl *old = at_SortedArray(&d->visited, pos); |
142 | if (cmpNewer_VisitedUrl_(&visit, old)) { | 157 | if (cmpNewer_VisitedUrl_(&visit, old)) { |
143 | old->when = visit.when; | 158 | old->when = visit.when; |
159 | unlock_Mutex(d->mtx); | ||
144 | deinit_VisitedUrl(&visit); | 160 | deinit_VisitedUrl(&visit); |
145 | return; | 161 | return; |
146 | } | 162 | } |
147 | } | 163 | } |
148 | insert_SortedArray(&d->visited, &visit); | 164 | insert_SortedArray(&d->visited, &visit); |
165 | unlock_Mutex(d->mtx); | ||
149 | } | 166 | } |
150 | 167 | ||
151 | void removeUrl_Visited(iVisited *d, const iString *url) { | 168 | void removeUrl_Visited(iVisited *d, const iString *url) { |
152 | size_t pos = find_Visited_(d, url); | 169 | iGuardMutex(d->mtx, { |
153 | if (pos != iInvalidPos) { | 170 | size_t pos = find_Visited_(d, url); |
154 | deinit_VisitedUrl(at_SortedArray(&d->visited, pos)); | 171 | if (pos != iInvalidPos) { |
155 | remove_Array(&d->visited.values, pos); | 172 | deinit_VisitedUrl(at_SortedArray(&d->visited, pos)); |
156 | } | 173 | remove_Array(&d->visited.values, pos); |
174 | } | ||
175 | }); | ||
157 | } | 176 | } |
158 | 177 | ||
159 | iTime urlVisitTime_Visited(const iVisited *d, const iString *url) { | 178 | iTime urlVisitTime_Visited(const iVisited *d, const iString *url) { |
@@ -161,9 +180,11 @@ iTime urlVisitTime_Visited(const iVisited *d, const iString *url) { | |||
161 | size_t pos; | 180 | size_t pos; |
162 | iZap(item); | 181 | iZap(item); |
163 | initCopy_String(&item.url, url); | 182 | initCopy_String(&item.url, url); |
183 | lock_Mutex(d->mtx); | ||
164 | if (locate_SortedArray(&d->visited, &item, &pos)) { | 184 | if (locate_SortedArray(&d->visited, &item, &pos)) { |
165 | item.when = ((const iVisitedUrl *) constAt_SortedArray(&d->visited, pos))->when; | 185 | item.when = ((const iVisitedUrl *) constAt_SortedArray(&d->visited, pos))->when; |
166 | } | 186 | } |
187 | unlock_Mutex(d->mtx); | ||
167 | deinit_String(&item.url); | 188 | deinit_String(&item.url); |
168 | return item.when; | 189 | return item.when; |
169 | } | 190 | } |
@@ -175,9 +196,14 @@ static int cmpWhenDescending_VisitedUrlPtr_(const void *a, const void *b) { | |||
175 | 196 | ||
176 | const iArray *list_Visited(const iVisited *d, size_t count) { | 197 | const iArray *list_Visited(const iVisited *d, size_t count) { |
177 | iPtrArray *urls = collectNew_PtrArray(); | 198 | iPtrArray *urls = collectNew_PtrArray(); |
178 | iConstForEach(Array, i, &d->visited.values) { | 199 | iGuardMutex(d->mtx, { |
179 | pushBack_PtrArray(urls, i.value); | 200 | iConstForEach(Array, i, &d->visited.values) { |
180 | } | 201 | pushBack_PtrArray(urls, i.value); |
202 | } | ||
203 | }); | ||
181 | sort_Array(urls, cmpWhenDescending_VisitedUrlPtr_); | 204 | sort_Array(urls, cmpWhenDescending_VisitedUrlPtr_); |
205 | if (count > 0 && size_Array(urls) > count) { | ||
206 | resize_Array(urls, count); | ||
207 | } | ||
182 | return urls; | 208 | return urls; |
183 | } | 209 | } |