Coverage Report

Created: 2026-08-31 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/menu.c
Line
Count
Source
1
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2019 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
24
#include "tmux.h"
25
26
struct menu_data {
27
  struct window   *w;
28
  int      flags;
29
30
  char      *style;
31
  char      *border_style;
32
  char      *selected_style;
33
34
  struct grid_cell   style_gc;
35
  struct grid_cell   border_style_gc;
36
  struct grid_cell   selected_style_gc;
37
  enum box_lines     border_lines;
38
39
  struct cmd_find_state  fs;
40
  key_code     key;
41
  struct mouse_event   m;
42
  struct screen    s;
43
44
  u_int      px;
45
  u_int      py;
46
47
  struct menu   *menu;
48
  int      choice;
49
50
  menu_choice_cb     cb;
51
  void      *data;
52
};
53
54
void
55
menu_add_items(struct menu *menu, const struct menu_item *items,
56
    struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
57
0
{
58
0
  const struct menu_item  *loop;
59
60
0
  for (loop = items; loop->name != NULL; loop++)
61
0
    menu_add_item(menu, loop, qitem, c, fs);
62
0
}
63
64
void
65
menu_add_item(struct menu *menu, const struct menu_item *item,
66
    struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
67
0
{
68
0
  struct menu_item  *new_item;
69
0
  const char    *key = NULL, *cmd, *suffix = "";
70
0
  char      *s, *trimmed, *name;
71
0
  u_int      width, max_width;
72
0
  int      line;
73
0
  size_t       keylen, slen;
74
75
0
  line = (item == NULL || item->name == NULL || *item->name == '\0');
76
0
  if (line && menu->count == 0)
77
0
    return;
78
0
  if (line && menu->items[menu->count - 1].name == NULL)
79
0
    return;
80
81
0
  menu->items = xreallocarray(menu->items, menu->count + 1,
82
0
      sizeof *menu->items);
83
0
  new_item = &menu->items[menu->count++];
84
0
  memset(new_item, 0, sizeof *new_item);
85
86
0
  if (line)
87
0
    return;
88
89
0
  if (fs != NULL)
90
0
    s = format_single_from_state(qitem, item->name, c, fs);
91
0
  else
92
0
    s = format_single(qitem, item->name, c, NULL, NULL, NULL);
93
0
  if (*s == '\0') { /* no item if empty after format expanded */
94
0
    free(s);
95
0
    menu->count--;
96
0
    return;
97
0
  }
98
0
  max_width = c->tty.sx - 4;
99
100
0
  slen = strlen(s);
101
0
  if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) {
102
0
    key = key_string_lookup_key(item->key, 0);
103
0
    keylen = strlen(key) + 3; /* 3 = space and two brackets */
104
105
    /*
106
     * Add the key if it is shorter than a quarter of the available
107
     * space or there is space for the entire item text and the
108
     * key.
109
     */
110
0
    if (keylen <= max_width / 4)
111
0
      max_width -= keylen;
112
0
    else if (keylen >= max_width || slen >= max_width - keylen)
113
0
      key = NULL;
114
0
  }
115
116
0
  if (slen > max_width) {
117
0
    max_width--;
118
0
    suffix = ">";
119
0
  }
120
0
  trimmed = format_trim_right(s, max_width);
121
0
  if (key != NULL) {
122
0
    xasprintf(&name, "%s%s#[default] #[align=right](%s)",
123
0
        trimmed, suffix, key);
124
0
  } else
125
0
    xasprintf(&name, "%s%s", trimmed, suffix);
126
0
  free(trimmed);
127
128
0
  new_item->name = name;
129
0
  free(s);
130
131
0
  cmd = item->command;
132
0
  if (cmd != NULL) {
133
0
    if (fs != NULL)
134
0
      s = format_single_from_state(qitem, cmd, c, fs);
135
0
    else
136
0
      s = format_single(qitem, cmd, c, NULL, NULL, NULL);
137
0
  } else
138
0
    s = NULL;
139
0
  new_item->command = s;
140
0
  new_item->key = item->key;
141
142
0
  width = format_width(new_item->name);
143
0
  if (*new_item->name == '-')
144
0
    width--;
145
0
  if (width > menu->width)
146
0
    menu->width = width;
147
0
}
148
149
struct menu *
150
menu_create(const char *title)
151
0
{
152
0
  struct menu *menu;
153
154
0
  menu = xcalloc(1, sizeof *menu);
155
0
  menu->title = xstrdup(title);
156
0
  menu->width = format_width(title);
157
158
0
  return (menu);
159
0
}
160
161
void
162
menu_free(struct menu *menu)
163
0
{
164
0
  u_int i;
165
166
0
  if (menu == NULL)
167
0
    return;
168
169
0
  for (i = 0; i < menu->count; i++) {
170
0
    free((void *)menu->items[i].name);
171
0
    free((void *)menu->items[i].command);
172
0
  }
173
0
  free(menu->items);
174
175
0
  free((void *)menu->title);
176
0
  free(menu);
177
0
}
178
179
static void
180
menu_reapply_styles(struct menu_data *md)
181
0
{
182
0
  struct options    *o = md->w->options;
183
0
  struct format_tree  *ft;
184
0
  struct style     sytmp;
185
186
0
  ft = format_create_defaults(NULL, NULL, md->fs.s, md->fs.wl, md->fs.wp);
187
188
  /* Reapply menu style from options. */
189
0
  memcpy(&md->style_gc, &grid_default_cell, sizeof md->style_gc);
190
0
  style_apply(&md->style_gc, o, "menu-style", ft);
191
0
  if (md->style != NULL) {
192
0
    style_set(&sytmp, &grid_default_cell);
193
0
    if (style_parse(&sytmp, &md->style_gc, md->style) == 0) {
194
0
      md->style_gc.fg = sytmp.gc.fg;
195
0
      md->style_gc.bg = sytmp.gc.bg;
196
0
    }
197
0
  }
198
199
  /* Reapply selected style from options. */
200
0
  memcpy(&md->selected_style_gc, &grid_default_cell,
201
0
      sizeof md->selected_style_gc);
202
0
  style_apply(&md->selected_style_gc, o, "menu-selected-style", ft);
203
0
  if (md->selected_style != NULL) {
204
0
    style_set(&sytmp, &grid_default_cell);
205
0
    if (style_parse(&sytmp, &md->selected_style_gc,
206
0
        md->selected_style) == 0) {
207
0
      md->selected_style_gc.fg = sytmp.gc.fg;
208
0
      md->selected_style_gc.bg = sytmp.gc.bg;
209
0
    }
210
0
  }
211
212
  /* Reapply border style from options. */
213
0
  memcpy(&md->border_style_gc, &grid_default_cell,
214
0
      sizeof md->border_style_gc);
215
0
  style_apply(&md->border_style_gc, o, "menu-border-style", ft);
216
0
  if (md->border_style != NULL) {
217
0
    style_set(&sytmp, &grid_default_cell);
218
0
    if (style_parse(&sytmp, &md->border_style_gc,
219
0
        md->border_style) == 0) {
220
0
      md->border_style_gc.fg = sytmp.gc.fg;
221
0
      md->border_style_gc.bg = sytmp.gc.bg;
222
0
    }
223
0
  }
224
225
0
  format_free(ft);
226
0
}
227
228
void
229
menu_update(struct menu_data *md)
230
0
{
231
0
  struct screen   *s = &md->s;
232
0
  struct menu   *menu = md->menu;
233
0
  struct screen_write_ctx  ctx;
234
235
0
  menu_reapply_styles(md);
236
237
0
  screen_write_start(&ctx, s);
238
0
  screen_write_clearscreen(&ctx, 8);
239
240
0
  if (md->border_lines != BOX_LINES_NONE) {
241
0
    screen_write_box(&ctx, menu->width + 4, menu->count + 2,
242
0
        md->border_lines, &md->border_style_gc, menu->title);
243
0
  }
244
245
0
  screen_write_menu(&ctx, menu, md->choice, md->border_lines,
246
0
      &md->style_gc, &md->border_style_gc, &md->selected_style_gc);
247
0
  screen_write_stop(&ctx);
248
0
}
249
250
static void
251
menu_free_data(struct menu_data *md)
252
0
{
253
0
  if (md != NULL) {
254
0
    if (md->cb != NULL)
255
0
      md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
256
257
0
    screen_free(&md->s);
258
0
    menu_free(md->menu);
259
0
    free(md->style);
260
0
    free(md->selected_style);
261
0
    free(md->border_style);
262
0
    free(md);
263
0
  }
264
0
}
265
266
void
267
menu_close(struct window *w)
268
0
{
269
0
  if (w->menu != NULL) {
270
0
    menu_free_data(w->menu);
271
0
    w->menu = NULL;
272
273
0
    redraw_invalidate_scene(w);
274
0
    window_update_focus(w);
275
0
    server_redraw_window(w);
276
0
  }
277
0
}
278
279
void
280
menu_destroy(struct window *w)
281
0
{
282
0
  menu_free_data(w->menu);
283
0
  w->menu = NULL;
284
0
}
285
286
void
287
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
288
0
{
289
0
  *cx = md->px + 2;
290
0
  if (md->choice == -1)
291
0
    *cy = md->py;
292
0
  else
293
0
    *cy = md->py + 1 + md->choice;
294
0
}
295
296
struct screen *
297
menu_screen(struct menu_data *md)
298
0
{
299
0
  return (&md->s);
300
0
}
301
302
u_int
303
menu_width(struct menu_data *md)
304
0
{
305
0
  return (md->menu->width + 4);
306
0
}
307
308
u_int
309
menu_height(struct menu_data *md)
310
0
{
311
0
  return (md->menu->count + 2);
312
0
}
313
314
u_int
315
menu_x(struct menu_data *md)
316
0
{
317
0
  return (md->px);
318
0
}
319
320
u_int
321
menu_y(struct menu_data *md)
322
0
{
323
0
  return (md->py);
324
0
}
325
326
int
327
menu_key(struct client *c, struct menu_data *md, struct key_event *event)
328
0
{
329
0
  struct menu     *menu = md->menu;
330
0
  struct mouse_event    *m = &event->m;
331
0
  u_int        i;
332
0
  int        n = menu->count, old = md->choice;
333
0
  int        move;
334
0
  const char      *name = NULL;
335
0
  const struct menu_item    *item;
336
0
  struct key_event     saved_event;
337
0
  struct cmdq_state   *state;
338
0
  enum cmd_parse_status    status;
339
0
  char        *error;
340
0
  key_code       key;
341
342
0
  if (KEYC_IS_MOUSE(event->key)) {
343
    /*
344
     * A mouse move with no button held reports as a release, so
345
     * treat it as highlight-only: it must never select or close the
346
     * menu, otherwise a menu opened without a button already down
347
     * (such as a submenu opened from another menu) would vanish as
348
     * soon as the mouse moved over it.
349
     */
350
0
    move = MOUSE_DRAG(m->b) && MOUSE_RELEASE(m->b);
351
0
    if (md->flags & MENU_NOMOUSE) {
352
0
      if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1)
353
0
        return (1);
354
0
      return (0);
355
0
    }
356
0
    if (m->x < md->px ||
357
0
        m->x > md->px + 4 + menu->width ||
358
0
        m->y < md->py + 1 ||
359
0
        m->y > md->py + 1 + n - 1) {
360
0
      if (~md->flags & MENU_STAYOPEN) {
361
0
        if (!move && MOUSE_RELEASE(m->b))
362
0
          return (1);
363
0
      } else {
364
0
        if (!MOUSE_RELEASE(m->b) &&
365
0
            !MOUSE_WHEEL(m->b) &&
366
0
            !MOUSE_DRAG(m->b))
367
0
          return (1);
368
0
      }
369
0
      if (md->choice != -1) {
370
0
        md->choice = -1;
371
0
        server_redraw_window_menu(md->w);
372
0
      }
373
0
      return (0);
374
0
    }
375
0
    if (~md->flags & MENU_STAYOPEN) {
376
0
      if (!move && MOUSE_RELEASE(m->b))
377
0
        goto chosen;
378
0
    } else {
379
0
      if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
380
0
        goto chosen;
381
0
    }
382
0
    md->choice = m->y - (md->py + 1);
383
0
    if (md->choice != old)
384
0
      server_redraw_window_menu(md->w);
385
0
    return (0);
386
0
  }
387
0
  for (i = 0; i < (u_int)n; i++) {
388
0
    name = menu->items[i].name;
389
0
    if (name == NULL || *name == '-')
390
0
      continue;
391
0
    key = (event->key & ~KEYC_MASK_FLAGS);
392
0
    if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) {
393
0
      md->choice = i;
394
0
      goto chosen;
395
0
    }
396
0
  }
397
0
  switch (event->key & ~KEYC_MASK_FLAGS) {
398
0
  case KEYC_BTAB:
399
0
  case KEYC_UP:
400
0
  case 'k':
401
0
    if (old == -1)
402
0
      old = 0;
403
0
    do {
404
0
      if (md->choice == -1 || md->choice == 0)
405
0
        md->choice = n - 1;
406
0
      else
407
0
        md->choice--;
408
0
      name = menu->items[md->choice].name;
409
0
    } while ((name == NULL || *name == '-') && md->choice != old);
410
0
    server_redraw_window_menu(md->w);
411
0
    return (0);
412
0
  case KEYC_BSPACE:
413
0
    if (~md->flags & MENU_TAB)
414
0
      break;
415
0
    return (1);
416
0
  case '\011': /* Tab */
417
0
    if (~md->flags & MENU_TAB)
418
0
      break;
419
0
    if (md->choice == n - 1)
420
0
      return (1);
421
    /* FALLTHROUGH */
422
0
  case KEYC_DOWN:
423
0
  case 'j':
424
0
    if (old == -1)
425
0
      old = 0;
426
0
    do {
427
0
      if (md->choice == -1 || md->choice == n - 1)
428
0
        md->choice = 0;
429
0
      else
430
0
        md->choice++;
431
0
      name = menu->items[md->choice].name;
432
0
    } while ((name == NULL || *name == '-') && md->choice != old);
433
0
    server_redraw_window_menu(md->w);
434
0
    return (0);
435
0
  case KEYC_PPAGE:
436
0
  case 'b'|KEYC_CTRL:
437
0
    if (md->choice < 6)
438
0
      md->choice = 0;
439
0
    else {
440
0
      i = 5;
441
0
      while (i > 0) {
442
0
        md->choice--;
443
0
        name = menu->items[md->choice].name;
444
0
        if (md->choice != 0 &&
445
0
            (name != NULL && *name != '-'))
446
0
          i--;
447
0
        else if (md->choice == 0)
448
0
          break;
449
0
      }
450
0
    }
451
0
    server_redraw_window_menu(md->w);
452
0
    break;
453
0
  case KEYC_NPAGE:
454
0
    if (md->choice > n - 6) {
455
0
      md->choice = n - 1;
456
0
      name = menu->items[md->choice].name;
457
0
    } else {
458
0
      i = 5;
459
0
      while (i > 0) {
460
0
        md->choice++;
461
0
        name = menu->items[md->choice].name;
462
0
        if (md->choice != n - 1 &&
463
0
            (name != NULL && *name != '-'))
464
0
          i--;
465
0
        else if (md->choice == n - 1)
466
0
          break;
467
0
      }
468
0
    }
469
0
    while ((name == NULL || *name == '-') && md->choice != 0) {
470
0
      md->choice--;
471
0
      name = menu->items[md->choice].name;
472
0
    }
473
0
    server_redraw_window_menu(md->w);
474
0
    break;
475
0
  case 'g':
476
0
  case KEYC_HOME:
477
0
    md->choice = 0;
478
0
    name = menu->items[md->choice].name;
479
0
    while ((name == NULL || *name == '-') && md->choice != n - 1) {
480
0
      md->choice++;
481
0
      name = menu->items[md->choice].name;
482
0
    }
483
0
    server_redraw_window_menu(md->w);
484
0
    break;
485
0
  case 'G':
486
0
  case KEYC_END:
487
0
    md->choice = n - 1;
488
0
    name = menu->items[md->choice].name;
489
0
    while ((name == NULL || *name == '-') && md->choice != 0) {
490
0
      md->choice--;
491
0
      name = menu->items[md->choice].name;
492
0
    }
493
0
    server_redraw_window_menu(md->w);
494
0
    break;
495
0
  case 'f'|KEYC_CTRL:
496
0
    break;
497
0
  case '\r':
498
0
    goto chosen;
499
0
  case '\033': /* Escape */
500
0
  case '['|KEYC_CTRL:
501
0
  case 'c'|KEYC_CTRL:
502
0
  case 'g'|KEYC_CTRL:
503
0
  case 'q':
504
0
    return (1);
505
0
  }
506
0
  return (0);
507
508
0
chosen:
509
0
  if (md->choice == -1)
510
0
    return (1);
511
0
  item = &menu->items[md->choice];
512
0
  if (item->name == NULL || *item->name == '-') {
513
0
    if (md->flags & MENU_STAYOPEN)
514
0
      return (0);
515
0
    return (1);
516
0
  }
517
0
  if (md->cb != NULL) {
518
0
    md->cb(md->menu, md->choice, item->key, md->data);
519
0
    md->cb = NULL;
520
0
    return (1);
521
0
  }
522
523
0
  if (md->key != KEYC_NONE) {
524
0
    memset(&saved_event, 0, sizeof saved_event);
525
0
    saved_event.key = md->key;
526
0
    memcpy(&saved_event.m, &md->m, sizeof saved_event.m);
527
0
    event = &saved_event;
528
0
  } else
529
0
    event = NULL;
530
0
  state = cmdq_new_state(&md->fs, event, 0);
531
532
0
  status = cmd_parse_and_append(item->command, NULL, c, state, &error);
533
0
  if (status == CMD_PARSE_ERROR) {
534
0
    cmdq_append(c, cmdq_get_error(error));
535
0
    free(error);
536
0
  }
537
0
  cmdq_free_state(state);
538
539
0
  return (1);
540
0
}
541
542
void
543
menu_resize(struct menu_data *md, struct window *w)
544
0
{
545
0
  u_int nx, ny, sx, sy;
546
547
0
  if (md == NULL)
548
0
    return;
549
550
0
  nx = md->px;
551
0
  ny = md->py;
552
553
0
  sx = md->menu->width + 4;
554
0
  sy = md->menu->count + 2;
555
556
0
  if (nx + sx > w->sx) {
557
0
    if (w->sx <= sx)
558
0
      nx = 0;
559
0
    else
560
0
      nx = w->sx - sx;
561
0
  }
562
563
0
  if (ny + sy > w->sy) {
564
0
    if (w->sy <= sy)
565
0
      ny = 0;
566
0
    else
567
0
      ny = w->sy - sy;
568
0
  }
569
0
  md->px = nx;
570
0
  md->py = ny;
571
0
}
572
573
int
574
menu_display(struct menu *menu, int flags, int starting_choice,
575
    struct cmdq_item *item, u_int px, u_int py, struct client *c,
576
    enum box_lines lines, const char *style, const char *selected_style,
577
    const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb,
578
    void *data)
579
0
{
580
0
  struct menu_data  *md;
581
0
  struct key_event  *event;
582
0
  int      choice;
583
0
  const char    *name;
584
0
  u_int      sx, sy;
585
0
  struct window   *w;
586
0
  struct options    *o;
587
588
0
  if (fs == NULL)
589
0
    w = c->session->curw->window;
590
0
  else
591
0
    w = fs->w;
592
0
  o = w->options;
593
594
0
  sx = menu->width + 4;
595
0
  sy = menu->count + 2;
596
0
  if (sx >= w->sx)
597
0
    px = 0;
598
0
  else if (px + sx > w->sx)
599
0
    px = w->sx - sx;
600
0
  if (sy >= w->sy)
601
0
    py = 0;
602
0
  else if (py + sy > w->sy)
603
0
    py = w->sy - sy;
604
0
  w->menu_last_px = px;
605
0
  w->menu_last_py = py;
606
607
0
  if (lines == BOX_LINES_DEFAULT)
608
0
    lines = options_get_number(o, "menu-border-lines");
609
610
0
  md = xcalloc(1, sizeof *md);
611
0
  md->w = w;
612
0
  md->flags = flags;
613
0
  md->border_lines = lines;
614
0
  md->key = KEYC_NONE;
615
616
0
  if (item != NULL) {
617
0
    event = cmdq_get_event(item);
618
0
    md->key = event->key;
619
0
    memcpy(&md->m, &event->m, sizeof md->m);
620
0
  }
621
622
0
  if (style != NULL)
623
0
    md->style = xstrdup(style);
624
0
  if (selected_style != NULL)
625
0
    md->selected_style = xstrdup(selected_style);
626
0
  if (border_style != NULL)
627
0
    md->border_style = xstrdup(border_style);
628
629
0
  if (fs != NULL)
630
0
    cmd_find_copy_state(&md->fs, fs);
631
0
  else if (cmd_find_from_window(&md->fs, w, 0) != 0)
632
0
    cmd_find_clear_state(&md->fs, 0);
633
0
  screen_init(&md->s, sx, sy, 0);
634
0
  if (~md->flags & MENU_NOMOUSE)
635
0
    md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON);
636
0
  md->s.mode &= ~MODE_CURSOR;
637
638
0
  md->px = px;
639
0
  md->py = py;
640
641
0
  md->menu = menu;
642
0
  md->choice = -1;
643
644
0
  md->cb = cb;
645
0
  md->data = data;
646
647
0
  if (md->flags & MENU_NOMOUSE) {
648
0
    if (starting_choice >= (int)menu->count) {
649
0
      starting_choice = menu->count - 1;
650
0
      choice = starting_choice + 1;
651
0
      for (;;) {
652
0
        name = menu->items[choice - 1].name;
653
0
        if (name != NULL && *name != '-') {
654
0
          md->choice = choice - 1;
655
0
          break;
656
0
        }
657
0
        if (--choice == 0)
658
0
          choice = menu->count;
659
0
        if (choice == starting_choice + 1)
660
0
          break;
661
0
      }
662
0
    } else if (starting_choice >= 0) {
663
0
      choice = starting_choice;
664
0
      for (;;) {
665
0
        name = menu->items[choice].name;
666
0
        if (name != NULL && *name != '-') {
667
0
          md->choice = choice;
668
0
          break;
669
0
        }
670
0
        if (++choice == (int)menu->count)
671
0
          choice = 0;
672
0
        if (choice == starting_choice)
673
0
          break;
674
0
      }
675
0
    }
676
0
  }
677
678
0
  menu_close(md->w);
679
0
  md->w->menu = md;
680
681
0
  redraw_invalidate_scene(md->w);
682
0
  window_update_focus(md->w);
683
0
  server_redraw_window(md->w);
684
0
  return (0);
685
0
}