Coverage Report

Created: 2025-08-08 06:46

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