xref: /openbsd/usr.bin/tmux/window-buffer.c (revision d3278555)
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