Add scroll-middle copy mode command to make cursor line in the middle,
[tmux-openbsd.git] / window-buffer.c
blob544a11554765204f78cbdc00caf651fd37ec230b
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include <vis.h>
27 #include "tmux.h"
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 *);
39 #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'"
41 #define WINDOW_BUFFER_DEFAULT_FORMAT \
42 "#{t/p:buffer_created}: #{buffer_sample}"
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 "}"
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 },
68 { NULL, KEYC_NONE, NULL }
71 const struct window_mode window_buffer_mode = {
72 .name = "buffer-mode",
73 .default_format = WINDOW_BUFFER_DEFAULT_FORMAT,
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,
82 enum window_buffer_sort_type {
83 WINDOW_BUFFER_BY_TIME,
84 WINDOW_BUFFER_BY_NAME,
85 WINDOW_BUFFER_BY_SIZE,
87 static const char *window_buffer_sort_list[] = {
88 "time",
89 "name",
90 "size"
92 static struct mode_tree_sort_criteria *window_buffer_sort;
94 struct window_buffer_itemdata {
95 const char *name;
96 u_int order;
97 size_t size;
100 struct window_buffer_modedata {
101 struct window_pane *wp;
102 struct cmd_find_state fs;
104 struct mode_tree_data *data;
105 char *command;
106 char *format;
107 char *key_format;
109 struct window_buffer_itemdata **item_list;
110 u_int item_size;
113 struct window_buffer_editdata {
114 u_int wp_id;
115 char *name;
116 struct paste_buffer *pb;
119 static struct window_buffer_itemdata *
120 window_buffer_add_item(struct window_buffer_modedata *data)
122 struct window_buffer_itemdata *item;
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);
130 static void
131 window_buffer_free_item(struct window_buffer_itemdata *item)
133 free((void *)item->name);
134 free(item);
137 static int
138 window_buffer_cmp(const void *a0, const void *b0)
140 const struct window_buffer_itemdata *const *a = a0;
141 const struct window_buffer_itemdata *const *b = b0;
142 int result = 0;
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;
149 /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */
150 if (result == 0)
151 result = strcmp((*a)->name, (*b)->name);
153 if (window_buffer_sort->reversed)
154 result = -result;
155 return (result);
158 static void
159 window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
160 __unused uint64_t *tag, const char *filter)
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;
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;
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);
186 window_buffer_sort = sort_crit;
187 qsort(data->item_list, data->item_size, sizeof *data->item_list,
188 window_buffer_cmp);
190 if (cmd_find_valid_state(&data->fs)) {
191 s = data->fs.s;
192 wl = data->fs.wl;
193 wp = data->fs.wp;
196 for (i = 0; i < data->item_size; i++) {
197 item = data->item_list[i];
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);
206 if (filter != NULL) {
207 cp = format_expand(ft, filter);
208 if (!format_true(cp)) {
209 free(cp);
210 format_free(ft);
211 continue;
213 free(cp);
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);
221 format_free(ft);
226 static void
227 window_buffer_draw(__unused void *modedata, void *itemdata,
228 struct screen_write_ctx *ctx, u_int sx, u_int sy)
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;
237 pb = paste_get_name(item->name);
238 if (pb == NULL)
239 return;
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);
255 if (end == pdata + psize)
256 break;
257 end++;
259 free(buf);
262 static int
263 window_buffer_search(__unused void *modedata, void *itemdata, const char *ss)
265 struct window_buffer_itemdata *item = itemdata;
266 struct paste_buffer *pb;
267 const char *bufdata;
268 size_t bufsize;
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);
278 static void
279 window_buffer_menu(void *modedata, struct client *c, key_code key)
281 struct window_buffer_modedata *data = modedata;
282 struct window_pane *wp = data->wp;
283 struct window_mode_entry *wme;
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);
291 static key_code
292 window_buffer_get_key(void *modedata, void *itemdata, u_int line)
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;
304 if (cmd_find_valid_state(&data->fs)) {
305 s = data->fs.s;
306 wl = data->fs.wl;
307 wp = data->fs.wp;
309 pb = paste_get_name(item->name);
310 if (pb == NULL)
311 return (KEYC_NONE);
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);
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);
326 static struct screen *
327 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
328 struct args *args)
330 struct window_pane *wp = wme->wp;
331 struct window_buffer_modedata *data;
332 struct screen *s;
334 wme->data = data = xcalloc(1, sizeof *data);
335 data->wp = wp;
336 cmd_find_copy_state(&data->fs, fs);
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));
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);
357 mode_tree_build(data->data);
358 mode_tree_draw(data->data);
360 return (s);
363 static void
364 window_buffer_free(struct window_mode_entry *wme)
366 struct window_buffer_modedata *data = wme->data;
367 u_int i;
369 if (data == NULL)
370 return;
372 mode_tree_free(data->data);
374 for (i = 0; i < data->item_size; i++)
375 window_buffer_free_item(data->item_list[i]);
376 free(data->item_list);
378 free(data->format);
379 free(data->key_format);
380 free(data->command);
382 free(data);
385 static void
386 window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
388 struct window_buffer_modedata *data = wme->data;
390 mode_tree_resize(data->data, sx, sy);
393 static void
394 window_buffer_update(struct window_mode_entry *wme)
396 struct window_buffer_modedata *data = wme->data;
398 mode_tree_build(data->data);
399 mode_tree_draw(data->data);
400 data->wp->flags |= PANE_REDRAW;
403 static void
404 window_buffer_do_delete(void *modedata, void *itemdata,
405 __unused struct client *c, __unused key_code key)
407 struct window_buffer_modedata *data = modedata;
408 struct window_buffer_itemdata *item = itemdata;
409 struct paste_buffer *pb;
411 if (item == mode_tree_get_current(data->data))
412 mode_tree_down(data->data, 0);
413 if ((pb = paste_get_name(item->name)) != NULL)
414 paste_free(pb);
417 static void
418 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c,
419 __unused key_code key)
421 struct window_buffer_modedata *data = modedata;
422 struct window_buffer_itemdata *item = itemdata;
424 if (paste_get_name(item->name) != NULL)
425 mode_tree_run_command(c, NULL, data->command, item->name);
428 static void
429 window_buffer_finish_edit(struct window_buffer_editdata *ed)
431 free(ed->name);
432 free(ed);
435 static void
436 window_buffer_edit_close_cb(char *buf, size_t len, void *arg)
438 struct window_buffer_editdata *ed = arg;
439 size_t oldlen;
440 const char *oldbuf;
441 struct paste_buffer *pb;
442 struct window_pane *wp;
443 struct window_buffer_modedata *data;
444 struct window_mode_entry *wme;
446 if (buf == NULL || len == 0) {
447 window_buffer_finish_edit(ed);
448 return;
451 pb = paste_get_name(ed->name);
452 if (pb == NULL || pb != ed->pb) {
453 window_buffer_finish_edit(ed);
454 return;
457 oldbuf = paste_buffer_data(pb, &oldlen);
458 if (oldlen != '\0' &&
459 oldbuf[oldlen - 1] != '\n' &&
460 buf[len - 1] == '\n')
461 len--;
462 if (len != 0)
463 paste_replace(pb, buf, len);
465 wp = window_pane_find_by_id(ed->wp_id);
466 if (wp != NULL) {
467 wme = TAILQ_FIRST(&wp->modes);
468 if (wme->mode == &window_buffer_mode) {
469 data = wme->data;
470 mode_tree_build(data->data);
471 mode_tree_draw(data->data);
473 wp->flags |= PANE_REDRAW;
475 window_buffer_finish_edit(ed);
478 static void
479 window_buffer_start_edit(struct window_buffer_modedata *data,
480 struct window_buffer_itemdata *item, struct client *c)
482 struct paste_buffer *pb;
483 const char *buf;
484 size_t len;
485 struct window_buffer_editdata *ed;
487 if ((pb = paste_get_name(item->name)) == NULL)
488 return;
489 buf = paste_buffer_data(pb, &len);
491 ed = xcalloc(1, sizeof *ed);
492 ed->wp_id = data->wp->id;
493 ed->name = xstrdup(paste_buffer_name(pb));
494 ed->pb = pb;
496 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0)
497 window_buffer_finish_edit(ed);
500 static void
501 window_buffer_key(struct window_mode_entry *wme, struct client *c,
502 __unused struct session *s, __unused struct winlink *wl, key_code key,
503 struct mouse_event *m)
505 struct window_pane *wp = wme->wp;
506 struct window_buffer_modedata *data = wme->data;
507 struct mode_tree_data *mtd = data->data;
508 struct window_buffer_itemdata *item;
509 int finished;
511 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
512 switch (key) {
513 case 'e':
514 item = mode_tree_get_current(mtd);
515 window_buffer_start_edit(data, item, c);
516 break;
517 case 'd':
518 item = mode_tree_get_current(mtd);
519 window_buffer_do_delete(data, item, c, key);
520 mode_tree_build(mtd);
521 break;
522 case 'D':
523 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0);
524 mode_tree_build(mtd);
525 break;
526 case 'P':
527 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0);
528 finished = 1;
529 break;
530 case 'p':
531 case '\r':
532 item = mode_tree_get_current(mtd);
533 window_buffer_do_paste(data, item, c, key);
534 finished = 1;
535 break;
537 if (finished || paste_get_top(NULL) == NULL)
538 window_pane_reset_mode(wp);
539 else {
540 mode_tree_draw(mtd);
541 wp->flags |= PANE_REDRAW;