Coverage Report

Created: 2026-07-16 06:53

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