1 /* $OpenBSD: window-buffer.c,v 1.30 2020/05/16 16:02:24 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 <paths.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <time.h> 26 #include <unistd.h> 27 #include <vis.h> 28 29 #include "tmux.h" 30 31 static struct screen *window_buffer_init(struct window_mode_entry *, 32 struct cmd_find_state *, struct args *); 33 static void window_buffer_free(struct window_mode_entry *); 34 static void window_buffer_resize(struct window_mode_entry *, u_int, 35 u_int); 36 static void window_buffer_key(struct window_mode_entry *, 37 struct client *, struct session *, 38 struct winlink *, key_code, struct mouse_event *); 39 40 #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" 41 42 #define WINDOW_BUFFER_DEFAULT_FORMAT \ 43 "#{t/p:buffer_created}: #{buffer_sample}" 44 45 static const struct menu_item window_buffer_menu_items[] = { 46 { "Paste", 'p', NULL }, 47 { "Paste Tagged", 'P', NULL }, 48 { "", KEYC_NONE, NULL }, 49 { "Tag", 't', NULL }, 50 { "Tag All", '\024', NULL }, 51 { "Tag None", 'T', NULL }, 52 { "", KEYC_NONE, NULL }, 53 { "Delete", 'd', NULL }, 54 { "Delete Tagged", 'D', NULL }, 55 { "", KEYC_NONE, NULL }, 56 { "Cancel", 'q', NULL }, 57 58 { NULL, KEYC_NONE, NULL } 59 }; 60 61 const struct window_mode window_buffer_mode = { 62 .name = "buffer-mode", 63 .default_format = WINDOW_BUFFER_DEFAULT_FORMAT, 64 65 .init = window_buffer_init, 66 .free = window_buffer_free, 67 .resize = window_buffer_resize, 68 .key = window_buffer_key, 69 }; 70 71 enum window_buffer_sort_type { 72 WINDOW_BUFFER_BY_TIME, 73 WINDOW_BUFFER_BY_NAME, 74 WINDOW_BUFFER_BY_SIZE, 75 }; 76 static const char *window_buffer_sort_list[] = { 77 "time", 78 "name", 79 "size" 80 }; 81 static struct mode_tree_sort_criteria *window_buffer_sort; 82 83 struct window_buffer_itemdata { 84 const char *name; 85 u_int order; 86 size_t size; 87 }; 88 89 struct window_buffer_modedata { 90 struct window_pane *wp; 91 struct cmd_find_state fs; 92 93 struct mode_tree_data *data; 94 char *command; 95 char *format; 96 97 struct window_buffer_itemdata **item_list; 98 u_int item_size; 99 }; 100 101 struct window_buffer_editdata { 102 u_int wp_id; 103 char *path; 104 char *name; 105 struct paste_buffer *pb; 106 }; 107 108 static struct window_buffer_itemdata * 109 window_buffer_add_item(struct window_buffer_modedata *data) 110 { 111 struct window_buffer_itemdata *item; 112 113 data->item_list = xreallocarray(data->item_list, data->item_size + 1, 114 sizeof *data->item_list); 115 item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); 116 return (item); 117 } 118 119 static void 120 window_buffer_free_item(struct window_buffer_itemdata *item) 121 { 122 free((void *)item->name); 123 free(item); 124 } 125 126 static int 127 window_buffer_cmp(const void *a0, const void *b0) 128 { 129 const struct window_buffer_itemdata *const *a = a0; 130 const struct window_buffer_itemdata *const *b = b0; 131 int result = 0; 132 133 if (window_buffer_sort->field == WINDOW_BUFFER_BY_TIME) 134 result = (*b)->order - (*a)->order; 135 else if (window_buffer_sort->field == WINDOW_BUFFER_BY_SIZE) 136 result = (*b)->size - (*a)->size; 137 138 /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */ 139 if (result == 0) 140 result = strcmp((*a)->name, (*b)->name); 141 142 if (window_buffer_sort->reversed) 143 result = -result; 144 return (result); 145 } 146 147 static void 148 window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, 149 __unused uint64_t *tag, const char *filter) 150 { 151 struct window_buffer_modedata *data = modedata; 152 struct window_buffer_itemdata *item; 153 u_int i; 154 struct paste_buffer *pb; 155 char *text, *cp; 156 struct format_tree *ft; 157 struct session *s = NULL; 158 struct winlink *wl = NULL; 159 struct window_pane *wp = NULL; 160 161 for (i = 0; i < data->item_size; i++) 162 window_buffer_free_item(data->item_list[i]); 163 free(data->item_list); 164 data->item_list = NULL; 165 data->item_size = 0; 166 167 pb = NULL; 168 while ((pb = paste_walk(pb)) != NULL) { 169 item = window_buffer_add_item(data); 170 item->name = xstrdup(paste_buffer_name(pb)); 171 paste_buffer_data(pb, &item->size); 172 item->order = paste_buffer_order(pb); 173 } 174 175 window_buffer_sort = sort_crit; 176 qsort(data->item_list, data->item_size, sizeof *data->item_list, 177 window_buffer_cmp); 178 179 if (cmd_find_valid_state(&data->fs)) { 180 s = data->fs.s; 181 wl = data->fs.wl; 182 wp = data->fs.wp; 183 } 184 185 for (i = 0; i < data->item_size; i++) { 186 item = data->item_list[i]; 187 188 pb = paste_get_name(item->name); 189 if (pb == NULL) 190 continue; 191 ft = format_create(NULL, NULL, FORMAT_NONE, 0); 192 format_defaults(ft, NULL, s, wl, wp); 193 format_defaults_paste_buffer(ft, pb); 194 195 if (filter != NULL) { 196 cp = format_expand(ft, filter); 197 if (!format_true(cp)) { 198 free(cp); 199 format_free(ft); 200 continue; 201 } 202 free(cp); 203 } 204 205 text = format_expand(ft, data->format); 206 mode_tree_add(data->data, NULL, item, item->order, item->name, 207 text, -1); 208 free(text); 209 210 format_free(ft); 211 } 212 213 } 214 215 static void 216 window_buffer_draw(__unused void *modedata, void *itemdata, 217 struct screen_write_ctx *ctx, u_int sx, u_int sy) 218 { 219 struct window_buffer_itemdata *item = itemdata; 220 struct paste_buffer *pb; 221 const char *pdata, *start, *end; 222 char *buf = NULL; 223 size_t psize; 224 u_int i, cx = ctx->s->cx, cy = ctx->s->cy; 225 226 pb = paste_get_name(item->name); 227 if (pb == NULL) 228 return; 229 230 pdata = end = paste_buffer_data(pb, &psize); 231 for (i = 0; i < sy; i++) { 232 start = end; 233 while (end != pdata + psize && *end != '\n') 234 end++; 235 buf = xreallocarray(buf, 4, end - start + 1); 236 utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_CSTYLE|VIS_TAB); 237 if (*buf != '\0') { 238 screen_write_cursormove(ctx, cx, cy + i, 0); 239 screen_write_nputs(ctx, sx, &grid_default_cell, "%s", 240 buf); 241 } 242 243 if (end == pdata + psize) 244 break; 245 end++; 246 } 247 free(buf); 248 } 249 250 static int 251 window_buffer_search(__unused void *modedata, void *itemdata, const char *ss) 252 { 253 struct window_buffer_itemdata *item = itemdata; 254 struct paste_buffer *pb; 255 const char *bufdata; 256 size_t bufsize; 257 258 if ((pb = paste_get_name(item->name)) == NULL) 259 return (0); 260 if (strstr(item->name, ss) != NULL) 261 return (1); 262 bufdata = paste_buffer_data(pb, &bufsize); 263 return (memmem(bufdata, bufsize, ss, strlen(ss)) != NULL); 264 } 265 266 static void 267 window_buffer_menu(void *modedata, struct client *c, key_code key) 268 { 269 struct window_buffer_modedata *data = modedata; 270 struct window_pane *wp = data->wp; 271 struct window_mode_entry *wme; 272 273 wme = TAILQ_FIRST(&wp->modes); 274 if (wme == NULL || wme->data != modedata) 275 return; 276 window_buffer_key(wme, c, NULL, NULL, key, NULL); 277 } 278 279 static struct screen * 280 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, 281 struct args *args) 282 { 283 struct window_pane *wp = wme->wp; 284 struct window_buffer_modedata *data; 285 struct screen *s; 286 287 wme->data = data = xcalloc(1, sizeof *data); 288 data->wp = wp; 289 cmd_find_copy_state(&data->fs, fs); 290 291 if (args == NULL || !args_has(args, 'F')) 292 data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT); 293 else 294 data->format = xstrdup(args_get(args, 'F')); 295 if (args == NULL || args->argc == 0) 296 data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); 297 else 298 data->command = xstrdup(args->argv[0]); 299 300 data->data = mode_tree_start(wp, args, window_buffer_build, 301 window_buffer_draw, window_buffer_search, window_buffer_menu, NULL, 302 data, window_buffer_menu_items, window_buffer_sort_list, 303 nitems(window_buffer_sort_list), &s); 304 mode_tree_zoom(data->data, args); 305 306 mode_tree_build(data->data); 307 mode_tree_draw(data->data); 308 309 return (s); 310 } 311 312 static void 313 window_buffer_free(struct window_mode_entry *wme) 314 { 315 struct window_buffer_modedata *data = wme->data; 316 u_int i; 317 318 if (data == NULL) 319 return; 320 321 mode_tree_free(data->data); 322 323 for (i = 0; i < data->item_size; i++) 324 window_buffer_free_item(data->item_list[i]); 325 free(data->item_list); 326 327 free(data->format); 328 free(data->command); 329 330 free(data); 331 } 332 333 static void 334 window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) 335 { 336 struct window_buffer_modedata *data = wme->data; 337 338 mode_tree_resize(data->data, sx, sy); 339 } 340 341 static void 342 window_buffer_do_delete(void *modedata, void *itemdata, 343 __unused struct client *c, __unused key_code key) 344 { 345 struct window_buffer_modedata *data = modedata; 346 struct window_buffer_itemdata *item = itemdata; 347 struct paste_buffer *pb; 348 349 if (item == mode_tree_get_current(data->data)) 350 mode_tree_down(data->data, 0); 351 if ((pb = paste_get_name(item->name)) != NULL) 352 paste_free(pb); 353 } 354 355 static void 356 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, 357 __unused key_code key) 358 { 359 struct window_buffer_modedata *data = modedata; 360 struct window_buffer_itemdata *item = itemdata; 361 362 if (paste_get_name(item->name) != NULL) 363 mode_tree_run_command(c, NULL, data->command, item->name); 364 } 365 366 static void 367 window_buffer_finish_edit(struct window_buffer_editdata *ed) 368 { 369 unlink(ed->path); 370 free(ed->path); 371 free(ed->name); 372 free(ed); 373 } 374 375 static void 376 window_buffer_edit_close_cb(int status, void *arg) 377 { 378 struct window_buffer_editdata *ed = arg; 379 FILE *f; 380 off_t len; 381 char *buf; 382 size_t oldlen; 383 const char *oldbuf; 384 struct paste_buffer *pb; 385 struct window_pane *wp; 386 struct window_buffer_modedata *data; 387 struct window_mode_entry *wme; 388 389 if (status != 0) { 390 window_buffer_finish_edit(ed); 391 return; 392 } 393 394 pb = paste_get_name(ed->name); 395 if (pb == NULL || pb != ed->pb) { 396 window_buffer_finish_edit(ed); 397 return; 398 } 399 400 f = fopen(ed->path, "r"); 401 if (f != NULL) { 402 fseeko(f, 0, SEEK_END); 403 len = ftello(f); 404 fseeko(f, 0, SEEK_SET); 405 406 if (len > 0 && 407 (uintmax_t)len <= (uintmax_t)SIZE_MAX && 408 (buf = malloc(len)) != NULL && 409 fread(buf, len, 1, f) == 1) { 410 oldbuf = paste_buffer_data(pb, &oldlen); 411 if (oldlen != '\0' && 412 oldbuf[oldlen - 1] != '\n' && 413 buf[len - 1] == '\n') 414 len--; 415 if (len != 0) 416 paste_replace(pb, buf, len); 417 } 418 fclose(f); 419 } 420 421 wp = window_pane_find_by_id(ed->wp_id); 422 if (wp != NULL) { 423 wme = TAILQ_FIRST(&wp->modes); 424 if (wme->mode == &window_buffer_mode) { 425 data = wme->data; 426 mode_tree_build(data->data); 427 mode_tree_draw(data->data); 428 } 429 wp->flags |= PANE_REDRAW; 430 } 431 window_buffer_finish_edit(ed); 432 } 433 434 static void 435 window_buffer_start_edit(struct window_buffer_modedata *data, 436 struct window_buffer_itemdata *item, struct client *c) 437 { 438 struct paste_buffer *pb; 439 int fd; 440 FILE *f; 441 const char *buf; 442 size_t len; 443 struct window_buffer_editdata *ed; 444 char *cmd; 445 char path[] = _PATH_TMP "tmux.XXXXXXXX"; 446 const char *editor; 447 u_int px, py, sx, sy; 448 449 if ((pb = paste_get_name(item->name)) == NULL) 450 return; 451 buf = paste_buffer_data(pb, &len); 452 453 editor = options_get_string(global_options, "editor"); 454 if (*editor == '\0') 455 return; 456 457 fd = mkstemp(path); 458 if (fd == -1) 459 return; 460 f = fdopen(fd, "w"); 461 if (fwrite(buf, len, 1, f) != 1) { 462 fclose(f); 463 return; 464 } 465 fclose(f); 466 467 ed = xcalloc(1, sizeof *ed); 468 ed->wp_id = data->wp->id; 469 ed->path = xstrdup(path); 470 ed->name = xstrdup(paste_buffer_name(pb)); 471 ed->pb = pb; 472 473 sx = c->tty.sx * 9 / 10; 474 sy = c->tty.sy * 9 / 10; 475 px = (c->tty.sx / 2) - (sx / 2); 476 py = (c->tty.sy / 2) - (sy / 2); 477 478 xasprintf(&cmd, "%s %s", editor, path); 479 if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, 480 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, window_buffer_edit_close_cb, 481 ed) != 0) 482 window_buffer_finish_edit(ed); 483 free(cmd); 484 } 485 486 static void 487 window_buffer_key(struct window_mode_entry *wme, struct client *c, 488 __unused struct session *s, __unused struct winlink *wl, key_code key, 489 struct mouse_event *m) 490 { 491 struct window_pane *wp = wme->wp; 492 struct window_buffer_modedata *data = wme->data; 493 struct mode_tree_data *mtd = data->data; 494 struct window_buffer_itemdata *item; 495 int finished; 496 497 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 498 switch (key) { 499 case 'e': 500 item = mode_tree_get_current(mtd); 501 window_buffer_start_edit(data, item, c); 502 break; 503 case 'd': 504 item = mode_tree_get_current(mtd); 505 window_buffer_do_delete(data, item, c, key); 506 mode_tree_build(mtd); 507 break; 508 case 'D': 509 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); 510 mode_tree_build(mtd); 511 break; 512 case 'P': 513 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); 514 finished = 1; 515 break; 516 case 'p': 517 case '\r': 518 item = mode_tree_get_current(mtd); 519 window_buffer_do_paste(data, item, c, key); 520 finished = 1; 521 break; 522 } 523 if (finished || paste_get_top(NULL) == NULL) 524 window_pane_reset_mode(wp); 525 else { 526 mode_tree_draw(mtd); 527 wp->flags |= PANE_REDRAW; 528 } 529 } 530