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