1 /* $OpenBSD: window-buffer.c,v 1.34 2021/06/10 07:45: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 -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->argc == 0) 347 data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); 348 else 349 data->command = xstrdup(args->argv[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 if ((pb = paste_get_name(item->name)) != NULL) 414 paste_free(pb); 415 } 416 417 static void 418 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, 419 __unused key_code key) 420 { 421 struct window_buffer_modedata *data = modedata; 422 struct window_buffer_itemdata *item = itemdata; 423 424 if (paste_get_name(item->name) != NULL) 425 mode_tree_run_command(c, NULL, data->command, item->name); 426 } 427 428 static void 429 window_buffer_finish_edit(struct window_buffer_editdata *ed) 430 { 431 free(ed->name); 432 free(ed); 433 } 434 435 static void 436 window_buffer_edit_close_cb(char *buf, size_t len, void *arg) 437 { 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; 445 446 if (buf == NULL || len == 0) { 447 window_buffer_finish_edit(ed); 448 return; 449 } 450 451 pb = paste_get_name(ed->name); 452 if (pb == NULL || pb != ed->pb) { 453 window_buffer_finish_edit(ed); 454 return; 455 } 456 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); 464 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); 472 } 473 wp->flags |= PANE_REDRAW; 474 } 475 window_buffer_finish_edit(ed); 476 } 477 478 static void 479 window_buffer_start_edit(struct window_buffer_modedata *data, 480 struct window_buffer_itemdata *item, struct client *c) 481 { 482 struct paste_buffer *pb; 483 const char *buf; 484 size_t len; 485 struct window_buffer_editdata *ed; 486 487 if ((pb = paste_get_name(item->name)) == NULL) 488 return; 489 buf = paste_buffer_data(pb, &len); 490 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; 495 496 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) 497 window_buffer_finish_edit(ed); 498 } 499 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) 504 { 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; 510 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; 536 } 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; 542 } 543 } 544