xref: /openbsd-src/usr.bin/tmux/paste.c (revision 232cb8cb3a0aa3add13488f1ce41de70485545ef)
1*232cb8cbSnicm /* $OpenBSD: paste.c,v 1.47 2024/10/12 08:13:52 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm  *
6311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm  * copyright notice and this permission notice appear in all copies.
9311827fbSnicm  *
10311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm  */
18311827fbSnicm 
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm 
217d053cf9Snicm #include <stdlib.h>
22311827fbSnicm #include <string.h>
2345b608c5Snicm #include <time.h>
245f093833Snicm #include <vis.h>
25311827fbSnicm 
26311827fbSnicm #include "tmux.h"
27311827fbSnicm 
285bde14a0Snicm /*
29a41fa27aSnicm  * Set of paste buffers. Note that paste buffer data is not necessarily a C
305bde14a0Snicm  * string!
315bde14a0Snicm  */
325bde14a0Snicm 
33909ecc87Snicm struct paste_buffer {
34909ecc87Snicm 	char		*data;
35909ecc87Snicm 	size_t		 size;
36909ecc87Snicm 
37909ecc87Snicm 	char		*name;
38bba10b08Snicm 	time_t		 created;
39909ecc87Snicm 	int		 automatic;
40909ecc87Snicm 	u_int		 order;
41909ecc87Snicm 
42909ecc87Snicm 	RB_ENTRY(paste_buffer) name_entry;
43909ecc87Snicm 	RB_ENTRY(paste_buffer) time_entry;
44909ecc87Snicm };
45909ecc87Snicm 
468d2662b6Snicm static u_int	paste_next_index;
478d2662b6Snicm static u_int	paste_next_order;
488d2662b6Snicm static u_int	paste_num_automatic;
49c97fab4eSnicm static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name;
50c97fab4eSnicm static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time;
51ca678466Snicm 
528d2662b6Snicm static int	paste_cmp_names(const struct paste_buffer *,
538d2662b6Snicm 		    const struct paste_buffer *);
548d2662b6Snicm RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names);
55a41fa27aSnicm 
568d2662b6Snicm static int	paste_cmp_times(const struct paste_buffer *,
578d2662b6Snicm 		    const struct paste_buffer *);
588d2662b6Snicm RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times);
59a41fa27aSnicm 
608d2662b6Snicm static int
61a41fa27aSnicm paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b)
62311827fbSnicm {
63a41fa27aSnicm 	return (strcmp(a->name, b->name));
64311827fbSnicm }
65311827fbSnicm 
668d2662b6Snicm static int
67a41fa27aSnicm paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b)
68a41fa27aSnicm {
69a41fa27aSnicm 	if (a->order > b->order)
70a41fa27aSnicm 		return (-1);
71a41fa27aSnicm 	if (a->order < b->order)
72a41fa27aSnicm 		return (1);
73a41fa27aSnicm 	return (0);
74a41fa27aSnicm }
75a41fa27aSnicm 
76909ecc87Snicm /* Get paste buffer name. */
77909ecc87Snicm const char *
78909ecc87Snicm paste_buffer_name(struct paste_buffer *pb)
79909ecc87Snicm {
80909ecc87Snicm 	return (pb->name);
81909ecc87Snicm }
82909ecc87Snicm 
83bba10b08Snicm /* Get paste buffer order. */
84bba10b08Snicm u_int
85bba10b08Snicm paste_buffer_order(struct paste_buffer *pb)
86bba10b08Snicm {
87bba10b08Snicm 	return (pb->order);
88bba10b08Snicm }
89bba10b08Snicm 
90bba10b08Snicm /* Get paste buffer created. */
91bba10b08Snicm time_t
92bba10b08Snicm paste_buffer_created(struct paste_buffer *pb)
93bba10b08Snicm {
94bba10b08Snicm 	return (pb->created);
95bba10b08Snicm }
96bba10b08Snicm 
97909ecc87Snicm /* Get paste buffer data. */
98909ecc87Snicm const char *
99909ecc87Snicm paste_buffer_data(struct paste_buffer *pb, size_t *size)
100909ecc87Snicm {
101909ecc87Snicm 	if (size != NULL)
102909ecc87Snicm 		*size = pb->size;
103909ecc87Snicm 	return (pb->data);
104909ecc87Snicm }
105909ecc87Snicm 
106bba10b08Snicm /* Walk paste buffers by time. */
107a41fa27aSnicm struct paste_buffer *
108a41fa27aSnicm paste_walk(struct paste_buffer *pb)
109a41fa27aSnicm {
110a41fa27aSnicm 	if (pb == NULL)
111a41fa27aSnicm 		return (RB_MIN(paste_time_tree, &paste_by_time));
112a41fa27aSnicm 	return (RB_NEXT(paste_time_tree, &paste_by_time, pb));
113a41fa27aSnicm }
114a41fa27aSnicm 
115e5d942bbSnicm int
116e5d942bbSnicm paste_is_empty(void)
117e5d942bbSnicm {
118e5d942bbSnicm 	return RB_ROOT(&paste_by_time) == NULL;
119e5d942bbSnicm }
120e5d942bbSnicm 
121a5427204Snicm /* Get the most recent automatic buffer. */
122311827fbSnicm struct paste_buffer *
123909ecc87Snicm paste_get_top(const char **name)
124311827fbSnicm {
125a41fa27aSnicm 	struct paste_buffer	*pb;
126a41fa27aSnicm 
127a41fa27aSnicm 	pb = RB_MIN(paste_time_tree, &paste_by_time);
128e5d942bbSnicm 	while (pb != NULL && !pb->automatic)
129e5d942bbSnicm 		pb = RB_NEXT(paste_time_tree, &paste_by_time, pb);
130a41fa27aSnicm 	if (pb == NULL)
131311827fbSnicm 		return (NULL);
132909ecc87Snicm 	if (name != NULL)
133909ecc87Snicm 		*name = pb->name;
134a41fa27aSnicm 	return (pb);
135311827fbSnicm }
136311827fbSnicm 
137a41fa27aSnicm /* Get a paste buffer by name. */
138a41fa27aSnicm struct paste_buffer *
139a41fa27aSnicm paste_get_name(const char *name)
140311827fbSnicm {
141a41fa27aSnicm 	struct paste_buffer	pbfind;
142311827fbSnicm 
143a41fa27aSnicm 	if (name == NULL || *name == '\0')
144a41fa27aSnicm 		return (NULL);
145a41fa27aSnicm 
146a41fa27aSnicm 	pbfind.name = (char *)name;
147a41fa27aSnicm 	return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind));
148a41fa27aSnicm }
149a41fa27aSnicm 
15064185147Snicm /* Free a paste buffer. */
15164185147Snicm void
15264185147Snicm paste_free(struct paste_buffer *pb)
153a41fa27aSnicm {
15499acee14Snicm 	notify_paste_buffer(pb->name, 1);
155d79e52f9Snicm 
156a41fa27aSnicm 	RB_REMOVE(paste_name_tree, &paste_by_name, pb);
157a41fa27aSnicm 	RB_REMOVE(paste_time_tree, &paste_by_time, pb);
158a41fa27aSnicm 	if (pb->automatic)
159a41fa27aSnicm 		paste_num_automatic--;
160311827fbSnicm 
1617d053cf9Snicm 	free(pb->data);
162a41fa27aSnicm 	free(pb->name);
1637d053cf9Snicm 	free(pb);
164311827fbSnicm }
165311827fbSnicm 
1665bde14a0Snicm /*
167a41fa27aSnicm  * Add an automatic buffer, freeing the oldest automatic item if at limit. Note
1685bde14a0Snicm  * that the caller is responsible for allocating data.
1695bde14a0Snicm  */
170311827fbSnicm void
1714aafca52Snicm paste_add(const char *prefix, char *data, size_t size)
172311827fbSnicm {
173a41fa27aSnicm 	struct paste_buffer	*pb, *pb1;
174a41fa27aSnicm 	u_int			 limit;
175311827fbSnicm 
1764aafca52Snicm 	if (prefix == NULL)
1774aafca52Snicm 		prefix = "buffer";
1784aafca52Snicm 
17901c63002Snicm 	if (size == 0) {
18001c63002Snicm 		free(data);
18192e1fe33Snicm 		return;
18201c63002Snicm 	}
18392e1fe33Snicm 
184d89252e5Snicm 	limit = options_get_number(global_options, "buffer-limit");
185a41fa27aSnicm 	RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) {
186a41fa27aSnicm 		if (paste_num_automatic < limit)
187a41fa27aSnicm 			break;
188a41fa27aSnicm 		if (pb->automatic)
18964185147Snicm 			paste_free(pb);
1906e06e05fSnicm 	}
191311827fbSnicm 
192311827fbSnicm 	pb = xmalloc(sizeof *pb);
193a41fa27aSnicm 
194a41fa27aSnicm 	pb->name = NULL;
195a41fa27aSnicm 	do {
196a41fa27aSnicm 		free(pb->name);
1974aafca52Snicm 		xasprintf(&pb->name, "%s%u", prefix, paste_next_index);
198a41fa27aSnicm 		paste_next_index++;
199a41fa27aSnicm 	} while (paste_get_name(pb->name) != NULL);
200311827fbSnicm 
201311827fbSnicm 	pb->data = data;
2021937104bSnicm 	pb->size = size;
203a41fa27aSnicm 
204a41fa27aSnicm 	pb->automatic = 1;
205a41fa27aSnicm 	paste_num_automatic++;
206a41fa27aSnicm 
207bba10b08Snicm 	pb->created = time(NULL);
208bba10b08Snicm 
209a41fa27aSnicm 	pb->order = paste_next_order++;
210a41fa27aSnicm 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
211a41fa27aSnicm 	RB_INSERT(paste_time_tree, &paste_by_time, pb);
212d79e52f9Snicm 
21399acee14Snicm 	notify_paste_buffer(pb->name, 0);
214311827fbSnicm }
215311827fbSnicm 
216a41fa27aSnicm /* Rename a paste buffer. */
217a41fa27aSnicm int
218a41fa27aSnicm paste_rename(const char *oldname, const char *newname, char **cause)
219a41fa27aSnicm {
220b736ab22Snicm 	struct paste_buffer	*pb, *pb_new;
221a41fa27aSnicm 
222a41fa27aSnicm 	if (cause != NULL)
223a41fa27aSnicm 		*cause = NULL;
224a41fa27aSnicm 
225a41fa27aSnicm 	if (oldname == NULL || *oldname == '\0') {
226a41fa27aSnicm 		if (cause != NULL)
227a41fa27aSnicm 			*cause = xstrdup("no buffer");
228a41fa27aSnicm 		return (-1);
229a41fa27aSnicm 	}
230a41fa27aSnicm 	if (newname == NULL || *newname == '\0') {
231a41fa27aSnicm 		if (cause != NULL)
232a41fa27aSnicm 			*cause = xstrdup("new name is empty");
233a41fa27aSnicm 		return (-1);
234a41fa27aSnicm 	}
235a41fa27aSnicm 
236a41fa27aSnicm 	pb = paste_get_name(oldname);
237a41fa27aSnicm 	if (pb == NULL) {
238a41fa27aSnicm 		if (cause != NULL)
239a41fa27aSnicm 			xasprintf(cause, "no buffer %s", oldname);
240a41fa27aSnicm 		return (-1);
241a41fa27aSnicm 	}
242a41fa27aSnicm 
243b736ab22Snicm 	pb_new = paste_get_name(newname);
244*232cb8cbSnicm 	if (pb_new == pb)
245*232cb8cbSnicm 		return (0);
24649828476Snicm 	if (pb_new != NULL)
24749828476Snicm 		paste_free(pb_new);
248b736ab22Snicm 
249a41fa27aSnicm 	RB_REMOVE(paste_name_tree, &paste_by_name, pb);
250a41fa27aSnicm 
251a41fa27aSnicm 	free(pb->name);
252a41fa27aSnicm 	pb->name = xstrdup(newname);
253a41fa27aSnicm 
254a41fa27aSnicm 	if (pb->automatic)
255a41fa27aSnicm 		paste_num_automatic--;
256a41fa27aSnicm 	pb->automatic = 0;
257a41fa27aSnicm 
258a41fa27aSnicm 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
259a41fa27aSnicm 
26099acee14Snicm 	notify_paste_buffer(oldname, 1);
26199acee14Snicm 	notify_paste_buffer(newname, 0);
262d79e52f9Snicm 
263a41fa27aSnicm 	return (0);
264a41fa27aSnicm }
2655bde14a0Snicm 
2665bde14a0Snicm /*
267a41fa27aSnicm  * Add or replace an item in the store. Note that the caller is responsible for
2685bde14a0Snicm  * allocating data.
2695bde14a0Snicm  */
270311827fbSnicm int
271a41fa27aSnicm paste_set(char *data, size_t size, const char *name, char **cause)
272311827fbSnicm {
27364185147Snicm 	struct paste_buffer	*pb, *old;
274311827fbSnicm 
275a41fa27aSnicm 	if (cause != NULL)
276a41fa27aSnicm 		*cause = NULL;
277a41fa27aSnicm 
2781ac29a77Snicm 	if (size == 0) {
2791ac29a77Snicm 		free(data);
2805bde14a0Snicm 		return (0);
2811ac29a77Snicm 	}
282a41fa27aSnicm 	if (name == NULL) {
2834aafca52Snicm 		paste_add(NULL, data, size);
284a41fa27aSnicm 		return (0);
285a41fa27aSnicm 	}
2865bde14a0Snicm 
287a41fa27aSnicm 	if (*name == '\0') {
288a41fa27aSnicm 		if (cause != NULL)
289a41fa27aSnicm 			*cause = xstrdup("empty buffer name");
290311827fbSnicm 		return (-1);
291a41fa27aSnicm 	}
292311827fbSnicm 
293a41fa27aSnicm 	pb = xmalloc(sizeof *pb);
294a41fa27aSnicm 
295a41fa27aSnicm 	pb->name = xstrdup(name);
296311827fbSnicm 
297311827fbSnicm 	pb->data = data;
2981937104bSnicm 	pb->size = size;
299311827fbSnicm 
300a41fa27aSnicm 	pb->automatic = 0;
301a41fa27aSnicm 	pb->order = paste_next_order++;
302a41fa27aSnicm 
303bba10b08Snicm 	pb->created = time(NULL);
304bba10b08Snicm 
30564185147Snicm 	if ((old = paste_get_name(name)) != NULL)
30664185147Snicm 		paste_free(old);
3070d382a95Snicm 
308a41fa27aSnicm 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
309a41fa27aSnicm 	RB_INSERT(paste_time_tree, &paste_by_time, pb);
310a41fa27aSnicm 
31199acee14Snicm 	notify_paste_buffer(name, 0);
312d79e52f9Snicm 
313311827fbSnicm 	return (0);
314311827fbSnicm }
3155f093833Snicm 
316a6c9106fSnicm /* Set paste data without otherwise changing it. */
317a6c9106fSnicm void
318a6c9106fSnicm paste_replace(struct paste_buffer *pb, char *data, size_t size)
319a6c9106fSnicm {
320a6c9106fSnicm 	free(pb->data);
321a6c9106fSnicm 	pb->data = data;
322a6c9106fSnicm 	pb->size = size;
323d79e52f9Snicm 
32499acee14Snicm 	notify_paste_buffer(pb->name, 0);
325a6c9106fSnicm }
326a6c9106fSnicm 
327dbbd1b46Snicm /* Convert start of buffer into a nice string. */
3285f093833Snicm char *
329f650d6adSnicm paste_make_sample(struct paste_buffer *pb)
3305f093833Snicm {
3315f093833Snicm 	char		*buf;
3325f093833Snicm 	size_t		 len, used;
333da484ce1Snicm 	const int	 flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
334dbbd1b46Snicm 	const size_t	 width = 200;
3355f093833Snicm 
3365f093833Snicm 	len = pb->size;
3375f093833Snicm 	if (len > width)
3385f093833Snicm 		len = width;
33964cf113cSnicm 	buf = xreallocarray(NULL, len, 4 + 4);
3405f093833Snicm 
341dbbd1b46Snicm 	used = utf8_strvis(buf, pb->data, len, flags);
342416cec24Snicm 	if (pb->size > width || used > width)
343dbbd1b46Snicm 		strlcpy(buf + width, "...", 4);
3445f093833Snicm 	return (buf);
3455f093833Snicm }
346