xref: /netbsd-src/external/bsd/tmux/dist/paste.c (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2007 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 
25 #include "tmux.h"
26 
27 /*
28  * Set of paste buffers. Note that paste buffer data is not necessarily a C
29  * string!
30  */
31 
32 struct paste_buffer {
33 	char		*data;
34 	size_t		 size;
35 
36 	char		*name;
37 	time_t		 created;
38 	int		 automatic;
39 	u_int		 order;
40 
41 	RB_ENTRY(paste_buffer) name_entry;
42 	RB_ENTRY(paste_buffer) time_entry;
43 };
44 
45 static u_int	paste_next_index;
46 static u_int	paste_next_order;
47 static u_int	paste_num_automatic;
48 static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name;
49 static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time;
50 
51 static int	paste_cmp_names(const struct paste_buffer *,
52 		    const struct paste_buffer *);
53 RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names);
54 
55 static int	paste_cmp_times(const struct paste_buffer *,
56 		    const struct paste_buffer *);
57 RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times);
58 
59 static int
60 paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b)
61 {
62 	return (strcmp(a->name, b->name));
63 }
64 
65 static int
66 paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b)
67 {
68 	if (a->order > b->order)
69 		return (-1);
70 	if (a->order < b->order)
71 		return (1);
72 	return (0);
73 }
74 
75 /* Get paste buffer name. */
76 const char *
77 paste_buffer_name(struct paste_buffer *pb)
78 {
79 	return (pb->name);
80 }
81 
82 /* Get paste buffer order. */
83 u_int
84 paste_buffer_order(struct paste_buffer *pb)
85 {
86 	return (pb->order);
87 }
88 
89 /* Get paste buffer created. */
90 time_t
91 paste_buffer_created(struct paste_buffer *pb)
92 {
93 	return (pb->created);
94 }
95 
96 /* Get paste buffer data. */
97 const char *
98 paste_buffer_data(struct paste_buffer *pb, size_t *size)
99 {
100 	if (size != NULL)
101 		*size = pb->size;
102 	return (pb->data);
103 }
104 
105 /* Walk paste buffers by time. */
106 struct paste_buffer *
107 paste_walk(struct paste_buffer *pb)
108 {
109 	if (pb == NULL)
110 		return (RB_MIN(paste_time_tree, &paste_by_time));
111 	return (RB_NEXT(paste_time_tree, &paste_by_time, pb));
112 }
113 
114 /* Get the most recent automatic buffer. */
115 struct paste_buffer *
116 paste_get_top(const char **name)
117 {
118 	struct paste_buffer	*pb;
119 
120 	pb = RB_MIN(paste_time_tree, &paste_by_time);
121 	if (pb == NULL)
122 		return (NULL);
123 	if (name != NULL)
124 		*name = pb->name;
125 	return (pb);
126 }
127 
128 /* Get a paste buffer by name. */
129 struct paste_buffer *
130 paste_get_name(const char *name)
131 {
132 	struct paste_buffer	pbfind;
133 
134 	if (name == NULL || *name == '\0')
135 		return (NULL);
136 
137 	pbfind.name = __UNCONST(name);
138 	return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind));
139 }
140 
141 /* Free a paste buffer. */
142 void
143 paste_free(struct paste_buffer *pb)
144 {
145 	RB_REMOVE(paste_name_tree, &paste_by_name, pb);
146 	RB_REMOVE(paste_time_tree, &paste_by_time, pb);
147 	if (pb->automatic)
148 		paste_num_automatic--;
149 
150 	free(pb->data);
151 	free(pb->name);
152 	free(pb);
153 }
154 
155 /*
156  * Add an automatic buffer, freeing the oldest automatic item if at limit. Note
157  * that the caller is responsible for allocating data.
158  */
159 void
160 paste_add(const char *prefix, char *data, size_t size)
161 {
162 	struct paste_buffer	*pb, *pb1;
163 	u_int			 limit;
164 
165 	if (prefix == NULL)
166 		prefix = "buffer";
167 
168 	if (size == 0) {
169 		free(data);
170 		return;
171 	}
172 
173 	limit = options_get_number(global_options, "buffer-limit");
174 	RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) {
175 		if (paste_num_automatic < limit)
176 			break;
177 		if (pb->automatic)
178 			paste_free(pb);
179 	}
180 
181 	pb = xmalloc(sizeof *pb);
182 
183 	pb->name = NULL;
184 	do {
185 		free(pb->name);
186 		xasprintf(&pb->name, "%s%u", prefix, paste_next_index);
187 		paste_next_index++;
188 	} while (paste_get_name(pb->name) != NULL);
189 
190 	pb->data = data;
191 	pb->size = size;
192 
193 	pb->automatic = 1;
194 	paste_num_automatic++;
195 
196 	pb->created = time(NULL);
197 
198 	pb->order = paste_next_order++;
199 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
200 	RB_INSERT(paste_time_tree, &paste_by_time, pb);
201 }
202 
203 /* Rename a paste buffer. */
204 int
205 paste_rename(const char *oldname, const char *newname, char **cause)
206 {
207 	struct paste_buffer	*pb, *pb_new;
208 
209 	if (cause != NULL)
210 		*cause = NULL;
211 
212 	if (oldname == NULL || *oldname == '\0') {
213 		if (cause != NULL)
214 			*cause = xstrdup("no buffer");
215 		return (-1);
216 	}
217 	if (newname == NULL || *newname == '\0') {
218 		if (cause != NULL)
219 			*cause = xstrdup("new name is empty");
220 		return (-1);
221 	}
222 
223 	pb = paste_get_name(oldname);
224 	if (pb == NULL) {
225 		if (cause != NULL)
226 			xasprintf(cause, "no buffer %s", oldname);
227 		return (-1);
228 	}
229 
230 	pb_new = paste_get_name(newname);
231 	if (pb_new != NULL) {
232 		if (cause != NULL)
233 			xasprintf(cause, "buffer %s already exists", newname);
234 		return (-1);
235 	}
236 
237 	RB_REMOVE(paste_name_tree, &paste_by_name, pb);
238 
239 	free(pb->name);
240 	pb->name = xstrdup(newname);
241 
242 	if (pb->automatic)
243 		paste_num_automatic--;
244 	pb->automatic = 0;
245 
246 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
247 
248 	return (0);
249 }
250 
251 /*
252  * Add or replace an item in the store. Note that the caller is responsible for
253  * allocating data.
254  */
255 int
256 paste_set(char *data, size_t size, const char *name, char **cause)
257 {
258 	struct paste_buffer	*pb, *old;
259 
260 	if (cause != NULL)
261 		*cause = NULL;
262 
263 	if (size == 0) {
264 		free(data);
265 		return (0);
266 	}
267 	if (name == NULL) {
268 		paste_add(NULL, data, size);
269 		return (0);
270 	}
271 
272 	if (*name == '\0') {
273 		if (cause != NULL)
274 			*cause = xstrdup("empty buffer name");
275 		return (-1);
276 	}
277 
278 	pb = xmalloc(sizeof *pb);
279 
280 	pb->name = xstrdup(name);
281 
282 	pb->data = data;
283 	pb->size = size;
284 
285 	pb->automatic = 0;
286 	pb->order = paste_next_order++;
287 
288 	pb->created = time(NULL);
289 
290 	if ((old = paste_get_name(name)) != NULL)
291 		paste_free(old);
292 
293 	RB_INSERT(paste_name_tree, &paste_by_name, pb);
294 	RB_INSERT(paste_time_tree, &paste_by_time, pb);
295 
296 	return (0);
297 }
298 
299 /* Set paste data without otherwise changing it. */
300 void
301 paste_replace(struct paste_buffer *pb, char *data, size_t size)
302 {
303 	free(pb->data);
304 	pb->data = data;
305 	pb->size = size;
306 }
307 
308 /* Convert start of buffer into a nice string. */
309 char *
310 paste_make_sample(struct paste_buffer *pb)
311 {
312 	char		*buf;
313 	size_t		 len, used;
314 	const int	 flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
315 	const size_t	 width = 200;
316 
317 	len = pb->size;
318 	if (len > width)
319 		len = width;
320 	buf = xreallocarray(NULL, len, 4 + 4);
321 
322 	used = utf8_strvis(buf, pb->data, len, flags);
323 	if (pb->size > width || used > width)
324 		strlcpy(buf + width, "...", 4);
325 	return (buf);
326 }
327