1 /* $OpenBSD$ */ 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 <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <time.h> 25 #include <unistd.h> 26 27 #include "tmux.h" 28 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 *); 38 39 #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -p -b '%%'" 40 41 #define WINDOW_BUFFER_DEFAULT_FORMAT \ 42 "#{t/p:buffer_created}: #{buffer_sample}" 43 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 "}" 54 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 }, 67 68 { NULL, KEYC_NONE, NULL } 69 }; 70 71 const struct window_mode window_buffer_mode = { 72 .name = "buffer-mode", 73 .default_format = WINDOW_BUFFER_DEFAULT_FORMAT, 74 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, 80 }; 81 82 enum window_buffer_sort_type { 83 WINDOW_BUFFER_BY_TIME, 84 WINDOW_BUFFER_BY_NAME, 85 WINDOW_BUFFER_BY_SIZE, 86 }; 87 static const char *window_buffer_sort_list[] = { 88 "time", 89 "name", 90 "size" 91 }; 92 static struct mode_tree_sort_criteria *window_buffer_sort; 93 94 struct window_buffer_itemdata { 95 const char *name; 96 u_int order; 97 size_t size; 98 }; 99 100 struct window_buffer_modedata { 101 struct window_pane *wp; 102 struct cmd_find_state fs; 103 104 struct mode_tree_data *data; 105 char *command; 106 char *format; 107 char *key_format; 108 109 struct window_buffer_itemdata **item_list; 110 u_int item_size; 111 }; 112 113 struct window_buffer_editdata { 114 u_int wp_id; 115 char *name; 116 struct paste_buffer *pb; 117 }; 118 119 static struct window_buffer_itemdata * 120 window_buffer_add_item(struct window_buffer_modedata *data) 121 { 122 struct window_buffer_itemdata *item; 123 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); 128 } 129 130 static void 131 window_buffer_free_item(struct window_buffer_itemdata *item) 132 { 133 free(__UNCONST(item->name)); 134 free(item); 135 } 136 137 static int 138 window_buffer_cmp(const void *a0, const void *b0) 139 { 140 const struct window_buffer_itemdata *const *a = a0; 141 const struct window_buffer_itemdata *const *b = b0; 142 int result = 0; 143 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; 148 149 /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */ 150 if (result == 0) 151 result = strcmp((*a)->name, (*b)->name); 152 153 if (window_buffer_sort->reversed) 154 result = -result; 155 return (result); 156 } 157 158 static void 159 window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, 160 __unused uint64_t *tag, const char *filter) 161 { 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; 171 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; 177 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); 184 } 185 186 window_buffer_sort = sort_crit; 187 qsort(data->item_list, data->item_size, sizeof *data->item_list, 188 window_buffer_cmp); 189 190 if (cmd_find_valid_state(&data->fs)) { 191 s = data->fs.s; 192 wl = data->fs.wl; 193 wp = data->fs.wp; 194 } 195 196 for (i = 0; i < data->item_size; i++) { 197 item = data->item_list[i]; 198 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); 205 206 if (filter != NULL) { 207 cp = format_expand(ft, filter); 208 if (!format_true(cp)) { 209 free(cp); 210 format_free(ft); 211 continue; 212 } 213 free(cp); 214 } 215 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); 220 221 format_free(ft); 222 } 223 224 } 225 226 static void 227 window_buffer_draw(__unused void *modedata, void *itemdata, 228 struct screen_write_ctx *ctx, u_int sx, u_int sy) 229 { 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; 236 237 pb = paste_get_name(item->name); 238 if (pb == NULL) 239 return; 240 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); 253 } 254 255 if (end == pdata + psize) 256 break; 257 end++; 258 } 259 free(buf); 260 } 261 262 static int 263 window_buffer_search(__unused void *modedata, void *itemdata, const char *ss) 264 { 265 struct window_buffer_itemdata *item = itemdata; 266 struct paste_buffer *pb; 267 const char *bufdata; 268 size_t bufsize; 269 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); 276 } 277 278 static void 279 window_buffer_menu(void *modedata, struct client *c, key_code key) 280 { 281 struct window_buffer_modedata *data = modedata; 282 struct window_pane *wp = data->wp; 283 struct window_mode_entry *wme; 284 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); 289 } 290 291 static key_code 292 window_buffer_get_key(void *modedata, void *itemdata, u_int line) 293 { 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; 303 304 if (cmd_find_valid_state(&data->fs)) { 305 s = data->fs.s; 306 wl = data->fs.wl; 307 wp = data->fs.wp; 308 } 309 pb = paste_get_name(item->name); 310 if (pb == NULL) 311 return (KEYC_NONE); 312 313 ft = format_create(NULL, NULL, FORMAT_NONE, 0); 314 format_defaults(ft, NULL, NULL, 0, NULL); 315 if (wp != NULL) 316 format_defaults(ft, NULL, s, wl, wp); 317 format_defaults_paste_buffer(ft, pb); 318 format_add(ft, "line", "%u", line); 319 320 expanded = format_expand(ft, data->key_format); 321 key = key_string_lookup_string(expanded); 322 free(expanded); 323 format_free(ft); 324 return (key); 325 } 326 327 static struct screen * 328 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, 329 struct args *args) 330 { 331 struct window_pane *wp = wme->wp; 332 struct window_buffer_modedata *data; 333 struct screen *s; 334 335 wme->data = data = xcalloc(1, sizeof *data); 336 data->wp = wp; 337 cmd_find_copy_state(&data->fs, fs); 338 339 if (args == NULL || !args_has(args, 'F')) 340 data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT); 341 else 342 data->format = xstrdup(args_get(args, 'F')); 343 if (args == NULL || !args_has(args, 'K')) 344 data->key_format = xstrdup(WINDOW_BUFFER_DEFAULT_KEY_FORMAT); 345 else 346 data->key_format = xstrdup(args_get(args, 'K')); 347 if (args == NULL || args_count(args) == 0) 348 data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); 349 else 350 data->command = xstrdup(args_string(args, 0)); 351 352 data->data = mode_tree_start(wp, args, window_buffer_build, 353 window_buffer_draw, window_buffer_search, window_buffer_menu, NULL, 354 window_buffer_get_key, data, window_buffer_menu_items, 355 window_buffer_sort_list, nitems(window_buffer_sort_list), &s); 356 mode_tree_zoom(data->data, args); 357 358 mode_tree_build(data->data); 359 mode_tree_draw(data->data); 360 361 return (s); 362 } 363 364 static void 365 window_buffer_free(struct window_mode_entry *wme) 366 { 367 struct window_buffer_modedata *data = wme->data; 368 u_int i; 369 370 if (data == NULL) 371 return; 372 373 mode_tree_free(data->data); 374 375 for (i = 0; i < data->item_size; i++) 376 window_buffer_free_item(data->item_list[i]); 377 free(data->item_list); 378 379 free(data->format); 380 free(data->key_format); 381 free(data->command); 382 383 free(data); 384 } 385 386 static void 387 window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) 388 { 389 struct window_buffer_modedata *data = wme->data; 390 391 mode_tree_resize(data->data, sx, sy); 392 } 393 394 static void 395 window_buffer_update(struct window_mode_entry *wme) 396 { 397 struct window_buffer_modedata *data = wme->data; 398 399 mode_tree_build(data->data); 400 mode_tree_draw(data->data); 401 data->wp->flags |= PANE_REDRAW; 402 } 403 404 static void 405 window_buffer_do_delete(void *modedata, void *itemdata, 406 __unused struct client *c, __unused key_code key) 407 { 408 struct window_buffer_modedata *data = modedata; 409 struct window_buffer_itemdata *item = itemdata; 410 struct paste_buffer *pb; 411 412 if (item == mode_tree_get_current(data->data) && 413 !mode_tree_down(data->data, 0)) { 414 /* 415 *If we were unable to select the item further down we are at 416 * the end of the list. Move one element up instead, to make 417 * sure that we preserve a valid selection or we risk having 418 * the tree build logic reset it to the first item. 419 */ 420 mode_tree_up(data->data, 0); 421 } 422 423 if ((pb = paste_get_name(item->name)) != NULL) 424 paste_free(pb); 425 } 426 427 static void 428 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, 429 __unused key_code key) 430 { 431 struct window_buffer_modedata *data = modedata; 432 struct window_buffer_itemdata *item = itemdata; 433 434 if (paste_get_name(item->name) != NULL) 435 mode_tree_run_command(c, NULL, data->command, item->name); 436 } 437 438 static void 439 window_buffer_finish_edit(struct window_buffer_editdata *ed) 440 { 441 free(ed->name); 442 free(ed); 443 } 444 445 static void 446 window_buffer_edit_close_cb(char *buf, size_t len, void *arg) 447 { 448 struct window_buffer_editdata *ed = arg; 449 size_t oldlen; 450 const char *oldbuf; 451 struct paste_buffer *pb; 452 struct window_pane *wp; 453 struct window_buffer_modedata *data; 454 struct window_mode_entry *wme; 455 456 if (buf == NULL || len == 0) { 457 window_buffer_finish_edit(ed); 458 return; 459 } 460 461 pb = paste_get_name(ed->name); 462 if (pb == NULL || pb != ed->pb) { 463 window_buffer_finish_edit(ed); 464 return; 465 } 466 467 oldbuf = paste_buffer_data(pb, &oldlen); 468 if (oldlen != '\0' && 469 oldbuf[oldlen - 1] != '\n' && 470 buf[len - 1] == '\n') 471 len--; 472 if (len != 0) 473 paste_replace(pb, buf, len); 474 475 wp = window_pane_find_by_id(ed->wp_id); 476 if (wp != NULL) { 477 wme = TAILQ_FIRST(&wp->modes); 478 if (wme->mode == &window_buffer_mode) { 479 data = wme->data; 480 mode_tree_build(data->data); 481 mode_tree_draw(data->data); 482 } 483 wp->flags |= PANE_REDRAW; 484 } 485 window_buffer_finish_edit(ed); 486 } 487 488 static void 489 window_buffer_start_edit(struct window_buffer_modedata *data, 490 struct window_buffer_itemdata *item, struct client *c) 491 { 492 struct paste_buffer *pb; 493 const char *buf; 494 size_t len; 495 struct window_buffer_editdata *ed; 496 497 if ((pb = paste_get_name(item->name)) == NULL) 498 return; 499 buf = paste_buffer_data(pb, &len); 500 501 ed = xcalloc(1, sizeof *ed); 502 ed->wp_id = data->wp->id; 503 ed->name = xstrdup(paste_buffer_name(pb)); 504 ed->pb = pb; 505 506 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) 507 window_buffer_finish_edit(ed); 508 } 509 510 static void 511 window_buffer_key(struct window_mode_entry *wme, struct client *c, 512 __unused struct session *s, __unused struct winlink *wl, key_code key, 513 struct mouse_event *m) 514 { 515 struct window_pane *wp = wme->wp; 516 struct window_buffer_modedata *data = wme->data; 517 struct mode_tree_data *mtd = data->data; 518 struct window_buffer_itemdata *item; 519 int finished; 520 521 if (paste_is_empty()) { 522 finished = 1; 523 goto out; 524 } 525 526 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 527 switch (key) { 528 case 'e': 529 item = mode_tree_get_current(mtd); 530 window_buffer_start_edit(data, item, c); 531 break; 532 case 'd': 533 item = mode_tree_get_current(mtd); 534 window_buffer_do_delete(data, item, c, key); 535 mode_tree_build(mtd); 536 break; 537 case 'D': 538 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); 539 mode_tree_build(mtd); 540 break; 541 case 'P': 542 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); 543 finished = 1; 544 break; 545 case 'p': 546 case '\r': 547 item = mode_tree_get_current(mtd); 548 window_buffer_do_paste(data, item, c, key); 549 finished = 1; 550 break; 551 } 552 553 out: 554 if (finished || paste_is_empty()) 555 window_pane_reset_mode(wp); 556 else { 557 mode_tree_draw(mtd); 558 wp->flags |= PANE_REDRAW; 559 } 560 } 561