1 /* $OpenBSD: window-buffer.c,v 1.40 2024/08/04 08:53:43 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 <stdlib.h> 22 #include <string.h> 23 #include <time.h> 24 #include <unistd.h> 25 #include <vis.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((void *)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 format_defaults(ft, NULL, s, wl, wp); 316 format_defaults_paste_buffer(ft, pb); 317 format_add(ft, "line", "%u", line); 318 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); 324 } 325 326 static struct screen * 327 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, 328 struct args *args) 329 { 330 struct window_pane *wp = wme->wp; 331 struct window_buffer_modedata *data; 332 struct screen *s; 333 334 wme->data = data = xcalloc(1, sizeof *data); 335 data->wp = wp; 336 cmd_find_copy_state(&data->fs, fs); 337 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)); 350 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); 356 357 mode_tree_build(data->data); 358 mode_tree_draw(data->data); 359 360 return (s); 361 } 362 363 static void 364 window_buffer_free(struct window_mode_entry *wme) 365 { 366 struct window_buffer_modedata *data = wme->data; 367 u_int i; 368 369 if (data == NULL) 370 return; 371 372 mode_tree_free(data->data); 373 374 for (i = 0; i < data->item_size; i++) 375 window_buffer_free_item(data->item_list[i]); 376 free(data->item_list); 377 378 free(data->format); 379 free(data->key_format); 380 free(data->command); 381 382 free(data); 383 } 384 385 static void 386 window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) 387 { 388 struct window_buffer_modedata *data = wme->data; 389 390 mode_tree_resize(data->data, sx, sy); 391 } 392 393 static void 394 window_buffer_update(struct window_mode_entry *wme) 395 { 396 struct window_buffer_modedata *data = wme->data; 397 398 mode_tree_build(data->data); 399 mode_tree_draw(data->data); 400 data->wp->flags |= PANE_REDRAW; 401 } 402 403 static void 404 window_buffer_do_delete(void *modedata, void *itemdata, 405 __unused struct client *c, __unused key_code key) 406 { 407 struct window_buffer_modedata *data = modedata; 408 struct window_buffer_itemdata *item = itemdata; 409 struct paste_buffer *pb; 410 411 if (item == mode_tree_get_current(data->data) && 412 !mode_tree_down(data->data, 0)) { 413 /* 414 *If we were unable to select the item further down we are at 415 * the end of the list. Move one element up instead, to make 416 * sure that we preserve a valid selection or we risk having 417 * the tree build logic reset it to the first item. 418 */ 419 mode_tree_up(data->data, 0); 420 } 421 422 if ((pb = paste_get_name(item->name)) != NULL) 423 paste_free(pb); 424 } 425 426 static void 427 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, 428 __unused key_code key) 429 { 430 struct window_buffer_modedata *data = modedata; 431 struct window_buffer_itemdata *item = itemdata; 432 433 if (paste_get_name(item->name) != NULL) 434 mode_tree_run_command(c, NULL, data->command, item->name); 435 } 436 437 static void 438 window_buffer_finish_edit(struct window_buffer_editdata *ed) 439 { 440 free(ed->name); 441 free(ed); 442 } 443 444 static void 445 window_buffer_edit_close_cb(char *buf, size_t len, void *arg) 446 { 447 struct window_buffer_editdata *ed = arg; 448 size_t oldlen; 449 const char *oldbuf; 450 struct paste_buffer *pb; 451 struct window_pane *wp; 452 struct window_buffer_modedata *data; 453 struct window_mode_entry *wme; 454 455 if (buf == NULL || len == 0) { 456 window_buffer_finish_edit(ed); 457 return; 458 } 459 460 pb = paste_get_name(ed->name); 461 if (pb == NULL || pb != ed->pb) { 462 window_buffer_finish_edit(ed); 463 return; 464 } 465 466 oldbuf = paste_buffer_data(pb, &oldlen); 467 if (oldlen != '\0' && 468 oldbuf[oldlen - 1] != '\n' && 469 buf[len - 1] == '\n') 470 len--; 471 if (len != 0) 472 paste_replace(pb, buf, len); 473 474 wp = window_pane_find_by_id(ed->wp_id); 475 if (wp != NULL) { 476 wme = TAILQ_FIRST(&wp->modes); 477 if (wme->mode == &window_buffer_mode) { 478 data = wme->data; 479 mode_tree_build(data->data); 480 mode_tree_draw(data->data); 481 } 482 wp->flags |= PANE_REDRAW; 483 } 484 window_buffer_finish_edit(ed); 485 } 486 487 static void 488 window_buffer_start_edit(struct window_buffer_modedata *data, 489 struct window_buffer_itemdata *item, struct client *c) 490 { 491 struct paste_buffer *pb; 492 const char *buf; 493 size_t len; 494 struct window_buffer_editdata *ed; 495 496 if ((pb = paste_get_name(item->name)) == NULL) 497 return; 498 buf = paste_buffer_data(pb, &len); 499 500 ed = xcalloc(1, sizeof *ed); 501 ed->wp_id = data->wp->id; 502 ed->name = xstrdup(paste_buffer_name(pb)); 503 ed->pb = pb; 504 505 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) 506 window_buffer_finish_edit(ed); 507 } 508 509 static void 510 window_buffer_key(struct window_mode_entry *wme, struct client *c, 511 __unused struct session *s, __unused struct winlink *wl, key_code key, 512 struct mouse_event *m) 513 { 514 struct window_pane *wp = wme->wp; 515 struct window_buffer_modedata *data = wme->data; 516 struct mode_tree_data *mtd = data->data; 517 struct window_buffer_itemdata *item; 518 int finished; 519 520 if (paste_is_empty()) { 521 finished = 1; 522 goto out; 523 } 524 525 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 526 switch (key) { 527 case 'e': 528 item = mode_tree_get_current(mtd); 529 window_buffer_start_edit(data, item, c); 530 break; 531 case 'd': 532 item = mode_tree_get_current(mtd); 533 window_buffer_do_delete(data, item, c, key); 534 mode_tree_build(mtd); 535 break; 536 case 'D': 537 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); 538 mode_tree_build(mtd); 539 break; 540 case 'P': 541 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); 542 finished = 1; 543 break; 544 case 'p': 545 case '\r': 546 item = mode_tree_get_current(mtd); 547 window_buffer_do_paste(data, item, c, key); 548 finished = 1; 549 break; 550 } 551 552 out: 553 if (finished || paste_is_empty()) 554 window_pane_reset_mode(wp); 555 else { 556 mode_tree_draw(mtd); 557 wp->flags |= PANE_REDRAW; 558 } 559 } 560