Coverage Report

Created: 2026-08-13 06:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/window-switch.c
Line
Count
Source
1
/* $OpenBSD: window-switch.c,v 1.2 2026/07/14 17:17:18 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2026 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
25
#include "tmux.h"
26
27
static struct screen  *window_switch_init(struct window_mode_entry *,
28
           struct cmdq_item *, struct cmd_find_state *,
29
           struct args *);
30
static void    window_switch_free(struct window_mode_entry *);
31
static void    window_switch_resize(struct window_mode_entry *, u_int,
32
           u_int);
33
static void    window_switch_key(struct window_mode_entry *,
34
           struct client *, struct session *,
35
           struct winlink *, key_code, struct mouse_event *);
36
static enum prompt_result window_switch_prompt_callback(void *, const char *,
37
           enum prompt_key_result);
38
39
0
#define WINDOW_SWITCH_DEFAULT_COMMAND "switch-client -Zt '%%'"
40
41
#define WINDOW_SWITCH_DEFAULT_FORMAT    \
42
0
  "#{?window_format," \
43
0
    "#{window_name} " \
44
0
    "#[dim]#{session_name}:#{window_index}#{window_flags}#[default] " \
45
0
    "#[dim]#{pane_current_command}#[default] " \
46
0
          "#[dim]#{?#{!=:#{pane_title},#{host_short}},#{pane_title},}#[default]" \
47
0
  "," \
48
0
    "#{session_name} " \
49
0
    "#[dim]#{session_windows} windows#[default] " \
50
0
    "#{?session_attached,attached,#[dim]detached#[default]} " \
51
0
    "#[dim]#{window_name}#[default]" \
52
0
  "}"
53
54
const struct window_mode window_switch_mode = {
55
  .name = "switch-mode",
56
  .default_format = WINDOW_SWITCH_DEFAULT_FORMAT,
57
58
  .init = window_switch_init,
59
  .free = window_switch_free,
60
  .resize = window_switch_resize,
61
  .key = window_switch_key,
62
};
63
64
enum window_switch_type {
65
  WINDOW_SWITCH_TYPE_SESSION,
66
  WINDOW_SWITCH_TYPE_WINDOW
67
};
68
69
struct window_switch_itemdata {
70
  enum window_switch_type  type;
71
  int      session;
72
  int      winlink;
73
74
  uint64_t     tag;
75
  char      *text;
76
  bitstr_t    *match;
77
78
  u_int      score;
79
  u_int      order;
80
};
81
82
struct window_switch_modedata {
83
  struct window_pane     *wp;
84
  struct screen       screen;
85
  int         zoomed;
86
87
  char         *format;
88
  char         *command;
89
90
  enum window_switch_type     type;
91
  char         *filter;
92
  struct prompt      *prompt;
93
  u_int         prompt_cx;
94
95
  struct window_switch_itemdata **item_list;
96
  u_int         item_size;
97
98
  struct window_switch_itemdata **matches;
99
  u_int         matches_size;
100
101
  u_int         current;
102
  u_int         offset;
103
};
104
105
static void
106
window_switch_free_item(struct window_switch_itemdata *item)
107
0
{
108
0
  free(item->match);
109
0
  free(item->text);
110
0
  free(item);
111
0
}
112
113
static struct window_switch_itemdata *
114
window_switch_add_item(struct window_switch_modedata *data)
115
0
{
116
0
  struct window_switch_itemdata *item;
117
118
0
  data->item_list = xreallocarray(data->item_list, data->item_size + 1,
119
0
      sizeof *data->item_list);
120
0
  item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
121
0
  return (item);
122
0
}
123
124
static void
125
window_switch_add_session(struct window_switch_modedata *data,
126
    struct session *s, u_int *order)
127
0
{
128
0
  struct window_switch_itemdata *item;
129
0
  struct format_tree    *ft;
130
131
0
  ft = format_create(NULL, NULL, FORMAT_NONE, 0);
132
0
  format_defaults(ft, NULL, s, NULL, NULL);
133
134
0
  item = window_switch_add_item(data);
135
0
  item->type = WINDOW_SWITCH_TYPE_SESSION;
136
0
  item->session = s->id;
137
0
  item->winlink = -1;
138
0
  item->tag = (uint64_t)s;
139
0
  item->order = (*order)++;
140
0
  item->text = format_expand(ft, data->format);
141
142
0
  format_free(ft);
143
0
}
144
145
static void
146
window_switch_add_window(struct window_switch_modedata *data,
147
    struct winlink *wl, u_int *order)
148
0
{
149
0
  struct window_switch_itemdata *item;
150
0
  struct format_tree    *ft;
151
152
0
  ft = format_create(NULL, NULL, FORMAT_NONE, 0);
153
0
  format_defaults(ft, NULL, wl->session, wl, NULL);
154
155
0
  item = window_switch_add_item(data);
156
0
  item->type = WINDOW_SWITCH_TYPE_WINDOW;
157
0
  item->session = wl->session->id;
158
0
  item->winlink = wl->idx;
159
0
  item->tag = (uint64_t)wl;
160
0
  item->order = (*order)++;
161
0
  item->text = format_expand(ft, data->format);
162
163
0
  format_free(ft);
164
0
}
165
166
static int
167
window_switch_compare(const void *a0, const void *b0)
168
0
{
169
0
  struct window_switch_itemdata *const *a = a0;
170
0
  struct window_switch_itemdata *const *b = b0;
171
172
0
  if ((*a)->score > (*b)->score)
173
0
    return (-1);
174
0
  if ((*a)->score < (*b)->score)
175
0
    return (1);
176
0
  if ((*a)->order < (*b)->order)
177
0
    return (-1);
178
0
  if ((*a)->order > (*b)->order)
179
0
    return (1);
180
0
  return (0);
181
0
}
182
183
static void
184
window_switch_build(struct window_switch_modedata *data)
185
0
{
186
0
  struct window_switch_itemdata  *item, **m = NULL;
187
0
  const char       *f = data->filter;
188
0
  u_int         ns, nw, i, n = 0, order = 0;
189
0
  u_int         sx = screen_size_x(&data->screen);
190
0
  struct session      **sl;
191
0
  struct winlink      **wl;
192
0
  struct sort_criteria      sort_crit;
193
194
0
  sort_crit.order = SORT_NAME;
195
0
  sort_crit.reversed = 0;
196
197
0
  for (i = 0; i < data->item_size; i++)
198
0
    window_switch_free_item(data->item_list[i]);
199
0
  free(data->item_list);
200
0
  data->item_list = NULL;
201
0
  data->item_size = 0;
202
203
0
  switch (data->type) {
204
0
  case WINDOW_SWITCH_TYPE_SESSION:
205
0
    sl = sort_get_sessions(&ns, &sort_crit);
206
0
    for (i = 0; i < ns; i++)
207
0
      window_switch_add_session(data, sl[i], &order);
208
0
    break;
209
0
  case WINDOW_SWITCH_TYPE_WINDOW:
210
0
    wl = sort_get_winlinks(&nw, &sort_crit);
211
0
    for (i = 0; i < nw; i++)
212
0
      window_switch_add_window(data, wl[i], &order);
213
0
    break;
214
0
  }
215
216
0
  for (i = 0; i < data->item_size; i++) {
217
0
    item = data->item_list[i];
218
0
    if (*f == '\0') {
219
0
      m = xreallocarray(m, n + 1, sizeof *m);
220
0
      m[n++] = item;
221
0
      continue;
222
0
    }
223
224
0
    item->match = fuzzy_match(f, item->text, sx, &item->score);
225
0
    if (item->match == NULL)
226
0
      continue;
227
0
    m = xreallocarray(m, n + 1, sizeof *m);
228
0
    m[n++] = item;
229
0
  }
230
0
  qsort(m, n, sizeof *m, window_switch_compare);
231
232
0
  free(data->matches);
233
0
  data->matches = m;
234
0
  data->matches_size = n;
235
0
}
236
237
static u_int
238
window_switch_visible(struct window_switch_modedata *data)
239
0
{
240
0
  u_int sy = screen_size_y(&data->screen);
241
242
0
  if (sy <= 1)
243
0
    return (0);
244
0
  return (sy - 1);
245
0
}
246
247
static void
248
window_switch_set_current(struct window_switch_modedata *data, u_int current)
249
0
{
250
0
  u_int visible = window_switch_visible(data);
251
252
0
  if (data->matches_size == 0) {
253
0
    data->current = 0;
254
0
    data->offset = 0;
255
0
    return;
256
0
  }
257
258
0
  if (current > data->matches_size - 1)
259
0
    current = data->matches_size - 1;
260
0
  data->current = current;
261
262
0
  if (data->current < data->offset)
263
0
    data->offset = data->current;
264
0
  else if (visible != 0 && data->current >= data->offset + visible)
265
0
    data->offset = data->current - visible + 1;
266
0
}
267
268
static void
269
window_switch_draw_screen(struct window_mode_entry *wme)
270
0
{
271
0
  struct window_pane    *wp = wme->wp;
272
0
  struct window_switch_modedata *data = wme->data;
273
0
  struct options      *oo = wp->options;
274
0
  struct screen_write_ctx    ctx;
275
0
  struct screen     *s = &data->screen;
276
0
  u_int        sx = screen_size_x(s), i, j;
277
0
  u_int        sy = screen_size_y(s), visible, idx;
278
0
  struct window_switch_itemdata *item;
279
0
  struct grid_cell     mgc, sgc, gc;
280
0
  const struct grid_cell    *dgc = &grid_default_cell;
281
0
  struct prompt_draw_data    pdd;
282
0
  screen_write_start(&ctx, s);
283
0
  screen_write_clearscreen(&ctx, 8);
284
285
0
  if (sy <= 1) {
286
0
    screen_write_stop(&ctx);
287
0
    return;
288
0
  }
289
290
0
  style_apply(&mgc, oo, "switch-mode-match-style", NULL);
291
0
  style_apply(&sgc, oo, "mode-style", NULL);
292
293
0
  visible = window_switch_visible(data);
294
0
  for (i = 0; i < visible; i++) {
295
0
    idx = data->offset + i;
296
0
    if (idx >= data->matches_size)
297
0
      break;
298
0
    item = data->matches[idx];
299
300
0
    screen_write_cursormove(&ctx, 0, i, 0);
301
0
    if (idx != data->current)
302
0
      format_draw(&ctx, dgc, sx, item->text, NULL, 0);
303
0
    else {
304
0
      screen_write_clearendofline(&ctx, sgc.bg);
305
0
      format_draw(&ctx, &sgc, sx, item->text, NULL, 0);
306
0
    }
307
308
0
    if (item->match == NULL)
309
0
      continue;
310
0
    for (j = 0; j < sx; j++) {
311
0
      if (!bit_test(item->match, j))
312
0
        continue;
313
0
      grid_get_cell(s->grid, j, i, &gc);
314
0
      gc.attr = mgc.attr;
315
0
      gc.fg = mgc.fg;
316
0
      gc.bg = mgc.bg;
317
0
      screen_write_cursormove(&ctx, j, i, 0);
318
0
      screen_write_cell(&ctx, &gc);
319
0
    }
320
0
  }
321
322
0
  if (data->prompt != NULL) {
323
0
    pdd.ctx = &ctx;
324
0
    pdd.cursor_x = &data->prompt_cx;
325
0
    pdd.area_x = 0;
326
0
    pdd.area_width = sx;
327
0
    pdd.prompt_line = sy - 1;
328
0
    s->mode |= MODE_CURSOR;
329
0
    prompt_draw(data->prompt, &pdd);
330
0
    screen_write_cursormove(&ctx, data->prompt_cx, sy - 1, 0);
331
0
  }
332
0
  screen_write_stop(&ctx);
333
0
}
334
335
static struct screen *
336
window_switch_init(struct window_mode_entry *wme,
337
    __unused struct cmdq_item *item, struct cmd_find_state *fs,
338
    struct args *args)
339
0
{
340
0
  struct window_pane    *wp = wme->wp;
341
0
  struct window_switch_modedata *data;
342
0
  struct screen     *s;
343
0
  struct prompt_create_data  pd;
344
345
0
  wme->data = data = xcalloc(1, sizeof *data);
346
0
  data->wp = wp;
347
348
0
  if (args_has(args, 'w'))
349
0
    data->type = WINDOW_SWITCH_TYPE_WINDOW;
350
0
  else
351
0
    data->type = WINDOW_SWITCH_TYPE_SESSION;
352
353
0
  data->filter = xstrdup("");
354
0
  if (args == NULL || !args_has(args, 'F'))
355
0
    data->format = xstrdup(WINDOW_SWITCH_DEFAULT_FORMAT);
356
0
  else
357
0
    data->format = xstrdup(args_get(args, 'F'));
358
0
  if (args == NULL || args_count(args) == 0)
359
0
    data->command = xstrdup(WINDOW_SWITCH_DEFAULT_COMMAND);
360
0
  else
361
0
    data->command = xstrdup(args_string(args, 0));
362
363
0
  memset(&pd, 0, sizeof pd);
364
0
  prompt_set_options(&pd, fs->s);
365
0
  pd.fs = fs;
366
0
  pd.prompt = "(search) ";
367
0
  pd.input = "";
368
0
  pd.type = PROMPT_TYPE_SEARCH;
369
0
  pd.flags = PROMPT_INCREMENTAL|PROMPT_NOFORMAT|PROMPT_ISMODE|
370
0
      PROMPT_EDITARROWS;
371
0
  pd.inputcb = window_switch_prompt_callback;
372
0
  pd.data = data;
373
0
  data->prompt = prompt_create(&pd);
374
0
  prompt_update(data->prompt, "(search) ", data->filter);
375
376
0
  if (!args_has(args, 'Z'))
377
0
    data->zoomed = -1;
378
0
  else {
379
0
    data->zoomed = (wp->window->flags & WINDOW_ZOOMED);
380
0
    if (!data->zoomed && window_zoom(wp) == 0)
381
0
      server_redraw_window(wp->window);
382
0
  }
383
384
0
  s = &data->screen;
385
0
  screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
386
387
0
  window_switch_build(data);
388
0
  prompt_incremental_start(data->prompt);
389
0
  window_switch_draw_screen(wme);
390
391
0
  return (s);
392
0
}
393
394
static void
395
window_switch_free(struct window_mode_entry *wme)
396
0
{
397
0
  struct window_switch_modedata *data = wme->data;
398
0
  u_int        i;
399
400
0
  if (data->zoomed == 0)
401
0
    server_unzoom_window(wme->wp->window);
402
403
0
  for (i = 0; i < data->item_size; i++)
404
0
    window_switch_free_item(data->item_list[i]);
405
0
  free(data->item_list);
406
407
0
  free(data->matches);
408
0
  free(data->filter);
409
0
  prompt_free(data->prompt);
410
0
  free(data->format);
411
0
  free(data->command);
412
0
  screen_free(&data->screen);
413
414
0
  free(data);
415
0
}
416
417
static void
418
window_switch_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
419
0
{
420
0
  struct window_switch_modedata *data = wme->data;
421
0
  struct screen     *s = &data->screen;
422
423
0
  screen_resize(s, sx, sy, 0);
424
0
  window_switch_build(data);
425
0
  window_switch_set_current(data, data->current);
426
0
  window_switch_draw_screen(wme);
427
0
}
428
429
static int
430
window_switch_run_command(struct window_switch_modedata *data, struct client *c)
431
0
{
432
0
  struct window_switch_itemdata *item;
433
0
  struct cmd_find_state    fs;
434
0
  struct session      *s;
435
0
  struct winlink      *wl;
436
0
  char        *target = NULL;
437
0
  struct cmdq_state   *state;
438
0
  char        *command, *error;
439
0
  enum cmd_parse_status    status;
440
441
0
  if (data->matches_size == 0)
442
0
    return (0);
443
0
  item = data->matches[data->current];
444
445
0
  cmd_find_clear_state(&fs, 0);
446
0
  switch (item->type) {
447
0
  case WINDOW_SWITCH_TYPE_SESSION:
448
0
    s = session_find_by_id(item->session);
449
0
    if (s != NULL) {
450
0
      xasprintf(&target, "=%s:", s->name);
451
0
      cmd_find_from_session(&fs, s, 0);
452
0
    }
453
0
    break;
454
0
  case WINDOW_SWITCH_TYPE_WINDOW:
455
0
    s = session_find_by_id(item->session);
456
0
    if (s != NULL) {
457
0
      wl = winlink_find_by_index(&s->windows, item->winlink);
458
0
      if (s != NULL && wl != NULL) {
459
0
        xasprintf(&target, "=%s:%u.", s->name, wl->idx);
460
0
        cmd_find_from_winlink(&fs, wl, 0);
461
0
      }
462
0
    }
463
0
    break;
464
0
  }
465
0
  if (target == NULL)
466
0
    return (0);
467
468
0
  command = cmd_template_replace(data->command, target, 1);
469
0
  if (command != NULL && *command != '\0') {
470
0
    state = cmdq_new_state(&fs, NULL, 0);
471
0
    status = cmd_parse_and_append(command, NULL, c, state, &error);
472
0
    if (status == CMD_PARSE_ERROR) {
473
0
      if (c != NULL) {
474
0
        *error = toupper((u_char)*error);
475
0
        status_message_set(c, -1, 1, 0, 0, "%s", error);
476
0
      }
477
0
      free(error);
478
0
    }
479
0
    cmdq_free_state(state);
480
0
  }
481
0
  free(command);
482
0
  free(target);
483
0
  return (1);
484
0
}
485
486
static enum prompt_result
487
window_switch_prompt_callback(void *arg, const char *s,
488
    enum prompt_key_result key)
489
0
{
490
0
  struct window_switch_modedata *data = arg;
491
492
0
  if (key != PROMPT_KEY_HANDLED)
493
0
    return (PROMPT_CONTINUE);
494
495
0
  if (s == NULL)
496
0
    s = "";
497
0
  else if (*s != '\0')
498
0
    s++;
499
500
0
  free(data->filter);
501
0
  data->filter = xstrdup(s);
502
0
  window_switch_build(data);
503
0
  data->current = 0;
504
0
  data->offset = 0;
505
506
0
  return (PROMPT_CONTINUE);
507
0
}
508
509
static void
510
window_switch_key(struct window_mode_entry *wme, struct client *c,
511
    __unused struct session *s, __unused struct winlink *wl, key_code key,
512
    struct mouse_event *m)
513
0
{
514
0
  struct window_pane    *wp = wme->wp;
515
0
  struct window_switch_modedata *data = wme->data;
516
0
  u_int        visible, current = data->current;
517
0
  u_int        x, y, size = data->matches_size;
518
0
  enum prompt_key_result     result;
519
0
  int        redraw = 0;
520
521
0
  if (KEYC_IS_MOUSE(key)) {
522
0
    if (m == NULL || cmd_mouse_at(wp, m, &x, &y, 0) != 0)
523
0
      return;
524
0
    if (data->prompt != NULL && screen_size_y(&data->screen) != 0 &&
525
0
        y == screen_size_y(&data->screen) - 1 &&
526
0
        MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_1 && !MOUSE_DRAG(m->b) &&
527
0
        !MOUSE_RELEASE(m->b)) {
528
0
      result = prompt_mouse(data->prompt, x, 0,
529
0
          screen_size_x(&data->screen), &redraw);
530
0
      if (redraw || result == PROMPT_KEY_HANDLED) {
531
0
        window_switch_draw_screen(wme);
532
0
        wp->flags |= PANE_REDRAW;
533
0
      }
534
0
      return;
535
0
    }
536
0
    switch (key) {
537
0
    case KEYC_WHEELUP_PANE:
538
0
      if (size != 0 && current != 0)
539
0
        window_switch_set_current(data, current - 1);
540
0
      goto moved;
541
0
    case KEYC_WHEELDOWN_PANE:
542
0
      if (size != 0 && current != size - 1)
543
0
        window_switch_set_current(data, current + 1);
544
0
      goto moved;
545
0
    case KEYC_MOUSEDOWN1_PANE:
546
0
    case KEYC_DOUBLECLICK1_PANE:
547
0
      if (y >= window_switch_visible(data) ||
548
0
          data->offset + y >= size)
549
0
        return;
550
0
      window_switch_set_current(data, data->offset + y);
551
0
      if (key == KEYC_DOUBLECLICK1_PANE) {
552
0
        if (window_switch_run_command(data, c))
553
0
          window_pane_reset_mode(wp);
554
0
        return;
555
0
      }
556
0
      goto moved;
557
0
    }
558
0
    return;
559
0
  }
560
561
0
  switch (key) {
562
0
  case 'p'|KEYC_CTRL:
563
0
  case 'k'|KEYC_CTRL:
564
0
    key = KEYC_UP;
565
0
    break;
566
0
  case 'n'|KEYC_CTRL:
567
0
  case 'j'|KEYC_CTRL:
568
0
    key = KEYC_DOWN;
569
0
    break;
570
0
  }
571
572
0
  switch (key) {
573
0
  case '\r':
574
0
    if (window_switch_run_command(data, c))
575
0
      window_pane_reset_mode(wp);
576
0
    return;
577
0
  case '\033': /* Escape */
578
0
  case '['|KEYC_CTRL:
579
0
  case 'c'|KEYC_CTRL:
580
0
  case 'g'|KEYC_CTRL:
581
0
    window_pane_reset_mode(wp);
582
0
    return;
583
0
  }
584
585
0
  if (data->prompt != NULL) {
586
0
    result = prompt_key(data->prompt, key, &redraw);
587
0
    if (redraw) {
588
0
      window_switch_draw_screen(wme);
589
0
      wp->flags |= PANE_REDRAW;
590
0
    }
591
0
    if (result == PROMPT_KEY_HANDLED ||
592
0
        result == PROMPT_KEY_NOT_HANDLED)
593
0
      return;
594
0
    current = data->current;
595
0
    size = data->matches_size;
596
0
  }
597
598
0
  switch (key) {
599
0
  case KEYC_UP:
600
0
    if (size == 0)
601
0
      goto moved;
602
0
    if (current == 0)
603
0
      window_switch_set_current(data, size - 1);
604
0
    else
605
0
      window_switch_set_current(data, current - 1);
606
0
    goto moved;
607
0
  case KEYC_DOWN:
608
0
    if (size == 0)
609
0
      goto moved;
610
0
    if (current == size - 1)
611
0
      window_switch_set_current(data, 0);
612
0
    else
613
0
      window_switch_set_current(data, current + 1);
614
0
    goto moved;
615
0
  case KEYC_PPAGE:
616
0
    visible = window_switch_visible(data);
617
0
    if (current >= visible)
618
0
      window_switch_set_current(data, current - visible);
619
0
    else
620
0
      window_switch_set_current(data, 0);
621
0
    goto moved;
622
0
  case KEYC_NPAGE:
623
0
    visible = window_switch_visible(data);
624
0
    window_switch_set_current(data, current + visible);
625
0
    goto moved;
626
0
  case KEYC_HOME:
627
0
    window_switch_set_current(data, 0);
628
0
    goto moved;
629
0
  case KEYC_END:
630
0
    if (size > 0)
631
0
      window_switch_set_current(data, size - 1);
632
0
    goto moved;
633
0
  }
634
635
0
moved:
636
0
  window_switch_draw_screen(wme);
637
0
  wp->flags |= PANE_REDRAW;
638
0
}