Coverage Report

Created: 2026-10-17 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/cmd-split-window.c
Line
Count
Source
1
/* $OpenBSD: cmd-split-window.c,v 1.156 2026/10/16 08:11:03 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2009 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 <errno.h>
22
#include <fcntl.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <unistd.h>
26
27
#include "tmux.h"
28
29
/*
30
 * Create a new pane.
31
 */
32
33
0
#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
34
35
static enum cmd_retval  cmd_split_window_exec(struct cmd *, struct cmdq_item *);
36
static void   cmd_split_window_mouse_resize(struct client *,
37
          struct mouse_event *);
38
39
const struct cmd_entry cmd_new_pane_entry = {
40
  .name = "new-pane",
41
  .description = "Create a floating pane.",
42
  .alias = "newp",
43
44
  .args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
45
  .usage = "[-AbCDdEfhIkKLMOPvWZ] [-B border-lines] "
46
     "[-c start-directory] [-e environment] "
47
     "[-F format] [-l size] [-m message] [-p percentage] "
48
     "[-s style] [-S active-border-style] "
49
     "[-R inactive-border-style] [-T title] [-x width] [-y height] "
50
     "[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " "
51
     "[shell-command [argument ...]]",
52
53
  .target = { 't', CMD_FIND_PANE, 0 },
54
55
  .flags = 0,
56
  .exec = cmd_split_window_exec
57
};
58
59
const struct cmd_entry cmd_split_window_entry = {
60
  .name = "split-window",
61
  .description = "Create a new pane by splitting a window.",
62
  .alias = "splitw",
63
64
  .args = { "bB:c:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL },
65
  .usage = "[-bdEfhIkPvWZ] [-B border-lines] [-c start-directory] "
66
     "[-e environment] [-F format] [-l size] [-m message] "
67
     "[-p percentage] [-s style] [-S active-border-style] "
68
     "[-R inactive-border-style] [-T title] "
69
           CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
70
71
  .target = { 't', CMD_FIND_PANE, 0 },
72
73
  .flags = 0,
74
  .exec = cmd_split_window_exec
75
};
76
77
static enum cmd_retval
78
cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
79
0
{
80
0
  struct args   *args = cmd_get_args(self);
81
0
  struct cmd_find_state *current = cmdq_get_current(item);
82
0
  struct cmd_find_state *target = cmdq_get_target(item);
83
0
  struct spawn_context   sc = { 0 };
84
0
  struct client   *tc = cmdq_get_target_client(item);
85
0
  struct session    *s = target->s;
86
0
  struct winlink    *wl = target->wl;
87
0
  struct window   *w = wl->window;
88
0
  struct window_pane  *wp = target->wp, *new_wp = NULL;
89
0
  struct layout_cell  *lc = NULL;
90
0
  struct event_payload  *ep;
91
0
  struct cmd_find_state  fs;
92
0
  struct key_event  *event = cmdq_get_event(item);
93
0
  int      input, empty, is_floating, flags = 0;
94
0
  int      restore_zoom = 0;
95
0
  const char    *template, *style, *value;
96
0
  char      *cause = NULL, *cp, *title;
97
0
  const struct options_table_entry *oe;
98
0
  struct args_value *av;
99
0
  enum pane_lines    lines;
100
0
  u_int      count = args_count(args);
101
102
0
  if (window_active_pane_is_over_zoom(w))
103
0
    restore_zoom = 1;
104
105
0
  if (cmd_get_entry(self) == &cmd_new_pane_entry)
106
0
    is_floating = !args_has(args, 'L');
107
0
  else {
108
0
    if (!window_pane_is_visible(wp))
109
0
      restore_zoom = 0;
110
0
    if (!restore_zoom)
111
0
      window_unzoom(w, 1);
112
0
    is_floating = window_pane_is_floating(wp);
113
0
    flags |= SPAWN_SPLIT;
114
0
  }
115
116
0
  if (args_has(args, 'O')) {
117
0
    if (!is_floating) {
118
0
      cmdq_error(item, "modal pane must be floating");
119
0
      return (CMD_RETURN_ERROR);
120
0
    }
121
0
    if (w->modal != NULL) {
122
0
      cmdq_error(item, "window already has a modal pane");
123
0
      return (CMD_RETURN_ERROR);
124
0
    }
125
0
  }
126
127
0
  if (args_has(args, 'M') && is_floating) {
128
0
    if (event == NULL || !event->m.valid || tc == NULL)
129
0
      return (CMD_RETURN_NORMAL);
130
0
  }
131
132
0
  if (is_floating)
133
0
    flags |= SPAWN_FLOATING;
134
0
  if (args_has(args, 'h'))
135
0
    flags |= SPAWN_HORIZONTAL;
136
0
  if (args_has(args, 'b'))
137
0
    flags |= SPAWN_BEFORE;
138
0
  if (args_has(args, 'f'))
139
0
    flags |= SPAWN_FULLSIZE;
140
0
  if (args_has(args, 'd'))
141
0
    flags |= SPAWN_DETACHED;
142
0
  if (args_has(args, 'Z'))
143
0
    flags |= SPAWN_ZOOM;
144
0
  if (args_has(args, 'O'))
145
0
    flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
146
0
  if (is_floating && args_has(args, 'A'))
147
0
    flags |= SPAWN_FLOATOVERZOOM;
148
0
  if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
149
0
    restore_zoom = 1;
150
151
0
  input = args_has(args, 'I');
152
0
  if (input || (count == 1 && *args_string(args, 0) == '\0'))
153
0
    empty = 1;
154
0
  else
155
0
    empty = args_has(args, 'E');
156
0
  if (empty &&
157
0
      count != 0 &&
158
0
      (count != 1 || *args_string(args, 0) != '\0')) {
159
0
    cmdq_error(item, "command cannot be given for empty pane");
160
0
    return (CMD_RETURN_ERROR);
161
0
  }
162
0
  if (empty)
163
0
    flags |= SPAWN_EMPTY;
164
165
0
  if ((value = args_get(args, 'B')) == NULL)
166
0
    lines = window_get_pane_lines(w);
167
0
  else {
168
0
    oe = options_search("pane-border-lines");
169
0
    lines = options_find_choice(oe, value, &cause);
170
0
    if (cause != NULL) {
171
0
      cmdq_error(item, "pane-border-lines %s", cause);
172
0
      free(cause);
173
0
      return (CMD_RETURN_ERROR);
174
0
    }
175
0
  }
176
177
0
  if (flags & SPAWN_FLOATING) {
178
0
    lc = layout_get_floating_cell(item, args, lines, w, wp, flags,
179
0
        &cause);
180
0
  } else
181
0
    lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause);
182
0
  if (cause != NULL) {
183
0
    cmdq_error(item, "%s", cause);
184
0
    free(cause);
185
0
    if (restore_zoom)
186
0
      window_pop_zoom(w);
187
0
    return (CMD_RETURN_ERROR);
188
0
  }
189
190
0
  sc.item = item;
191
0
  sc.s = s;
192
0
  sc.wl = wl;
193
194
0
  sc.wp0 = wp;
195
0
  sc.lc = lc;
196
197
0
  args_to_vector(args, &sc.argc, &sc.argv);
198
0
  sc.environ = environ_create();
199
200
0
  av = args_first_value(args, 'e');
201
0
  while (av != NULL) {
202
0
    environ_put(sc.environ, av->string, 0);
203
0
    av = args_next_value(av);
204
0
  }
205
206
0
  sc.idx = -1;
207
0
  sc.cwd = args_get(args, 'c');
208
0
  sc.flags = flags;
209
210
0
  if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
211
0
    cmdq_error(item, "create pane failed: %s", cause);
212
0
    free(cause);
213
    /*
214
     * spawn_pane has already torn the half-built pane down (its
215
     * fork-failure path removes the pane and destroys the layout
216
     * cell), so new_wp is NULL and there is nothing for fail to do.
217
     */
218
0
    goto fail;
219
0
  }
220
0
  if (args_has(args, 'K') && args_has(args, 'O'))
221
0
    new_wp->flags |= PANE_CAPTUREALLKEYS;
222
0
  if (args_has(args, 'C') && args_has(args, 'O'))
223
0
    new_wp->flags |= PANE_CLOSEONCLICK;
224
0
  if (args_has(args, 'D') && args_has(args, 'O'))
225
0
    new_wp->flags |= PANE_CLOSEONCANCEL;
226
227
0
  style = args_get(args, 's');
228
0
  if (style != NULL) {
229
0
    if (options_set_string(new_wp->options, "window-style", 0,
230
0
        "%s", style) == NULL) {
231
0
      cmdq_error(item, "bad style: %s", style);
232
0
      goto fail;
233
0
    }
234
0
    options_set_string(new_wp->options, "window-active-style", 0,
235
0
        "%s", style);
236
0
    new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
237
0
        PANE_THEMECHANGED);
238
0
  }
239
0
  style = args_get(args, 'S');
240
0
  if (style != NULL) {
241
0
    if (options_set_string(new_wp->options,
242
0
        "pane-active-border-style", 0, "%s", style) == NULL) {
243
0
      cmdq_error(item, "bad active border style: %s", style);
244
0
      goto fail;
245
0
    }
246
0
  }
247
0
  style = args_get(args, 'R');
248
0
  if (style != NULL) {
249
0
    if (options_set_string(new_wp->options, "pane-border-style", 0,
250
0
        "%s", style) == NULL) {
251
0
      cmdq_error(item, "bad inactive border style: %s",
252
0
          style);
253
0
      goto fail;
254
0
    }
255
0
  }
256
0
  if (args_has(args, 'B'))
257
0
    options_set_number(new_wp->options, "pane-border-lines", lines);
258
0
  if (args_has(args, 'k') || args_has(args, 'm')) {
259
0
    options_set_number(new_wp->options, "remain-on-exit", 3);
260
0
    if (args_has(args, 'm')) {
261
0
      options_set_string(new_wp->options,
262
0
          "remain-on-exit-format", 0, "%s",
263
0
          args_get(args, 'm'));
264
0
    }
265
0
  }
266
0
  if (args_has(args, 'T')) {
267
0
    title = format_single_from_target(item, args_get(args, 'T'));
268
0
    screen_set_title(&new_wp->base, title, 0);
269
0
    ep = event_payload_create();
270
0
    cmd_find_from_pane(&fs, new_wp, 0);
271
0
    event_payload_set_target(ep, &fs);
272
0
    event_payload_set_pane(ep, "pane", new_wp);
273
0
    event_payload_set_window(ep, "window", new_wp->window);
274
0
    event_payload_set_string(ep, "new_title", "%s", title);
275
0
    events_fire("pane-title-changed", ep);
276
0
    free(title);
277
0
  }
278
279
0
  if (input) {
280
0
    switch (window_pane_start_input(new_wp, item, &cause)) {
281
0
    case -1:
282
0
      cmdq_error(item, "%s", cause);
283
0
      free(cause);
284
0
      goto fail;
285
0
    case 1:
286
0
      input = 0;
287
0
      break;
288
0
    }
289
0
  }
290
0
  if (~flags & SPAWN_DETACHED)
291
0
    cmd_find_from_winlink_pane(current, wl, new_wp, 0);
292
293
0
  window_pop_zoom(w);
294
0
  server_redraw_window(w);
295
0
  server_redraw_session(s);
296
297
0
  if (args_has(args, 'M') && is_floating) {
298
0
    tc->tty.mouse_last_pane = new_wp->id;
299
0
    tc->tty.mouse_drag_update = cmd_split_window_mouse_resize;
300
0
    cmd_split_window_mouse_resize(tc, &event->m);
301
0
  }
302
303
0
  if (args_has(args, 'P')) {
304
0
    if ((template = args_get(args, 'F')) == NULL)
305
0
      template = SPLIT_WINDOW_TEMPLATE;
306
0
    cp = format_single(item, template, tc, s, wl, new_wp);
307
0
    cmdq_print(item, "%s", cp);
308
0
    free(cp);
309
0
  }
310
311
0
  cmd_find_from_winlink_pane(&fs, wl, new_wp, 0);
312
0
  cmdq_insert_hook(s, item, &fs, "after-split-window");
313
314
0
  if (sc.argv != NULL)
315
0
    cmd_free_argv(sc.argc, sc.argv);
316
0
  environ_free(sc.environ);
317
0
  if (input)
318
0
    return (CMD_RETURN_WAIT);
319
320
0
  if (args_has(args, 'W')) {
321
    /*
322
     * With -W, block this command queue item until the pane's
323
     * command exits; window_pane_wait_finish will be called to
324
     * continue it.
325
     */
326
0
    new_wp->wait_item = item;
327
0
    return (CMD_RETURN_WAIT);
328
0
  }
329
0
  return (CMD_RETURN_NORMAL);
330
331
0
fail:
332
  /*
333
   * If the pane was spawned before we failed, tear it down here; this
334
   * also destroys its layout cell. spawn_pane's own failure path has
335
   * already done this, so new_wp is NULL in that case.
336
   */
337
0
  if (new_wp != NULL) {
338
0
    server_client_remove_pane(new_wp);
339
0
    if (!is_floating)
340
0
      layout_close_pane(new_wp);
341
0
    window_remove_pane(wp->window, new_wp);
342
0
  }
343
0
  window_pop_zoom(w);
344
0
  if (sc.argv != NULL)
345
0
    cmd_free_argv(sc.argc, sc.argv);
346
0
  environ_free(sc.environ);
347
348
0
  return (CMD_RETURN_ERROR);
349
350
0
}
351
352
static void
353
cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
354
0
{
355
0
  struct window_pane  *wp;
356
0
  struct window   *w;
357
0
  struct layout_cell  *lc;
358
0
  enum pane_lines    lines;
359
0
  u_int      sx, sy;
360
0
  int      x, y, xoff, yoff, border;
361
0
  int      oxoff, oyoff, osx, osy;
362
363
0
  if (c->tty.mouse_last_pane == -1)
364
0
    return;
365
0
  wp = window_pane_find_by_id(c->tty.mouse_last_pane);
366
0
  if (wp == NULL || !window_pane_is_floating(wp)) {
367
0
    c->tty.mouse_drag_update = NULL;
368
0
    return;
369
0
  }
370
0
  w = wp->window;
371
0
  lc = wp->layout_cell;
372
373
0
  x = m->x + m->ox;
374
0
  y = m->y + m->oy;
375
0
  if (m->statusat == 0 && y >= (int)m->statuslines)
376
0
    y -= m->statuslines;
377
0
  else if (m->statusat > 0 && y >= m->statusat)
378
0
    y = m->statusat - 1;
379
0
  if (m->sideat == 0 && x >= (int)m->sidewidth)
380
0
    x -= m->sidewidth;
381
0
  else if (m->sideat > 0 && x >= m->sideat)
382
0
    x = m->sideat - 1;
383
384
0
  lines = window_pane_get_pane_lines(wp);
385
0
  border = (lines != PANE_LINES_NONE);
386
387
0
  if (x >= (int)c->tty.mouse_drag_x) {
388
0
    xoff = c->tty.mouse_drag_x + border;
389
0
    sx = x - c->tty.mouse_drag_x + 1;
390
0
  } else {
391
0
    sx = c->tty.mouse_drag_x - x + 1;
392
0
    xoff = c->tty.mouse_drag_x - sx + 1;
393
0
    if (border)
394
0
      xoff++;
395
0
  }
396
0
  if (y >= (int)c->tty.mouse_drag_y) {
397
0
    yoff = c->tty.mouse_drag_y + border;
398
0
    sy = y - c->tty.mouse_drag_y + 1;
399
0
  } else {
400
0
    sy = c->tty.mouse_drag_y - y + 1;
401
0
    yoff = c->tty.mouse_drag_y - sy + 1;
402
0
    if (border)
403
0
      yoff++;
404
0
  }
405
406
0
  if (border) {
407
0
    if (sx <= 2)
408
0
      sx = PANE_MINIMUM;
409
0
    else
410
0
      sx -= 2;
411
0
    if (sy <= 2)
412
0
      sy = PANE_MINIMUM;
413
0
    else
414
0
      sy -= 2;
415
0
  }
416
0
  if (sx < PANE_MINIMUM)
417
0
    sx = PANE_MINIMUM;
418
0
  if (sy < PANE_MINIMUM)
419
0
    sy = PANE_MINIMUM;
420
421
0
  oxoff = wp->xoff;
422
0
  oyoff = wp->yoff;
423
0
  osx = wp->sx;
424
0
  osy = wp->sy;
425
426
0
  layout_set_size(lc, sx, sy, xoff, yoff);
427
0
  layout_fix_panes(w, NULL);
428
429
0
  window_redraw_floating_pane(wp, oxoff, oyoff, osx, osy);
430
0
  server_redraw_window_borders(w);
431
0
}