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