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