1 /* $OpenBSD: window-buffer.c,v 1.40 2024/08/04 08:53:43 nicm Exp $ */
2
3 /*
4 * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include <vis.h>
26
27 #include "tmux.h"
28
29 static struct screen *window_buffer_init(struct window_mode_entry *,
30 struct cmd_find_state *, struct args *);
31 static void window_buffer_free(struct window_mode_entry *);
32 static void window_buffer_resize(struct window_mode_entry *, u_int,
33 u_int);
34 static void window_buffer_update(struct window_mode_entry *);
35 static void window_buffer_key(struct window_mode_entry *,
36 struct client *, struct session *,
37 struct winlink *, key_code, struct mouse_event *);
38
39 #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -p -b '%%'"
40
41 #define WINDOW_BUFFER_DEFAULT_FORMAT \
42 "#{t/p:buffer_created}: #{buffer_sample}"
43
44 #define WINDOW_BUFFER_DEFAULT_KEY_FORMAT \
45 "#{?#{e|<:#{line},10}," \
46 "#{line}" \
47 "," \
48 "#{?#{e|<:#{line},36}," \
49 "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
50 "," \
51 "" \
52 "}" \
53 "}"
54
55 static const struct menu_item window_buffer_menu_items[] = {
56 { "Paste", 'p', NULL },
57 { "Paste Tagged", 'P', NULL },
58 { "", KEYC_NONE, NULL },
59 { "Tag", 't', NULL },
60 { "Tag All", '\024', NULL },
61 { "Tag None", 'T', NULL },
62 { "", KEYC_NONE, NULL },
63 { "Delete", 'd', NULL },
64 { "Delete Tagged", 'D', NULL },
65 { "", KEYC_NONE, NULL },
66 { "Cancel", 'q', NULL },
67
68 { NULL, KEYC_NONE, NULL }
69 };
70
71 const struct window_mode window_buffer_mode = {
72 .name = "buffer-mode",
73 .default_format = WINDOW_BUFFER_DEFAULT_FORMAT,
74
75 .init = window_buffer_init,
76 .free = window_buffer_free,
77 .resize = window_buffer_resize,
78 .update = window_buffer_update,
79 .key = window_buffer_key,
80 };
81
82 enum window_buffer_sort_type {
83 WINDOW_BUFFER_BY_TIME,
84 WINDOW_BUFFER_BY_NAME,
85 WINDOW_BUFFER_BY_SIZE,
86 };
87 static const char *window_buffer_sort_list[] = {
88 "time",
89 "name",
90 "size"
91 };
92 static struct mode_tree_sort_criteria *window_buffer_sort;
93
94 struct window_buffer_itemdata {
95 const char *name;
96 u_int order;
97 size_t size;
98 };
99
100 struct window_buffer_modedata {
101 struct window_pane *wp;
102 struct cmd_find_state fs;
103
104 struct mode_tree_data *data;
105 char *command;
106 char *format;
107 char *key_format;
108
109 struct window_buffer_itemdata **item_list;
110 u_int item_size;
111 };
112
113 struct window_buffer_editdata {
114 u_int wp_id;
115 char *name;
116 struct paste_buffer *pb;
117 };
118
119 static struct window_buffer_itemdata *
window_buffer_add_item(struct window_buffer_modedata * data)120 window_buffer_add_item(struct window_buffer_modedata *data)
121 {
122 struct window_buffer_itemdata *item;
123
124 data->item_list = xreallocarray(data->item_list, data->item_size + 1,
125 sizeof *data->item_list);
126 item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
127 return (item);
128 }
129
130 static void
window_buffer_free_item(struct window_buffer_itemdata * item)131 window_buffer_free_item(struct window_buffer_itemdata *item)
132 {
133 free((void *)item->name);
134 free(item);
135 }
136
137 static int
window_buffer_cmp(const void * a0,const void * b0)138 window_buffer_cmp(const void *a0, const void *b0)
139 {
140 const struct window_buffer_itemdata *const *a = a0;
141 const struct window_buffer_itemdata *const *b = b0;
142 int result = 0;
143
144 if (window_buffer_sort->field == WINDOW_BUFFER_BY_TIME)
145 result = (*b)->order - (*a)->order;
146 else if (window_buffer_sort->field == WINDOW_BUFFER_BY_SIZE)
147 result = (*b)->size - (*a)->size;
148
149 /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */
150 if (result == 0)
151 result = strcmp((*a)->name, (*b)->name);
152
153 if (window_buffer_sort->reversed)
154 result = -result;
155 return (result);
156 }
157
158 static void
window_buffer_build(void * modedata,struct mode_tree_sort_criteria * sort_crit,__unused uint64_t * tag,const char * filter)159 window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
160 __unused uint64_t *tag, const char *filter)
161 {
162 struct window_buffer_modedata *data = modedata;
163 struct window_buffer_itemdata *item;
164 u_int i;
165 struct paste_buffer *pb;
166 char *text, *cp;
167 struct format_tree *ft;
168 struct session *s = NULL;
169 struct winlink *wl = NULL;
170 struct window_pane *wp = NULL;
171
172 for (i = 0; i < data->item_size; i++)
173 window_buffer_free_item(data->item_list[i]);
174 free(data->item_list);
175 data->item_list = NULL;
176 data->item_size = 0;
177
178 pb = NULL;
179 while ((pb = paste_walk(pb)) != NULL) {
180 item = window_buffer_add_item(data);
181 item->name = xstrdup(paste_buffer_name(pb));
182 paste_buffer_data(pb, &item->size);
183 item->order = paste_buffer_order(pb);
184 }
185
186 window_buffer_sort = sort_crit;
187 qsort(data->item_list, data->item_size, sizeof *data->item_list,
188 window_buffer_cmp);
189
190 if (cmd_find_valid_state(&data->fs)) {
191 s = data->fs.s;
192 wl = data->fs.wl;
193 wp = data->fs.wp;
194 }
195
196 for (i = 0; i < data->item_size; i++) {
197 item = data->item_list[i];
198
199 pb = paste_get_name(item->name);
200 if (pb == NULL)
201 continue;
202 ft = format_create(NULL, NULL, FORMAT_NONE, 0);
203 format_defaults(ft, NULL, s, wl, wp);
204 format_defaults_paste_buffer(ft, pb);
205
206 if (filter != NULL) {
207 cp = format_expand(ft, filter);
208 if (!format_true(cp)) {
209 free(cp);
210 format_free(ft);
211 continue;
212 }
213 free(cp);
214 }
215
216 text = format_expand(ft, data->format);
217 mode_tree_add(data->data, NULL, item, item->order, item->name,
218 text, -1);
219 free(text);
220
221 format_free(ft);
222 }
223
224 }
225
226 static void
window_buffer_draw(__unused void * modedata,void * itemdata,struct screen_write_ctx * ctx,u_int sx,u_int sy)227 window_buffer_draw(__unused void *modedata, void *itemdata,
228 struct screen_write_ctx *ctx, u_int sx, u_int sy)
229 {
230 struct window_buffer_itemdata *item = itemdata;
231 struct paste_buffer *pb;
232 const char *pdata, *start, *end;
233 char *buf = NULL;
234 size_t psize;
235 u_int i, cx = ctx->s->cx, cy = ctx->s->cy;
236
237 pb = paste_get_name(item->name);
238 if (pb == NULL)
239 return;
240
241 pdata = end = paste_buffer_data(pb, &psize);
242 for (i = 0; i < sy; i++) {
243 start = end;
244 while (end != pdata + psize && *end != '\n')
245 end++;
246 buf = xreallocarray(buf, 4, end - start + 1);
247 utf8_strvis(buf, start, end - start,
248 VIS_OCTAL|VIS_CSTYLE|VIS_TAB);
249 if (*buf != '\0') {
250 screen_write_cursormove(ctx, cx, cy + i, 0);
251 screen_write_nputs(ctx, sx, &grid_default_cell, "%s",
252 buf);
253 }
254
255 if (end == pdata + psize)
256 break;
257 end++;
258 }
259 free(buf);
260 }
261
262 static int
window_buffer_search(__unused void * modedata,void * itemdata,const char * ss)263 window_buffer_search(__unused void *modedata, void *itemdata, const char *ss)
264 {
265 struct window_buffer_itemdata *item = itemdata;
266 struct paste_buffer *pb;
267 const char *bufdata;
268 size_t bufsize;
269
270 if ((pb = paste_get_name(item->name)) == NULL)
271 return (0);
272 if (strstr(item->name, ss) != NULL)
273 return (1);
274 bufdata = paste_buffer_data(pb, &bufsize);
275 return (memmem(bufdata, bufsize, ss, strlen(ss)) != NULL);
276 }
277
278 static void
window_buffer_menu(void * modedata,struct client * c,key_code key)279 window_buffer_menu(void *modedata, struct client *c, key_code key)
280 {
281 struct window_buffer_modedata *data = modedata;
282 struct window_pane *wp = data->wp;
283 struct window_mode_entry *wme;
284
285 wme = TAILQ_FIRST(&wp->modes);
286 if (wme == NULL || wme->data != modedata)
287 return;
288 window_buffer_key(wme, c, NULL, NULL, key, NULL);
289 }
290
291 static key_code
window_buffer_get_key(void * modedata,void * itemdata,u_int line)292 window_buffer_get_key(void *modedata, void *itemdata, u_int line)
293 {
294 struct window_buffer_modedata *data = modedata;
295 struct window_buffer_itemdata *item = itemdata;
296 struct format_tree *ft;
297 struct session *s = NULL;
298 struct winlink *wl = NULL;
299 struct window_pane *wp = NULL;
300 struct paste_buffer *pb;
301 char *expanded;
302 key_code key;
303
304 if (cmd_find_valid_state(&data->fs)) {
305 s = data->fs.s;
306 wl = data->fs.wl;
307 wp = data->fs.wp;
308 }
309 pb = paste_get_name(item->name);
310 if (pb == NULL)
311 return (KEYC_NONE);
312
313 ft = format_create(NULL, NULL, FORMAT_NONE, 0);
314 format_defaults(ft, NULL, NULL, 0, NULL);
315 format_defaults(ft, NULL, s, wl, wp);
316 format_defaults_paste_buffer(ft, pb);
317 format_add(ft, "line", "%u", line);
318
319 expanded = format_expand(ft, data->key_format);
320 key = key_string_lookup_string(expanded);
321 free(expanded);
322 format_free(ft);
323 return (key);
324 }
325
326 static struct screen *
window_buffer_init(struct window_mode_entry * wme,struct cmd_find_state * fs,struct args * args)327 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
328 struct args *args)
329 {
330 struct window_pane *wp = wme->wp;
331 struct window_buffer_modedata *data;
332 struct screen *s;
333
334 wme->data = data = xcalloc(1, sizeof *data);
335 data->wp = wp;
336 cmd_find_copy_state(&data->fs, fs);
337
338 if (args == NULL || !args_has(args, 'F'))
339 data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT);
340 else
341 data->format = xstrdup(args_get(args, 'F'));
342 if (args == NULL || !args_has(args, 'K'))
343 data->key_format = xstrdup(WINDOW_BUFFER_DEFAULT_KEY_FORMAT);
344 else
345 data->key_format = xstrdup(args_get(args, 'K'));
346 if (args == NULL || args_count(args) == 0)
347 data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND);
348 else
349 data->command = xstrdup(args_string(args, 0));
350
351 data->data = mode_tree_start(wp, args, window_buffer_build,
352 window_buffer_draw, window_buffer_search, window_buffer_menu, NULL,
353 window_buffer_get_key, data, window_buffer_menu_items,
354 window_buffer_sort_list, nitems(window_buffer_sort_list), &s);
355 mode_tree_zoom(data->data, args);
356
357 mode_tree_build(data->data);
358 mode_tree_draw(data->data);
359
360 return (s);
361 }
362
363 static void
window_buffer_free(struct window_mode_entry * wme)364 window_buffer_free(struct window_mode_entry *wme)
365 {
366 struct window_buffer_modedata *data = wme->data;
367 u_int i;
368
369 if (data == NULL)
370 return;
371
372 mode_tree_free(data->data);
373
374 for (i = 0; i < data->item_size; i++)
375 window_buffer_free_item(data->item_list[i]);
376 free(data->item_list);
377
378 free(data->format);
379 free(data->key_format);
380 free(data->command);
381
382 free(data);
383 }
384
385 static void
window_buffer_resize(struct window_mode_entry * wme,u_int sx,u_int sy)386 window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
387 {
388 struct window_buffer_modedata *data = wme->data;
389
390 mode_tree_resize(data->data, sx, sy);
391 }
392
393 static void
window_buffer_update(struct window_mode_entry * wme)394 window_buffer_update(struct window_mode_entry *wme)
395 {
396 struct window_buffer_modedata *data = wme->data;
397
398 mode_tree_build(data->data);
399 mode_tree_draw(data->data);
400 data->wp->flags |= PANE_REDRAW;
401 }
402
403 static void
window_buffer_do_delete(void * modedata,void * itemdata,__unused struct client * c,__unused key_code key)404 window_buffer_do_delete(void *modedata, void *itemdata,
405 __unused struct client *c, __unused key_code key)
406 {
407 struct window_buffer_modedata *data = modedata;
408 struct window_buffer_itemdata *item = itemdata;
409 struct paste_buffer *pb;
410
411 if (item == mode_tree_get_current(data->data) &&
412 !mode_tree_down(data->data, 0)) {
413 /*
414 *If we were unable to select the item further down we are at
415 * the end of the list. Move one element up instead, to make
416 * sure that we preserve a valid selection or we risk having
417 * the tree build logic reset it to the first item.
418 */
419 mode_tree_up(data->data, 0);
420 }
421
422 if ((pb = paste_get_name(item->name)) != NULL)
423 paste_free(pb);
424 }
425
426 static void
window_buffer_do_paste(void * modedata,void * itemdata,struct client * c,__unused key_code key)427 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c,
428 __unused key_code key)
429 {
430 struct window_buffer_modedata *data = modedata;
431 struct window_buffer_itemdata *item = itemdata;
432
433 if (paste_get_name(item->name) != NULL)
434 mode_tree_run_command(c, NULL, data->command, item->name);
435 }
436
437 static void
window_buffer_finish_edit(struct window_buffer_editdata * ed)438 window_buffer_finish_edit(struct window_buffer_editdata *ed)
439 {
440 free(ed->name);
441 free(ed);
442 }
443
444 static void
window_buffer_edit_close_cb(char * buf,size_t len,void * arg)445 window_buffer_edit_close_cb(char *buf, size_t len, void *arg)
446 {
447 struct window_buffer_editdata *ed = arg;
448 size_t oldlen;
449 const char *oldbuf;
450 struct paste_buffer *pb;
451 struct window_pane *wp;
452 struct window_buffer_modedata *data;
453 struct window_mode_entry *wme;
454
455 if (buf == NULL || len == 0) {
456 window_buffer_finish_edit(ed);
457 return;
458 }
459
460 pb = paste_get_name(ed->name);
461 if (pb == NULL || pb != ed->pb) {
462 window_buffer_finish_edit(ed);
463 return;
464 }
465
466 oldbuf = paste_buffer_data(pb, &oldlen);
467 if (oldlen != '\0' &&
468 oldbuf[oldlen - 1] != '\n' &&
469 buf[len - 1] == '\n')
470 len--;
471 if (len != 0)
472 paste_replace(pb, buf, len);
473
474 wp = window_pane_find_by_id(ed->wp_id);
475 if (wp != NULL) {
476 wme = TAILQ_FIRST(&wp->modes);
477 if (wme->mode == &window_buffer_mode) {
478 data = wme->data;
479 mode_tree_build(data->data);
480 mode_tree_draw(data->data);
481 }
482 wp->flags |= PANE_REDRAW;
483 }
484 window_buffer_finish_edit(ed);
485 }
486
487 static void
window_buffer_start_edit(struct window_buffer_modedata * data,struct window_buffer_itemdata * item,struct client * c)488 window_buffer_start_edit(struct window_buffer_modedata *data,
489 struct window_buffer_itemdata *item, struct client *c)
490 {
491 struct paste_buffer *pb;
492 const char *buf;
493 size_t len;
494 struct window_buffer_editdata *ed;
495
496 if ((pb = paste_get_name(item->name)) == NULL)
497 return;
498 buf = paste_buffer_data(pb, &len);
499
500 ed = xcalloc(1, sizeof *ed);
501 ed->wp_id = data->wp->id;
502 ed->name = xstrdup(paste_buffer_name(pb));
503 ed->pb = pb;
504
505 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0)
506 window_buffer_finish_edit(ed);
507 }
508
509 static void
window_buffer_key(struct window_mode_entry * wme,struct client * c,__unused struct session * s,__unused struct winlink * wl,key_code key,struct mouse_event * m)510 window_buffer_key(struct window_mode_entry *wme, struct client *c,
511 __unused struct session *s, __unused struct winlink *wl, key_code key,
512 struct mouse_event *m)
513 {
514 struct window_pane *wp = wme->wp;
515 struct window_buffer_modedata *data = wme->data;
516 struct mode_tree_data *mtd = data->data;
517 struct window_buffer_itemdata *item;
518 int finished;
519
520 if (paste_is_empty()) {
521 finished = 1;
522 goto out;
523 }
524
525 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
526 switch (key) {
527 case 'e':
528 item = mode_tree_get_current(mtd);
529 window_buffer_start_edit(data, item, c);
530 break;
531 case 'd':
532 item = mode_tree_get_current(mtd);
533 window_buffer_do_delete(data, item, c, key);
534 mode_tree_build(mtd);
535 break;
536 case 'D':
537 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0);
538 mode_tree_build(mtd);
539 break;
540 case 'P':
541 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0);
542 finished = 1;
543 break;
544 case 'p':
545 case '\r':
546 item = mode_tree_get_current(mtd);
547 window_buffer_do_paste(data, item, c, key);
548 finished = 1;
549 break;
550 }
551
552 out:
553 if (finished || paste_is_empty())
554 window_pane_reset_mode(wp);
555 else {
556 mode_tree_draw(mtd);
557 wp->flags |= PANE_REDRAW;
558 }
559 }
560