Coverage Report

Created: 2026-04-12 06:58

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