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 -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; 298 struct winlink *wl; 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->argc == 0) 348 data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); 349 else 350 data->command = xstrdup(args->argv[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 if ((pb = paste_get_name(item->name)) != NULL) 415 paste_free(pb); 416 } 417 418 static void 419 window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, 420 __unused key_code key) 421 { 422 struct window_buffer_modedata *data = modedata; 423 struct window_buffer_itemdata *item = itemdata; 424 425 if (paste_get_name(item->name) != NULL) 426 mode_tree_run_command(c, NULL, data->command, item->name); 427 } 428 429 static void 430 window_buffer_finish_edit(struct window_buffer_editdata *ed) 431 { 432 free(ed->name); 433 free(ed); 434 } 435 436 static void 437 window_buffer_edit_close_cb(char *buf, size_t len, void *arg) 438 { 439 struct window_buffer_editdata *ed = arg; 440 size_t oldlen; 441 const char *oldbuf; 442 struct paste_buffer *pb; 443 struct window_pane *wp; 444 struct window_buffer_modedata *data; 445 struct window_mode_entry *wme; 446 447 if (buf == NULL || len == 0) { 448 window_buffer_finish_edit(ed); 449 return; 450 } 451 452 pb = paste_get_name(ed->name); 453 if (pb == NULL || pb != ed->pb) { 454 window_buffer_finish_edit(ed); 455 return; 456 } 457 458 oldbuf = paste_buffer_data(pb, &oldlen); 459 if (oldlen != '\0' && 460 oldbuf[oldlen - 1] != '\n' && 461 buf[len - 1] == '\n') 462 len--; 463 if (len != 0) 464 paste_replace(pb, buf, len); 465 466 wp = window_pane_find_by_id(ed->wp_id); 467 if (wp != NULL) { 468 wme = TAILQ_FIRST(&wp->modes); 469 if (wme->mode == &window_buffer_mode) { 470 data = wme->data; 471 mode_tree_build(data->data); 472 mode_tree_draw(data->data); 473 } 474 wp->flags |= PANE_REDRAW; 475 } 476 window_buffer_finish_edit(ed); 477 } 478 479 static void 480 window_buffer_start_edit(struct window_buffer_modedata *data, 481 struct window_buffer_itemdata *item, struct client *c) 482 { 483 struct paste_buffer *pb; 484 const char *buf; 485 size_t len; 486 struct window_buffer_editdata *ed; 487 488 if ((pb = paste_get_name(item->name)) == NULL) 489 return; 490 buf = paste_buffer_data(pb, &len); 491 492 ed = xcalloc(1, sizeof *ed); 493 ed->wp_id = data->wp->id; 494 ed->name = xstrdup(paste_buffer_name(pb)); 495 ed->pb = pb; 496 497 if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) 498 window_buffer_finish_edit(ed); 499 } 500 501 static void 502 window_buffer_key(struct window_mode_entry *wme, struct client *c, 503 __unused struct session *s, __unused struct winlink *wl, key_code key, 504 struct mouse_event *m) 505 { 506 struct window_pane *wp = wme->wp; 507 struct window_buffer_modedata *data = wme->data; 508 struct mode_tree_data *mtd = data->data; 509 struct window_buffer_itemdata *item; 510 int finished; 511 512 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 513 switch (key) { 514 case 'e': 515 item = mode_tree_get_current(mtd); 516 window_buffer_start_edit(data, item, c); 517 break; 518 case 'd': 519 item = mode_tree_get_current(mtd); 520 window_buffer_do_delete(data, item, c, key); 521 mode_tree_build(mtd); 522 break; 523 case 'D': 524 mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); 525 mode_tree_build(mtd); 526 break; 527 case 'P': 528 mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); 529 finished = 1; 530 break; 531 case 'p': 532 case '\r': 533 item = mode_tree_get_current(mtd); 534 window_buffer_do_paste(data, item, c, key); 535 finished = 1; 536 break; 537 } 538 if (finished || paste_get_top(NULL) == NULL) 539 window_pane_reset_mode(wp); 540 else { 541 mode_tree_draw(mtd); 542 wp->flags |= PANE_REDRAW; 543 } 544 } 545