1 #include "search.h"
2
3 #include <string.h>
4 #include <wchar.h>
5 #include <wctype.h>
6
7 #include <wayland-client.h>
8 #include <xkbcommon/xkbcommon-compose.h>
9
10 #define LOG_MODULE "search"
11 #define LOG_ENABLE_DBG 0
12 #include "log.h"
13 #include "config.h"
14 #include "extract.h"
15 #include "grid.h"
16 #include "input.h"
17 #include "misc.h"
18 #include "render.h"
19 #include "selection.h"
20 #include "shm.h"
21 #include "util.h"
22 #include "xmalloc.h"
23
24 /*
25 * Ensures a "new" viewport doesn't contain any unallocated rows.
26 *
27 * This is done by first checking if the *first* row is NULL. If so,
28 * we move the viewport *forward*, until the first row is non-NULL. At
29 * this point, the entire viewport should be allocated rows only.
30 *
31 * If the first row already was non-NULL, we instead check the *last*
32 * row, and if it is NULL, we move the viewport *backward* until the
33 * last row is non-NULL.
34 */
35 static int
ensure_view_is_allocated(struct terminal * term,int new_view)36 ensure_view_is_allocated(struct terminal *term, int new_view)
37 {
38 int view_end = (new_view + term->rows - 1) & (term->grid->num_rows - 1);
39
40 if (term->grid->rows[new_view] == NULL) {
41 while (term->grid->rows[new_view] == NULL)
42 new_view = (new_view + 1) & (term->grid->num_rows - 1);
43 }
44
45 else if (term->grid->rows[view_end] == NULL) {
46 while (term->grid->rows[view_end] == NULL) {
47 new_view--;
48 if (new_view < 0)
49 new_view += term->grid->num_rows;
50 view_end = (new_view + term->rows - 1) & (term->grid->num_rows - 1);
51 }
52 }
53
54 #if defined(_DEBUG)
55 for (size_t r = 0; r < term->rows; r++)
56 xassert(term->grid->rows[(new_view + r) & (term->grid->num_rows - 1)] != NULL);
57 #endif
58
59 return new_view;
60 }
61
62 static bool
search_ensure_size(struct terminal * term,size_t wanted_size)63 search_ensure_size(struct terminal *term, size_t wanted_size)
64 {
65 while (wanted_size >= term->search.sz) {
66 size_t new_sz = term->search.sz == 0 ? 64 : term->search.sz * 2;
67 wchar_t *new_buf = realloc(term->search.buf, new_sz * sizeof(term->search.buf[0]));
68
69 if (new_buf == NULL) {
70 LOG_ERRNO("failed to resize search buffer");
71 return false;
72 }
73
74 term->search.buf = new_buf;
75 term->search.sz = new_sz;
76 }
77
78 return true;
79 }
80
81 static bool
has_wrapped_around(const struct terminal * term,int abs_row_no)82 has_wrapped_around(const struct terminal *term, int abs_row_no)
83 {
84 int scrollback_start = term->grid->offset + term->rows;
85 int rebased_row = abs_row_no - scrollback_start + term->grid->num_rows;
86 rebased_row &= term->grid->num_rows - 1;
87
88 return rebased_row == 0;
89 }
90
91 static void
search_cancel_keep_selection(struct terminal * term)92 search_cancel_keep_selection(struct terminal *term)
93 {
94 struct wl_window *win = term->window;
95 wayl_win_subsurface_destroy(&win->search);
96
97 free(term->search.buf);
98 term->search.buf = NULL;
99 term->search.len = 0;
100 term->search.sz = 0;
101 term->search.cursor = 0;
102 term->search.match = (struct coord){-1, -1};
103 term->search.match_len = 0;
104 term->is_searching = false;
105 term->render.search_glyph_offset = 0;
106
107 /* Reset IME state */
108 if (term_ime_is_enabled(term)) {
109 term_ime_disable(term);
110 term_ime_enable(term);
111 }
112
113 term_xcursor_update(term);
114 render_refresh(term);
115 }
116
117 void
search_begin(struct terminal * term)118 search_begin(struct terminal *term)
119 {
120 LOG_DBG("search: begin");
121
122 search_cancel_keep_selection(term);
123 selection_cancel(term);
124
125 /* Reset IME state */
126 if (term_ime_is_enabled(term)) {
127 term_ime_disable(term);
128 term_ime_enable(term);
129 }
130
131 /* On-demand instantiate wayland surface */
132 bool ret = wayl_win_subsurface_new(term->window, &term->window->search);
133 xassert(ret);
134
135 term->search.original_view = term->grid->view;
136 term->search.view_followed_offset = term->grid->view == term->grid->offset;
137 term->is_searching = true;
138
139 term->search.len = 0;
140 term->search.sz = 64;
141 term->search.buf = xmalloc(term->search.sz * sizeof(term->search.buf[0]));
142 term->search.buf[0] = L'\0';
143
144 term_xcursor_update(term);
145 render_refresh_search(term);
146 }
147
148 void
search_cancel(struct terminal * term)149 search_cancel(struct terminal *term)
150 {
151 if (!term->is_searching)
152 return;
153
154 search_cancel_keep_selection(term);
155 selection_cancel(term);
156 }
157
158 void
search_selection_cancelled(struct terminal * term)159 search_selection_cancelled(struct terminal *term)
160 {
161 term->search.match = (struct coord){-1, -1};
162 term->search.match_len = 0;
163 render_refresh_search(term);
164 }
165
166 static void
search_update_selection(struct terminal * term,int start_row,int start_col,int end_row,int end_col)167 search_update_selection(struct terminal *term,
168 int start_row, int start_col,
169 int end_row, int end_col)
170 {
171 bool move_viewport = true;
172
173 int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
174 if (view_end >= term->grid->view) {
175 /* Viewport does *not* wrap around */
176 if (start_row >= term->grid->view && end_row <= view_end)
177 move_viewport = false;
178 } else {
179 /* Viewport wraps */
180 if (start_row >= term->grid->view || end_row <= view_end)
181 move_viewport = false;
182 }
183
184 if (move_viewport) {
185 int old_view = term->grid->view;
186 int new_view = start_row - term->rows / 2;
187
188 while (new_view < 0)
189 new_view += term->grid->num_rows;
190
191 new_view = ensure_view_is_allocated(term, new_view);
192
193 /* Don't scroll past scrollback history */
194 int end = (term->grid->offset + term->rows - 1) & (term->grid->num_rows - 1);
195 if (end >= term->grid->offset) {
196 /* Not wrapped */
197 if (new_view >= term->grid->offset && new_view <= end)
198 new_view = term->grid->offset;
199 } else {
200 if (new_view >= term->grid->offset || new_view <= end)
201 new_view = term->grid->offset;
202 }
203
204 #if defined(_DEBUG)
205 /* Verify all to-be-visible rows have been allocated */
206 for (int r = 0; r < term->rows; r++)
207 xassert(term->grid->rows[(new_view + r) & (term->grid->num_rows - 1)] != NULL);
208 #endif
209
210 /* Update view */
211 term->grid->view = new_view;
212 if (new_view != old_view)
213 term_damage_view(term);
214 }
215
216 /* Selection endpoint is inclusive */
217 if (--end_col < 0) {
218 end_col = term->cols - 1;
219 end_row--;
220 }
221
222 /* Begin a new selection if the start coords changed */
223 if (start_row != term->search.match.row ||
224 start_col != term->search.match.col)
225 {
226 int selection_row = start_row - term->grid->view;
227 while (selection_row < 0)
228 selection_row += term->grid->num_rows;
229
230 xassert(selection_row >= 0 &&
231 selection_row < term->grid->num_rows);
232 selection_start(
233 term, start_col, selection_row, SELECTION_CHAR_WISE, false);
234 }
235
236 /* Update selection endpoint */
237 {
238 int selection_row = end_row - term->grid->view;
239 while (selection_row < 0)
240 selection_row += term->grid->num_rows;
241
242 xassert(selection_row >= 0 &&
243 selection_row < term->grid->num_rows);
244 selection_update(term, end_col, selection_row);
245 }
246 }
247
248 static ssize_t
matches_cell(const struct terminal * term,const struct cell * cell,size_t search_ofs)249 matches_cell(const struct terminal *term, const struct cell *cell, size_t search_ofs)
250 {
251 assert(search_ofs < term->search.len);
252
253 wchar_t base = cell->wc;
254 const struct composed *composed = NULL;
255
256 if (base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI)
257 {
258 composed = composed_lookup(term->composed, base - CELL_COMB_CHARS_LO);
259 base = composed->chars[0];
260 }
261
262 if (composed == NULL && base == 0 && term->search.buf[search_ofs] == L' ')
263 return 1;
264
265 if (wcsncasecmp(&base, &term->search.buf[search_ofs], 1) != 0)
266 return -1;
267
268 if (composed != NULL) {
269 if (search_ofs + 1 + composed->count > term->search.len)
270 return -1;
271
272 for (size_t j = 1; j < composed->count; j++) {
273 if (composed->chars[j] != term->search.buf[search_ofs + 1 + j])
274 return -1;
275 }
276 }
277
278 return composed != NULL ? 1 + composed->count : 1;
279 }
280
281 static void
search_find_next(struct terminal * term)282 search_find_next(struct terminal *term)
283 {
284 bool backward = term->search.direction == SEARCH_BACKWARD;
285 term->search.direction = SEARCH_BACKWARD;
286
287 if (term->search.len == 0) {
288 term->search.match = (struct coord){-1, -1};
289 term->search.match_len = 0;
290 selection_cancel(term);
291 return;
292 }
293
294 int start_row = term->search.match.row;
295 int start_col = term->search.match.col;
296 size_t len = term->search.match_len;
297
298 xassert((len == 0 && start_row == -1 && start_col == -1) ||
299 (len > 0 && start_row >= 0 && start_col >= 0));
300
301 if (len == 0) {
302 if (backward) {
303 start_row = grid_row_absolute_in_view(term->grid, term->rows - 1);
304 start_col = term->cols - 1;
305 } else {
306 start_row = grid_row_absolute_in_view(term->grid, 0);
307 start_col = 0;
308 }
309 }
310
311 LOG_DBG("search: update: %s: starting at row=%d col=%d (offset = %d, view = %d)",
312 backward ? "backward" : "forward", start_row, start_col,
313 term->grid->offset, term->grid->view);
314
315 #define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) & (term->grid->num_rows - 1))
316 #define ROW_INC(_r) ((_r) = ((_r) + 1) & (term->grid->num_rows - 1))
317
318 /* Scan backward from current end-of-output */
319 /* TODO: don't search "scrollback" in alt screen? */
320 for (size_t r = 0;
321 r < term->grid->num_rows;
322 backward ? ROW_DEC(start_row) : ROW_INC(start_row), r++)
323 {
324 for (;
325 backward ? start_col >= 0 : start_col < term->cols;
326 backward ? start_col-- : start_col++)
327 {
328 const struct row *row = term->grid->rows[start_row];
329 if (row == NULL)
330 continue;
331
332 if (matches_cell(term, &row->cells[start_col], 0) < 0)
333 continue;
334
335 /*
336 * Got a match on the first letter. Now we'll see if the
337 * rest of the search buffer matches.
338 */
339
340 LOG_DBG("search: initial match at row=%d, col=%d", start_row, start_col);
341
342 int end_row = start_row;
343 int end_col = start_col;
344 size_t match_len = 0;
345
346 for (size_t i = 0; i < term->search.len;) {
347 if (end_col >= term->cols) {
348 end_row = (end_row + 1) & (term->grid->num_rows - 1);
349 end_col = 0;
350
351 if (has_wrapped_around(term, end_row))
352 break;
353
354 row = term->grid->rows[end_row];
355 }
356
357 if (row->cells[end_col].wc >= CELL_SPACER) {
358 end_col++;
359 continue;
360 }
361
362 ssize_t additional_chars = matches_cell(term, &row->cells[end_col], i);
363 if (additional_chars < 0)
364 break;
365
366 i += additional_chars;
367 match_len += additional_chars;
368 end_col++;
369 }
370
371 if (match_len != term->search.len) {
372 /* Didn't match (completely) */
373 continue;
374 }
375
376 /*
377 * We matched the entire buffer. Move view to ensure the
378 * match is visible, create a selection and return.
379 */
380 search_update_selection(term, start_row, start_col, end_row, end_col);
381
382 /* Update match state */
383 term->search.match.row = start_row;
384 term->search.match.col = start_col;
385 term->search.match_len = match_len;
386
387 return;
388 }
389
390 start_col = backward ? term->cols - 1 : 0;
391 }
392
393 /* No match */
394 LOG_DBG("no match");
395 term->search.match = (struct coord){-1, -1};
396 term->search.match_len = 0;
397 selection_cancel(term);
398 #undef ROW_DEC
399 }
400
401 void
search_add_chars(struct terminal * term,const char * src,size_t count)402 search_add_chars(struct terminal *term, const char *src, size_t count)
403 {
404 const char *_src = src;
405 mbstate_t ps = {0};
406 size_t wchars = mbsnrtowcs(NULL, &_src, count, 0, &ps);
407
408 if (wchars == -1) {
409 LOG_ERRNO("failed to convert %.*s to wchars", (int)count, src);
410 return;
411 }
412
413 _src = src;
414 ps = (mbstate_t){0};
415 wchar_t wcs[wchars + 1];
416 mbsnrtowcs(wcs, &_src, count, wchars, &ps);
417
418 /* Strip non-printable characters */
419 for (size_t i = 0, j = 0, orig_wchars = wchars; i < orig_wchars; i++) {
420 if (iswprint(wcs[i]))
421 wcs[j++] = wcs[i];
422 else
423 wchars--;
424 }
425
426 if (!search_ensure_size(term, term->search.len + wchars))
427 return;
428
429 xassert(term->search.len + wchars < term->search.sz);
430
431 memmove(&term->search.buf[term->search.cursor + wchars],
432 &term->search.buf[term->search.cursor],
433 (term->search.len - term->search.cursor) * sizeof(wchar_t));
434
435 memcpy(&term->search.buf[term->search.cursor], wcs, wchars * sizeof(wchar_t));
436
437 term->search.len += wchars;
438 term->search.cursor += wchars;
439 term->search.buf[term->search.len] = L'\0';
440 }
441
442 static void
search_match_to_end_of_word(struct terminal * term,bool spaces_only)443 search_match_to_end_of_word(struct terminal *term, bool spaces_only)
444 {
445 if (term->search.match_len == 0)
446 return;
447
448 xassert(term->selection.end.row != -1);
449
450 const bool move_cursor = term->search.cursor == term->search.len;
451
452 const struct coord old_end = term->selection.end;
453 struct coord new_end = old_end;
454 struct row *row = NULL;
455
456 #define newline(coord) __extension__ \
457 ({ \
458 bool wrapped_around = false; \
459 if (++(coord).col >= term->cols) { \
460 (coord).row = ((coord).row + 1) & (term->grid->num_rows - 1); \
461 (coord).col = 0; \
462 row = term->grid->rows[(coord).row]; \
463 if (has_wrapped_around(term, (coord.row))) \
464 wrapped_around = true; \
465 } \
466 wrapped_around; \
467 })
468
469 /* First character to consider is the *next* character */
470 if (newline(new_end))
471 return;
472
473 xassert(term->grid->rows[new_end.row] != NULL);
474
475 /* Find next word boundary */
476 new_end.row -= term->grid->view;
477 selection_find_word_boundary_right(term, &new_end, spaces_only);
478 new_end.row += term->grid->view;
479
480 struct coord pos = old_end;
481 row = term->grid->rows[pos.row];
482
483 struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
484 if (ctx == NULL)
485 return;
486
487 do {
488 if (newline(pos))
489 break;
490 if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
491 break;
492 } while (pos.col != new_end.col || pos.row != new_end.row);
493
494 wchar_t *new_text;
495 size_t new_len;
496
497 if (!extract_finish_wide(ctx, &new_text, &new_len))
498 return;
499
500 if (!search_ensure_size(term, term->search.len + new_len))
501 return;
502
503 for (size_t i = 0; i < new_len; i++) {
504 if (new_text[i] == L'\n') {
505 /* extract() adds newlines, which we never match against */
506 continue;
507 }
508
509 term->search.buf[term->search.len++] = new_text[i];
510 }
511
512 term->search.buf[term->search.len] = L'\0';
513 free(new_text);
514
515 if (move_cursor)
516 term->search.cursor = term->search.len;
517
518 /* search_update_selection() expected end coordinate to be *exclusive* */
519 newline(new_end);
520 search_update_selection(
521 term, term->search.match.row, term->search.match.col,
522 new_end.row, new_end.col);
523
524 term->search.match_len = term->search.len;
525
526 #undef newline
527 }
528
529 static size_t
distance_next_word(const struct terminal * term)530 distance_next_word(const struct terminal *term)
531 {
532 size_t cursor = term->search.cursor;
533
534 /* First eat non-whitespace. This is the word we're skipping past */
535 while (cursor < term->search.len) {
536 if (iswspace(term->search.buf[cursor++]))
537 break;
538 }
539
540 xassert(cursor == term->search.len || iswspace(term->search.buf[cursor - 1]));
541
542 /* Now skip past whitespace, so that we end up at the beginning of
543 * the next word */
544 while (cursor < term->search.len) {
545 if (!iswspace(term->search.buf[cursor++]))
546 break;
547 }
548
549 xassert(cursor == term->search.len || !iswspace(term->search.buf[cursor - 1]));
550
551 if (cursor < term->search.len && !iswspace(term->search.buf[cursor]))
552 cursor--;
553
554 return cursor - term->search.cursor;
555 }
556
557 static size_t
distance_prev_word(const struct terminal * term)558 distance_prev_word(const struct terminal *term)
559 {
560 int cursor = term->search.cursor;
561
562 /* First, eat whitespace prefix */
563 while (cursor > 0) {
564 if (!iswspace(term->search.buf[--cursor]))
565 break;
566 }
567
568 xassert(cursor == 0 || !iswspace(term->search.buf[cursor]));
569
570 /* Now eat non-whitespace. This is the word we're skipping past */
571 while (cursor > 0) {
572 if (iswspace(term->search.buf[--cursor]))
573 break;
574 }
575
576 xassert(cursor == 0 || iswspace(term->search.buf[cursor]));
577 if (cursor > 0 && iswspace(term->search.buf[cursor]))
578 cursor++;
579
580 return term->search.cursor - cursor;
581 }
582
583 static void
from_clipboard_cb(char * text,size_t size,void * user)584 from_clipboard_cb(char *text, size_t size, void *user)
585 {
586 struct terminal *term = user;
587 search_add_chars(term, text, size);
588 }
589
590 static void
from_clipboard_done(void * user)591 from_clipboard_done(void *user)
592 {
593 struct terminal *term = user;
594
595 LOG_DBG("search: buffer: %ls", term->search.buf);
596 search_find_next(term);
597 render_refresh_search(term);
598 }
599
600 static bool
execute_binding(struct seat * seat,struct terminal * term,enum bind_action_search action,uint32_t serial,bool * update_search_result,bool * redraw)601 execute_binding(struct seat *seat, struct terminal *term,
602 enum bind_action_search action, uint32_t serial,
603 bool *update_search_result, bool *redraw)
604 {
605 *update_search_result = *redraw = false;
606
607 switch (action) {
608 case BIND_ACTION_SEARCH_NONE:
609 return false;
610
611 case BIND_ACTION_SEARCH_CANCEL:
612 if (term->search.view_followed_offset)
613 term->grid->view = term->grid->offset;
614 else {
615 term->grid->view = ensure_view_is_allocated(
616 term, term->search.original_view);
617 }
618 term_damage_view(term);
619 search_cancel(term);
620 return true;
621
622 case BIND_ACTION_SEARCH_COMMIT:
623 selection_finalize(seat, term, serial);
624 search_cancel_keep_selection(term);
625 return true;
626
627 case BIND_ACTION_SEARCH_FIND_PREV:
628 if (term->search.match_len > 0) {
629 int new_col = term->search.match.col - 1;
630 int new_row = term->search.match.row;
631
632 if (new_col < 0) {
633 new_col = term->cols - 1;
634 new_row--;
635 }
636
637 if (new_row >= 0) {
638 term->search.match.col = new_col;
639 term->search.match.row = new_row;
640 }
641 }
642 *update_search_result = *redraw = true;
643 return true;
644
645 case BIND_ACTION_SEARCH_FIND_NEXT:
646 if (term->search.match_len > 0) {
647 int new_col = term->search.match.col + 1;
648 int new_row = term->search.match.row;
649
650 if (new_col >= term->cols) {
651 new_col = 0;
652 new_row++;
653 }
654
655 if (new_row < term->grid->num_rows) {
656 term->search.match.col = new_col;
657 term->search.match.row = new_row;
658 term->search.direction = SEARCH_FORWARD;
659 }
660 }
661 *update_search_result = *redraw = true;
662 return true;
663
664 case BIND_ACTION_SEARCH_EDIT_LEFT:
665 if (term->search.cursor > 0) {
666 term->search.cursor--;
667 *redraw = true;
668 }
669 return true;
670
671 case BIND_ACTION_SEARCH_EDIT_LEFT_WORD: {
672 size_t diff = distance_prev_word(term);
673 term->search.cursor -= diff;
674 xassert(term->search.cursor <= term->search.len);
675
676 if (diff > 0)
677 *redraw = true;
678 return true;
679 }
680
681 case BIND_ACTION_SEARCH_EDIT_RIGHT:
682 if (term->search.cursor < term->search.len) {
683 term->search.cursor++;
684 *redraw = true;
685 }
686 return true;
687
688 case BIND_ACTION_SEARCH_EDIT_RIGHT_WORD: {
689 size_t diff = distance_next_word(term);
690 term->search.cursor += diff;
691 xassert(term->search.cursor <= term->search.len);
692
693 if (diff > 0)
694 *redraw = true;
695 return true;
696 }
697
698 case BIND_ACTION_SEARCH_EDIT_HOME:
699 if (term->search.cursor != 0) {
700 term->search.cursor = 0;
701 *redraw = true;
702 }
703 return true;
704
705 case BIND_ACTION_SEARCH_EDIT_END:
706 if (term->search.cursor != term->search.len) {
707 term->search.cursor = term->search.len;
708 *redraw = true;
709 }
710 return true;
711
712 case BIND_ACTION_SEARCH_DELETE_PREV:
713 if (term->search.cursor > 0) {
714 memmove(
715 &term->search.buf[term->search.cursor - 1],
716 &term->search.buf[term->search.cursor],
717 (term->search.len - term->search.cursor) * sizeof(wchar_t));
718 term->search.cursor--;
719 term->search.buf[--term->search.len] = L'\0';
720 *update_search_result = *redraw = true;
721 }
722 return true;
723
724 case BIND_ACTION_SEARCH_DELETE_PREV_WORD: {
725 size_t diff = distance_prev_word(term);
726 size_t old_cursor = term->search.cursor;
727 size_t new_cursor = old_cursor - diff;
728
729 if (diff > 0) {
730 memmove(&term->search.buf[new_cursor],
731 &term->search.buf[old_cursor],
732 (term->search.len - old_cursor) * sizeof(wchar_t));
733
734 term->search.len -= diff;
735 term->search.cursor = new_cursor;
736 *update_search_result = *redraw = true;
737 }
738 return true;
739 }
740
741 case BIND_ACTION_SEARCH_DELETE_NEXT:
742 if (term->search.cursor < term->search.len) {
743 memmove(
744 &term->search.buf[term->search.cursor],
745 &term->search.buf[term->search.cursor + 1],
746 (term->search.len - term->search.cursor - 1) * sizeof(wchar_t));
747 term->search.buf[--term->search.len] = L'\0';
748 *update_search_result = *redraw = true;
749 }
750 return true;
751
752 case BIND_ACTION_SEARCH_DELETE_NEXT_WORD: {
753 size_t diff = distance_next_word(term);
754 size_t cursor = term->search.cursor;
755
756 if (diff > 0) {
757 memmove(&term->search.buf[cursor],
758 &term->search.buf[cursor + diff],
759 (term->search.len - (cursor + diff)) * sizeof(wchar_t));
760
761 term->search.len -= diff;
762 *update_search_result = *redraw = true;
763 }
764 return true;
765 }
766
767 case BIND_ACTION_SEARCH_EXTEND_WORD:
768 search_match_to_end_of_word(term, false);
769 *update_search_result = false;
770 *redraw = true;
771 return true;
772
773 case BIND_ACTION_SEARCH_EXTEND_WORD_WS:
774 search_match_to_end_of_word(term, true);
775 *update_search_result = false;
776 *redraw = true;
777 return true;
778
779 case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
780 text_from_clipboard(
781 seat, term, &from_clipboard_cb, &from_clipboard_done, term);
782 *update_search_result = *redraw = true;
783 return true;
784
785 case BIND_ACTION_SEARCH_PRIMARY_PASTE:
786 text_from_primary(
787 seat, term, &from_clipboard_cb, &from_clipboard_done, term);
788 *update_search_result = *redraw = true;
789 return true;
790
791 case BIND_ACTION_SEARCH_COUNT:
792 BUG("Invalid action type");
793 return true;
794 }
795
796 BUG("Unhandled action type");
797 return false;
798 }
799
800 void
search_input(struct seat * seat,struct terminal * term,uint32_t key,xkb_keysym_t sym,xkb_mod_mask_t mods,xkb_mod_mask_t consumed,const xkb_keysym_t * raw_syms,size_t raw_count,uint32_t serial)801 search_input(struct seat *seat, struct terminal *term, uint32_t key,
802 xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
803 const xkb_keysym_t *raw_syms, size_t raw_count,
804 uint32_t serial)
805 {
806 LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods);
807
808 enum xkb_compose_status compose_status = seat->kbd.xkb_compose_state != NULL
809 ? xkb_compose_state_get_status(seat->kbd.xkb_compose_state)
810 : XKB_COMPOSE_NOTHING;
811
812 bool update_search_result = false;
813 bool redraw = false;
814
815 /* Key bindings */
816 tll_foreach(seat->kbd.bindings.search, it) {
817 const struct key_binding *bind = &it->item;
818
819 /* Match translated symbol */
820 if (bind->sym == sym &&
821 bind->mods == (mods & ~consumed)) {
822
823 if (execute_binding(seat, term, bind->action, serial,
824 &update_search_result, &redraw))
825 {
826 goto update_search;
827 }
828 return;
829 }
830
831 if (bind->mods != mods)
832 continue;
833
834 /* Match untranslated symbols */
835 for (size_t i = 0; i < raw_count; i++) {
836 if (bind->sym == raw_syms[i]) {
837 if (execute_binding(seat, term, bind->action, serial,
838 &update_search_result, &redraw))
839 {
840 goto update_search;
841 }
842 return;
843 }
844 }
845
846 /* Match raw key code */
847 tll_foreach(bind->key_codes, code) {
848 if (code->item == key) {
849 if (execute_binding(seat, term, bind->action, serial,
850 &update_search_result, &redraw))
851 {
852 goto update_search;
853 }
854 return;
855 }
856 }
857 }
858
859 uint8_t buf[64] = {0};
860 int count = 0;
861
862 if (compose_status == XKB_COMPOSE_COMPOSED) {
863 count = xkb_compose_state_get_utf8(
864 seat->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
865 xkb_compose_state_reset(seat->kbd.xkb_compose_state);
866 } else if (compose_status == XKB_COMPOSE_CANCELLED) {
867 count = 0;
868 } else {
869 count = xkb_state_key_get_utf8(
870 seat->kbd.xkb_state, key, (char *)buf, sizeof(buf));
871 }
872
873 update_search_result = redraw = count > 0;
874
875 if (count == 0)
876 return;
877
878 search_add_chars(term, (const char *)buf, count);
879
880 update_search:
881 LOG_DBG("search: buffer: %ls", term->search.buf);
882 if (update_search_result)
883 search_find_next(term);
884 if (redraw)
885 render_refresh_search(term);
886 }
887