1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2017 Nicholas Marriott <[email protected]> |
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 | static struct screen *window_buffer_init(struct window_mode_entry *, |
28 | struct cmd_find_state *, struct args *); |
29 | static void window_buffer_free(struct window_mode_entry *); |
30 | static void window_buffer_resize(struct window_mode_entry *, u_int, |
31 | u_int); |
32 | static void window_buffer_key(struct window_mode_entry *, |
33 | struct client *, struct session *, |
34 | struct winlink *, key_code, struct mouse_event *); |
35 | |
36 | #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" |
37 | |
38 | #define WINDOW_BUFFER_DEFAULT_FORMAT \ |
39 | "#{buffer_size} bytes (#{t:buffer_created})" |
40 | |
41 | static const struct menu_item [] = { |
42 | { "Paste" , 'p', NULL }, |
43 | { "Paste Tagged" , 'P', NULL }, |
44 | { "" , KEYC_NONE, NULL }, |
45 | { "Tag" , 't', NULL }, |
46 | { "Tag All" , '\024', NULL }, |
47 | { "Tag None" , 'T', NULL }, |
48 | { "" , KEYC_NONE, NULL }, |
49 | { "Delete" , 'd', NULL }, |
50 | { "Delete Tagged" , 'D', NULL }, |
51 | { "" , KEYC_NONE, NULL }, |
52 | { "Cancel" , 'q', NULL }, |
53 | |
54 | { NULL, KEYC_NONE, NULL } |
55 | }; |
56 | |
57 | const struct window_mode window_buffer_mode = { |
58 | .name = "buffer-mode" , |
59 | .default_format = WINDOW_BUFFER_DEFAULT_FORMAT, |
60 | |
61 | .init = window_buffer_init, |
62 | .free = window_buffer_free, |
63 | .resize = window_buffer_resize, |
64 | .key = window_buffer_key, |
65 | }; |
66 | |
67 | enum window_buffer_sort_type { |
68 | WINDOW_BUFFER_BY_TIME, |
69 | WINDOW_BUFFER_BY_NAME, |
70 | WINDOW_BUFFER_BY_SIZE, |
71 | }; |
72 | static const char *window_buffer_sort_list[] = { |
73 | "time" , |
74 | "name" , |
75 | "size" |
76 | }; |
77 | static struct mode_tree_sort_criteria *window_buffer_sort; |
78 | |
79 | struct window_buffer_itemdata { |
80 | const char *name; |
81 | u_int order; |
82 | size_t size; |
83 | }; |
84 | |
85 | struct 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 | |
93 | struct window_buffer_itemdata **item_list; |
94 | u_int item_size; |
95 | }; |
96 | |
97 | static struct window_buffer_itemdata * |
98 | window_buffer_add_item(struct window_buffer_modedata *data) |
99 | { |
100 | struct window_buffer_itemdata *item; |
101 | |
102 | data->item_list = xreallocarray(data->item_list, data->item_size + 1, |
103 | sizeof *data->item_list); |
104 | item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); |
105 | return (item); |
106 | } |
107 | |
108 | static void |
109 | window_buffer_free_item(struct window_buffer_itemdata *item) |
110 | { |
111 | free((void *)item->name); |
112 | free(item); |
113 | } |
114 | |
115 | static int |
116 | window_buffer_cmp(const void *a0, const void *b0) |
117 | { |
118 | const struct window_buffer_itemdata *const *a = a0; |
119 | const struct window_buffer_itemdata *const *b = b0; |
120 | int result = 0; |
121 | |
122 | if (window_buffer_sort->field == WINDOW_BUFFER_BY_TIME) |
123 | result = (*b)->order - (*a)->order; |
124 | else if (window_buffer_sort->field == WINDOW_BUFFER_BY_SIZE) |
125 | result = (*b)->size - (*a)->size; |
126 | |
127 | /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */ |
128 | if (result == 0) |
129 | result = strcmp((*a)->name, (*b)->name); |
130 | |
131 | if (window_buffer_sort->reversed) |
132 | result = -result; |
133 | return (result); |
134 | } |
135 | |
136 | static void |
137 | window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, |
138 | __unused uint64_t *tag, const char *filter) |
139 | { |
140 | struct window_buffer_modedata *data = modedata; |
141 | struct window_buffer_itemdata *item; |
142 | u_int i; |
143 | struct paste_buffer *pb; |
144 | char *text, *cp; |
145 | struct format_tree *ft; |
146 | struct session *s = NULL; |
147 | struct winlink *wl = NULL; |
148 | struct window_pane *wp = NULL; |
149 | |
150 | for (i = 0; i < data->item_size; i++) |
151 | window_buffer_free_item(data->item_list[i]); |
152 | free(data->item_list); |
153 | data->item_list = NULL; |
154 | data->item_size = 0; |
155 | |
156 | pb = NULL; |
157 | while ((pb = paste_walk(pb)) != NULL) { |
158 | item = window_buffer_add_item(data); |
159 | item->name = xstrdup(paste_buffer_name(pb)); |
160 | paste_buffer_data(pb, &item->size); |
161 | item->order = paste_buffer_order(pb); |
162 | } |
163 | |
164 | window_buffer_sort = sort_crit; |
165 | qsort(data->item_list, data->item_size, sizeof *data->item_list, |
166 | window_buffer_cmp); |
167 | |
168 | if (cmd_find_valid_state(&data->fs)) { |
169 | s = data->fs.s; |
170 | wl = data->fs.wl; |
171 | wp = data->fs.wp; |
172 | } |
173 | |
174 | for (i = 0; i < data->item_size; i++) { |
175 | item = data->item_list[i]; |
176 | |
177 | pb = paste_get_name(item->name); |
178 | if (pb == NULL) |
179 | continue; |
180 | ft = format_create(NULL, NULL, FORMAT_NONE, 0); |
181 | format_defaults(ft, NULL, s, wl, wp); |
182 | format_defaults_paste_buffer(ft, pb); |
183 | |
184 | if (filter != NULL) { |
185 | cp = format_expand(ft, filter); |
186 | if (!format_true(cp)) { |
187 | free(cp); |
188 | format_free(ft); |
189 | continue; |
190 | } |
191 | free(cp); |
192 | } |
193 | |
194 | text = format_expand(ft, data->format); |
195 | mode_tree_add(data->data, NULL, item, item->order, item->name, |
196 | text, -1); |
197 | free(text); |
198 | |
199 | format_free(ft); |
200 | } |
201 | |
202 | } |
203 | |
204 | static void |
205 | window_buffer_draw(__unused void *modedata, void *itemdata, |
206 | struct screen_write_ctx *ctx, u_int sx, u_int sy) |
207 | { |
208 | struct window_buffer_itemdata *item = itemdata; |
209 | struct paste_buffer *pb; |
210 | const char *pdata, *start, *end; |
211 | char *buf = NULL; |
212 | size_t psize; |
213 | u_int i, cx = ctx->s->cx, cy = ctx->s->cy; |
214 | |
215 | pb = paste_get_name(item->name); |
216 | if (pb == NULL) |
217 | return; |
218 | |
219 | pdata = end = paste_buffer_data(pb, &psize); |
220 | for (i = 0; i < sy; i++) { |
221 | start = end; |
222 | while (end != pdata + psize && *end != '\n') |
223 | end++; |
224 | buf = xreallocarray(buf, 4, end - start + 1); |
225 | utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_TAB); |
226 | if (*buf != '\0') { |
227 | screen_write_cursormove(ctx, cx, cy + i, 0); |
228 | screen_write_nputs(ctx, sx, &grid_default_cell, "%s" , |
229 | buf); |
230 | } |
231 | |
232 | if (end == pdata + psize) |
233 | break; |
234 | end++; |
235 | } |
236 | free(buf); |
237 | } |
238 | |
239 | static int |
240 | window_buffer_search(__unused void *modedata, void *itemdata, const char *ss) |
241 | { |
242 | struct window_buffer_itemdata *item = itemdata; |
243 | struct paste_buffer *pb; |
244 | const char *bufdata; |
245 | size_t bufsize; |
246 | |
247 | if ((pb = paste_get_name(item->name)) == NULL) |
248 | return (0); |
249 | if (strstr(item->name, ss) != NULL) |
250 | return (1); |
251 | bufdata = paste_buffer_data(pb, &bufsize); |
252 | return (memmem(bufdata, bufsize, ss, strlen(ss)) != NULL); |
253 | } |
254 | |
255 | static void |
256 | (void *modedata, struct client *c, key_code key) |
257 | { |
258 | struct window_buffer_modedata *data = modedata; |
259 | struct window_pane *wp = data->wp; |
260 | struct window_mode_entry *wme; |
261 | |
262 | wme = TAILQ_FIRST(&wp->modes); |
263 | if (wme == NULL || wme->data != modedata) |
264 | return; |
265 | window_buffer_key(wme, c, NULL, NULL, key, NULL); |
266 | } |
267 | |
268 | static struct screen * |
269 | window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, |
270 | struct args *args) |
271 | { |
272 | struct window_pane *wp = wme->wp; |
273 | struct window_buffer_modedata *data; |
274 | struct screen *s; |
275 | |
276 | wme->data = data = xcalloc(1, sizeof *data); |
277 | data->wp = wp; |
278 | cmd_find_copy_state(&data->fs, fs); |
279 | |
280 | if (args == NULL || !args_has(args, 'F')) |
281 | data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT); |
282 | else |
283 | data->format = xstrdup(args_get(args, 'F')); |
284 | if (args == NULL || args->argc == 0) |
285 | data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); |
286 | else |
287 | data->command = xstrdup(args->argv[0]); |
288 | |
289 | data->data = mode_tree_start(wp, args, window_buffer_build, |
290 | window_buffer_draw, window_buffer_search, window_buffer_menu, data, |
291 | window_buffer_menu_items, window_buffer_sort_list, |
292 | nitems(window_buffer_sort_list), &s); |
293 | mode_tree_zoom(data->data, args); |
294 | |
295 | mode_tree_build(data->data); |
296 | mode_tree_draw(data->data); |
297 | |
298 | return (s); |
299 | } |
300 | |
301 | static void |
302 | window_buffer_free(struct window_mode_entry *wme) |
303 | { |
304 | struct window_buffer_modedata *data = wme->data; |
305 | u_int i; |
306 | |
307 | if (data == NULL) |
308 | return; |
309 | |
310 | mode_tree_free(data->data); |
311 | |
312 | for (i = 0; i < data->item_size; i++) |
313 | window_buffer_free_item(data->item_list[i]); |
314 | free(data->item_list); |
315 | |
316 | free(data->format); |
317 | free(data->command); |
318 | |
319 | free(data); |
320 | } |
321 | |
322 | static void |
323 | window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) |
324 | { |
325 | struct window_buffer_modedata *data = wme->data; |
326 | |
327 | mode_tree_resize(data->data, sx, sy); |
328 | } |
329 | |
330 | static void |
331 | window_buffer_do_delete(void *modedata, void *itemdata, |
332 | __unused struct client *c, __unused key_code key) |
333 | { |
334 | struct window_buffer_modedata *data = modedata; |
335 | struct window_buffer_itemdata *item = itemdata; |
336 | struct paste_buffer *pb; |
337 | |
338 | if (item == mode_tree_get_current(data->data)) |
339 | mode_tree_down(data->data, 0); |
340 | if ((pb = paste_get_name(item->name)) != NULL) |
341 | paste_free(pb); |
342 | } |
343 | |
344 | static void |
345 | window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, |
346 | __unused key_code key) |
347 | { |
348 | struct window_buffer_modedata *data = modedata; |
349 | struct window_buffer_itemdata *item = itemdata; |
350 | struct paste_buffer *pb; |
351 | |
352 | if ((pb = paste_get_name(item->name)) != NULL) |
353 | mode_tree_run_command(c, NULL, data->command, item->name); |
354 | } |
355 | |
356 | static void |
357 | window_buffer_key(struct window_mode_entry *wme, struct client *c, |
358 | __unused struct session *s, __unused struct winlink *wl, key_code key, |
359 | struct mouse_event *m) |
360 | { |
361 | struct window_pane *wp = wme->wp; |
362 | struct window_buffer_modedata *data = wme->data; |
363 | struct mode_tree_data *mtd = data->data; |
364 | struct window_buffer_itemdata *item; |
365 | int finished; |
366 | |
367 | finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); |
368 | switch (key) { |
369 | case 'd': |
370 | item = mode_tree_get_current(mtd); |
371 | window_buffer_do_delete(data, item, c, key); |
372 | mode_tree_build(mtd); |
373 | break; |
374 | case 'D': |
375 | mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); |
376 | mode_tree_build(mtd); |
377 | break; |
378 | case 'P': |
379 | mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); |
380 | finished = 1; |
381 | break; |
382 | case 'p': |
383 | case '\r': |
384 | item = mode_tree_get_current(mtd); |
385 | window_buffer_do_paste(data, item, c, key); |
386 | finished = 1; |
387 | break; |
388 | } |
389 | if (finished || paste_get_top(NULL) == NULL) |
390 | window_pane_reset_mode(wp); |
391 | else { |
392 | mode_tree_draw(mtd); |
393 | wp->flags |= PANE_REDRAW; |
394 | } |
395 | } |
396 | |