diff --git a/Makefile.am b/Makefile.am index 2887925bf..49e88b821 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,7 +101,6 @@ dist_tmux_SOURCES = \ cmd-detach-client.c \ cmd-display-menu.c \ cmd-display-message.c \ - cmd-display-panes.c \ cmd-find-window.c \ cmd-find.c \ cmd-if-shell.c \ @@ -224,6 +223,7 @@ dist_tmux_SOURCES = \ window-clock.c \ window-copy.c \ window-customize.c \ + window-panes.c \ window-switch.c \ window-tree.c \ window-visible.c \ diff --git a/README.ja b/README.ja index aeb6704ae..3d3d0b43c 100644 --- a/README.ja +++ b/README.ja @@ -56,7 +56,7 @@ gitコミットについての連絡先 tmux-users@googlegroups.com -本ファイル、CHANGES、 FAQ、SYNCINGそしてTODOはISC licenseで保護されています。 +本ファイル、CHANGES、 FAQ、SYNCING.mdそしてTODOはISC licenseで保護されています。 その他のファイルのライセンスや著作権については、ファイルの上部に明記されています。 -- Nicholas Marriott diff --git a/SYNCING b/SYNCING deleted file mode 100644 index 51b780100..000000000 --- a/SYNCING +++ /dev/null @@ -1,173 +0,0 @@ -Preamble -======== - -Tmux portable relies on repositories "tmux" and "tmux-obsd". -Here's a description of them: - -* "tmux" is the portable version, the one which contains code for other - operating systems, and autotools, etc., which isn't found or needed in the - OpenBSD base system. - -* "tmux-obsd" is the version of tmux in OpenBSD base system which provides - the basis of the portable tmux version. - -Note: The "tmux-obsd" repository is actually handled by "git cvsimport" -running at 15 minute intervals, so a commit made to OpenBSD's tmux CVS -repository will take at least that long to appear in this git repository. -(It might take longer, depending on the CVS mirror used to import the -OpenBSD code). - -If you've never used git before, git tracks meta-data about the committer -and the author, as part of a commit, hence: - -% git config [--global] user.name "Your name" -% git config [--global] user.email "you@yourdomain.com" - -Note that, if you already have this in the global ~/.gitconfig option, then -this will be used. Setting this per-repository would involve not using the -"--global" flag above. If you wish to use the same credentials always, -pass the "--global" option, as shown. - -This is a one-off operation once the repository has been cloned, assuming -this information has ever been set before. - -Cloning repositories -==================== - -This involves having both tmux and tmux-obsd cloned, as in: - -% cd /some/where/useful -% git clone https://github.com/tmux/tmux.git -% git clone https://github.com/ThomasAdam/tmux-obsd.git - -Note that you do not need additional checkouts to manage the sync -- an -existing clone of either repositories will suffice. So if you already have -these checkouts existing, skip that. - -Adding in git-remotes -===================== - -Because the portable "tmux" git repository and the "tmux-obsd" -repository do not inherently share any history between each other, the -history has been faked between them. This "faking of history" is something -which has to be told to git for the purposes of comparing the "tmux" and -"tmux-obsd" repositories for syncing. To do this, we must reference the -clone of the "tmux-obsd" repository from the "tmux" repository, as -shown by the following command: - -% cd /path/to/tmux -% git remote add obsd-tmux file:///path/to/tmux-obsd - -So that now, the remote "obsd-tmux" can be used to reference branches and -commits from the "tmux-obsd" repository, but from the context of the -portable "tmux" repository, which makes sense because it's the "tmux" -repository which will have the updates applied to them. - -Fetching updates -================ - -To ensure the latest commits from "tmux-obsd" can be found from within -"tmux", we have to ensure the "master" branch from "tmux-obsd" is -up-to-date first, and then reference that update in "tmux", as in: - -% cd /path/to/tmux-obsd -% git checkout master -% git pull - -Then back in "tmux": - -% cd /path/to/tmux -% git fetch obsd-tmux - -Creating the necessary branches -=============================== - -Now that "tmux" can see commits and branches from "tmux-obsd" by way -of the remote name "obsd-tmux", we can now create the master branch from -"tmux-obsd" in the "tmux" repository: - -% git checkout -b obsd-master obsd-tmux/master - -Adding in the fake history points -================================= - -To tie both the "master" branch from "tmux" and the "obsd-master" -branch from "tmux-obsd" together, the fake history points added to the -"tmux" repository need to be added. To do this, we must add an -additional refspec line, as in: - -% cd /path/to/tmux -% git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*' -% git fetch origin - -Performing the Sync -=================== - -Make sure the "master" branch is checked out: - -% git checkout master - -The following will show commits on OpenBSD not yet synched with "tmux": - -% git log master..obsd-master - -From there, merge the result in, fixing up any conflicts which might arise. - -% git merge obsd-master - -Then ensure things look correct by BUILDING the result of that sync: - -% make clean && ./autogen.sh && ./configure && make - -Compare the git merge result with what's on origin/master -- that is, check -which commits you're about to push: - -% git log origin/master..master - -And if happy: - -% git push origin master - -Keeping an eye on libutil in OpenBSD -==================================== - -A lot of the compat/ code in tmux comes from libutil, especially imsg. -Sometimes the API can change, etc., which might cause interesting problems -trying to run the portable version of tmux. It's worth checking -periodically for any changes to libutil in OpenBSD and syncing those files -to compat/ as and when appropriate. - -Release tmux for next version -============================= - -1. Update and commit README and CHANGES. The former should be checked for - anything outdated and updated with a list of things that might break - upgrades and the latter should mention all the major changes since - the last version. - -2. Make sure configure.ac has the new version number. - -3. Tag with: - - % git tag -a 2.X - - Where "2.X" is the next version. - - Push the tag out with: - - % git push --tags - -4. Build the tarball with 'make dist'. - -5. Check the tarball. If it's good, go here to select the tag just pushed: - - https://github.com/tmux/tmux/tags - - Click the "Add release notes", upload the tarball and add a link in the - description field to the CHANGES file. - -6. Clone the tmux.github.io repository, and change the RELEASE version in the - Makefile. Commit it, and run 'make' to replace %%RELEASE%%. Push the - result out. - -7. Change version back to master in configure.ac. diff --git a/SYNCING.md b/SYNCING.md new file mode 100644 index 000000000..9b710a463 --- /dev/null +++ b/SYNCING.md @@ -0,0 +1,234 @@ +# Preamble + +tmux portable is maintained from two repositories: + +* `tmux` is the portable repository. It contains the portability layer, + autotools build files, regression tests, documentation, and code needed for + platforms outside OpenBSD. +* `tmux-openbsd-cutover` is the OpenBSD tmux import repository. Its `master` + branch is the OpenBSD tmux history after the OpenBSD Git cutover. Portable + merges this branch when importing OpenBSD changes. + +To create the commits in the tmux-openbsd-cutover repo, the source repo is the +Github mirror of OpenBSD's src repo: + +```text +https://github.com/openbsd/src.git +``` + +The update automation filters the OpenBSD source tree to `usr.bin/tmux/`, +publishes that filtered history as `openbsd-git` in the cutover repository, +cherry-picks new filtered commits onto cutover `master`, then merges cutover +`master` into portable `master`. + +If you've never used git before, configure your identity before committing: + +```sh +git config [--global] user.name "Your name" +git config [--global] user.email "you@yourdomain.com" +``` + +# Repository layout + +The usual local layout is: + +```sh +cd /some/where/useful +git clone https://github.com/tmux/tmux.git tmux-portable +git clone https://github.com/ThomasAdam/tmux-obsd.git tmux-openbsd-cutover +``` + +The exact directory names do not matter, but the examples below use: + +```text +/path/to/tmux-portable +/path/to/tmux-openbsd-cutover +``` + +The cutover repository has three important branches: + +* `master` is the branch portable consumes. It should contain tmux source + only, not automation files. +* `openbsd-git` is the raw filtered OpenBSD tmux branch generated from + OpenBSD src `master`. +* `automation` is an orphan branch containing the GitHub Actions workflow. It + is separate so that workflow files are not merged into portable. + +# Adding the OpenBSD remote to portable + +In the portable repository, add the cutover repository as a remote: + +```sh +cd /path/to/tmux-portable +git remote add tmux-openbsd /path/to/tmux-openbsd-cutover +git config remote.tmux-openbsd.tagOpt --no-tags +``` + +If the remote already exists, update it instead: + +```sh +git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover +git config remote.tmux-openbsd.tagOpt --no-tags +``` + +Fetch the cutover master branch explicitly: + +```sh +git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master +``` + +# Automated syncing + +The normal sync is performed by the GitHub Actions workflow in the +`automation` branch of the cutover repository. That workflow: + +1. Fetches or clones OpenBSD src using a blobless clone. +2. Filters `usr.bin/tmux/` into a local `tmux-openbsd` branch. +3. Updates `tmux-openbsd-cutover/openbsd-git`. +4. Cherry-picks new `openbsd-git` commits onto cutover `master`. +5. Merges cutover `master` into portable `master`. +6. Pushes the changed repositories. + +The workflow deliberately runs from the orphan `automation` branch but checks +out cutover `master` as the branch to update. This keeps `.github/` out of +cutover `master`, so portable does not import workflow files when it merges +OpenBSD changes. + +# Manual portable merge + +If the workflow fails while merging into portable, do the merge locally and +push the result. + +Start from an up-to-date portable master: + +```sh +cd /path/to/tmux-portable +git fetch origin +git checkout master +git pull --ff-only origin master +``` + +Fetch the cutover branch: + +```sh +git remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true +git config remote.tmux-openbsd.tagOpt --no-tags +git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master +``` + +Merge it: + +```sh +git merge --no-ff --log refs/remotes/tmux-openbsd/master +``` + +Resolve conflicts by deciding whether portable or OpenBSD owns the file. + +Useful commands: + +```sh +git checkout --ours path/to/file +git add path/to/file +``` + +This keeps the portable version of a conflicted file. + +```sh +git checkout --theirs path/to/file +git add path/to/file +``` + +This takes the OpenBSD/cutover version of a conflicted file. + +Before committing, inspect the result: + +```sh +git status +git diff --check +git diff --cached --stat +``` + +Then finish and publish: + +```sh +git commit +git push origin master +``` + +If the merge attempt is wrong, abort it: + +```sh +git merge --abort +``` + +After pushing a manually resolved merge, rerun the workflow. It should either +no-op or continue from the now-merged portable state. + +# Manual cutover update + +Normally this is handled by the workflow. If it must be done manually, update +`openbsd-git` in the cutover repository from a filtered OpenBSD src checkout, +then cherry-pick the new filtered commits onto cutover `master`. + +The important range is: + +```text +origin/openbsd-git..openbsd-git +``` + +where `origin/openbsd-git` is the previously published filtered OpenBSD tmux +branch and `openbsd-git` is the newly generated filtered branch. + +In the cutover repository: + +```sh +cd /path/to/tmux-openbsd-cutover +git checkout master +git cherry-pick origin/openbsd-git..openbsd-git +git push origin master openbsd-git +``` + +Do not put workflow files on cutover `master`; keep them on the orphan +`automation` branch. + +# Keeping an eye on libutil in OpenBSD + +A lot of the `compat/` code in tmux comes from OpenBSD libraries, especially +imsg. Sometimes APIs change in OpenBSD in ways that require corresponding +portable changes. It is worth checking periodically for relevant OpenBSD +libutil changes and syncing those files into `compat/` as appropriate. + +# Release tmux for next version + +1. Update and commit README and CHANGES. The former should be checked for + anything outdated and updated with a list of things that might break + upgrades and the latter should mention all the major changes since the last + version. +2. Make sure `configure.ac` has the new version number. +3. Tag with: + + ```sh + % git tag -a 2.X + ``` + + Where `2.X` is the next version. + + Push the tag out with: + + ```sh + % git push --tags + ``` + +4. Build the tarball with `make dist`. +5. Check the tarball. If it is good, go here to select the tag just pushed: + + ```text + https://github.com/tmux/tmux/tags + ``` + + Click "Add release notes", upload the tarball and add a link in the + description field to the CHANGES file. +6. Clone the tmux.github.io repository, and change the RELEASE version in the + Makefile. Commit it, and run `make` to replace `%%RELEASE%%`. Push the + result out. +7. Change version back to master in `configure.ac`. diff --git a/cfg.c b/cfg.c index ad9cdb0f0..a15ebf900 100644 --- a/cfg.c +++ b/cfg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cfg.c,v 1.89 2026/06/25 11:39:11 nicm Exp $ */ +/* $OpenBSD: cfg.c,v 1.90 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -266,7 +266,8 @@ cfg_show_causes(struct session *s) wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) - window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL, + NULL); for (i = 0; i < cfg_ncauses; i++) { window_copy_add(wp, 0, "%s", cfg_causes[i]); free(cfg_causes[i]); diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 8d85c57b2..dcc5a6af4 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-break-pane.c,v 1.72 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-break-pane.c,v 1.73 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -98,9 +98,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) struct session *src_s = source->s; struct session *dst_s = target->s; struct window_pane *wp = source->wp; - struct window *w = wl->window; + struct window *w = wl->window, *old_w = w; char *cause, *cp; - int idx = target->idx, before; + int idx = target->idx, before, old_idx = wl->idx; const char *template, *name = args_get(args, 'n'); if (args_has(args, 'W')) @@ -137,6 +137,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) wl = winlink_find_by_window(&dst_s->windows, w); if (wl == NULL) return (CMD_RETURN_ERROR); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); goto out; } if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { @@ -175,6 +176,8 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) idx = -1 - options_get_number(dst_s->options, "base-index"); wl = session_attach(dst_s, w, idx, &cause); /* can't fail */ window_remove_ref(w, __func__); + events_fire_window("window-created", w); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); if (!args_has(args, 'd')) { session_select(dst_s, wl->idx); cmd_find_from_session(current, dst_s, 0); diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index f9ce7d3b7..2dc304ff2 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-choose-tree.c,v 1.57 2026/06/26 14:40:30 nicm Exp $ */ +/* $OpenBSD: cmd-choose-tree.c,v 1.58 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2012 Thomas Adam @@ -97,6 +97,21 @@ const struct cmd_entry cmd_switch_mode_entry = { .exec = cmd_choose_tree_exec }; +const struct cmd_entry cmd_display_panes_entry = { + .name = "display-panes", + .alias = "displayp", + + .args = { "d:kNs:t:Z", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-kNZ] [-d duration] [-s source-window] " + CMD_TARGET_PANE_USAGE " [template]", + + .source = { 's', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_choose_tree_exec +}; + static enum args_parse_type cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx, __unused char **cause) @@ -131,9 +146,11 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) mode = &window_customize_mode; else if (cmd_get_entry(self) == &cmd_switch_mode_entry) mode = &window_switch_mode; + else if (cmd_get_entry(self) == &cmd_display_panes_entry) + mode = &window_panes_mode; else mode = &window_tree_mode; - window_pane_set_mode(wp, NULL, mode, target, args); + window_pane_set_mode(wp, NULL, mode, item, target, args); return (CMD_RETURN_NORMAL); } diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index 051cb58a2..e6888ca7e 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-copy-mode.c,v 1.53 2026/07/08 08:07:42 nicm Exp $ */ +/* $OpenBSD: cmd-copy-mode.c,v 1.54 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -79,7 +79,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) } if (cmd_get_entry(self) == &cmd_clock_mode_entry) { - window_pane_set_mode(wp, NULL, &window_clock_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_clock_mode, item, NULL, + NULL); return (CMD_RETURN_NORMAL); } @@ -90,7 +91,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) line_numbers = 1; if (event != NULL && KEYC_IS_MOUSE(event->key)) line_numbers = 0; - if (!window_pane_set_mode(wp, swp, &window_copy_mode, NULL, args)) { + if (!window_pane_set_mode(wp, swp, &window_copy_mode, item, NULL, + args)) { window_copy_set_line_numbers(wp, line_numbers); if (args_has(args, 'M')) window_copy_start_drag(c, &event->m); diff --git a/cmd-display-menu.c b/cmd-display-menu.c index ba1bcf4ae..2aaf3238c 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-display-menu.c,v 1.51 2026/07/07 09:45:09 nicm Exp $ */ +/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -92,7 +92,7 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause) } static int -cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, +cmd_display_menu_get_popup_pos(struct client *tc, struct cmdq_item *item, struct args *args, u_int *px, u_int *py, u_int w, u_int h) { struct tty *tty = &tc->tty; @@ -127,8 +127,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, } /* Position of the previous menu, for -x/-y L. */ - format_add(ft, "popup_last_x", "%u", tc->menu_last_px); - format_add(ft, "popup_last_y", "%u", tc->menu_last_py + h); + format_add(ft, "popup_last_x", "%u", target->w->menu_last_px); + format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h); /* * If there are any status lines, add this window position and the @@ -288,6 +288,184 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, return (1); } +static int +cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item, + struct args *args, u_int *px, u_int *py, u_int w, u_int h) +{ + struct cmd_find_state *target = cmdq_get_target(item); + struct key_event *event = cmdq_get_event(item); + struct session *s = tc->session; + struct winlink *wl = target->wl; + struct window *window = target->w; + struct window_pane *wp = target->wp; + struct style_ranges *ranges = NULL; + struct style_range *sr = NULL; + const char *xp, *yp; + char *p; + u_int line, ox, oy, sx, sy, lines, position; + long n, max_x, max_y, mouse_x = 0, mouse_y = 0; + struct format_tree *ft; + + max_x = window->sx > w ? window->sx - w : 0; + max_y = window->sy > h ? window->sy - h : 0; + + tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy); + + ft = format_create_from_target(item); + if (event->m.valid) { + mouse_x = event->m.x + ox; + if (event->m.statusat == 0) { + if (event->m.y >= event->m.statuslines) + mouse_y = event->m.y - event->m.statuslines + oy; + else + mouse_y = oy; + } else if (event->m.statusat > 0 && + event->m.y >= (u_int)event->m.statusat) { + mouse_y = oy + sy - 1; + } else + mouse_y = event->m.y + oy; + format_add(ft, "popup_mouse_x", "%ld", mouse_x); + format_add(ft, "popup_mouse_y", "%ld", mouse_y); + } + + format_add(ft, "popup_last_x", "%u", window->menu_last_px); + format_add(ft, "popup_last_y", "%u", window->menu_last_py + h); + + lines = status_line_size(tc); + position = options_get_number(s->options, "status-position"); + if (status_at_line(tc) != -1 && lines != 0) { + for (line = 0; line < lines; line++) { + ranges = &tc->status.entries[line].ranges; + TAILQ_FOREACH(sr, ranges, entry) { + if (sr->type != STYLE_RANGE_WINDOW) + continue; + if (sr->argument == (u_int)wl->idx) + break; + } + if (sr != NULL) + break; + } + if (sr != NULL) { + format_add(ft, "popup_window_status_line_x", "%u", + sr->start + ox); + if (position == 0) { + format_add(ft, "popup_window_status_line_y", + "%u", h); + } else { + format_add(ft, "popup_window_status_line_y", + "%u", window->sy); + } + } + if (position == 0) + format_add(ft, "popup_status_line_y", "%u", h); + else + format_add(ft, "popup_status_line_y", "%u", window->sy); + } + + format_add(ft, "popup_width", "%u", w); + format_add(ft, "popup_height", "%u", h); + + n = ((long)window->sx - 1) / 2 - w / 2; + if (n < 0) + format_add(ft, "popup_centre_x", "%u", 0); + else + format_add(ft, "popup_centre_x", "%ld", n); + n = ((long)window->sy - 1) / 2 + h / 2; + if (n >= window->sy) + format_add(ft, "popup_centre_y", "%ld", max_y); + else + format_add(ft, "popup_centre_y", "%ld", n); + + if (event->m.valid) { + n = mouse_x - w / 2; + if (n < 0) + format_add(ft, "popup_mouse_centre_x", "%u", 0); + else + format_add(ft, "popup_mouse_centre_x", "%ld", n); + n = mouse_y - h / 2; + if (n + h >= window->sy) + format_add(ft, "popup_mouse_centre_y", "%ld", max_y); + else + format_add(ft, "popup_mouse_centre_y", "%ld", n); + n = mouse_y + h; + if (n >= window->sy) + format_add(ft, "popup_mouse_top", "%u", window->sy - 1); + else + format_add(ft, "popup_mouse_top", "%ld", n); + n = mouse_y - h; + if (n < 0) + format_add(ft, "popup_mouse_bottom", "%u", 0); + else + format_add(ft, "popup_mouse_bottom", "%ld", n); + } + + n = wp->yoff + h; + if (n >= window->sy) + format_add(ft, "popup_pane_top", "%ld", max_y); + else + format_add(ft, "popup_pane_top", "%ld", n); + format_add(ft, "popup_pane_bottom", "%u", wp->yoff + wp->sy); + format_add(ft, "popup_pane_left", "%u", wp->xoff); + n = (long)wp->xoff + wp->sx - w; + if (n < 0) + format_add(ft, "popup_pane_right", "%u", 0); + else + format_add(ft, "popup_pane_right", "%ld", n); + + xp = args_get(args, 'x'); + if (xp == NULL || strcmp(xp, "C") == 0) + xp = "#{popup_centre_x}"; + else if (strcmp(xp, "R") == 0) + xp = "#{popup_pane_right}"; + else if (strcmp(xp, "P") == 0) + xp = "#{popup_pane_left}"; + else if (strcmp(xp, "M") == 0) + xp = "#{popup_mouse_centre_x}"; + else if (strcmp(xp, "L") == 0) + xp = "#{popup_last_x}"; + else if (strcmp(xp, "W") == 0) + xp = "#{popup_window_status_line_x}"; + p = format_expand(ft, xp); + n = strtol(p, NULL, 10); + if (n < 0) + n = 0; + if (n > max_x) + n = max_x; + *px = n; + log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w); + free(p); + + yp = args_get(args, 'y'); + if (yp == NULL || strcmp(yp, "C") == 0) + yp = "#{popup_centre_y}"; + else if (strcmp(yp, "P") == 0) + yp = "#{popup_pane_bottom}"; + else if (strcmp(yp, "M") == 0) + yp = "#{popup_mouse_top}"; + else if (strcmp(yp, "L") == 0) + yp = "#{popup_last_y}"; + else if (strcmp(yp, "S") == 0) + yp = "#{popup_status_line_y}"; + else if (strcmp(yp, "W") == 0) + yp = "#{popup_window_status_line_y}"; + p = format_expand(ft, yp); + n = strtol(p, NULL, 10); + if (n < h) + n = 0; + else + n -= h; + if (n < 0) + n = 0; + if (n > max_y) + n = max_y; + *py = n; + log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h); + free(p); + + format_free(ft); + return (1); +} + static enum cmd_retval cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) { @@ -308,10 +486,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) struct options *o = target->s->curw->window->options; struct options_entry *oe; - - if (tc->overlay_draw != NULL) - return (CMD_RETURN_NORMAL); - if (args_has(args, 'C')) { if (strcmp(args_get(args, 'C'), "-") == 0) starting_choice = -1; @@ -357,8 +531,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) } if (menu->count == 0) goto out; - if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, menu->width + 4, - menu->count + 2)) + if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, + menu->width + 4, menu->count + 2)) goto out; value = args_get(args, 'b'); @@ -379,7 +553,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines, style, selected_style, border_style, target, NULL, NULL) != 0) goto out; - return (CMD_RETURN_WAIT); + return (CMD_RETURN_NORMAL); out: menu_free(menu); @@ -445,7 +619,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) w = tty->sx; if (h > tty->sy) h = tty->sy; - if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, w, h)) + if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py, + w, h)) goto out; value = args_get(args, 'd'); diff --git a/cmd-display-panes.c b/cmd-display-panes.c deleted file mode 100644 index 01ccc101f..000000000 --- a/cmd-display-panes.c +++ /dev/null @@ -1,399 +0,0 @@ -/* $OpenBSD: cmd-display-panes.c,v 1.56 2026/06/25 16:32:42 nicm Exp $ */ - -/* - * Copyright (c) 2009 Nicholas Marriott - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include - -#include "tmux.h" - -/* - * Display panes on a client. - */ - -static enum args_parse_type cmd_display_panes_args_parse(struct args *, - u_int, char **); -static enum cmd_retval cmd_display_panes_exec(struct cmd *, - struct cmdq_item *); - -const struct cmd_entry cmd_display_panes_entry = { - .name = "display-panes", - .alias = "displayp", - - .args = { "bd:Nt:", 0, 1, cmd_display_panes_args_parse }, - .usage = "[-bN] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", - - .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, - .exec = cmd_display_panes_exec -}; - -struct cmd_display_panes_data { - struct cmdq_item *item; - struct cmd_parse_tree *tree; -}; - -struct cmd_display_panes_ctx { - struct client *c; - int ox; - int oy; - u_int sx; - u_int sy; - u_int statuslines; - int statustop; -}; - -static enum args_parse_type -cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx, - __unused char **cause) -{ - return (ARGS_PARSE_COMMANDS_OR_STRING); -} - -static void -cmd_display_panes_put(struct cmd_display_panes_ctx *ctx, - struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len) -{ - struct client *c = ctx->c; - struct tty *tty = &c->tty; - struct visible_ranges *r; - struct visible_range *ri; - u_int i, j; - - r = window_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy, len, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - for (j = ri->px; j < ri->px + ri->nx; j++) { - tty_cursor(tty, j - ctx->ox, cy); - tty_putn(tty, &buf[j - ri->px], 1, 1); - } - } -} - -static void -cmd_display_panes_draw_format(struct cmd_display_panes_ctx *ctx, - struct window_pane *wp, u_int xoff, u_int yoff, u_int sx, - const struct grid_cell *gc) -{ - struct client *c = ctx->c; - struct tty *tty = &c->tty; - struct session *s = c->session; - struct screen *sc = wp->screen, screen; - struct screen_write_ctx sctx; - struct visible_ranges *r; - struct visible_range *ri; - const char *format; - char *expanded; - u_int i, px = ctx->ox + xoff; - - format = options_get_string(s->options, "display-panes-format"); - expanded = format_single(NULL, format, c, s, s->curw, wp); - - screen_init(&screen, sx, 1, 0); - screen_write_start(&sctx, &screen); - screen_write_fast_copy(&sctx, sc, 0, sc->grid->hsize, sx, 1); - screen_write_cursormove(&sctx, 0, 0, 0); - format_draw(&sctx, gc, sx, expanded, NULL, 0); - screen_write_stop(&sctx); - free(expanded); - - r = window_visible_ranges(wp, px, wp->yoff, sx, NULL); - for (i = 0; i < r->used; i++) { - ri = &r->ranges[i]; - tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx, - ri->px - ctx->ox, yoff, NULL); - } - screen_free(&screen); -} - -static void -cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx, - struct window_pane *wp) -{ - struct client *c = ctx->c; - struct tty *tty = &c->tty; - struct session *s = c->session; - struct options *oo = s->options; - struct window *w = wp->window; - struct grid_cell fgc, bgc; - u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy; - u_int cx, cy; - const char *name; - struct format_tree *ft; - char buf[16], lbuf[16], *ptr; - size_t len, llen; - - if (wp->xoff + (int)wp->sx <= ctx->ox || - wp->xoff >= ctx->ox + (int)ctx->sx || - wp->yoff + (int)wp->sy <= ctx->oy || - wp->yoff >= ctx->oy + (int)ctx->sy) - return; - - if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) { - /* All visible. */ - xoff = wp->xoff - ctx->ox; - sx = wp->sx; - } else if (wp->xoff < ctx->ox && - wp->xoff + wp->sx > ctx->ox + ctx->sx) { - /* Both left and right not visible. */ - xoff = 0; - sx = ctx->sx; - } else if (wp->xoff < ctx->ox) { - /* Left not visible. */ - xoff = 0; - sx = wp->sx - (ctx->ox - wp->xoff); - } else { - /* Right not visible. */ - xoff = wp->xoff - ctx->ox; - sx = wp->sx - xoff; - } - if (wp->yoff >= ctx->oy && wp->yoff + wp->sy <= ctx->oy + ctx->sy) { - /* All visible. */ - yoff = wp->yoff - ctx->oy; - sy = wp->sy; - } else if (wp->yoff < ctx->oy && - wp->yoff + wp->sy > ctx->oy + ctx->sy) { - /* Both top and bottom not visible. */ - yoff = 0; - sy = ctx->sy; - } else if (wp->yoff < ctx->oy) { - /* Top not visible. */ - yoff = 0; - sy = wp->sy - (ctx->oy - wp->yoff); - } else { - /* Bottom not visible. */ - yoff = wp->yoff - ctx->oy; - sy = wp->sy - yoff; - } - - if (ctx->statustop) - yoff += ctx->statuslines; - px = sx / 2; - py = sy / 2; - - if (window_pane_index(wp, &pane) != 0) - fatalx("index not found"); - len = xsnprintf(buf, sizeof buf, "%u", pane); - - if (sx < len) - return; - if (w->active == wp) - name = "display-panes-active-colour"; - else - name = "display-panes-colour"; - ft = format_create_defaults(NULL, c, s, NULL, wp); - style_apply(&fgc, oo, name, ft); - format_free(ft); - - memcpy(&bgc, &grid_default_cell, sizeof bgc); - bgc.bg = fgc.fg; - - if (pane > 9 && pane < 35) - llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); - else - llen = 0; - - if (sx < len * 6 || sy < 5) { - tty_attributes(tty, &fgc, NULL); - if (sx >= len + llen + 1) { - len += llen + 1; - cx = xoff + px - len / 2; - cy = yoff + py; - cmd_display_panes_put(ctx, wp, cx, cy, buf, len); - cx += len; - cmd_display_panes_put(ctx, wp, cx, cy, " ", 1); - cx++; - cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen); - } else { - cx = xoff + px - len / 2; - cy = yoff + py; - cmd_display_panes_put(ctx, wp, cx, cy, buf, len); - } - goto out; - } - - px -= len * 3; - py -= 2; - - tty_attributes(tty, &bgc, NULL); - for (ptr = buf; *ptr != '\0'; ptr++) { - if (*ptr < '0' || *ptr > '9') - continue; - idx = *ptr - '0'; - - for (j = 0; j < 5; j++) { - for (i = px; i < px + 5; i++) { - if (!window_clock_table[idx][j][i - px]) - continue; - cx = xoff + i; - cy = yoff + py + j; - cmd_display_panes_put(ctx, wp, cx, cy, " ", 1); - } - } - px += 6; - } - - if (sy <= 6) - goto out; - cmd_display_panes_draw_format(ctx, wp, xoff, yoff, sx, &fgc); - if (llen != 0) { - tty_attributes(tty, &fgc, NULL); - cx = xoff + sx / 2 + len * 3 - llen - 1; - cy = yoff + py + 5; - cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen); - } - -out: - tty_cursor(tty, 0, 0); -} - -static void -cmd_display_panes_draw(struct client *c, __unused void *data) -{ - struct session *s = c->session; - struct window *w = s->curw->window; - struct window_pane *wp; - struct cmd_display_panes_ctx ctx; - u_int lines; - - log_debug("%s: %s @%u", __func__, c->name, w->id); - - memset(&ctx, 0, sizeof ctx); - ctx.c = c; - tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy); - if (options_get_number(s->options, "status-position") == 0) { - lines = status_line_size(c); - if (c->message_string != NULL || c->prompt != NULL) - lines = (lines == 0 ? 1 : lines); - ctx.statuslines = lines; - ctx.statustop = 1; - } - - TAILQ_FOREACH(wp, &w->panes, entry) { - if (window_pane_is_visible(wp)) - cmd_display_panes_draw_pane(&ctx, wp); - } -} - -static void -cmd_display_panes_free(__unused struct client *c, void *data) -{ - struct cmd_display_panes_data *cdata = data; - - if (cdata->item != NULL) - cmdq_continue(cdata->item); - cmd_parse_free(cdata->tree); - free(cdata); -} - -static int -cmd_display_panes_key(struct client *c, void *data, struct key_event *event) -{ - struct cmd_display_panes_data *cdata = data; - char *expanded; - struct cmd_invoke_input ci = { 0 }; - struct cmdq_item *item = cdata->item, *new_item; - struct cmdq_state *cs = NULL; - struct window *w = c->session->curw->window; - struct window_pane *wp; - u_int index; - key_code key; - - if (event->key >= '0' && event->key <= '9') - index = event->key - '0'; - else if ((event->key & KEYC_MASK_MODIFIERS) == 0) { - key = (event->key & KEYC_MASK_KEY); - if (key >= 'a' && key <= 'z') - index = 10 + (key - 'a'); - else - return (-1); - } else - return (-1); - - wp = window_pane_at_index(w, index); - if (wp == NULL) - return (1); - window_unzoom(w, 1); - - xasprintf(&expanded, "%%%u", wp->id); - ci.argc = 1; - ci.argv = &expanded; - - if (item != NULL) - cs = cmdq_get_state(item); - new_item = cmd_invoke_get(cdata->tree, cs, &ci); - if (item == NULL) - cmdq_append(c, new_item); - else - cmdq_insert_after(item, new_item); - - free(expanded); - return (1); -} - -static enum cmd_retval -cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = cmd_get_args(self); - struct client *tc = cmdq_get_target_client(item); - struct session *s = tc->session; - u_int delay; - char *cause; - struct cmd_display_panes_data *cdata; - int wait = !args_has(args, 'b'); - - if (tc->overlay_draw != NULL) - return (CMD_RETURN_NORMAL); - - if (!args_has(args, 'd')) - delay = options_get_number(s->options, "display-panes-time"); - else { - delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause); - if (cause != NULL) { - cmdq_error(item, "delay %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - } - - cdata = xcalloc(1, sizeof *cdata); - if (wait) - cdata->item = item; - cdata->tree = args_to_commands(self, item, 0, "select-pane -t \"%%%\"", - 0, &cause); - if (cdata->tree == NULL) { - cmdq_error(item, "%s", cause); - free(cause); - free(cdata); - return (CMD_RETURN_ERROR); - } - - if (args_has(args, 'N')) { - server_client_set_overlay(tc, delay, NULL, NULL, - cmd_display_panes_draw, NULL, cmd_display_panes_free, NULL, - cdata); - } else { - server_client_set_overlay(tc, delay, NULL, NULL, - cmd_display_panes_draw, cmd_display_panes_key, - cmd_display_panes_free, NULL, cdata); - } - - if (!wait) - return (CMD_RETURN_NORMAL); - return (CMD_RETURN_WAIT); -} diff --git a/cmd-find-window.c b/cmd-find-window.c index 28a8949cd..0091ae907 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-find-window.c,v 1.56 2023/12/27 20:42:01 nicm Exp $ */ +/* $OpenBSD: cmd-find-window.c,v 1.57 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -109,7 +109,8 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) args_set(new_args, 'Z', NULL, 0); args_set(new_args, 'f', filter, 0); - window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); + window_pane_set_mode(wp, NULL, &window_tree_mode, item, target, + new_args); args_free(new_args); return (CMD_RETURN_NORMAL); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index bbee45b96..659f3bee0 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.69 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.71 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -458,6 +458,13 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "size or position %s", cause); @@ -498,6 +505,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) } else server_status_session(dst_s); + window_fire_pane_moved(src_wp, src_w, src_wl->idx, dst_w, dst_idx); if (window_count_panes(src_w, 1) == 0) server_kill_window(src_w, 1); else diff --git a/cmd-queue.c b/cmd-queue.c index 411563101..2aa8a500a 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-queue.c,v 1.120 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-queue.c,v 1.121 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2013 Nicholas Marriott @@ -147,6 +147,13 @@ cmdq_get_name(struct cmdq_item *item) return (item->name); } +/* Get item command. */ +struct cmd * +cmdq_get_cmd(struct cmdq_item *item) +{ + return (item->cmd); +} + /* Get item client. */ struct client * cmdq_get_client(struct cmdq_item *item) diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 4d62aa69a..c173d7238 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-run-shell.c,v 1.91 2026/06/01 08:27:37 nicm Exp $ */ +/* $OpenBSD: cmd-run-shell.c,v 1.92 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -100,7 +100,8 @@ cmd_run_shell_print(struct job *job, const char *msg) wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) - window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL, + NULL); window_copy_add(wp, 1, "%s", msg); } diff --git a/cmd-split-window.c b/cmd-split-window.c index 6c657fab5..5de8a6755 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.141 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.144 2026/07/14 15:06:54 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -33,13 +33,15 @@ #define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *); +static void cmd_split_window_mouse_resize(struct client *, + struct mouse_event *); const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bB:c:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-bdefhIklPvWZ] [-B border-lines] " + .args = { "bB:c:de:EfF:hIkl:LMm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-bdefhIklMPvWZ] [-B border-lines] " "[-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " @@ -57,12 +59,12 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL }, - .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " - "[-F format] [-l size] [-m message] [-p percentage] " - "[-s style] [-S active-border-style] " - "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " " - "[shell-command [argument ...]]", + .args = { "bB:c:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL }, + .usage = "[-bdefhIklPvWZ] [-B border-lines] [-c start-directory] " + "[-e environment] [-F format] [-l size] [-m message] " + "[-p percentage] [-s style] [-S active-border-style] " + "[-R inactive-border-style] [-T title] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -85,6 +87,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *lc = NULL; struct event_payload *ep; struct cmd_find_state fs; + struct key_event *event = cmdq_get_event(item); int input, empty, is_floating, flags = 0; const char *template, *style, *value; char *cause = NULL, *cp, *title; @@ -95,14 +98,28 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_new_pane_entry) is_floating = !args_has(args, 'L'); - else + else { is_floating = window_pane_is_floating(wp); + flags |= SPAWN_SPLIT; + } - flags = is_floating ? SPAWN_FLOATING : 0; + if (args_has(args, 'M') && is_floating) { + if (event == NULL || !event->m.valid || tc == NULL) + return (CMD_RETURN_NORMAL); + } + + if (is_floating) + flags |= SPAWN_FLOATING; + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; + if (args_has(args, 'd')) + flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + flags |= SPAWN_ZOOM; input = args_has(args, 'I'); if (input || (count == 1 && *args_string(args, 0) == '\0')) @@ -130,9 +147,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } } - if (is_floating) - lc = layout_get_floating_cell(item, args, lines, w, wp, &cause); - else + if (flags & SPAWN_FLOATING) { + lc = layout_get_floating_cell(item, args, lines, w, wp, flags, + &cause); + } else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "%s", cause); @@ -158,12 +176,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) sc.idx = -1; sc.cwd = args_get(args, 'c'); - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { cmdq_error(item, "create pane failed: %s", cause); @@ -239,15 +252,21 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) break; } } - if (!args_has(args, 'd')) + if (~flags & SPAWN_DETACHED) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - if (!is_floating) { + if (~flags & SPAWN_FLOATING) { window_pop_zoom(wp->window); server_redraw_window(wp->window); } server_redraw_session(s); + if (args_has(args, 'M') && is_floating) { + tc->tty.mouse_last_pane = new_wp->id; + tc->tty.mouse_drag_update = cmd_split_window_mouse_resize; + cmd_split_window_mouse_resize(tc, &event->m); + } + if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = SPLIT_WINDOW_TEMPLATE; @@ -295,3 +314,73 @@ fail: return (CMD_RETURN_ERROR); } + +static void +cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m) +{ + struct window_pane *wp; + struct window *w; + struct layout_cell *lc; + enum pane_lines lines; + u_int sx, sy; + int x, y, xoff, yoff, border; + + if (c->tty.mouse_last_pane == -1) + return; + wp = window_pane_find_by_id(c->tty.mouse_last_pane); + if (wp == NULL || !window_pane_is_floating(wp)) { + c->tty.mouse_drag_update = NULL; + return; + } + w = wp->window; + lc = wp->layout_cell; + + x = m->x + m->ox; + y = m->y + m->oy; + if (m->statusat == 0 && y >= (int)m->statuslines) + y -= m->statuslines; + else if (m->statusat > 0 && y >= m->statusat) + y = m->statusat - 1; + + lines = window_pane_get_pane_lines(wp); + border = (lines != PANE_LINES_NONE); + + if (x >= (int)c->tty.mouse_drag_x) { + xoff = c->tty.mouse_drag_x + border; + sx = x - c->tty.mouse_drag_x + 1; + } else { + sx = c->tty.mouse_drag_x - x + 1; + xoff = c->tty.mouse_drag_x - sx + 1; + if (border) + xoff++; + } + if (y >= (int)c->tty.mouse_drag_y) { + yoff = c->tty.mouse_drag_y + border; + sy = y - c->tty.mouse_drag_y + 1; + } else { + sy = c->tty.mouse_drag_y - y + 1; + yoff = c->tty.mouse_drag_y - sy + 1; + if (border) + yoff++; + } + + if (border) { + if (sx <= 2) + sx = PANE_MINIMUM; + else + sx -= 2; + if (sy <= 2) + sy = PANE_MINIMUM; + else + sy -= 2; + } + if (sx < PANE_MINIMUM) + sx = PANE_MINIMUM; + if (sy < PANE_MINIMUM) + sy = PANE_MINIMUM; + + layout_set_size(lc, sx, sy, xoff, yoff); + layout_fix_panes(w, NULL); + server_redraw_window(w); + server_redraw_window_borders(w); +} diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index da7ea3706..b2e53f315 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.53 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.54 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -68,11 +68,14 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *tmp_wp, *src_wp, *dst_wp; struct layout_cell *src_lc, *dst_lc; u_int sx, sy, xoff, yoff; + int src_idx, dst_idx; dst_w = target->wl->window; dst_wp = target->wp; + dst_idx = target->wl->idx; src_w = source->wl->window; src_wp = source->wp; + src_idx = source->wl->idx; if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) server_redraw_window(dst_w); @@ -180,6 +183,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) redraw_invalidate_scene(dst_w); server_redraw_window(dst_w); + if (src_w != dst_w) { + window_fire_pane_moved(src_wp, src_w, src_idx, dst_w, dst_idx); + window_fire_pane_moved(dst_wp, dst_w, dst_idx, src_w, src_idx); + } events_fire_window("window-layout-changed", src_w); if (src_w != dst_w) events_fire_window("window-layout-changed", dst_w); diff --git a/cmd.c b/cmd.c index 331177bbb..97858447c 100644 --- a/cmd.c +++ b/cmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd.c,v 1.186 2026/07/12 20:35:52 nicm Exp $ */ +/* $OpenBSD: cmd.c,v 1.187 2026/07/13 09:37:39 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -672,7 +672,7 @@ cmd_template_replace(const char *template, const char *s, int idx) ptr++; slen = strlen(s); - if (slen > SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) + if (slen >= SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) fatalx("argument too long"); buf = xrealloc(buf, len + (slen * 3) + 1); for (cp = s; *cp != '\0'; cp++) { diff --git a/compat.h b/compat.h index 8d71a9cf1..66a22640b 100644 --- a/compat.h +++ b/compat.h @@ -410,11 +410,6 @@ int asprintf(char **, const char *, ...); int vasprintf(char **, const char *, va_list); #endif -#ifndef HAVE_FGETLN -/* fgetln.c */ -char *fgetln(FILE *, size_t *); -#endif - #ifndef HAVE_GETLINE /* getline.c */ ssize_t getline(char **, size_t *, FILE *); diff --git a/compat/fgetln.c b/compat/fgetln.c deleted file mode 100644 index c71918cbb..000000000 --- a/compat/fgetln.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2015 Joerg Jung - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* - * portable fgetln() version, NOT reentrant - */ - -#include -#include -#include - -#include "compat.h" - -char * -fgetln(FILE *fp, size_t *len) -{ - static char *buf = NULL; - static size_t bufsz = 0; - size_t r = 0; - char *p; - int c, e; - - if (!fp || !len) { - errno = EINVAL; - return NULL; - } - if (!buf) { - if (!(buf = calloc(1, BUFSIZ))) - return NULL; - bufsz = BUFSIZ; - } - while ((c = getc(fp)) != EOF) { - buf[r++] = c; - if (r == bufsz) { - if (!(p = reallocarray(buf, 2, bufsz))) { - e = errno; - free(buf); - errno = e; - buf = NULL, bufsz = 0; - return NULL; - } - buf = p, bufsz = 2 * bufsz; - } - if (c == '\n') - break; - } - return (*len = r) ? buf : NULL; -} diff --git a/configure.ac b/configure.ac index c118800b8..51700a2ad 100644 --- a/configure.ac +++ b/configure.ac @@ -177,7 +177,6 @@ AC_REPLACE_FUNCS([ \ clock_gettime \ closefrom \ explicit_bzero \ - fgetln \ freezero \ getdtablecount \ getdtablesize \ diff --git a/format.c b/format.c index be4a5ba23..fc01cfc74 100644 --- a/format.c +++ b/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.403 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.404 2026/07/14 15:06:54 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -85,13 +85,13 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) return (strcmp(fj1->cmd, fj2->cmd)); } -/* Maimum pad and trim width. */ +/* Maximum pad and trim width. */ #define FORMAT_MAX_WIDTH 10000 -/* Maimum repeat size. */ +/* Maximum repeat size. */ #define FORMAT_MAX_REPEAT 10000 -/* Maimum precision. */ +/* Maximum precision. */ #define FORMAT_MAX_PRECISION 100 /* Format modifiers. */ diff --git a/hooks.c b/hooks.c index c0d0d5018..f4db07ded 100644 --- a/hooks.c +++ b/hooks.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hooks.c,v 1.13 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: hooks.c,v 1.14 2026/07/13 22:03:08 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -261,6 +261,19 @@ hooks_add_event(const char *name) TAILQ_INSERT_TAIL(&hooks_events, he, entry); } +/* Return if a hook event sink exists. */ +int +hooks_is_event(const char *name) +{ + struct hooks_event *he; + + TAILQ_FOREACH(he, &hooks_events, entry) { + if (strcmp(he->name, name) == 0) + return (1); + } + return (0); +} + /* Return if an event name can be fired through the hooks path. */ int hooks_valid_event_name(const char *name) diff --git a/input.c b/input.c index 0ccef3b11..0aac9bfbb 100644 --- a/input.c +++ b/input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input.c,v 1.266 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: input.c,v 1.268 2026/07/13 15:03:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1051,6 +1051,10 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len) return; window_update_activity(wp->window); + if (~wp->flags & PANE_ACTIVITY) { + wp->flags |= PANE_ACTIVITY; + events_fire_pane("pane-activity", wp); + } wp->flags |= PANE_CHANGED; /* Flag new input while in a mode. */ @@ -1316,8 +1320,10 @@ input_c0_dispatch(struct input_ctx *ictx) case '\000': /* NUL */ break; case '\007': /* BEL */ - if (wp != NULL) + if (wp != NULL) { + events_fire_pane("pane-bell", wp); alerts_queue(wp->window, WINDOW_BELL); + } break; case '\010': /* BS */ screen_write_backspace(sctx); @@ -2199,7 +2205,7 @@ input_csi_dispatch_winops(struct input_ctx *ictx) screen_pop_title(sctx->s); if (wp == NULL) break; - events_fire_pane("pane-title-changed", wp); + input_fire_pane_title_changed(wp, sctx->s->title); server_redraw_window_borders(w); server_status_window(w); break; diff --git a/key-bindings.c b/key-bindings.c index 6b56f7a80..c44d94d55 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -1,4 +1,4 @@ -/* $OpenBSD: key-bindings.c,v 1.186 2026/07/07 12:30:36 nicm Exp $ */ +/* $OpenBSD: key-bindings.c,v 1.188 2026/07/13 16:30:28 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -49,6 +49,9 @@ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \ " ''" \ + " '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Hide Line Numbers,Show Line Numbers},}' 'L' {send -X line-numbers-toggle}" \ + " '#{?#{==:#{pane_mode},copy-mode},#{?refresh_active,Refresh Off,Refresh On},}' 'r' {send -X refresh-toggle}" \ + " ''" \ " '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \ " ''" \ " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {if -F '#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}' 'copy-mode -t='; send -Xt= search-backward -- \"#{q:mouse_word}\"}" \ @@ -494,10 +497,10 @@ key_bindings_init(void) /* Mouse button 1 down on pane. */ "bind -n MouseDown1Pane { select-pane -t=; send -M }", - "bind -n C-MouseDown1Pane { swap-pane -s@ }", /* Mouse button 1 drag on pane. */ "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", + "bind -n C-MouseDrag1Pane { new-pane -M }", "bind -n M-MouseDrag1Pane { move-pane -M }", /* Mouse wheel up on pane. */ @@ -574,6 +577,7 @@ key_bindings_init(void) "bind -Tcopy-mode , { send -X jump-reverse }", "bind -Tcopy-mode \\; { send -X jump-again }", "bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }", + "bind -Tcopy-mode L { send -X line-numbers-toggle }", "bind -Tcopy-mode N { send -X search-reverse }", "bind -Tcopy-mode P { send -X toggle-position }", "bind -Tcopy-mode R { send -X rectangle-toggle }", diff --git a/layout-set.c b/layout-set.c index 7e944d32f..65cb38918 100644 --- a/layout-set.c +++ b/layout-set.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout-set.c,v 1.39 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout-set.c,v 1.40 2026/07/13 09:42:12 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -135,6 +135,21 @@ layout_set_first_tiled(struct window *w) return (NULL); } +static void +layout_set_link_floating(struct window *w, struct layout_cell *lcroot) +{ + struct window_pane *wp; + struct layout_cell *lc; + + TAILQ_FOREACH(wp, &w->panes, entry) { + lc = wp->layout_cell; + if (!layout_cell_is_tiled(lc)) { + TAILQ_INSERT_TAIL(&lcroot->cells, lc, entry); + lc->parent = lcroot; + } + } +} + static void layout_set_even(struct window *w, enum layout_type type) { @@ -267,6 +282,8 @@ layout_set_main_h(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -364,6 +381,8 @@ layout_set_main_h_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -461,6 +480,8 @@ layout_set_main_v(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -559,6 +580,8 @@ layout_set_main_v_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -700,6 +723,7 @@ layout_set_tiled(struct window *w) w->sy - used); } + layout_set_link_floating(w, lcroot); layout_fix_offsets(w); layout_fix_panes(w, NULL); diff --git a/layout.c b/layout.c index 177d2f4cf..c3554718c 100644 --- a/layout.c +++ b/layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout.c,v 1.93 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout.c,v 1.95 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -370,13 +370,30 @@ layout_fix_offsets(struct window *w) layout_fix_offsets1(lc); } +static int +layout_cell_is_last_tiled(struct layout_cell *lc) +{ + struct layout_cell *lcchild, *lcparent = lc->parent; + + if (lcparent == NULL) + return (layout_cell_is_tiled(lc)); + + TAILQ_FOREACH_REVERSE(lcchild, &lcparent->cells, layout_cells, entry) { + if (layout_cell_is_tiled(lcchild) || + layout_cell_has_tiled_child(lcchild)) + break; + } + + return (lcchild == lc); +} + /* Is this a top cell? */ static int -layout_cell_is_top(struct window *w, struct layout_cell *lc) +layout_cell_is_top(struct layout_cell *root, struct layout_cell *lc) { struct layout_cell *next; - while (lc != w->layout_root) { + while (lc != root) { next = lc->parent; if (next == NULL) return (0); @@ -390,24 +407,17 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc) /* Is this a bottom cell? */ static int -layout_cell_is_bottom(struct window *w, struct layout_cell *lc) +layout_cell_is_bottom(struct layout_cell *root, struct layout_cell *lc) { - struct layout_cell *next, *edge; + struct layout_cell *next; - while (lc != w->layout_root) { + while (lc != root) { next = lc->parent; if (next == NULL) return (0); - if (next->type == LAYOUT_TOPBOTTOM) { - edge = TAILQ_LAST(&next->cells, layout_cells); - while (edge != NULL) { - if (~edge->flags & LAYOUT_CELL_FLOATING) - break; - edge = TAILQ_PREV(edge, layout_cells, entry); - } - if (lc != edge) - return (0); - } + if (next->type == LAYOUT_TOPBOTTOM && + !layout_cell_is_last_tiled(lc)) + return (0); lc = next; } return (1); @@ -417,14 +427,14 @@ layout_cell_is_bottom(struct window *w, struct layout_cell *lc) * Returns 1 if we need to add an extra line for the pane status line. This is * the case for the most upper or lower panes only. */ -static int -layout_add_horizontal_border(struct window *w, struct layout_cell *lc, +int +layout_add_horizontal_border(struct layout_cell *root, struct layout_cell *lc, int status) { if (status == PANE_STATUS_TOP) - return (layout_cell_is_top(w, lc)); + return (layout_cell_is_top(root, lc)); if (status == PANE_STATUS_BOTTOM) - return (layout_cell_is_bottom(w, lc)); + return (layout_cell_is_bottom(root, lc)); return (0); } @@ -433,13 +443,11 @@ void layout_fix_panes(struct window *w, struct window_pane *skip) { struct window_pane *wp; - struct layout_cell *lc; + struct layout_cell *lc, *root = w->layout_root; int status, sb_w, sb_pad; int old_xoff, old_yoff, changed = 0; u_int sx, sy, old_sx, old_sy; - status = window_get_pane_status(w); - TAILQ_FOREACH(wp, &w->panes, entry) { if ((lc = wp->layout_cell) == NULL || wp == skip) continue; @@ -454,8 +462,9 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sx = lc->g.sx; sy = lc->g.sy; + status = window_pane_get_pane_status(wp); if (!window_pane_is_floating(wp) && - layout_add_horizontal_border(w, lc, status)) { + layout_add_horizontal_border(root, lc, status)) { if (status == PANE_STATUS_TOP) wp->yoff++; if (sy > 1) @@ -523,7 +532,7 @@ static u_int layout_resize_check(struct window *w, struct layout_cell *lc, enum layout_type type) { - struct layout_cell *lcchild; + struct layout_cell *lcchild, *root = w->layout_root; struct style *sb_style = &w->active->scrollbar_style; u_int available, minimum; int status; @@ -541,7 +550,7 @@ layout_resize_check(struct window *w, struct layout_cell *lc, minimum = PANE_MINIMUM; } else { available = lc->g.sy; - if (layout_add_horizontal_border(w, lc, status)) + if (layout_add_horizontal_border(root, lc, status)) minimum = PANE_MINIMUM + 1; else minimum = PANE_MINIMUM; @@ -1256,9 +1265,10 @@ int layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, enum layout_type type) { - struct style *sb_style = &wp->scrollbar_style; - u_int minimum, sx = lc->g.sx, sy = lc->g.sy; - int status; + struct layout_cell *root = wp->window->layout_root; + struct style *sb_style = &wp->scrollbar_style; + u_int minimum, sx = lc->g.sx, sy = lc->g.sy; + int status; if (lc->flags & LAYOUT_CELL_FLOATING) fatalx("floating cells cannot be split"); @@ -1276,7 +1286,7 @@ layout_split_check_space(struct window_pane *wp, struct layout_cell *lc, return (0); break; case LAYOUT_TOPBOTTOM: - if (layout_add_horizontal_border(wp->window, lc, status)) + if (layout_add_horizontal_border(root, lc, status)) minimum = PANE_MINIMUM * 2 + 2; else minimum = PANE_MINIMUM * 2 + 1; @@ -1509,7 +1519,7 @@ layout_close_pane(struct window_pane *wp) int layout_spread_cell(struct window *w, struct layout_cell *parent) { - struct layout_cell *lc; + struct layout_cell *lc, *root = w->layout_root; u_int number, each, size, this, remainder; int change, changed, status; @@ -1524,7 +1534,7 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) if (parent->type == LAYOUT_LEFTRIGHT) size = parent->g.sx; else if (parent->type == LAYOUT_TOPBOTTOM) { - if (layout_add_horizontal_border(w, parent, status)) + if (layout_add_horizontal_border(root, parent, status)) size = parent->g.sy - 1; else size = parent->g.sy; @@ -1555,7 +1565,7 @@ layout_spread_cell(struct window *w, struct layout_cell *parent) } layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change); } else if (parent->type == LAYOUT_TOPBOTTOM) { - if (layout_add_horizontal_border(w, lc, status)) + if (layout_add_horizontal_border(root, lc, status)) this = each + 1; else this = each; @@ -1598,7 +1608,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct window *w, struct window_pane *wp, int flags, char **cause) { struct layout_cell *lc; - enum layout_type type; + enum layout_type type = LAYOUT_TOPBOTTOM; u_int curval; int size = -1; char *error = NULL; @@ -1608,12 +1618,11 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - type = LAYOUT_TOPBOTTOM; - if (args_has(args, 'h')) + if (flags & SPAWN_HORIZONTAL) type = LAYOUT_LEFTRIGHT; if (args_has(args, 'l') || args_has(args, 'p')) { - if (args_has(args, 'f')) { + if (flags & SPAWN_FULLSIZE) { if (type == LAYOUT_TOPBOTTOM) curval = w->sy; else @@ -1641,12 +1650,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) *cause = xstrdup("no space for a new pane"); @@ -1656,17 +1660,24 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - enum pane_lines lines, struct window *w, struct window_pane *wp, + enum pane_lines lines, struct window *w, struct window_pane *wp, int flags, char **cause) { - struct layout_cell *lcnew; + struct layout_cell *lcnew, *lc = wp->layout_cell; struct layout_geometry fg; layout_geometry_init(&fg); - if (layout_floating_args_parse(item, args, lines, w, &fg, cause) != 0) - return (NULL); + if (flags & SPAWN_SPLIT) { + if (layout_split_floating_cell(lc, w, &fg, lines, flags, cause) + != 0) + return (NULL); + } else { + if (layout_floating_args_parse(item, args, lines, w, &fg, cause) + != 0) + return (NULL); + } - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lcnew = layout_floating_pane(w, wp, &fg); return (lcnew); } @@ -1766,6 +1777,131 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args, return (0); } +int +layout_split_floating_cell(struct layout_cell *lc, struct window *w, + struct layout_geometry *out, enum pane_lines lines, int flags, + char **cause) +{ + struct layout_geometry old, new; + int tborder = 1, bborder = w->sy - 1; + int lborder = 3, rborder = w->sx - 3; + int border = lines != PANE_LINES_NONE ? 1 : 0; + int size, space; + + /* First, move the target cell in-bounds. */ + memcpy(&old, &lc->g, sizeof old); + if (lborder > old.xoff - border) + old.xoff = lborder + border; + if (rborder < old.xoff + (int)old.sx + border) + old.xoff = rborder - (int)old.sx - border; + if (tborder > old.yoff - border) + old.yoff = tborder + border; + if (bborder < old.yoff + (int)old.sy + border) + old.yoff = bborder - (int)old.sy - border; + + /* Move the new cell to its ideal position. */ + memcpy(&new, &old, sizeof new); + if (flags & SPAWN_HORIZONTAL) { + if (flags & SPAWN_BEFORE) + new.xoff -= old.sx + 2 * border; + else + new.xoff += old.sx + 2 * border; + } else { + if (flags & SPAWN_BEFORE) + new.yoff -= old.sy + 2 * border; + else + new.yoff += old.sy + 2 * border; + } + + /* + * The position of the new cell is checked to see if it is in bounds. + * If it isn't, the availible space is split and equally given to both + * cells. Only one border is check because the target cell is in bounds + * already. + */ + if (lborder > new.xoff - border) { + /* + * The space for both panes is calculated. Since the offsets are + * associated to where pane contents start, we remove pane + * borders from the space. '1' is added in case the space is + * odd. + */ + space = old.xoff + old.sx - lborder - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = lborder + border; + old.xoff = new.xoff + new.sx + 2 * border; + /* + * If the original space was to be odd (now even), subtract 1 + * from the rightmost cell + */ + if (space % 2 == 0) + old.sx -= 1; + } else if (rborder < new.xoff + (int)new.sx + border) { + space = rborder - old.xoff - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = old.xoff + old.sx + 2 * border; + if (space % 2 == 0) + new.sx -= 1; + } else if (tborder > new.yoff - border) { + space = old.sy + old.yoff - tborder - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = tborder + border; + old.yoff = new.yoff + new.sy + 2 * border; + if (space % 2 == 0) + old.sy -= 1; + } else if (bborder < new.yoff + (int)new.sy + border) { + space = bborder - old.yoff - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = old.yoff + old.sy + 2 * border; + if (space % 2 == 0) + new.sy -= 1; + } + + /* + * Expand the cell to occupy the whole availible space where it was + * spawned. + */ + if (flags & SPAWN_FULLSIZE) { + if (flags & SPAWN_HORIZONTAL) { + new.yoff = tborder + border; + new.sy = bborder - tborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.xoff = lborder + border; + new.sx = old.xoff - new.xoff - 2 * border; + } else { + new.sx = rborder - new.xoff - border; + } + } else { + new.xoff = lborder + border; + new.sx = rborder - lborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.yoff = tborder + border; + new.sy = old.yoff - new.yoff - 2 * border; + } else { + new.sy = bborder - new.yoff - border; + } + } + } + + if (new.sx < PANE_MINIMUM || new.sy < PANE_MINIMUM || + old.sx < PANE_MINIMUM || old.sy < PANE_MINIMUM) { + *cause = xstrdup("no space for a new pane"); + return (-1); + } + + layout_set_size(lc, old.sx, old.sy, old.xoff, old.yoff); + memcpy(out, &new, sizeof *out); + return (0); +} + /* * Removes a cell from the tiled layout by giving the cell's space to the * nearest neighbour. diff --git a/menu.c b/menu.c index 666d59df1..72372d5ff 100644 --- a/menu.c +++ b/menu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: menu.c,v 1.68 2026/07/07 09:45:09 nicm Exp $ */ +/* $OpenBSD: menu.c,v 1.69 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -24,7 +24,7 @@ #include "tmux.h" struct menu_data { - struct cmdq_item *item; + struct window *w; int flags; char *style; @@ -37,8 +37,9 @@ struct menu_data { enum box_lines border_lines; struct cmd_find_state fs; + key_code key; + struct mouse_event m; struct screen s; - struct visible_ranges r; u_int px; u_int py; @@ -175,46 +176,14 @@ menu_free(struct menu *menu) free(menu); } -struct screen * -menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) -{ - struct menu_data *md = data; - - *cx = md->px + 2; - if (md->choice == -1) - *cy = md->py; - else - *cy = md->py + 1 + md->choice; - - return (&md->s); -} - -/* Return parts of the input range which are not obstructed by the menu. */ -struct visible_ranges * -menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, - u_int nx) -{ - struct menu_data *md = data; - struct menu *menu = md->menu; - - server_client_overlay_range(md->px, md->py, menu->width + 4, - menu->count + 2, px, py, nx, &md->r); - return (&md->r); -} - static void -menu_reapply_styles(struct menu_data *md, struct client *c) +menu_reapply_styles(struct menu_data *md) { - struct session *s = c->session; - struct options *o; + struct options *o = md->w->options; struct format_tree *ft; struct style sytmp; - if (s == NULL) - return; - o = s->curw->window->options; - - ft = format_create_defaults(NULL, c, s, s->curw, NULL); + ft = format_create_defaults(NULL, NULL, md->fs.s, md->fs.wl, md->fs.wp); /* Reapply menu style from options. */ memcpy(&md->style_gc, &grid_default_cell, sizeof md->style_gc); @@ -257,16 +226,13 @@ menu_reapply_styles(struct menu_data *md, struct client *c) } void -menu_draw_cb(struct client *c, void *data) +menu_update(struct menu_data *md) { - struct menu_data *md = data; - struct tty *tty = &c->tty; struct screen *s = &md->s; struct menu *menu = md->menu; struct screen_write_ctx ctx; - u_int i, px = md->px, py = md->py; - menu_reapply_styles(md, c); + menu_reapply_styles(md); screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); @@ -279,45 +245,97 @@ menu_draw_cb(struct client *c, void *data) screen_write_menu(&ctx, menu, md->choice, md->border_lines, &md->style_gc, &md->border_style_gc, &md->selected_style_gc); screen_write_stop(&ctx); +} - for (i = 0; i < screen_size_y(&md->s); i++) - tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, NULL); +static void +menu_free_data(struct menu_data *md) +{ + if (md != NULL) { + if (md->cb != NULL) + md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); + + screen_free(&md->s); + menu_free(md->menu); + free(md->style); + free(md->selected_style); + free(md->border_style); + free(md); + } } void -menu_free_cb(__unused struct client *c, void *data) +menu_close(struct window *w) { - struct menu_data *md = data; + if (w->menu != NULL) { + menu_free_data(w->menu); + w->menu = NULL; - if (md->item != NULL) - cmdq_continue(md->item); + redraw_invalidate_scene(w); + window_update_focus(w); + server_redraw_window(w); + } +} - if (md->cb != NULL) - md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); +void +menu_destroy(struct window *w) +{ + menu_free_data(w->menu); + w->menu = NULL; +} - free(md->r.ranges); - screen_free(&md->s); +void +menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy) +{ + *cx = md->px + 2; + if (md->choice == -1) + *cy = md->py; + else + *cy = md->py + 1 + md->choice; +} - menu_free(md->menu); - free(md->style); - free(md->selected_style); - free(md->border_style); - free(md); +struct screen * +menu_screen(struct menu_data *md) +{ + return (&md->s); +} + +u_int +menu_width(struct menu_data *md) +{ + return (md->menu->width + 4); +} + +u_int +menu_height(struct menu_data *md) +{ + return (md->menu->count + 2); +} + +u_int +menu_x(struct menu_data *md) +{ + return (md->px); +} + +u_int +menu_y(struct menu_data *md) +{ + return (md->py); } int -menu_key_cb(struct client *c, void *data, struct key_event *event) +menu_key(struct client *c, struct menu_data *md, struct key_event *event) { - struct menu_data *md = data; struct menu *menu = md->menu; struct mouse_event *m = &event->m; u_int i; - int count = menu->count, old = md->choice; + int n = menu->count, old = md->choice; int move; const char *name = NULL; const struct menu_item *item; struct cmd_parse_tree *tree; struct cmdq_item *new_item; + struct key_event saved_event; struct cmdq_state *state; char *error; @@ -338,7 +356,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) if (m->x < md->px || m->x > md->px + 4 + menu->width || m->y < md->py + 1 || - m->y > md->py + 1 + count - 1) { + m->y > md->py + 1 + n - 1) { if (~md->flags & MENU_STAYOPEN) { if (!move && MOUSE_RELEASE(m->b)) return (1); @@ -350,7 +368,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) } if (md->choice != -1) { md->choice = -1; - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); } return (0); } @@ -363,10 +381,10 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) } md->choice = m->y - (md->py + 1); if (md->choice != old) - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); return (0); } - for (i = 0; i < (u_int)count; i++) { + for (i = 0; i < (u_int)n; i++) { name = menu->items[i].name; if (name == NULL || *name == '-') continue; @@ -383,12 +401,12 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) old = 0; do { if (md->choice == -1 || md->choice == 0) - md->choice = count - 1; + md->choice = n - 1; else md->choice--; name = menu->items[md->choice].name; } while ((name == NULL || *name == '-') && md->choice != old); - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); return (0); case KEYC_BSPACE: if (~md->flags & MENU_TAB) @@ -397,7 +415,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) case '\011': /* Tab */ if (~md->flags & MENU_TAB) break; - if (md->choice == count - 1) + if (md->choice == n - 1) return (1); /* FALLTHROUGH */ case KEYC_DOWN: @@ -405,13 +423,13 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) if (old == -1) old = 0; do { - if (md->choice == -1 || md->choice == count - 1) + if (md->choice == -1 || md->choice == n - 1) md->choice = 0; else md->choice++; name = menu->items[md->choice].name; } while ((name == NULL || *name == '-') && md->choice != old); - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); return (0); case KEYC_PPAGE: case 'b'|KEYC_CTRL: @@ -429,21 +447,21 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) break; } } - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); break; case KEYC_NPAGE: - if (md->choice > count - 6) { - md->choice = count - 1; + if (md->choice > n - 6) { + md->choice = n - 1; name = menu->items[md->choice].name; } else { i = 5; while (i > 0) { md->choice++; name = menu->items[md->choice].name; - if (md->choice != count - 1 && + if (md->choice != n - 1 && (name != NULL && *name != '-')) i--; - else if (md->choice == count - 1) + else if (md->choice == n - 1) break; } } @@ -451,27 +469,27 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) md->choice--; name = menu->items[md->choice].name; } - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); break; case 'g': case KEYC_HOME: md->choice = 0; name = menu->items[md->choice].name; - while ((name == NULL || *name == '-') && md->choice != count - 1) { + while ((name == NULL || *name == '-') && md->choice != n - 1) { md->choice++; name = menu->items[md->choice].name; } - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); break; case 'G': case KEYC_END: - md->choice = count - 1; + md->choice = n - 1; name = menu->items[md->choice].name; while ((name == NULL || *name == '-') && md->choice != 0) { md->choice--; name = menu->items[md->choice].name; } - c->flags |= CLIENT_REDRAWOVERLAY; + server_redraw_window_menu(md->w); break; case 'f'|KEYC_CTRL: break; @@ -496,14 +514,17 @@ chosen: return (1); } if (md->cb != NULL) { - md->cb(md->menu, md->choice, item->key, md->data); - md->cb = NULL; - return (1); + md->cb(md->menu, md->choice, item->key, md->data); + md->cb = NULL; + return (1); } - if (md->item != NULL) - event = cmdq_get_event(md->item); - else + if (md->key != KEYC_NONE) { + memset(&saved_event, 0, sizeof saved_event); + saved_event.key = md->key; + memcpy(&saved_event.m, &md->m, sizeof saved_event.m); + event = &saved_event; + } else event = NULL; state = cmdq_new_state(&md->fs, event, 0); tree = cmd_parse_from_string(item->command, NULL, &error); @@ -520,11 +541,10 @@ chosen: return (1); } -static void -menu_resize_cb(struct client *c, void *data) +void +menu_resize(struct menu_data *md, struct window *w) { - struct menu_data *md = data; - u_int nx, ny, w, h; + u_int nx, ny, sx, sy; if (md == NULL) return; @@ -532,56 +552,74 @@ menu_resize_cb(struct client *c, void *data) nx = md->px; ny = md->py; - w = md->menu->width + 4; - h = md->menu->count + 2; + sx = md->menu->width + 4; + sy = md->menu->count + 2; - if (nx + w > c->tty.sx) { - if (c->tty.sx <= w) + if (nx + sx > w->sx) { + if (w->sx <= sx) nx = 0; else - nx = c->tty.sx - w; + nx = w->sx - sx; } - if (ny + h > c->tty.sy) { - if (c->tty.sy <= h) + if (ny + sy > w->sy) { + if (w->sy <= sy) ny = 0; else - ny = c->tty.sy - h; + ny = w->sy - sy; } md->px = nx; md->py = ny; } -struct menu_data * -menu_prepare(struct menu *menu, int flags, int starting_choice, +int +menu_display(struct menu *menu, int flags, int starting_choice, struct cmdq_item *item, u_int px, u_int py, struct client *c, enum box_lines lines, const char *style, const char *selected_style, const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; + struct key_event *event; int choice; const char *name; - struct options *o = c->session->curw->window->options; + u_int sx, sy; + struct window *w; + struct options *o; - if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) - return (NULL); - if (px + menu->width + 4 > c->tty.sx) - px = c->tty.sx - menu->width - 4; - if (py + menu->count + 2 > c->tty.sy) - py = c->tty.sy - menu->count - 2; + if (fs == NULL) + w = c->session->curw->window; + else + w = fs->w; + o = w->options; - /* Remember where this menu is so -x/-y L can reuse the position. */ - c->menu_last_px = px; - c->menu_last_py = py; + sx = menu->width + 4; + sy = menu->count + 2; + if (sx >= w->sx) + px = 0; + else if (px + sx > w->sx) + px = w->sx - sx; + if (sy >= w->sy) + py = 0; + else if (py + sy > w->sy) + py = w->sy - sy; + w->menu_last_px = px; + w->menu_last_py = py; if (lines == BOX_LINES_DEFAULT) lines = options_get_number(o, "menu-border-lines"); md = xcalloc(1, sizeof *md); - md->item = item; + md->w = w; md->flags = flags; md->border_lines = lines; + md->key = KEYC_NONE; + + if (item != NULL) { + event = cmdq_get_event(item); + md->key = event->key; + memcpy(&md->m, &event->m, sizeof md->m); + } if (style != NULL) md->style = xstrdup(style); @@ -592,7 +630,9 @@ menu_prepare(struct menu *menu, int flags, int starting_choice, if (fs != NULL) cmd_find_copy_state(&md->fs, fs); - screen_init(&md->s, menu->width + 4, menu->count + 2, 0); + else if (cmd_find_from_window(&md->fs, w, 0) != 0) + cmd_find_clear_state(&md->fs, 0); + screen_init(&md->s, sx, sy, 0); if (~md->flags & MENU_NOMOUSE) md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON); md->s.mode &= ~MODE_CURSOR; @@ -603,6 +643,9 @@ menu_prepare(struct menu *menu, int flags, int starting_choice, md->menu = menu; md->choice = -1; + md->cb = cb; + md->data = data; + if (md->flags & MENU_NOMOUSE) { if (starting_choice >= (int)menu->count) { starting_choice = menu->count - 1; @@ -634,25 +677,11 @@ menu_prepare(struct menu *menu, int flags, int starting_choice, } } - md->cb = cb; - md->data = data; - return (md); -} + menu_close(md->w); + md->w->menu = md; -int -menu_display(struct menu *menu, int flags, int starting_choice, - struct cmdq_item *item, u_int px, u_int py, struct client *c, - enum box_lines lines, const char *style, const char *selected_style, - const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, - void *data) -{ - struct menu_data *md; - - md = menu_prepare(menu, flags, starting_choice, item, px, py, c, lines, - style, selected_style, border_style, fs, cb, data); - if (md == NULL) - return (-1); - server_client_set_overlay(c, 0, menu_check_cb, menu_mode_cb, - menu_draw_cb, menu_key_cb, menu_free_cb, menu_resize_cb, md); + redraw_invalidate_scene(md->w); + window_update_focus(md->w); + server_redraw_window(md->w); return (0); } diff --git a/mode-tree.c b/mode-tree.c index ac44fec24..a5b2f935e 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mode-tree.c,v 1.99 2026/07/06 12:08:52 nicm Exp $ */ +/* $OpenBSD: mode-tree.c,v 1.100 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -1409,6 +1409,8 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, x -= (menu->width + 4) / 2; else x = 0; + x += mtd->wp->xoff; + y += mtd->wp->yoff; if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) { mode_tree_remove_ref(mtd); diff --git a/options-table.c b/options-table.c index 55f169683..d06397849 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.233 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.237 2026/07/14 17:17:17 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -239,33 +239,40 @@ static const char *options_table_status_format_default[] = { }; /* Helpers for hook options. */ -#define OPTIONS_TABLE_HOOK(hook_name, default_value) \ +#define OPTIONS_TABLE_HOOK(hook_name, default_value, hook_text) \ { .name = hook_name, \ .type = OPTIONS_TABLE_COMMAND, \ .scope = OPTIONS_TABLE_SESSION, \ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \ .default_str = default_value, \ - .separator = "" \ + .separator = "", \ + .text = hook_text \ } -#define OPTIONS_TABLE_PANE_HOOK(hook_name, default_value) \ +#define OPTIONS_TABLE_PANE_HOOK(hook_name, default_value, hook_text) \ { .name = hook_name, \ .type = OPTIONS_TABLE_COMMAND, \ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, \ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \ .default_str = default_value, \ - .separator = "" \ + .separator = "", \ + .text = hook_text \ } -#define OPTIONS_TABLE_WINDOW_HOOK(hook_name, default_value) \ +#define OPTIONS_TABLE_WINDOW_HOOK(hook_name, default_value, hook_text) \ { .name = hook_name, \ .type = OPTIONS_TABLE_COMMAND, \ .scope = OPTIONS_TABLE_WINDOW, \ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \ .default_str = default_value, \ - .separator = "" \ + .separator = "", \ + .text = hook_text \ } +#define OPTIONS_TABLE_AFTER_HOOK(command_name) \ + OPTIONS_TABLE_HOOK("after-" command_name, "", \ + "Run after the " command_name " command completes.") + /* Map of name conversions. */ const struct options_name_map options_other_names[] = { { "display-panes-color", "display-panes-colour" }, @@ -807,40 +814,6 @@ const struct options_table_entry options_table[] = { "the client to another session if any exist." }, - { .name = "display-panes-active-colour", - .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_SESSION, - .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "themered", - .text = "Colour of the active pane for 'display-panes'." - }, - - { .name = "display-panes-colour", - .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_SESSION, - .flags = OPTIONS_TABLE_IS_COLOUR, - .default_str = "themeblue", - .text = "Colour of not active panes for 'display-panes'." - }, - - { .name = "display-panes-format", - .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_SESSION, - .default_str = "#[align=right]#{pane_width}x#{pane_height}", - .text = "Format of text shown by 'display-panes', expanded for each " - "pane." - }, - - { .name = "display-panes-time", - .type = OPTIONS_TABLE_NUMBER, - .scope = OPTIONS_TABLE_SESSION, - .minimum = 1, - .maximum = INT_MAX, - .default_num = 1000, - .unit = "milliseconds", - .text = "Time for which 'display-panes' should show pane numbers." - }, - { .name = "display-time", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SESSION, @@ -1347,6 +1320,49 @@ const struct options_table_entry options_table[] = { .text = "Time format of the clock in clock mode." }, + { .name = "display-panes-active-colour", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "themered", + .text = "Colour of the active pane for 'display-panes'." + }, + + { .name = "display-panes-border-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=themedarkgrey,fg=themelightgrey", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the pane borders in 'display-panes'." + }, + + { .name = "display-panes-colour", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .flags = OPTIONS_TABLE_IS_COLOUR, + .default_str = "themeblue", + .text = "Colour of not active panes for 'display-panes'." + }, + + { .name = "display-panes-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "#[align=right]#{pane_width}x#{pane_height}", + .text = "Format of text shown by 'display-panes', expanded for each " + "pane." + }, + + { .name = "display-panes-time", + .type = OPTIONS_TABLE_NUMBER, + .scope = OPTIONS_TABLE_WINDOW, + .minimum = 1, + .maximum = INT_MAX, + .default_num = 1000, + .unit = "milliseconds", + .text = "Time for which 'display-panes' should show pane numbers." + }, + { .name = "copy-mode-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, @@ -1881,86 +1897,151 @@ const struct options_table_entry options_table[] = { }, /* Hook options. */ - OPTIONS_TABLE_HOOK("after-bind-key", ""), - OPTIONS_TABLE_HOOK("after-capture-pane", ""), - OPTIONS_TABLE_HOOK("after-copy-mode", ""), - OPTIONS_TABLE_HOOK("after-display-message", ""), - OPTIONS_TABLE_HOOK("after-display-panes", ""), - OPTIONS_TABLE_HOOK("after-kill-pane", ""), - OPTIONS_TABLE_HOOK("after-list-buffers", ""), - OPTIONS_TABLE_HOOK("after-list-clients", ""), - OPTIONS_TABLE_HOOK("after-list-keys", ""), - OPTIONS_TABLE_HOOK("after-list-panes", ""), - OPTIONS_TABLE_HOOK("after-list-sessions", ""), - OPTIONS_TABLE_HOOK("after-list-windows", ""), - OPTIONS_TABLE_HOOK("after-load-buffer", ""), - OPTIONS_TABLE_HOOK("after-lock-server", ""), - OPTIONS_TABLE_HOOK("after-new-session", ""), - OPTIONS_TABLE_HOOK("after-new-window", ""), - OPTIONS_TABLE_HOOK("after-paste-buffer", ""), - OPTIONS_TABLE_HOOK("after-pipe-pane", ""), - OPTIONS_TABLE_HOOK("after-queue", ""), - OPTIONS_TABLE_HOOK("after-refresh-client", ""), - OPTIONS_TABLE_HOOK("after-rename-session", ""), - OPTIONS_TABLE_HOOK("after-rename-window", ""), - OPTIONS_TABLE_HOOK("after-resize-pane", ""), - OPTIONS_TABLE_HOOK("after-resize-window", ""), - OPTIONS_TABLE_HOOK("after-save-buffer", ""), - OPTIONS_TABLE_HOOK("after-select-layout", ""), - OPTIONS_TABLE_HOOK("after-select-pane", ""), - OPTIONS_TABLE_HOOK("after-select-window", ""), - OPTIONS_TABLE_HOOK("after-send-keys", ""), - OPTIONS_TABLE_HOOK("after-set-buffer", ""), - OPTIONS_TABLE_HOOK("after-set-environment", ""), - OPTIONS_TABLE_HOOK("after-set-hook", ""), - OPTIONS_TABLE_HOOK("after-set-option", ""), - OPTIONS_TABLE_HOOK("after-show-environment", ""), - OPTIONS_TABLE_HOOK("after-show-messages", ""), - OPTIONS_TABLE_HOOK("after-show-options", ""), - OPTIONS_TABLE_HOOK("after-split-window", ""), - OPTIONS_TABLE_HOOK("after-unbind-key", ""), - OPTIONS_TABLE_HOOK("alert-activity", ""), - OPTIONS_TABLE_HOOK("alert-bell", ""), - OPTIONS_TABLE_HOOK("alert-silence", ""), - OPTIONS_TABLE_HOOK("client-active", ""), - OPTIONS_TABLE_HOOK("client-attached", ""), - OPTIONS_TABLE_HOOK("client-detached", ""), - OPTIONS_TABLE_HOOK("client-focus-in", ""), - OPTIONS_TABLE_HOOK("client-focus-out", ""), - OPTIONS_TABLE_HOOK("client-resized", ""), - OPTIONS_TABLE_HOOK("client-session-changed", ""), - OPTIONS_TABLE_HOOK("client-light-theme", ""), - OPTIONS_TABLE_HOOK("client-dark-theme", ""), - OPTIONS_TABLE_HOOK("command-error", ""), - OPTIONS_TABLE_HOOK("marked-pane-changed", ""), - OPTIONS_TABLE_PANE_HOOK("pane-command-finished", ""), - OPTIONS_TABLE_PANE_HOOK("pane-command-started", ""), - OPTIONS_TABLE_PANE_HOOK("pane-created", ""), - OPTIONS_TABLE_PANE_HOOK("pane-died", ""), - OPTIONS_TABLE_PANE_HOOK("pane-exited", ""), - OPTIONS_TABLE_PANE_HOOK("pane-focus-in", ""), - OPTIONS_TABLE_PANE_HOOK("pane-focus-out", ""), - OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", ""), - OPTIONS_TABLE_PANE_HOOK("pane-mode-entered", ""), - OPTIONS_TABLE_PANE_HOOK("pane-mode-exited", ""), - OPTIONS_TABLE_PANE_HOOK("pane-prompt-closed", ""), - OPTIONS_TABLE_PANE_HOOK("pane-prompt-opened", ""), - OPTIONS_TABLE_PANE_HOOK("pane-resized", ""), - OPTIONS_TABLE_PANE_HOOK("pane-set-clipboard", ""), - OPTIONS_TABLE_PANE_HOOK("pane-shell-prompt", ""), - OPTIONS_TABLE_PANE_HOOK("pane-title-changed", ""), - OPTIONS_TABLE_HOOK("session-closed", ""), - OPTIONS_TABLE_HOOK("session-created", ""), - OPTIONS_TABLE_HOOK("session-renamed", ""), - OPTIONS_TABLE_HOOK("session-window-changed", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", ""), - OPTIONS_TABLE_HOOK("window-linked", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-renamed", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-resized", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-unzoomed", ""), - OPTIONS_TABLE_WINDOW_HOOK("window-zoomed", ""), - OPTIONS_TABLE_HOOK("window-unlinked", ""), + OPTIONS_TABLE_AFTER_HOOK("bind-key"), + OPTIONS_TABLE_AFTER_HOOK("capture-pane"), + OPTIONS_TABLE_AFTER_HOOK("copy-mode"), + OPTIONS_TABLE_AFTER_HOOK("display-message"), + OPTIONS_TABLE_AFTER_HOOK("display-panes"), + OPTIONS_TABLE_AFTER_HOOK("kill-pane"), + OPTIONS_TABLE_AFTER_HOOK("list-buffers"), + OPTIONS_TABLE_AFTER_HOOK("list-clients"), + OPTIONS_TABLE_AFTER_HOOK("list-keys"), + OPTIONS_TABLE_AFTER_HOOK("list-panes"), + OPTIONS_TABLE_AFTER_HOOK("list-sessions"), + OPTIONS_TABLE_AFTER_HOOK("list-windows"), + OPTIONS_TABLE_AFTER_HOOK("load-buffer"), + OPTIONS_TABLE_AFTER_HOOK("lock-server"), + OPTIONS_TABLE_AFTER_HOOK("new-session"), + OPTIONS_TABLE_AFTER_HOOK("new-window"), + OPTIONS_TABLE_AFTER_HOOK("paste-buffer"), + OPTIONS_TABLE_AFTER_HOOK("pipe-pane"), + OPTIONS_TABLE_AFTER_HOOK("refresh-client"), + OPTIONS_TABLE_AFTER_HOOK("rename-session"), + OPTIONS_TABLE_AFTER_HOOK("rename-window"), + OPTIONS_TABLE_AFTER_HOOK("resize-pane"), + OPTIONS_TABLE_AFTER_HOOK("resize-window"), + OPTIONS_TABLE_AFTER_HOOK("save-buffer"), + OPTIONS_TABLE_AFTER_HOOK("select-layout"), + OPTIONS_TABLE_AFTER_HOOK("select-pane"), + OPTIONS_TABLE_AFTER_HOOK("select-window"), + OPTIONS_TABLE_AFTER_HOOK("send-keys"), + OPTIONS_TABLE_AFTER_HOOK("set-buffer"), + OPTIONS_TABLE_AFTER_HOOK("set-environment"), + OPTIONS_TABLE_AFTER_HOOK("set-hook"), + OPTIONS_TABLE_AFTER_HOOK("set-option"), + OPTIONS_TABLE_AFTER_HOOK("show-environment"), + OPTIONS_TABLE_AFTER_HOOK("show-messages"), + OPTIONS_TABLE_AFTER_HOOK("show-options"), + OPTIONS_TABLE_AFTER_HOOK("split-window"), + OPTIONS_TABLE_AFTER_HOOK("unbind-key"), + OPTIONS_TABLE_HOOK("alert-activity", "", + "Run when a window has activity."), + OPTIONS_TABLE_HOOK("alert-bell", "", + "Run when a window has received a bell."), + OPTIONS_TABLE_HOOK("alert-silence", "", + "Run when a window has been silent."), + OPTIONS_TABLE_HOOK("client-active", "", + "Run when a client becomes the latest active client of its " + "session."), + OPTIONS_TABLE_HOOK("client-attached", "", + "Run when a client is attached."), + OPTIONS_TABLE_HOOK("client-created", "", + "Run when a client is created."), + OPTIONS_TABLE_HOOK("client-closed", "", + "Run when a client is closed."), + OPTIONS_TABLE_HOOK("client-detached", "", + "Run when a client is detached."), + OPTIONS_TABLE_HOOK("client-focus-in", "", + "Run when focus enters a client."), + OPTIONS_TABLE_HOOK("client-focus-out", "", + "Run when focus exits a client."), + OPTIONS_TABLE_HOOK("client-resized", "", + "Run when a client is resized."), + OPTIONS_TABLE_HOOK("client-session-changed", "", + "Run when a client's attached session is changed."), + OPTIONS_TABLE_HOOK("client-light-theme", "", + "Run when a client switches to a light theme."), + OPTIONS_TABLE_HOOK("client-dark-theme", "", + "Run when a client switches to a dark theme."), + OPTIONS_TABLE_HOOK("command-error", "", + "Run when a command fails."), + OPTIONS_TABLE_HOOK("marked-pane-changed", "", + "Run when the marked pane is set or cleared."), + OPTIONS_TABLE_PANE_HOOK("pane-activity", "", + "Run when there is new output in a pane."), + OPTIONS_TABLE_PANE_HOOK("pane-bell", "", + "Run when a pane receives a bell."), + OPTIONS_TABLE_PANE_HOOK("pane-command-finished", "", + "Run when an OSC 133 command finishes in a pane."), + OPTIONS_TABLE_PANE_HOOK("pane-command-started", "", + "Run when an OSC 133 command starts in a pane."), + OPTIONS_TABLE_PANE_HOOK("pane-created", "", + "Run when a pane is created or respawned."), + OPTIONS_TABLE_PANE_HOOK("pane-died", "", + "Run when the program running in a pane exits, but remain-on-exit " + "is on so the pane has not closed."), + OPTIONS_TABLE_PANE_HOOK("pane-exited", "", + "Run when the program running in a pane exits."), + OPTIONS_TABLE_PANE_HOOK("pane-focus-in", "", + "Run when the focus enters a pane, if the focus-events option is " + "on."), + OPTIONS_TABLE_PANE_HOOK("pane-focus-out", "", + "Run when the focus exits a pane, if the focus-events option is " + "on."), + OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", "", + "Run when a pane changes mode."), + OPTIONS_TABLE_PANE_HOOK("pane-mode-entered", "", + "Run when a pane enters a mode."), + OPTIONS_TABLE_PANE_HOOK("pane-mode-exited", "", + "Run when a pane exits a mode."), + OPTIONS_TABLE_PANE_HOOK("pane-moved", "", + "Run when a pane is moved to another window."), + OPTIONS_TABLE_PANE_HOOK("pane-prompt-closed", "", + "Run when a prompt in a pane is closed."), + OPTIONS_TABLE_PANE_HOOK("pane-prompt-opened", "", + "Run when a prompt is opened in a pane."), + OPTIONS_TABLE_PANE_HOOK("pane-resized", "", + "Run when a pane is resized."), + OPTIONS_TABLE_PANE_HOOK("pane-set-clipboard", "", + "Run when the terminal clipboard is set using the xterm escape " + "sequence."), + OPTIONS_TABLE_PANE_HOOK("pane-shell-prompt", "", + "Run when an OSC 133 shell prompt starts in a pane."), + OPTIONS_TABLE_PANE_HOOK("pane-title-changed", "", + "Run when a pane title is changed."), + OPTIONS_TABLE_HOOK("session-closed", "", + "Run when a session is closed."), + OPTIONS_TABLE_HOOK("session-created", "", + "Run when a new session is created."), + OPTIONS_TABLE_HOOK("session-added-to-group", "", + "Run when a session is added to a session group."), + OPTIONS_TABLE_HOOK("session-renamed", "", + "Run when a session is renamed."), + OPTIONS_TABLE_HOOK("session-removed-from-group", "", + "Run when a session is removed from a session group."), + OPTIONS_TABLE_HOOK("session-window-changed", "", + "Run when a session changes its active window."), + OPTIONS_TABLE_WINDOW_HOOK("window-created", "", + "Run when a window is created."), + OPTIONS_TABLE_WINDOW_HOOK("window-closed", "", + "Run when a window is closed."), + OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", "", + "Run when a window layout is changed."), + OPTIONS_TABLE_HOOK("window-linked", "", + "Run when a window is linked into a session."), + OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", "", + "Run when a window changes its active pane."), + OPTIONS_TABLE_WINDOW_HOOK("window-renamed", "", + "Run when a window is renamed."), + OPTIONS_TABLE_WINDOW_HOOK("window-resized", "", + "Run when a window is resized. This may be after the " + "client-resized hook is run."), + OPTIONS_TABLE_WINDOW_HOOK("window-unzoomed", "", + "Run when a window is unzoomed."), + OPTIONS_TABLE_WINDOW_HOOK("window-zoomed", "", + "Run when a window is zoomed."), + OPTIONS_TABLE_HOOK("window-unlinked", "", + "Run when a window is unlinked from a session."), { .name = NULL } }; diff --git a/popup.c b/popup.c index fad0eb498..3bc7eafa6 100644 --- a/popup.c +++ b/popup.c @@ -1,4 +1,4 @@ -/* $OpenBSD: popup.c,v 1.75 2026/07/09 07:32:58 nicm Exp $ */ +/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -42,7 +42,6 @@ struct popup_data { struct colour_palette palette; struct visible_ranges r; - struct visible_ranges or[2]; struct job *job; struct input_ctx *ictx; @@ -50,8 +49,6 @@ struct popup_data { popup_close_cb cb; void *arg; - struct menu *menu; - struct menu_data *md; int close; /* Current position and size. */ @@ -75,19 +72,6 @@ struct popup_data { u_int lb; }; -static const struct menu_item popup_menu_items[] = { - { "Close", 'q', NULL }, - { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL }, - { "", KEYC_NONE, NULL }, - { "Fill Space", 'F', NULL }, - { "Centre", 'C', NULL }, - { "", KEYC_NONE, NULL }, - { "To Horizontal Pane", 'h', NULL }, - { "To Vertical Pane", 'v', NULL }, - - { NULL, KEYC_NONE, NULL } -}; - static void popup_free(struct popup_data *pd) { @@ -97,8 +81,6 @@ popup_free(struct popup_data *pd) job_free(pd->job); input_free(pd->ictx); - free(pd->or[0].ranges); - free(pd->or[1].ranges); free(pd->r.ranges); screen_free(&pd->s); colour_palette_free(&pd->palette); @@ -205,9 +187,6 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) { struct popup_data *pd = data; - if (pd->md != NULL) - return (menu_mode_cb(c, pd->md, cx, cy)); - if (pd->border_lines == BOX_LINES_NONE) { *cx = pd->px + pd->s.cx; *cy = pd->py + pd->s.cy; @@ -220,52 +199,11 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) /* Return parts of the input range which are not obstructed by the popup. */ static struct visible_ranges * -popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx) +popup_check_cb(__unused struct client* c, void *data, u_int px, u_int py, + u_int nx) { struct popup_data *pd = data; struct visible_ranges *r = &pd->r; - struct visible_ranges *mr; - u_int i, j, k = 0; - - if (pd->md != NULL) { - /* - * Work out the visible ranges for the menu (that is, the - * ranges not covered by the menu). A menu should have at most - * two ranges and we rely on this being the case. - */ - mr = menu_check_cb(c, pd->md, px, py, nx); - if (mr->used > 2) - fatalx("too many menu ranges"); - - /* - * Walk the ranges still visible under the menu and check if - * each is visible under the popup as well. At most there can be - * three total ranges if popup and menu do not intersect. - */ - for (i = 0; i < mr->used; i++) { - server_client_overlay_range(pd->px, pd->py, pd->sx, - pd->sy, mr->ranges[i].px, py, mr->ranges[i].nx, - &pd->or[i]); - } - - /* - * We now have nonoverlapping ranges from left to right. - * Combine them together into the output. - */ - server_client_ensure_ranges(r, 3); - for (i = 0; i < mr->used; i++) { - for (j = 0; j < pd->or[i].used; j++) { - if (pd->or[i].ranges[j].nx == 0) - continue; - if (k >= 3) - fatalx("too many popup & menu ranges"); - r->ranges[k].px = pd->or[i].ranges[j].px; - r->ranges[k].nx = pd->or[i].ranges[j].nx; - k++; - } - } - return (r); - } server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, r); @@ -315,34 +253,21 @@ popup_draw_cb(struct client *c, void *data) style_ctx.dim = 0; style_ctx.hyperlinks = s.hyperlinks; - if (pd->md != NULL) { - c->overlay_check = menu_check_cb; - c->overlay_data = pd->md; - } else { - c->overlay_check = NULL; - c->overlay_data = NULL; - } + c->overlay_check = NULL; + c->overlay_data = NULL; for (i = 0; i < pd->sy; i++) tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx); screen_free(&s); - if (pd->md != NULL) { - c->overlay_check = NULL; - c->overlay_data = NULL; - menu_draw_cb(c, pd->md); - } c->overlay_check = popup_check_cb; c->overlay_data = pd; } static void -popup_free_cb(struct client *c, void *data) +popup_free_cb(__unused struct client *c, void *data) { struct popup_data *pd = data; struct cmdq_item *item = pd->item; - if (pd->md != NULL) - menu_free_cb(c, pd->md); - if (pd->cb != NULL) pd->cb(pd->status, pd->arg); @@ -364,8 +289,6 @@ popup_resize_cb(__unused struct client *c, void *data) if (pd == NULL) return; - if (pd->md != NULL) - menu_free_cb(c, pd->md); /* Adjust position and size. */ if (pd->psy > tty->sy) @@ -397,96 +320,6 @@ popup_resize_cb(__unused struct client *c, void *data) } } -static void -popup_make_pane(struct popup_data *pd, enum layout_type type) -{ - struct client *c = pd->c; - struct session *s = c->session; - struct window *w = s->curw->window; - struct layout_cell *lc; - struct window_pane *wp = w->active, *new_wp; - u_int hlimit; - const char *shell; - - window_unzoom(w, 1); - - lc = layout_split_pane(wp, type, -1, 0); - if (lc == NULL) - return; - hlimit = options_get_number(s->options, "history-limit"); - new_wp = window_add_pane(wp->window, NULL, hlimit, 0); - layout_assign_pane(lc, new_wp, 0); - - if (pd->job != NULL) { - new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty, - sizeof new_wp->tty); - pd->job = NULL; - } - - screen_set_title(&pd->s, new_wp->base.title, 0); - screen_free(&new_wp->base); - memcpy(&new_wp->base, &pd->s, sizeof wp->base); - screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1); - screen_init(&pd->s, 1, 1, 0); - - shell = options_get_string(s->options, "default-shell"); - if (!checkshell(shell)) - shell = _PATH_BSHELL; - new_wp->shell = xstrdup(shell); - - window_pane_set_event(new_wp); - window_set_active_pane(w, new_wp, 1); - new_wp->flags |= PANE_CHANGED; - - pd->close = 1; -} - -static void -popup_menu_done(__unused struct menu *menu, __unused u_int choice, - key_code key, void *data) -{ - struct popup_data *pd = data; - struct client *c = pd->c; - struct paste_buffer *pb; - const char *buf; - size_t len; - - pd->md = NULL; - pd->menu = NULL; - server_redraw_client(pd->c); - - switch (key) { - case 'p': - pb = paste_get_top(NULL); - if (pb != NULL) { - buf = paste_buffer_data(pb, &len); - bufferevent_write(job_get_event(pd->job), buf, len); - } - break; - case 'F': - pd->sx = c->tty.sx; - pd->sy = c->tty.sy; - pd->px = 0; - pd->py = 0; - server_redraw_client(c); - break; - case 'C': - pd->px = c->tty.sx / 2 - pd->sx / 2; - pd->py = c->tty.sy / 2 - pd->sy / 2; - server_redraw_client(c); - break; - case 'h': - popup_make_pane(pd, LAYOUT_LEFTRIGHT); - break; - case 'v': - popup_make_pane(pd, LAYOUT_TOPBOTTOM); - break; - case 'q': - pd->close = 1; - break; - } -} - static void popup_handle_drag(struct client *c, struct popup_data *pd, struct mouse_event *m) @@ -552,21 +385,9 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) struct mouse_event *m = &event->m; const char *buf; size_t len; - u_int px, py, x; + u_int px, py; enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE; - if (pd->md != NULL) { - if (menu_key_cb(c, pd->md, event) == 1) { - pd->md = NULL; - pd->menu = NULL; - if (pd->close) - server_client_clear_overlay(c); - else - server_redraw_client(c); - } - return (0); - } - if (KEYC_IS_MOUSE(event->key)) { if (pd->dragging != OFF) { popup_handle_drag(c, pd, m); @@ -576,8 +397,6 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) m->x > pd->px + pd->sx - 1 || m->y < pd->py || m->y > pd->py + pd->sy - 1) { - if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3) - goto menu; return (0); } if (pd->border_lines != BOX_LINES_NONE) { @@ -593,7 +412,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) if ((m->b & MOUSE_MASK_MODIFIERS) == 0 && MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 && (border == LEFT || border == TOP)) - goto menu; + goto out; if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) || (border != NONE && !MOUSE_DRAG(m->lb))) { if (!MOUSE_DRAG(m->b)) @@ -633,17 +452,6 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) } return (0); -menu: - pd->menu = menu_create(""); - menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL); - if (m->x >= (pd->menu->width + 4) / 2) - x = m->x - (pd->menu->width + 4) / 2; - else - x = 0; - pd->md = menu_prepare(pd->menu, 0, 0, NULL, x, m->y, c, - BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, popup_menu_done, pd); - c->flags |= CLIENT_REDRAWOVERLAY; - out: pd->lx = m->x; pd->ly = m->y; @@ -664,13 +472,8 @@ popup_job_update_cb(struct job *job) if (size == 0) return; - if (pd->md != NULL) { - c->overlay_check = menu_check_cb; - c->overlay_data = pd->md; - } else { - c->overlay_check = NULL; - c->overlay_data = NULL; - } + c->overlay_check = NULL; + c->overlay_data = NULL; input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size); c->overlay_check = popup_check_cb; c->overlay_data = pd; diff --git a/prompt-history.c b/prompt-history.c index ad1ab4047..109043aa7 100644 --- a/prompt-history.c +++ b/prompt-history.c @@ -1,4 +1,4 @@ -/* $OpenBSD: prompt-history.c,v 1.1 2026/06/25 11:39:11 nicm Exp $ */ +/* $OpenBSD: prompt-history.c,v 1.2 2026/07/13 19:35:22 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -79,8 +79,9 @@ void prompt_load_history(void) { FILE *f; - char *history_file, *line, *tmp; - size_t length; + char *history_file, *line = NULL; + size_t length = 0; + ssize_t got; if ((history_file = prompt_find_history_file()) == NULL) return; @@ -94,23 +95,13 @@ prompt_load_history(void) } free(history_file); - for (;;) { - if ((line = fgetln(f, &length)) == NULL) - break; - - if (length > 0) { - if (line[length - 1] == '\n') { - line[length - 1] = '\0'; - prompt_add_typed_history(line); - } else { - tmp = xmalloc(length + 1); - memcpy(tmp, line, length); - tmp[length] = '\0'; - prompt_add_typed_history(tmp); - free(tmp); - } - } + while ((got = getline(&line, &length, f)) != -1) { + if (got > 0 && line[got - 1] == '\n') + line[got - 1] = '\0'; + if (got > 0) + prompt_add_typed_history(line); } + free(line); fclose(f); } diff --git a/prompt.c b/prompt.c index 6187a7a12..c12a249a9 100644 --- a/prompt.c +++ b/prompt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: prompt.c,v 1.4 2026/06/26 14:40:30 nicm Exp $ */ +/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -42,6 +42,8 @@ struct prompt { char *word_separators; struct grid_cell style; struct grid_cell command_style; + char *style_str; + char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; @@ -61,11 +63,30 @@ struct prompt { char *complete_display; }; +struct prompt_layout { + u_int area_x; + u_int area_width; + + u_int content_x; + u_int content_width; + + u_int label_width; + u_int input_x; + u_int cursor_x; + + u_int input_offset; + u_int input_width; +}; + static char *prompt_complete(struct prompt *, const char *, u_int); static void prompt_clear_complete(struct prompt *); -static char *prompt_expand(struct prompt *); +static struct format_tree *prompt_format_tree(struct prompt *); +static char *prompt_expand1(struct prompt *, struct format_tree *); +static void prompt_effective_style(struct prompt *, struct style *, + struct format_tree *); +static void prompt_layout(struct prompt *, u_int, u_int, + struct prompt_layout *, char **, struct style *); static int prompt_replace_complete(struct prompt *, const char *); -static u_int prompt_width(struct prompt *, u_int); /* Get prompt flags as a string. */ static const char * @@ -120,6 +141,8 @@ prompt_set_options(struct prompt_create_data *pd, struct session *s) style_apply(&pd->style, oo, "message-style", NULL); style_apply(&pd->command_style, oo, "message-command-style", NULL); + pd->style_str = options_get_string(oo, "message-style"); + pd->command_style_str = options_get_string(oo, "message-command-style"); n = options_get_number(oo, "prompt-cursor-style"); screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); n = options_get_number(oo, "prompt-command-cursor-style"); @@ -179,6 +202,8 @@ prompt_create(const struct prompt_create_data *pd) memcpy(&pr->style, &pd->style, sizeof pr->style); memcpy(&pr->command_style, &pd->command_style, sizeof pr->command_style); + pr->style_str = xstrdup(pd->style_str); + pr->command_style_str = xstrdup(pd->command_style_str); pr->cstyle = pd->cstyle; pr->command_cstyle = pd->command_cstyle; pr->ccolour = pd->ccolour; @@ -201,6 +226,8 @@ prompt_free(struct prompt *pr) if (pr->freecb != NULL && pr->data != NULL) pr->freecb(pr->data); free(pr->message_format); + free(pr->style_str); + free(pr->command_style_str); free(pr->word_separators); free(pr->last); free(pr->string); @@ -321,16 +348,17 @@ prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, * 0 otherwise. */ static int -prompt_redraw_quote(const struct prompt *pr, u_int pcursor, - struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, +prompt_redraw_quote(const struct prompt *pr, u_int pcursor, u_int input_x, + struct screen_write_ctx *ctx, u_int offset, u_int pw, u_int *w, struct grid_cell *gc) { struct utf8_data ud; - if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { + if (pr->flags & PROMPT_QUOTENEXT && + pcursor >= offset && + ctx->s->cx == input_x + pcursor - offset) { utf8_set(&ud, '^'); - return (prompt_redraw_character(ctx, offset, pwidth, - width, gc, &ud)); + return (prompt_redraw_character(ctx, offset, pw, w, gc, &ud)); } return (1); } @@ -368,12 +396,12 @@ prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, free(ud); } -/* Expand prompt string using the current input. */ -static char * -prompt_expand(struct prompt *pr) +/* Create the prompt format tree using the current input. */ +static struct format_tree * +prompt_format_tree(struct prompt *pr) { struct format_tree *ft; - char *expanded, *prompt, *tmp; + char *tmp; if (cmd_find_valid_state(&pr->state)) ft = format_create_from_state(NULL, NULL, &pr->state); @@ -385,31 +413,145 @@ prompt_expand(struct prompt *pr) format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); - prompt = format_expand_time(ft, pr->string); - format_add(ft, "message", "%s", prompt); if (pr->flags & PROMPT_COMMANDMODE) format_add(ft, "command_prompt", "1"); else format_add(ft, "command_prompt", "0"); + return (ft); +} + +/* Expand prompt string using the current input. */ +static char * +prompt_expand1(struct prompt *pr, struct format_tree *ft) +{ + char *expanded, *prompt; + + prompt = format_expand_time(ft, pr->string); + format_add(ft, "message", "%s", prompt); expanded = format_expand_time(ft, pr->message_format); free(prompt); - format_free(ft); return (expanded); } -/* Work out the width used by the prompt string. */ -static u_int -prompt_width(struct prompt *pr, u_int aw) +/* Get the effective message style for the current prompt. */ +static void +prompt_effective_style(struct prompt *pr, struct style *sy, + struct format_tree *ft) { - char *expanded; - u_int start; + const char *s; + struct grid_cell *gc; + char *expanded; - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - free(expanded); - return (start); + if (pr->flags & PROMPT_COMMANDMODE) { + s = pr->command_style_str; + gc = &pr->command_style; + } else { + s = pr->style_str; + gc = &pr->style; + } + + style_set(sy, gc); + if (s != NULL) { + expanded = format_expand_time(ft, s); + if (style_parse(sy, &grid_default_cell, expanded) != 0) + style_set(sy, gc); + free(expanded); + } +} + +/* Work out where the editable prompt content appears. */ +static void +prompt_layout(struct prompt *pr, u_int ax, u_int aw, struct prompt_layout *pl, + char **expanded, struct style *sy) +{ + char *local = NULL; + struct format_tree *ft; + u_int pcursor, pwidth, end, width, offset, avail; + + memset(pl, 0, sizeof *pl); + pl->area_x = ax; + pl->area_width = aw; + + ft = prompt_format_tree(pr); + if (sy != NULL) + prompt_effective_style(pr, sy, ft); + if (expanded != NULL) + *expanded = prompt_expand1(pr, ft); + else { + local = prompt_expand1(pr, ft); + expanded = &local; + } + format_free(ft); + + if (aw == 0) { + free(local); + return; + } + + pl->label_width = format_width(*expanded); + if (pl->label_width > aw) + pl->label_width = aw; + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + + avail = aw - pl->label_width; + if (avail == 0) { + pl->input_offset = 0; + pl->input_width = 0; + pl->cursor_x = pl->label_width; + } else { + if (pcursor >= avail) { + offset = (pcursor - avail) + 1; + width = avail; + } else { + offset = 0; + width = pwidth; + } + if (width > avail) + width = avail; + + pl->input_offset = offset; + pl->input_width = width; + pl->cursor_x = pl->label_width + pcursor - offset; + } + + pl->content_width = pl->label_width + pl->input_width; + if (pr->complete_display != NULL && + pr->index == utf8_strlen(pr->buffer) && + pl->cursor_x < aw) { + avail = aw - pl->cursor_x; + width = utf8_cstrwidth(pr->complete_display); + if (width > avail) + width = avail; + end = pl->cursor_x + width; + if (end > pl->content_width) + pl->content_width = end; + } + if (pl->content_width > aw) + pl->content_width = aw; + + if (sy != NULL) { + switch (sy->align) { + case STYLE_ALIGN_CENTRE: + case STYLE_ALIGN_ABSOLUTE_CENTRE: + pl->content_x = ax + (aw - pl->content_width) / 2; + break; + case STYLE_ALIGN_RIGHT: + pl->content_x = ax + aw - pl->content_width; + break; + default: + pl->content_x = ax; + break; + } + } else + pl->content_x = ax; + + pl->input_x = pl->content_x + pl->label_width; + pl->cursor_x += pl->content_x; + free(local); } /* Choose a completion from a mouse position. */ @@ -460,70 +602,59 @@ prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) { struct screen_write_ctx *ctx = pd->ctx; struct screen *s = ctx->s; - u_int ax = pd->area_x, py = pd->prompt_line; - u_int aw = pd->area_width, *cx = pd->cursor_x; + u_int ax = pd->area_x, py = pd->prompt_line, *cx; + u_int aw = pd->area_width; struct grid_cell gc; - u_int i, offset, left, start, width; - u_int pcursor, pwidth; + struct prompt_layout pl; + struct style sy; + u_int i, width, pcursor; char *expanded; - /* Choose the cursor colour and style for this prompt. */ if (pr->flags & PROMPT_COMMANDMODE) { - memcpy(&gc, &pr->command_style, sizeof gc); s->default_cstyle = pr->command_cstyle; s->default_mode = pr->command_cmode; s->default_ccolour = pr->command_ccolour; } else { - memcpy(&gc, &pr->style, sizeof gc); s->default_cstyle = pr->cstyle; s->default_mode = pr->cmode; s->default_ccolour = pr->ccolour; } - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - *cx = ax + start; + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + memcpy(&gc, &sy.gc, sizeof gc); + cx = pd->cursor_x; + *cx = pl.cursor_x; screen_write_cursormove(ctx, ax, py, 0); - format_draw(ctx, &gc, aw, expanded, NULL, 0); - screen_write_cursormove(ctx, ax + start, py, 0); - free(expanded); - - left = aw - start; - if (left == 0) - return; + if (sy.fill != 8) + screen_write_clearcharacter(ctx, aw, sy.fill); pcursor = utf8_strwidth(pr->buffer, pr->index); - pwidth = utf8_strwidth(pr->buffer, -1); - if (pr->flags & PROMPT_QUOTENEXT) - pwidth++; - if (pcursor >= left) { - /* - * The cursor would be outside the screen so start drawing - * with it on the right. - */ - offset = (pcursor - left) + 1; - pwidth = left; - } else - offset = 0; - if (pwidth > left) - pwidth = left; - *cx = ax + start + pcursor - offset; - width = 0; - for (i = 0; pr->buffer[i].size != 0; i++) { - if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, - &width, &gc)) - break; - if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc, - &pr->buffer[i])) - break; + if (pl.content_width != 0) { + screen_write_cursormove(ctx, pl.content_x, py, 0); + if (pl.label_width != 0) + format_draw(ctx, &gc, pl.label_width, expanded, NULL, + 0); + + screen_write_cursormove(ctx, pl.input_x, py, 0); + width = 0; + for (i = 0; pr->buffer[i].size != 0; i++) { + if (!prompt_redraw_quote(pr, pcursor, pl.input_x, + ctx, pl.input_offset, pl.input_width, &width, + &gc)) + break; + if (!prompt_redraw_character(ctx, pl.input_offset, + pl.input_width, &width, &gc, &pr->buffer[i])) + break; + } + prompt_redraw_quote(pr, pcursor, pl.input_x, ctx, + pl.input_offset, pl.input_width, &width, &gc); + + prompt_draw_complete(pr, ctx, pl.content_x, pl.content_width, + pl.cursor_x, py, &gc); } - prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc); - - prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc); + free(expanded); } /* Move cursor in prompt from a mouse position. */ @@ -532,36 +663,34 @@ prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) { struct utf8_data *ud; enum prompt_key_result result; - u_int cx, start, left, pcursor, pwidth, offset, width; - u_int target; + struct prompt_layout pl; + struct style sy; + char *expanded; + u_int pwidth, width, target; size_t idx; if (x < ax || x >= ax + aw) return (PROMPT_KEY_NOT_HANDLED); - start = prompt_width(pr, aw); - left = aw - start; - if (left == 0) + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + free(expanded); + + if (pl.input_width == 0) return (PROMPT_KEY_HANDLED); - pcursor = utf8_strwidth(pr->buffer, pr->index); pwidth = utf8_strwidth(pr->buffer, -1); if (pr->flags & PROMPT_QUOTENEXT) pwidth++; - if (pcursor >= left) - offset = (pcursor - left) + 1; - else - offset = 0; - cx = ax + start + pcursor - offset; - result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw); + result = prompt_mouse_complete(pr, x, pl.cursor_x, pl.content_x, + pl.content_width, redraw); if (result != PROMPT_KEY_NOT_HANDLED) return (result); - if (x <= ax + start) - target = offset; + if (x <= pl.input_x) + target = pl.input_offset; else - target = offset + x - (ax + start); + target = pl.input_offset + x - pl.input_x; if (target > pwidth) target = pwidth; diff --git a/regress/alerts.sh b/regress/alerts.sh index cfb84c711..60d9a05f7 100644 --- a/regress/alerts.sh +++ b/regress/alerts.sh @@ -46,7 +46,7 @@ wait_for() value=$($TMUX show -gqv "$option" 2>/dev/null || true) [ "$value" = "$expected" ] && return 0 i=$((i + 1)) - sleep 0.2 + sleep 0.5 done fail "expected $option to be '$expected' but got '$value'" } @@ -62,7 +62,7 @@ assert_unchanged() [ "$value" = "$expected" ] || \ fail "expected $option to remain '$expected' but got '$value'" i=$((i + 1)) - sleep 0.2 + sleep 0.5 done } @@ -77,7 +77,7 @@ wait_for_fmt() value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) [ "$value" = "$expected" ] && return 0 i=$((i + 1)) - sleep 0.2 + sleep 0.5 done fail "expected $fmt for $target to be '$expected' but got '$value'" } @@ -94,7 +94,7 @@ assert_fmt_unchanged() [ "$value" = "$expected" ] || \ fail "expected $fmt for $target to remain '$expected' but got '$value'" i=$((i + 1)) - sleep 0.2 + sleep 0.5 done } @@ -254,6 +254,19 @@ assert_unchanged @log '' $TMUX set -wut mon:w0 monitor-activity || fail "unset monitor-activity w0 failed" +# Creating a non-current window counts as activity when monitor-activity is +# enabled in the window defaults. +$TMUX set -wg monitor-activity on || fail "set global monitor-activity failed" +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX neww -d -t mon: -n newact cat || fail "new-window newact failed" +wait_for @log '|alert-activity:mon:newact' +wait_for_fmt mon:newact '#{window_activity_flag}' 1 +flags_have mon:newact '#' +$TMUX set -wug monitor-activity || + fail "unset global monitor-activity failed" +$TMUX selectw -t mon:newact || fail "select-window newact failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + # monitor-silence: a quiet window fires alert-silence after the interval # and sets the silence flag, shown as ~ in window_flags. The hook fires # only once while the flag remains set even though the timer keeps @@ -273,6 +286,22 @@ wait_for_fmt mon:silw '#{window_silence_flag}' 0 flags_lack mon:silw '~' $TMUX selectw -t mon:w0 || fail "select-window w0 failed" +# Activity resets the silence timer: output before the original deadline +# prevents an alert until a full quiet interval has passed afterwards. +$TMUX neww -d -t mon: -n silreset cat || fail "new-window silreset failed" +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX set -wt mon:silreset monitor-silence 10 || + fail "set monitor-silence silreset failed" +sleep 3 +activity mon:silreset +assert_unchanged @log '' +wait_for @log '|alert-silence:mon:silreset' +wait_for_fmt mon:silreset '#{window_silence_flag}' 1 +$TMUX set -wt mon:silreset monitor-silence 0 || + fail "reset monitor-silence silreset failed" +$TMUX selectw -t mon:silreset || fail "select-window silreset failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + # A window linked into two sessions: the hook fires once per winlink and # the flag is set in both sessions. Selecting the window in one session # clears the flags on every winlink. diff --git a/regress/copy-mode-redraw.sh b/regress/copy-mode-redraw.sh new file mode 100644 index 000000000..af7fcc34f --- /dev/null +++ b/regress/copy-mode-redraw.sh @@ -0,0 +1,169 @@ +#!/bin/sh + +# Exercise copy mode redraw through a real client. An inner tmux is attached +# inside an outer tmux pane; the outer pane is captured to inspect what the +# inner client actually drew. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +fail() { + echo "$*" >&2 + exit 1 +} + +capture() { + $TMUX capturep -pS0 -E- >$TMP || exit 1 +} + +check_line() { + line=$1 + want=$2 + got=$(sed -n "$line"p $TMP) + [ "$got" = "$want" ] || fail "line $line: expected '$want', got '$got'" +} + +check_grep() { + pattern=$1 + grep -Fq "$pattern" $TMP || fail "missing pattern: $pattern" +} + +check_no_grep() { + pattern=$1 + grep -Fq "$pattern" $TMP && fail "unexpected pattern: $pattern" +} + +redraw() { + $TMUX2 send -X cursor-right || exit 1 + sleep 1 + capture +} + +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null + +TMP=$(mktemp) +BEFORE=$(mktemp) +AFTER=$(mktemp) +trap "rm -f $TMP $BEFORE $AFTER; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15 + +$TMUX2 -f/dev/null new -d -x48 -y8 -s test \ + "awk 'BEGIN { for (i = 0; i < 90; i++) { if (i % 2 == 0) printf \"L%03d-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END\\n\", i; else printf \"S%03d\\n\", i } }'; exec sleep 100" || exit 1 +$TMUX2 set -g status off || exit 1 +$TMUX2 set -g mode-keys vi || exit 1 +$TMUX2 set -g copy-mode-line-numbers off || exit 1 +$TMUX2 set -g copy-mode-position-format "#[align=right][23/100-LONGTAIL]" || exit 1 + +$TMUX -f/dev/null new -d -x48 -y8 || exit 1 +$TMUX set -g status off || exit 1 +$TMUX send -l "$TMUX2 attach" || exit 1 +$TMUX send Enter || exit 1 +sleep 1 + +CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1) +[ -n "$CLIENT" ] || fail "no inner client" + +$TMUX2 copy-mode || exit 1 +$TMUX2 send -X history-top || exit 1 +sleep 1 + +# Shrinking and moving the position indicator should not leave stale text from +# the previous indicator, for different widths and alignments. +for size in 20 48; do + $TMUX resizew -x$size -y8 || exit 1 + $TMUX2 refresh -t"$CLIENT" -c || exit 1 + sleep 1 + for align in left centre right; do + $TMUX2 set -g copy-mode-position-format "#[align=$align][23/100-LONGTAIL]" || exit 1 + redraw + check_grep "[23/100-LONGTAIL]" + + $TMUX2 set -g copy-mode-position-format "#[align=$align][1/100]" || exit 1 + redraw + check_grep "[1/100]" + check_no_grep "LONGTAIL" + check_no_grep "[1/100]]" + done + + $TMUX2 set -g copy-mode-position-format "#[align=right][1/100]" || exit 1 + redraw + $TMUX2 set -g copy-mode-position-format "#[align=right][23/100]" || exit 1 + redraw + check_grep "[23/100]" +done + +# Scrolling from long pane content to short pane content should clear the +# remainder of the old line without relying on a full pre-clear. +$TMUX resizew -x48 -y8 || exit 1 +$TMUX2 refresh -t"$CLIENT" -c || exit 1 +$TMUX2 send -X cancel || exit 1 +$TMUX2 copy-mode -H || exit 1 +$TMUX2 send -X history-top || exit 1 +sleep 1 +capture +check_line 1 "L000-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END" + +$TMUX2 send -X scroll-down || exit 1 +sleep 1 +capture +check_line 1 "S001" + +$TMUX2 send -X scroll-down || exit 1 +sleep 1 +capture +check_line 1 "L002-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END" + +$TMUX2 send -X scroll-down || exit 1 +sleep 1 +capture +check_line 1 "S003" + +# Scrolling from long content to a tabbed short line should still clear the old +# line tail. +$TMUX2 send -X cancel || exit 1 +$TMUX resizew -x40 -y8 || exit 1 +$TMUX2 refresh -t"$CLIENT" -c || exit 1 +$TMUX2 new-window \ + "printf 'LONGTAIL-ABCDEFGHIJKLMNOPQRSTUV\nA\tB\nLONGTAIL-123456789012345678\nS\n'; i=0; while [ \$i -lt 20 ]; do printf 'FILLER-%02d\n' \$i; i=\$((i + 1)); done; exec sleep 100" || \ + exit 1 +sleep 1 +$TMUX2 copy-mode -H || exit 1 +$TMUX2 send -X history-top || exit 1 +sleep 1 +capture +check_line 1 "LONGTAIL-ABCDEFGHIJKLMNOPQRSTUV" + +$TMUX2 send -X scroll-down || exit 1 +sleep 1 +capture +check_line 1 "A B" +sed -n 1p $TMP | grep -Fq "LONGTAIL" && \ + fail "short tabbed line left stale tail" + +# Reflow can leave padding from tabs at the start of wrapped lines. Entering +# copy mode should redraw those padding cells as spaces, not skip them and move +# the following text left. +$TMUX2 send -X cancel || exit 1 +$TMUX resizew -x80 -y24 || exit 1 +$TMUX2 refresh -t"$CLIENT" -c || exit 1 +$TMUX2 new-window "cat ../tmux.c; exec sleep 100" || exit 1 +sleep 1 +$TMUX2 splitw -hd || exit 1 +sleep 1 +$TMUX capturep -pS0 -E- >$BEFORE || exit 1 +$TMUX2 copy-mode -H -t:.0 || exit 1 +sleep 1 +$TMUX capturep -pS0 -E- >$AFTER || exit 1 + +if ! cmp -s "$BEFORE" "$AFTER"; then + diff -u "$BEFORE" "$AFTER" >&2 + fail "copy-mode redraw moved reflowed tab padding" +fi + +exit 0 diff --git a/regress/copy-mode-selection-scroll.sh b/regress/copy-mode-selection-scroll.sh new file mode 100644 index 000000000..7e7aee9ce --- /dev/null +++ b/regress/copy-mode-selection-scroll.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null + +cleanup() +{ + $TMUX kill-server 2>/dev/null + $TMUX2 kill-server 2>/dev/null +} +fail() +{ + echo "$1" + cleanup + exit 1 +} +expect_buffer() +{ + expected=$1 + actual=$($TMUX show-buffer) + [ "$actual" = "$expected" ] || + fail "unexpected buffer: expected [$expected], got [$actual]" +} +wheel() +{ + button=$1 + col=$2 + row=$3 + seq=$(printf '\033[<%s;%s;%sM' "$button" "$col" "$row") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} +trap cleanup 0 +trap 'exit 1' 1 2 3 15 + +$TMUX new -d -x40 -y10 \ + 'i=0; while [ $i -lt 80 ]; do printf "line %02d xxxxxxxxxx\n" $i; i=$((i + 1)); done; cat' || + exit 1 +$TMUX set -g window-size manual || exit 1 +$TMUX set -g mouse on || exit 1 + +$TMUX copy-mode || exit 1 +$TMUX send-keys -X history-top || exit 1 +$TMUX send-keys -N10 -X cursor-down || exit 1 +$TMUX send-keys -X start-of-line || exit 1 +$TMUX send-keys -X begin-selection || exit 1 +$TMUX send-keys -N2 -X cursor-down || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 + +initial=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx') +expect_buffer "$initial" + +$TMUX send-keys -X stop-selection || exit 1 +$TMUX send-keys -N3 -X scroll-down || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -N2 -X scroll-up || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -X scroll-middle || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -X scroll-bottom || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -X scroll-top || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -X recentre-top-bottom || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$initial" + +$TMUX send-keys -X other-end || exit 1 +$TMUX send-keys -X cursor-down || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 + +extended_end=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx') +expect_buffer "$extended_end" + +$TMUX send-keys -X stop-selection || exit 1 +$TMUX send-keys -X other-end || exit 1 +$TMUX send-keys -X other-end || exit 1 +$TMUX send-keys -X cursor-up || exit 1 +$TMUX send-keys -X copy-selection-no-clear || exit 1 + +extended_start=$(printf 'line 09 xxxxxxxxxx\nline 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx') +expect_buffer "$extended_start" + +$TMUX2 new-session -d -x40 -y10 "$TMUX attach" || exit 1 +sleep 1 +OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1) +[ -n "$OUTER" ] || fail "no outer pane" + +wheel 65 5 5 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$extended_start" + +wheel 64 5 5 +$TMUX send-keys -X copy-selection-no-clear || exit 1 +expect_buffer "$extended_start" + +exit 0 diff --git a/regress/customize-mode.sh b/regress/customize-mode.sh new file mode 100644 index 000000000..89ebc6a42 --- /dev/null +++ b/regress/customize-mode.sh @@ -0,0 +1,746 @@ +#!/bin/sh + +# Exercise customize-mode-specific mutations. mode-mutation.sh covers stale +# mode-tree data while customize-mode is open; this covers the actions unique to +# customize mode: adding, setting, editing, resetting, renaming array keys and +# unsetting options, hooks, environment variables and key bindings. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR + +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +OUT="$TEST_TMUX -LtestB$$ -f/dev/null" +FORMAT='CM #{?is_option,O,#{?is_environment,E,K}} #{option_name}#{environment_name}#{key}=#{option_value}#{environment_value}' + +cleanup_servers() +{ + $TMUX kill-server 2>/dev/null + $OUT kill-server 2>/dev/null + sleep 0.5 +} + +cleanup() +{ + cleanup_servers + rm -rf "$TMP" +} +trap cleanup EXIT + +fail() +{ + echo "$1" >&2 + cleanup + exit 1 +} + +capture() +{ + $OUT capture-pane -p -t out:0 2>/dev/null +} + +settle() +{ + sleep 0.3 +} + +wait_clients() +{ + i=0 + while [ "$i" -lt 50 ]; do + c=$($TMUX list-clients -F x 2>/dev/null | grep -c x) + [ "$c" -eq "$1" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "expected $1 clients, have $c" +} + +wait_mode() +{ + want=$1 + + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t cm:0 '#{pane_in_mode}' \ + 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "pane mode state is $got, expected $want" +} + +assert_alive() +{ + $TMUX display-message -p 'alive' >/dev/null 2>&1 || + fail "$1: server exited" +} + +start_client() +{ + cleanup_servers + $TMUX new-session -d -s cm -n main -x 80 -y 24 'cat' || + fail "new-session failed" + $OUT new-session -d -s out -x 80 -y 24 "$TMUX attach -t cm" || + fail "outer client failed" + wait_clients 1 +} + +open_customize() +{ + if [ "$#" -eq 0 ]; then + $TMUX customize-mode -t cm:0 -F "$FORMAT" || + fail "customize-mode failed" + else + $TMUX customize-mode -t cm:0 -F "$FORMAT" -f "$1" || + fail "customize-mode failed" + fi + wait_mode 1 + settle +} + +send() +{ + $TMUX send-keys -t cm:0 "$@" || fail "send-keys failed: $*" +} + +send_literal() +{ + $TMUX send-keys -t cm:0 -l "$1" || fail "send literal failed: $1" +} + +repeat_key() +{ + key=$1 + count=$2 + + i=0 + while [ "$i" -lt "$count" ]; do + send "$key" + i=$((i + 1)) + done +} + +replace_prompt() +{ + send C-u + send_literal "$1" + send Enter + settle +} + +assert_equals() +{ + got=$1 + want=$2 + what=$3 + + [ "$got" = "$want" ] || + fail "$what: got '$got', expected '$want'" +} + +assert_empty() +{ + got=$1 + what=$2 + + [ -z "$got" ] || fail "$what: got '$got', expected empty" +} + +assert_contains() +{ + text=$1 + needle=$2 + what=$3 + + echo "$text" | grep -F -q "$needle" || + fail "$what: missing '$needle' in '$text'" +} + +assert_not_contains() +{ + text=$1 + needle=$2 + what=$3 + + echo "$text" | grep -F -q "$needle" && + fail "$what: found '$needle' in '$text'" + return 0 +} + +test_user_option() +{ + start_client + + open_customize + send Enter + settle + send_literal "cm_add added" + send Enter + settle + assert_equals "$($TMUX show-options -sqv @cm_add)" "added" \ + "added server user option" + + send q + wait_mode 0 + open_customize '#{==:#{option_name},@cm_add}' + send Right j Enter + settle + replace_prompt "changed" + assert_equals "$($TMUX show-options -sqv @cm_add)" "changed" \ + "set user option" + + send u y + settle + assert_empty "$($TMUX show-options -sqv @cm_add)" "unset user option" + send q + wait_mode 0 +} + +test_reset_window_pane_option() +{ + start_client + + $TMUX set-option -g window-style fg=red || + fail "set global window-style failed" + $TMUX set-window-option -t cm:0 window-style fg=green || + fail "set window window-style failed" + $TMUX set-option -p -t cm:0.0 window-style fg=blue || + fail "set pane window-style failed" + + open_customize '#{==:#{option_name},window-style}' + send j j Right j + settle + send d y + settle + + assert_equals "$($TMUX show-option -gqv window-style)" "default" \ + "reset global window-style" + assert_empty "$($TMUX show-options -wqv -t cm:0 window-style)" \ + "reset window window-style" + assert_empty "$($TMUX show-option -p -t cm:0.0 -qv window-style)" \ + "reset pane window-style" + send q + wait_mode 0 +} + +test_environment() +{ + start_client + + open_customize + repeat_key j 6 + send Enter + settle + send_literal "CM_ENV=one" + send Enter + settle + assert_equals "$($TMUX show-environment -t cm CM_ENV)" \ + "CM_ENV=one" "add session environment" + + send q + wait_mode 0 + open_customize '#{==:#{environment_name},CM_ENV}' + repeat_key j 6 + send Right j Enter + settle + replace_prompt "two" + assert_equals "$($TMUX show-environment -t cm CM_ENV)" \ + "CM_ENV=two" "set session environment" + + send S + settle + replace_prompt "global" + assert_equals "$($TMUX show-environment -g CM_ENV)" \ + "CM_ENV=global" "set global environment" + + send u y + settle + $TMUX show-environment -t cm CM_ENV >/dev/null 2>&1 && + fail "unset session environment: variable still exists" + send q + wait_mode 0 +} + +test_user_hook() +{ + start_client + + open_customize + repeat_key j 3 + send Enter + settle + send_literal "cm_hook display-message hook" + send Enter + settle + assert_equals "$($TMUX show-option -qv @cm_hook)" \ + "display-message hook" "add user hook" + + send q + wait_mode 0 + open_customize '#{==:#{option_name},@cm_hook}' + repeat_key j 3 + send Right j + settle + assert_contains "$(capture)" "@cm_hook" "user hook shown as hook" + + send u y + settle + assert_empty "$($TMUX show-option -qv @cm_hook)" "unset user hook" + send q + wait_mode 0 +} + +test_hook_array() +{ + start_client + + $TMUX set-hook -g 'after-new-session[0]' 'display-message old' || + fail "set hook array failed" + + open_customize '#{==:#{option_name},after-new-session}' + repeat_key j 3 + send Right j Right j + settle + + send a + settle + replace_prompt "1" + hooks=$($TMUX show-hooks -g after-new-session) + assert_contains "$hooks" "after-new-session[1] display-message old" \ + "renamed hook array key" + assert_not_contains "$hooks" "after-new-session[0]" \ + "old hook array key removed" + + send Enter + settle + replace_prompt "display-message new" + hooks=$($TMUX show-hooks -g after-new-session) + assert_contains "$hooks" "after-new-session[1] display-message new" \ + "set hook array value" + + send u y + settle + hooks=$($TMUX show-hooks -g after-new-session) + assert_not_contains "$hooks" "after-new-session[1]" \ + "unset hook array item" + send q + wait_mode 0 +} + +test_changed_only() +{ + start_client + + $TMUX set-option -s @cm_changed yes || + fail "set changed server option failed" + $TMUX bind-key -T prefix User1 display-message changed-only || + fail "set changed key failed" + + open_customize + send C + settle + send M-+ + settle + screen=$(capture) + assert_contains "$screen" "@cm_changed" "changed-only user option" + assert_contains "$screen" "User1" "changed-only key" + assert_not_contains "$screen" "Global Environment" \ + "changed-only hides environment" + send q + wait_mode 0 +} + +test_reset_key() +{ + start_client + + orig=$($TMUX list-keys -T prefix -F '#{key_command}' c) || + fail "default c binding not found" + $TMUX bind-key -T prefix c display-message changed || + fail "change c binding failed" + + open_customize '#{==:#{key},c}' + repeat_key j 10 + send Right j d y + settle + after=$($TMUX list-keys -T prefix -F '#{key_command}' c) || + fail "reset c binding not found" + assert_equals "$after" "$orig" "reset key binding" + send q + wait_mode 0 +} + +test_tagged_unset() +{ + start_client + + $TMUX set-option -s @cm_tag_a one || fail "set tag option a failed" + $TMUX set-option -s @cm_tag_b two || fail "set tag option b failed" + + open_customize '#{m/r:^@cm_tag_,#{option_name}}' + send Right j t j t U y + settle + assert_empty "$($TMUX show-options -sqv @cm_tag_a)" \ + "tagged unset option a" + assert_empty "$($TMUX show-options -sqv @cm_tag_b)" \ + "tagged unset option b" + send q + wait_mode 0 +} + +test_key_binding() +{ + start_client + + open_customize + repeat_key j 10 + send Enter + settle + send_literal "User0 display-message before" + send Enter + settle + keys=$($TMUX list-keys -T prefix User0 2>/dev/null) || + fail "added key binding not found" + assert_contains "$keys" "display-message before" "add key binding" + + send q + wait_mode 0 + open_customize '#{==:#{key},User0}' + repeat_key j 10 + send Right j Right j + settle + + send s + settle + replace_prompt "display-message after" + keys=$($TMUX list-keys -T prefix User0 2>/dev/null) || + fail "edited key binding not found" + assert_contains "$keys" "display-message after" "set key command" + + send j s + settle + replace_prompt "custom note" + note=$($TMUX list-keys -T prefix -F '#{key_note}' User0) + assert_equals "$note" "custom note" "set key note" + + send j s + settle + keys=$($TMUX list-keys -T prefix User0 2>/dev/null) || + fail "repeat key binding not found" + assert_contains "$keys" "bind-key -r" "toggle key repeat" + + send u y + settle + $TMUX list-keys -T prefix User0 >/dev/null 2>&1 && + fail "unset key binding: binding still exists" + send q + wait_mode 0 +} + +test_editor() +{ + start_client + + editor="$TMP/editor.sh" + editor_value="$TMP/editor-value" + { + echo '#!/bin/sh' + printf 'cat "%s" > "$1"\n' "$editor_value" + } > "$editor" || fail "write editor helper failed" + chmod +x "$editor" || fail "chmod editor helper failed" + + $TMUX set-option -g editor "$editor" || fail "set editor failed" + $TMUX set-option -g @cm_edit old || fail "set edit option failed" + + printf 'edited' > "$editor_value" || fail "write editor value failed" + open_customize '#{==:#{option_name},@cm_edit}' + send j Right j e + + i=0 + while [ "$i" -lt 50 ]; do + value=$($TMUX show-option -gqv @cm_edit) + [ "$value" = "edited" ] && break + sleep 0.2 + i=$((i + 1)) + done + assert_equals "$value" "edited" "edit option in editor" + send q + wait_mode 0 + + $TMUX bind-key -T prefix User2 display-message old || + fail "set editor key failed" + printf 'display-message edited' > "$editor_value" || + fail "write editor key command failed" + open_customize '#{==:#{key},User2}' + repeat_key j 10 + send Right j Right j e + i=0 + while [ "$i" -lt 50 ]; do + value=$($TMUX list-keys -T prefix -F '#{key_command}' User2) + [ "$value" = "display-message edited" ] && break + sleep 0.2 + i=$((i + 1)) + done + assert_equals "$value" "display-message edited" \ + "edit key command in editor" + + printf 'edited note' > "$editor_value" || + fail "write editor key note failed" + send j e + i=0 + while [ "$i" -lt 50 ]; do + value=$($TMUX list-keys -T prefix -F '#{key_note}' User2) + [ "$value" = "edited note" ] && break + sleep 0.2 + i=$((i + 1)) + done + assert_equals "$value" "edited note" "edit key note in editor" + send q + wait_mode 0 + + $TMUX set-environment -t cm CM_EDIT_ENV old || + fail "set editor environment failed" + printf 'env-edited' > "$editor_value" || + fail "write editor environment failed" + open_customize '#{==:#{environment_name},CM_EDIT_ENV}' + repeat_key j 6 + send Right j e + i=0 + while [ "$i" -lt 50 ]; do + value=$($TMUX show-environment -t cm CM_EDIT_ENV) + [ "$value" = "CM_EDIT_ENV=env-edited" ] && break + sleep 0.2 + i=$((i + 1)) + done + assert_equals "$value" "CM_EDIT_ENV=env-edited" \ + "edit environment in editor" + send q + wait_mode 0 +} + +test_stale_current_items() +{ + start_client + + $TMUX set-option -s @cm_stale_option old || + fail "set stale option failed" + open_customize '#{==:#{option_name},@cm_stale_option}' + send Right j + settle + $TMUX set-option -su @cm_stale_option || + fail "external unset stale option failed" + send Enter + send u y + send d y + settle + assert_alive "stale option current item" + send q + wait_mode 0 + + $TMUX set-environment -t cm CM_STALE_ENV old || + fail "set stale environment failed" + open_customize '#{==:#{environment_name},CM_STALE_ENV}' + repeat_key j 6 + send Right j + settle + $TMUX set-environment -t cm -u CM_STALE_ENV || + fail "external unset stale environment failed" + send Enter + send S + send u y + settle + assert_alive "stale environment current item" + send q + wait_mode 0 + + $TMUX bind-key -T prefix User3 display-message stale || + fail "set stale key failed" + open_customize '#{==:#{key},User3}' + repeat_key j 10 + send Right j + settle + $TMUX unbind-key -T prefix User3 || + fail "external unbind stale key failed" + send s + send d y + send u y + settle + assert_alive "stale key current item" + send q + wait_mode 0 +} + +test_prompt_invalidation() +{ + start_client + + $TMUX set-option -s @cm_prompt_stale old || + fail "set prompt stale option failed" + open_customize '#{==:#{option_name},@cm_prompt_stale}' + send Right j Enter + settle + $TMUX set-option -su @cm_prompt_stale || + fail "external unset prompt option failed" + replace_prompt "new" + assert_empty "$($TMUX show-options -sqv @cm_prompt_stale)" \ + "prompt stale option should not be recreated" + assert_alive "stale option prompt" + send q + wait_mode 0 + + $TMUX bind-key -T prefix User4 display-message old || + fail "set prompt stale key failed" + open_customize '#{==:#{key},User4}' + repeat_key j 10 + send Right j Right j s + settle + $TMUX unbind-key -T prefix User4 || + fail "external unbind prompt key failed" + replace_prompt "display-message new" + $TMUX list-keys -T prefix User4 >/dev/null 2>&1 && + fail "prompt stale key should stay unbound" + assert_alive "stale key prompt" + send q + wait_mode 0 + + $TMUX set-hook -g 'after-new-session[0]' 'display-message old' || + fail "set prompt stale hook array failed" + $TMUX set-hook -g 'after-new-session[1]' 'display-message taken' || + fail "set prompt stale hook array target failed" + open_customize '#{==:#{option_name},after-new-session}' + repeat_key j 3 + send Right j Right j a + settle + replace_prompt "1" + hooks=$($TMUX show-hooks -g after-new-session) + assert_contains "$hooks" "after-new-session[0] display-message old" \ + "array rename to existing key keeps old key" + assert_contains "$hooks" "after-new-session[1] display-message taken" \ + "array rename to existing key keeps target key" + assert_alive "existing array key prompt" + send q + wait_mode 0 +} + +test_editor_invalidation() +{ + start_client + + editor="$TMP/slow-editor.sh" + { + echo '#!/bin/sh' + echo 'sleep 1' + echo 'printf edited > "$1"' + } > "$editor" || fail "write slow editor helper failed" + chmod +x "$editor" || fail "chmod slow editor helper failed" + $TMUX set-option -g editor "$editor" || fail "set slow editor failed" + + $TMUX set-option -g @cm_editor_stale old || + fail "set editor stale option failed" + open_customize '#{==:#{option_name},@cm_editor_stale}' + send j Right j e + settle + $TMUX set-option -gu @cm_editor_stale || + fail "external unset editor option failed" + sleep 2 + assert_empty "$($TMUX show-option -gqv @cm_editor_stale)" \ + "editor stale option should not be recreated" + assert_alive "stale option editor" + send q + wait_mode 0 + + $TMUX bind-key -T prefix User5 display-message old || + fail "set editor stale key failed" + open_customize '#{==:#{key},User5}' + repeat_key j 10 + send Right j Right j e + settle + $TMUX unbind-key -T prefix User5 || + fail "external unbind editor key failed" + sleep 2 + $TMUX list-keys -T prefix User5 >/dev/null 2>&1 && + fail "editor stale key should stay unbound" + assert_alive "stale key editor" + send q + wait_mode 0 + + $TMUX set-environment -t cm CM_EDITOR_STALE old || + fail "set editor stale environment failed" + open_customize '#{==:#{environment_name},CM_EDITOR_STALE}' + repeat_key j 6 + send Right j e + settle + $TMUX set-environment -t cm -u CM_EDITOR_STALE || + fail "external unset editor environment failed" + sleep 2 + assert_equals "$($TMUX show-environment -t cm CM_EDITOR_STALE)" \ + "CM_EDITOR_STALE=edited" "editor stale environment is reset" + assert_alive "stale environment editor" + send q + wait_mode 0 +} + +test_invalid_prompts() +{ + start_client + + open_customize + send Enter + settle + send_literal "bad value" + send Enter + settle + assert_empty "$($TMUX show-options -sqv bad)" "invalid user option name" + assert_alive "invalid user option prompt" + send q + wait_mode 0 + + open_customize + repeat_key j 10 + send Enter + settle + send_literal "NoSuchKey display-message invalid" + send Enter + settle + assert_alive "invalid key prompt" + send q + wait_mode 0 + + $TMUX bind-key -T prefix User6 display-message valid || + fail "set invalid command key failed" + open_customize '#{==:#{key},User6}' + repeat_key j 10 + send Right j Right j s + settle + replace_prompt "not-a-command -Z" + keys=$($TMUX list-keys -T prefix -F '#{key_command}' User6) + assert_equals "$keys" "display-message valid" \ + "invalid key command keeps old command" + assert_alive "invalid key command prompt" + send q + wait_mode 0 +} + +cleanup_servers +test_user_option +test_reset_window_pane_option +test_environment +test_user_hook +test_hook_array +test_changed_only +test_reset_key +test_tagged_unset +test_key_binding +test_editor +test_stale_current_items +test_prompt_invalidation +test_editor_invalidation +test_invalid_prompts +cleanup +exit 0 diff --git a/regress/display-panes.sh b/regress/display-panes.sh new file mode 100644 index 000000000..83d21848b --- /dev/null +++ b/regress/display-panes.sh @@ -0,0 +1,254 @@ +#!/bin/sh + +# Tests for display-panes as panes-mode. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +cleanup() +{ + $TMUX kill-server 2>/dev/null + $TMUX2 kill-server 2>/dev/null + rm -rf "$TMP" +} +trap cleanup EXIT + +fail() +{ + echo "$1" >&2 + exit 1 +} + +wait_format() +{ + target=$1 + format=$2 + want=$3 + + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t "$target" "$format" 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "$target: expected '$want' for $format, got '$got'" +} + +wait_option() +{ + option=$1 + want=$2 + + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX show -gv "$option" 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "$option: expected '$want', got '$got'" +} + +wait_clients() +{ + i=0 + while [ "$i" -lt 50 ]; do + c=$($TMUX list-clients -F x 2>/dev/null | grep -c x) + [ "$c" -eq "$1" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "expected $1 clients, have $c" +} + +$TMUX new-session -d -s m -x 80 -y 24 'cat' || exit 1 +$TMUX split-window -t m:0 'cat' || exit 1 +p0=$($TMUX display-message -p -t m:0.0 '#{pane_id}') +p1=$($TMUX display-message -p -t m:0.1 '#{pane_id}') +$TMUX set -g display-panes-format 'P#{pane_index}' || + fail "set display-panes-format failed" + +$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t m" || exit 1 +wait_clients 1 + +capture() +{ + $TMUX2 capture-pane -p -t out:0 2>/dev/null +} + +wait_capture() +{ + i=0 + while [ "$i" -lt 50 ]; do + CAPTURED=$(capture) + printf '%s\n' "$CAPTURED" | grep -F -q "$1" && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for '$1' in capture" +} + +# Entry, default zoom, and exit. +$TMUX display-panes -d 0 -t "$p0" || fail "display-panes failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_format "$p0" '#{window_zoomed_flag}' '1' +wait_capture 'P0' +wait_capture 'P1' +$TMUX send-keys -t "$p0" q || fail "exit panes mode failed" +wait_format "$p0" '#{pane_in_mode}' '0' +wait_format "$p0" '#{window_zoomed_flag}' '0' + +# -Z starts unzoomed. +$TMUX display-panes -Zd 0 -t "$p0" || fail "display-panes -Z failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_format "$p0" '#{window_zoomed_flag}' '0' +$TMUX send-keys -t "$p0" q || fail "exit unzoomed panes mode failed" +wait_format "$p0" '#{pane_in_mode}' '0' + +# Selection keys run the command template and close the mode. +$TMUX set -g @picked none || fail "set @picked failed" +$TMUX display-panes -d 0 -t "$p0" 'set -g @picked %%' || + fail "display-panes selection failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +$TMUX send-keys -t "$p0" 1 || fail "select pane failed" +wait_format "$p0" '#{pane_in_mode}' '0' +wait_option @picked "$p1" + +# Commands after display-panes run immediately while the mode remains. +$TMUX set -g @after none || fail "set @after failed" +$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast || + fail "display-panes immediate command failed" +wait_option @after fast +wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_format "$p0" '#{pane_in_mode}' '0' + +# Existing zoom is restored on exit. +$TMUX select-pane -t "$p0" || fail "select p0 failed" +$TMUX resize-pane -Z -t "$p0" || fail "zoom failed" +wait_format "$p0" '#{window_zoomed_flag}' '1' +$TMUX display-panes -d 0 -t "$p0" || fail "display-panes zoomed failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +$TMUX send-keys -t "$p0" q || fail "exit zoomed panes mode failed" +wait_format "$p0" '#{pane_in_mode}' '0' +wait_format "$p0" '#{window_zoomed_flag}' '1' +$TMUX resize-pane -Z -t "$p0" || fail "unzoom cleanup failed" + +# Panes mode can be stacked above another mode and returns to it on exit. +$TMUX copy-mode -t "$p0" || fail "copy-mode failed" +wait_format "$p0" '#{pane_mode}' 'copy-mode' +$TMUX display-panes -d 0 -t "$p0" 'set -g @picked %%' || + fail "display-panes over copy-mode failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +$TMUX send-keys -t "$p0" 1 || fail "select from stacked mode failed" +wait_format "$p0" '#{pane_mode}' 'copy-mode' +wait_option @picked "$p1" +$TMUX send-keys -t "$p0" -X cancel || fail "copy-mode cancel failed" +wait_format "$p0" '#{pane_in_mode}' '0' + +# Panes mode is not kept underneath another mode. +$TMUX display-panes -d 0 -t "$p0" || fail "display-panes no-stack failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +$TMUX copy-mode -t "$p0" || fail "copy-mode over panes-mode failed" +wait_format "$p0" '#{pane_mode}' 'copy-mode' +$TMUX send-keys -t "$p0" -X cancel || fail "copy-mode cancel failed" +wait_format "$p0" '#{pane_in_mode}' '0' +wait_format "$p0" '#{window_zoomed_flag}' '0' + +# -s shows panes from a different source window but enters the mode in the +# target pane. +$TMUX new-window -d -t m: -n src 'cat' || fail "new source window failed" +$TMUX split-window -t m:src 'cat' || fail "split source window failed" +sp0=$($TMUX display-message -p -t m:src.0 '#{pane_id}') +sp1=$($TMUX display-message -p -t m:src.1 '#{pane_id}') +$TMUX respawn-pane -k -t "$sp0" 'printf "SOURCE-PANE\n"; exec cat' || + fail "write source marker failed" +$TMUX set -g display-panes-format '' || fail "clear source display format failed" +$TMUX set -w -t m:src display-panes-format 'SOURCE-ONLY' || + fail "set source window display format failed" +$TMUX set -g @picked none || fail "reset @picked failed" +$TMUX display-panes -d 0 -s m:src -t "$p0" 'set -g @picked %%' || + fail "display-panes source window failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_capture 'SOURCE-PANE' +CAPTURED=$(capture) +printf '%s\n' "$CAPTURED" | grep -F -q 'SOURCE-ONLY' && + fail "used source window display-panes-format" +$TMUX send-keys -t "$p0" 1 || fail "select source pane failed" +wait_format "$p0" '#{pane_in_mode}' '0' +wait_option @picked "$sp1" +$TMUX kill-window -t m:src || fail "kill source window failed" +$TMUX set -g display-panes-format 'P#{pane_index}' || + fail "reset display-panes-format after source failed" + +# pane-border-status rows are drawn as plain preview borders and do not shift +# pane content into the status row. +$TMUX set -w -t m:0 pane-border-status top || + fail "set pane-border-status failed" +$TMUX set -g display-panes-format '' || fail "clear display-panes-format failed" +$TMUX respawn-pane -k -t "$p0" 'printf "STATUS-TOP\n"; exec cat' || + fail "write status marker failed" +wait_capture 'STATUS-TOP' +$TMUX display-panes -d 0 -t "$p0" || fail "display-panes status failed" +wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_capture 'STATUS-TOP' +CAPTURED=$(capture) +first=$(printf '%s\n' "$CAPTURED" | sed -n '1p') +second=$(printf '%s\n' "$CAPTURED" | sed -n '2p') +printf '%s\n' "$first" | grep -F -q 'STATUS-TOP' && + fail "pane content drawn in status border row" +printf '%s\n' "$second" | grep -F -q 'STATUS-TOP' || + fail "pane content not drawn below status border row" +$TMUX send-keys -t "$p0" q || fail "exit status panes mode failed" +wait_format "$p0" '#{pane_in_mode}' '0' +$TMUX set -w -t m:0 pane-border-status off || + fail "reset pane-border-status failed" +$TMUX set -g display-panes-format 'P#{pane_index}' || + fail "reset display-panes-format failed" + +# Floating panes are always framed in panes-mode, independent of their real +# pane-border-lines setting. +fp=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 30 -y 8 -X 8 -Y 3 \ + -t m:0 'cat') || fail "new borderless floating pane failed" +$TMUX set -g display-panes-format '' || fail "clear display-panes-format failed" +$TMUX display-panes -Zd 0 -t "$fp" || fail "display-panes floating failed" +wait_format "$fp" '#{pane_mode}' 'panes-mode' +wait_capture '┌' +wait_capture '┘' +$TMUX send-keys -t "$fp" q || fail "exit floating panes mode failed" +wait_format "$fp" '#{pane_in_mode}' '0' + +$TMUX display-panes -d 0 -t "$fp" || fail "display-panes floating zoomed failed" +wait_format "$fp" '#{pane_mode}' 'panes-mode' +wait_format "$fp" '#{window_zoomed_flag}' '1' +wait_capture '┌' +wait_capture '┘' +$TMUX send-keys -t "$fp" q || fail "exit zoomed floating panes mode failed" +wait_format "$fp" '#{pane_in_mode}' '0' +wait_format "$fp" '#{window_zoomed_flag}' '0' + +$TMUX resize-pane -t "$fp" -x 48 -y 14 || + fail "resize floating pane larger failed" +$TMUX set -g display-panes-format '#[align=right]FP#{pane_width}x#{pane_height}' || + fail "set floating display-panes-format failed" +$TMUX display-panes -Zd 0 -t "$fp" || fail "display-panes floating format failed" +wait_format "$fp" '#{pane_mode}' 'panes-mode' +wait_capture 'FP48x14' +$TMUX resize-pane -t "$fp" -x 30 -y 10 || + fail "resize floating pane smaller failed" +wait_capture 'FP30x10' +$TMUX send-keys -t "$fp" q || fail "exit floating format panes mode failed" +wait_format "$fp" '#{pane_in_mode}' '0' + +$TMUX kill-pane -t "$fp" || fail "kill floating pane failed" +$TMUX set -g display-panes-format 'P#{pane_index}' || + fail "reset display-panes-format after floating failed" + +exit 0 diff --git a/regress/hooks-notify.sh b/regress/hooks-notify.sh index 37d1cd651..6eb6ae49e 100644 --- a/regress/hooks-notify.sh +++ b/regress/hooks-notify.sh @@ -11,11 +11,13 @@ OUT=$(mktemp -d) TMUX_TMPDIR="$OUT" export TMUX_TMPDIR TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" fail() { echo "$*" >&2 $TMUX kill-server 2>/dev/null || true + $TMUX2 kill-server 2>/dev/null || true rm -rf "$OUT" exit 1 } @@ -23,6 +25,7 @@ fail() cleanup() { $TMUX kill-server 2>/dev/null || true + $TMUX2 kill-server 2>/dev/null || true rm -rf "$OUT" } trap cleanup EXIT @@ -92,6 +95,23 @@ wait_for @w 'window-unlinked:main:mywin' $TMUX set-hook -gu window-linked || fail "unset window-linked failed" $TMUX set-hook -gu window-unlinked || fail "unset window-unlinked failed" +# window-created and window-closed are actual window lifetime hooks. +$TMUX set -g @wc 0 || fail "set @wc failed" +$TMUX set-hook -g window-created \ + 'set -gF @wc "#{@wc}|#{hook}:#{hook_window}:#{hook_window_name}"' || + fail "set-hook window-created failed" +$TMUX set-hook -g window-closed \ + 'set -gF @wc "#{@wc}|#{hook}:#{hook_window}:#{hook_window_name}"' || + fail "set-hook window-closed failed" +$TMUX neww -d -t main: -n lifetime || fail "new-window lifetime failed" +window=$($TMUX display -pt main:lifetime '#{window_id}') || + fail "display lifetime window failed" +wait_for @wc "0|window-created:$window:lifetime" +$TMUX kill-window -t main:lifetime || fail "kill-window lifetime failed" +wait_for @wc "0|window-created:$window:lifetime|window-closed:$window:lifetime" +$TMUX set-hook -gu window-created || fail "unset window-created failed" +$TMUX set-hook -gu window-closed || fail "unset window-closed failed" + # window-renamed sees the window id. window=$($TMUX display -pt main:0 '#{window_id}') || fail "display window_id failed" @@ -114,26 +134,75 @@ $TMUX set-hook -gu window-layout-changed || fail "unset window-layout-changed failed" # window-pane-changed from select-pane. +old_pane=$($TMUX display -pt main:0.0 '#{pane_id}') || + fail "display old pane failed" +new_pane=$($TMUX display -pt main:0.1 '#{pane_id}') || + fail "display new pane failed" $TMUX set -g @p 0 || fail "set @p failed" $TMUX set-hook -g window-pane-changed \ - 'set -gF @p "#{hook}:#{hook_window}"' || + 'set -gF @p "#{hook}:#{hook_window}:#{hook_pane}:#{hook_old_pane}:#{hook_new_pane}"' || fail "set-hook window-pane-changed failed" $TMUX selectp -t main:0.1 || fail "select-pane failed" -wait_for @p "window-pane-changed:$window" +wait_for @p "window-pane-changed:$window:$new_pane:$old_pane:$new_pane" + +# window-pane-changed from removing the active pane. +$TMUX set -g @p 0 || fail "reset @p failed" +$TMUX killp -t "$new_pane" || fail "kill active pane failed" +wait_for @p "window-pane-changed:$window:$old_pane:$new_pane:$old_pane" $TMUX set-hook -gu window-pane-changed || fail "unset window-pane-changed failed" # session-window-changed from select-window. $TMUX neww -d -t main: -n w2 || fail "new-window w2 failed" +old_window=$($TMUX display -pt main:0 '#{window_id}') || + fail "display old window failed" +new_window=$($TMUX display -pt main:w2 '#{window_id}') || + fail "display new window failed" +old_index=$($TMUX display -pt main:0 '#{window_index}') || + fail "display old window index failed" +new_index=$($TMUX display -pt main:w2 '#{window_index}') || + fail "display new window index failed" $TMUX set -g @c 0 || fail "set @c failed" $TMUX set-hook -g session-window-changed \ - 'set -gF @c "#{hook}:#{hook_session_name}"' || + 'set -gF @c "#{hook}:#{hook_session_name}:#{hook_window}:#{hook_old_window}:#{hook_new_window}:#{hook_old_window_index}->#{hook_new_window_index}"' || fail "set-hook session-window-changed failed" $TMUX selectw -t main:w2 || fail "select-window failed" -wait_for @c 'session-window-changed:main' +wait_for @c "session-window-changed:main:$new_window:$old_window:$new_window:$old_index->$new_index" $TMUX set-hook -gu session-window-changed || fail "unset session-window-changed failed" +# session-added-to-group and session-removed-from-group carry group details. +$TMUX set -g @sg 0 || fail "set @sg failed" +$TMUX set-hook -g session-added-to-group \ + 'set -gF @sg "#{@sg}|#{hook}:#{hook_session_name}:#{hook_group}:#{hook_group_size}"' || + fail "set-hook session-added-to-group failed" +$TMUX set-hook -g session-removed-from-group \ + 'set -gF @sg "#{@sg}|#{hook}:#{hook_session_name}:#{hook_group}:#{hook_group_size}"' || + fail "set-hook session-removed-from-group failed" +$TMUX new -d -s group-a || fail "new-session group-a failed" +$TMUX new -d -s group-b -t group-a || fail "new-session group-b failed" +wait_for @sg "0|session-added-to-group:group-a:group-a:1|session-added-to-group:group-b:group-a:2" +$TMUX kill-session -t group-b || fail "kill-session group-b failed" +wait_for @sg "0|session-added-to-group:group-a:group-a:1|session-added-to-group:group-b:group-a:2|session-removed-from-group:group-b:group-a:2" +$TMUX set-hook -gu session-added-to-group || + fail "unset session-added-to-group failed" +$TMUX set-hook -gu session-removed-from-group || + fail "unset session-removed-from-group failed" +$TMUX kill-session -t group-a || fail "kill-session group-a failed" + +# window-resized carries old and new dimensions. +old_size=$($TMUX display -pt main:0 '#{window_width},#{window_height}') || + fail "display old window size failed" +$TMUX set -g @wr 0 || fail "set @wr failed" +$TMUX set-hook -g window-resized \ + 'set -gF @wr "#{hook}:#{hook_window}:#{hook_old_width},#{hook_old_height}->#{hook_width},#{hook_height}"' || + fail "set-hook window-resized failed" +$TMUX resizew -t main:0 -x 70 -y 20 || fail "resize-window failed" +new_size=$($TMUX display -pt main:0 '#{window_width},#{window_height}') || + fail "display new window size failed" +wait_for @wr "window-resized:$old_window:$old_size->$new_size" +$TMUX set-hook -gu window-resized || fail "unset window-resized failed" + # pane-mode-changed from entering and leaving copy mode. $TMUX set -g @m 0 || fail "set @m failed" pane=$($TMUX display -pt main:0.0 '#{pane_id}') || @@ -168,18 +237,44 @@ $TMUX set-hook -gu pane-died || fail "unset pane-died failed" $TMUX killp -t "$pane" || fail "kill-pane failed" $TMUX set -g remain-on-exit off || fail "reset remain-on-exit failed" -# pane-title-changed when a pane sets its title. +# pane-title-changed from split-window -T. $TMUX set -g @t 0 || fail "set @t failed" $TMUX set-hook -g pane-title-changed \ - 'set -gF @t "#{hook}:#{hook_pane}:#{pane_title}"' || + 'set -gF @t "#{hook}:#{hook_pane}:#{hook_new_title}:#{pane_title}"' || fail "set-hook pane-title-changed failed" +pane=$($TMUX splitw -d -t main:0 -T splittitle -P -F '#{pane_id}' \ + 'sleep 30') || + fail "split-window title option failed" +wait_for @t "pane-title-changed:$pane:splittitle:splittitle" +$TMUX killp -t "$pane" || fail "kill-pane title option failed" + +# pane-title-changed from select-pane -T. +$TMUX set -g @t 0 || fail "reset @t select title failed" +pane=$($TMUX display -pt main:0.0 '#{pane_id}') || + fail "display select title pane failed" +$TMUX selectp -t "$pane" -T selecttitle || fail "select-pane title failed" +wait_for @t "pane-title-changed:$pane:selecttitle:selecttitle" + +# pane-title-changed when a pane sets its title. +$TMUX set -g @t 0 || fail "reset @t OSC title failed" pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \ 'printf "\033]2;mytitle\007"; sleep 30') || fail "split-window title failed" -wait_for @t "pane-title-changed:$pane:mytitle" +wait_for @t "pane-title-changed:$pane:mytitle:mytitle" +$TMUX killp -t "$pane" || fail "kill-pane title failed" + +# pane-title-changed from popping the title stack. +$TMUX set -g @t 0 || fail "reset @t title pop failed" +$TMUX set-hook -g pane-title-changed \ + 'set -gF @t "#{@t}|#{hook_new_title}"' || + fail "set-hook pane-title-changed pop failed" +pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \ + 'printf "\033]2;stackbase\007\033[22;0t\033]2;stacktemp\007\033[23;0t"; sleep 30') || + fail "split-window title pop failed" +wait_for @t "0|stackbase|stacktemp|stackbase" $TMUX set-hook -gu pane-title-changed || fail "unset pane-title-changed failed" -$TMUX killp -t "$pane" || fail "kill-pane title failed" +$TMUX killp -t "$pane" || fail "kill-pane title pop failed" # pane-created payload from a command pane, an empty pane, and a respawn. $TMUX set -g @pc 0 || fail "set @pc failed" @@ -204,6 +299,82 @@ wait_for @pc "pane-created:$pane:\"sleep 30\":0:1" $TMUX killp -t "$pane" || fail "kill-pane pane-created respawn failed" $TMUX set-hook -gu pane-created || fail "unset pane-created failed" +# pane-moved from break-pane and join-pane. +$TMUX neww -d -t main: -n move-a 'sleep 30' || + fail "new-window move-a failed" +pane=$($TMUX splitw -d -t main:move-a -P -F '#{pane_id}' 'sleep 30') || + fail "split-window pane-moved failed" +target=$($TMUX display -pt main:move-a.0 '#{pane_id}') || + fail "display pane-moved target failed" +old_window=$($TMUX display -pt "$pane" '#{window_id}') || + fail "display pane-moved old window failed" +old_index=$($TMUX display -pt "$pane" '#{window_index}') || + fail "display pane-moved old index failed" +$TMUX set -g @pm 0 || fail "set @pm failed" +$TMUX set-hook -g pane-moved \ + 'set -gF @pm "#{hook}:#{hook_pane}:#{hook_old_window}:#{hook_old_window_index}->#{hook_new_window}:#{hook_new_window_index}"' || + fail "set-hook pane-moved failed" +$TMUX breakp -d -s "$pane" -n move-b -t main: || + fail "break-pane pane-moved failed" +new_window=$($TMUX display -pt "$pane" '#{window_id}') || + fail "display pane-moved break new window failed" +new_index=$($TMUX display -pt "$pane" '#{window_index}') || + fail "display pane-moved break new index failed" +wait_for @pm "pane-moved:$pane:$old_window:$old_index->$new_window:$new_index" + +$TMUX set -g @pm 0 || fail "reset @pm join failed" +old_window=$new_window +old_index=$new_index +$TMUX joinp -d -s "$pane" -t "$target" || + fail "join-pane pane-moved failed" +new_window=$($TMUX display -pt "$pane" '#{window_id}') || + fail "display pane-moved join new window failed" +new_index=$($TMUX display -pt "$pane" '#{window_index}') || + fail "display pane-moved join new index failed" +wait_for @pm "pane-moved:$pane:$old_window:$old_index->$new_window:$new_index" +$TMUX kill-window -t main:move-a || fail "kill-window move-a failed" + +# pane-moved from cross-window swap-pane fires once for each pane. +$TMUX neww -d -t main: -n swap-a 'sleep 30' || + fail "new-window swap-a failed" +$TMUX neww -d -t main: -n swap-b 'sleep 30' || + fail "new-window swap-b failed" +pane1=$($TMUX display -pt main:swap-a.0 '#{pane_id}') || + fail "display swap pane1 failed" +pane2=$($TMUX display -pt main:swap-b.0 '#{pane_id}') || + fail "display swap pane2 failed" +window1=$($TMUX display -pt "$pane1" '#{window_id}') || + fail "display swap window1 failed" +window2=$($TMUX display -pt "$pane2" '#{window_id}') || + fail "display swap window2 failed" +index1=$($TMUX display -pt "$pane1" '#{window_index}') || + fail "display swap index1 failed" +index2=$($TMUX display -pt "$pane2" '#{window_index}') || + fail "display swap index2 failed" +$TMUX set -g @pm 0 || fail "reset @pm swap failed" +$TMUX set-hook -g pane-moved \ + 'set -gF @pm "#{@pm}|#{hook_pane}:#{hook_old_window}:#{hook_old_window_index}->#{hook_new_window}:#{hook_new_window_index}"' || + fail "set-hook pane-moved swap failed" +$TMUX swapp -d -s "$pane1" -t "$pane2" || + fail "swap-pane pane-moved failed" +wait_for @pm "0|$pane1:$window1:$index1->$window2:$index2|$pane2:$window2:$index2->$window1:$index1" +$TMUX set-hook -gu pane-moved || fail "unset pane-moved failed" +$TMUX kill-window -t main:swap-a || fail "kill-window swap-a failed" +$TMUX kill-window -t main:swap-b || fail "kill-window swap-b failed" + +# pane-bell fires for the actual pane even when bell alerts are disabled. +pane=$($TMUX neww -d -t main: -n raw-bell -P -F '#{pane_id}' 'cat') || + fail "new-window pane-bell failed" +$TMUX setw -t main:raw-bell monitor-bell off || + fail "set monitor-bell off failed" +$TMUX set -g @pb 0 || fail "set @pb failed" +$TMUX set-hook -g pane-bell 'set -gF @pb "#{hook}:#{hook_pane}"' || + fail "set-hook pane-bell failed" +$TMUX send-keys -t "$pane" -H 07 0a || fail "send-keys pane-bell failed" +wait_for @pb "pane-bell:$pane" +$TMUX set-hook -gu pane-bell || fail "unset pane-bell failed" +$TMUX kill-window -t main:raw-bell || fail "kill-window pane-bell failed" + # pane-resized carries old and new dimensions. pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'sleep 30') || fail "split-window pane-resized failed" @@ -276,6 +447,13 @@ $TMUX killp -t "$pane" || fail "kill-pane zoom failed" # client-attached and client-detached using a control client. $TMUX set -g @a 0 || fail "set @a failed" +$TMUX set -g @cl 0 || fail "set @cl failed" +$TMUX set-hook -g client-created \ + 'set -gF @cl "#{?#{m:client-*,#{hook_client}},#{@cl}|#{hook}:#{hook_client},#{@cl}}"' || + fail "set-hook client-created failed" +$TMUX set-hook -g client-closed \ + 'set -gF @cl "#{?#{m:client-*,#{hook_client}},#{@cl}|#{hook}:#{hook_client},#{@cl}}"' || + fail "set-hook client-closed failed" $TMUX set-hook -g client-attached 'set -gF @a "#{hook}"' || fail "set-hook client-attached failed" $TMUX set-hook -g client-detached 'set -gF @a "#{hook}"' || @@ -284,9 +462,72 @@ mkfifo "$OUT/fifo" || fail "mkfifo failed" $TMUX -C attach -t main <"$OUT/fifo" >"$OUT/control.out" 2>&1 & exec 3>"$OUT/fifo" wait_for @a 'client-attached' + +# client-session-changed carries old and new sessions. +$TMUX new -d -s client-target || fail "new-session client target failed" +client=$($TMUX list-clients -F '#{client_name} #{client_control_mode}' | + awk '$2 == 1 { print $1; exit }') || + fail "list control client failed" +[ -n "$client" ] || fail "no control client found" +wait_for @cl "0|client-created:$client" +old_session=$($TMUX display -pt main '#{session_id}') || + fail "display old client session failed" +new_session=$($TMUX display -pt client-target '#{session_id}') || + fail "display new client session failed" +$TMUX set -g @cs 0 || fail "set @cs failed" +$TMUX set-hook -g client-session-changed \ + 'set -gF @cs "#{hook}:#{hook_client}:#{hook_session}:#{hook_old_session}:#{hook_new_session}"' || + fail "set-hook client-session-changed failed" +$TMUX switch-client -c "$client" -t client-target || + fail "switch-client session failed" +wait_for @cs "client-session-changed:$client:$new_session:$old_session:$new_session" +$TMUX set-hook -gu client-session-changed || + fail "unset client-session-changed failed" + exec 3>&- wait_for @a 'client-detached' +wait_for @cl "0|client-created:$client|client-closed:$client" +$TMUX set-hook -gu client-created || fail "unset client-created failed" +$TMUX set-hook -gu client-closed || fail "unset client-closed failed" $TMUX set-hook -gu client-attached || fail "unset client-attached failed" $TMUX set-hook -gu client-detached || fail "unset client-detached failed" +# client-resized carries old and new dimensions. +$TMUX2 new -d -s resize -x 80 -y 24 'sleep 30' || + fail "new-session client-resized failed" +$TMUX2 set -g @cr 0 || fail "set @cr failed" +$TMUX2 set-hook -g client-resized \ + 'set -gF @cr "#{hook}:#{hook_client}:#{hook_old_width},#{hook_old_height}->#{hook_width},#{hook_height}"' || + fail "set-hook client-resized failed" +$TMUX neww -d -n resize-client "$TMUX2 attach -t resize" || + fail "new outer window for client-resized failed" +i=0 +client= +while [ $i -lt 30 ]; do + client=$($TMUX2 list-clients -F '#{client_name}' 2>/dev/null | + awk 'NR == 1 { print; exit }') + [ -n "$client" ] && break + i=$((i + 1)) + sleep 0.2 +done +[ -n "$client" ] || fail "inner client did not attach" +old_size=$($TMUX2 list-clients -F '#{client_width},#{client_height}') || + fail "display old client size failed" +$TMUX resizew -t resize-client -x 70 -y 20 || + fail "resize outer client window failed" +i=0 +while [ $i -lt 30 ]; do + value=$($TMUX2 show -gqv @cr 2>/dev/null || true) + new_size=$($TMUX2 list-clients -F '#{client_width},#{client_height}' \ + 2>/dev/null || true) + expected="client-resized:$client:$old_size->$new_size" + [ "$new_size" != "$old_size" ] && [ "$value" = "$expected" ] && + break + i=$((i + 1)) + sleep 0.2 +done +[ "$value" = "$expected" ] || + fail "expected @cr to be '$expected' but got '$value'" +$TMUX2 set-hook -gu client-resized || fail "unset client-resized failed" + exit 0 diff --git a/regress/mode-kill.sh b/regress/mode-kill.sh index a3e0d09b9..d9fb4dc64 100755 --- a/regress/mode-kill.sh +++ b/regress/mode-kill.sh @@ -8,9 +8,11 @@ # - copy-mode -k (window-copy.c); # - choose-tree -k (window-tree.c); # - choose-buffer -k (window-buffer.c). +# - display-panes -k (window-panes.c). # # choose-tree and choose-buffer share cmd_choose_tree_exec(), which also backs -# choose-client and customize-mode, so those are not repeated. +# choose-client, customize-mode, and display-panes, so choose-client and +# customize-mode are not repeated. # # Each mode is entered in the active pane of a two-pane window: exiting with # -k must remove that pane and leave the other. copy-mode is left with the @@ -147,7 +149,7 @@ wait_mode "$active" 0 pane_gone "$active" && fail 'copy-mode: pane killed without -k' $TMUX kill-window -t m:w 2>/dev/null -# --- choose-tree -k and choose-buffer -k kill the pane ---------------------- +# --- choose-tree -k, choose-buffer -k, and display-panes -k kill the pane --- # # These need the client to draw the mode before a key acts, so attach one now. # A paste buffer is needed for choose-buffer to have something to show, and a @@ -168,4 +170,11 @@ wait_for 'BUFMARK' $TMUX send-keys -t m:w q || fail "choose-buffer exit failed" check_killed 'choose-buffer -k' +open_window +$TMUX set -g display-panes-format 'DPMARK' || fail "set display-panes-format" +$TMUX display-panes -k -d 0 -t m:w || fail "display-panes -k failed" +wait_for 'DPMARK' +$TMUX send-keys -t m:w q || fail "display-panes exit failed" +check_killed 'display-panes -k' + exit 0 diff --git a/regress/new-pane-mouse.sh b/regress/new-pane-mouse.sh new file mode 100644 index 000000000..d2ae9ee97 --- /dev/null +++ b/regress/new-pane-mouse.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +# Test new-pane -M creates a floating pane from a mouse drag. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +cleanup() +{ + $TMUX kill-server >/dev/null 2>&1 + $TMUX2 kill-server >/dev/null 2>&1 +} +fail() +{ + echo "$*" >&2 + cleanup + exit 1 +} +must_equal() +{ + got=$1 + want=$2 + [ "$got" = "$want" ] || fail "got '$got', expected '$want'" +} + +# drag STARTCOL STARTROW ENDCOL ENDROW +# +# Write an SGR Ctrl-mouse press, drag update and release at 1-based positions +# to the outer pane holding the inner client. +drag() +{ + scol="$1" + srow="$2" + ecol="$3" + erow="$4" + + seq=$(printf '\033[<16;%s;%sM' "$scol" "$srow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<48;%s;%sM' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<16;%s;%sm' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} + +cleanup + +$TMUX new-session -d -s inner -x 80 -y 24 || exit 1 +$TMUX set -g mouse on +$TMUX set -g default-command 'sleep 100' + +$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t inner" || exit 1 +sleep 1 +OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1) +[ -n "$OUTER" ] || fail "No outer pane." +BASE=$($TMUX list-panes -F '#{pane_id}' | head -1) +[ -n "$BASE" ] || fail "No base pane." + +# Drag from SGR column 3,row 1 to column 10,row 5. These are window positions +# 2,0 to 9,4. With the default border, the floating pane content is inset by +# one cell and has size 6x3. +drag 3 1 10 5 + +id=$($TMUX list-panes -F '#{?pane_floating_flag,#{pane_id},}' | tail -n 1) +[ -n "$id" ] || fail "no floating pane created" + +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 3 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 6 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 3 + +$TMUX new-pane -d -M -L -t "$BASE" 'sleep 100' || fail "new-pane -M -L failed" + +cleanup +exit 0 diff --git a/regress/screen-redraw-menus.sh b/regress/screen-redraw-menus.sh new file mode 100644 index 000000000..1bd3bc99b --- /dev/null +++ b/regress/screen-redraw-menus.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +# Exercise drawing of window menus in the screen-redraw.c scene. Menus are +# window-owned and drawn as REDRAW_MENU spans over panes. Captures use -e so +# menu-style, menu-selected-style, and menu-border-style are included. +# +# Run with GENERATE=1 to (re)create the golden files. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX= +TMUX2= +SETUP=0 +RESULTS=screen-redraw-results + +TMP=$(mktemp) +TMP2=$(mktemp) + +cleanup() { + rm -f "$TMP" "$TMP2" + [ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null + [ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null +} +trap cleanup 0 1 15 + +fail() { + echo "$*" >&2 + exit 1 +} + +trim() { + awk ' + { lines[NR] = $0 } + END { + n = NR + while (n > 0 && lines[n] == "") + n-- + for (i = 1; i <= n; i++) + print lines[i] + } + ' "$TMP" >"$TMP2" || exit 1 + mv "$TMP2" "$TMP" || exit 1 +} + +compare() { + sleep 1 + $TMUX capturep -pe >$TMP || exit 1 + trim + if [ -n "$GENERATE" ]; then + cp $TMP "$RESULTS/$1.result" || exit 1 + echo "generated $1" + else + cmp -s $TMP "$RESULTS/$1.result" || \ + fail "scene $1 differs from $RESULTS/$1.result" + fi +} + +C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"MENU%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" + +setup() { + [ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null + [ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null + SETUP=$((SETUP + 1)) + TMUX="$TEST_TMUX -LtestA$$-$SETUP -f/dev/null" + TMUX2="$TEST_TMUX -LtestB$$-$SETUP -f/dev/null" + $TMUX2 new -d -x"$1" -y"$2" "$C" || exit 1 + $TMUX2 set -g status off || exit 1 + $TMUX2 set -g window-size manual || exit 1 + $TMUX2 resizew -x"$1" -y"$2" || exit 1 + $TMUX2 setw menu-style "fg=colour250,bg=colour235" || exit 1 + $TMUX2 setw menu-selected-style "fg=colour16,bg=colour220" || exit 1 + $TMUX2 setw menu-border-style "fg=colour45,bg=colour235" || exit 1 + + $TMUX new -d -x"$1" -y"$2" || exit 1 + $TMUX set -g status off || exit 1 + $TMUX set -g window-size manual || exit 1 + $TMUX set -g default-terminal "tmux-256color" || exit 1 + $TMUX send -l "$TMUX2 attach" || exit 1 + $TMUX send Enter || exit 1 + sleep 1 +} + +menu() { + $TMUX2 display-menu -T "Menu" -C 1 "$@" \ + "Alpha item" a "" \ + "Beta item" b "" \ + "" "" "" \ + "Gamma item" g "" || exit 1 + sleep 1 +} + +# Basic menu over a single pane. +setup 40 14 +menu -x6 -y8 +compare menu-basic + +# Menu over a split: drawn on top of the pane border. +setup 40 14 +$TMUX2 splitw -h "$C" || exit 1 +menu -x8 -y9 +compare menu-over-split + +# Menu with no border lines. +setup 40 14 +menu -b none -x6 -y8 +compare menu-noborder + +# Menu with double border lines. +setup 40 14 +menu -b double -x6 -y8 +compare menu-double + +# Window menu styles are re-expanded from window options when redrawn. +setup 40 14 +menu -x6 -y8 +$TMUX2 setw menu-style "fg=colour196,bg=colour22" || exit 1 +$TMUX2 setw menu-selected-style "fg=colour231,bg=colour88" || exit 1 +$TMUX2 setw menu-border-style "fg=colour51,bg=colour22" || exit 1 +$TMUX2 refresh-client -S || exit 1 +compare menu-restyled + +# Menu wider than the window: it should be accepted and clipped by the scene. +setup 12 6 +$TMUX2 display-menu -T "Menu" -C 0 -x0 -y5 \ + "This item is much wider than the window" t "" \ + "Second item is also too wide" s "" || exit 1 +compare menu-clipped + +exit 0 diff --git a/regress/screen-redraw-results/menu-basic.result b/regress/screen-redraw-results/menu-basic.result new file mode 100644 index 000000000..8ee309b45 --- /dev/null +++ b/regress/screen-redraw-results/menu-basic.result @@ -0,0 +1,13 @@ +MENU00 abcdefghij +MENU01 abcdefghij +MENU02┌─Menu───────────┐ +MENU03│ Alpha item (a) │ +MENU04│ Beta item (b) │ +MENU05├────────────────┤ +MENU06│ Gamma item (g) │ +MENU07└────────────────┘ +MENU08 abcdefghij +MENU09 abcdefghij +MENU10 abcdefghij +MENU11 abcdefghij +MENU12 abcdefghij diff --git a/regress/screen-redraw-results/menu-clipped.result b/regress/screen-redraw-results/menu-clipped.result new file mode 100644 index 000000000..db9d06f2b --- /dev/null +++ b/regress/screen-redraw-results/menu-clipped.result @@ -0,0 +1,5 @@ +fghij +┌─Menu─────┐ +│ window> │ +│ oo wide> │ +└──────────┘ diff --git a/regress/screen-redraw-results/menu-double.result b/regress/screen-redraw-results/menu-double.result new file mode 100644 index 000000000..7ab65b7b6 --- /dev/null +++ b/regress/screen-redraw-results/menu-double.result @@ -0,0 +1,13 @@ +MENU00 abcdefghij +MENU01 abcdefghij +MENU02╔═Menu═══════════╗ +MENU03║ Alpha item (a) ║ +MENU04║ Beta item (b) ║ +MENU05╠════════════════╣ +MENU06║ Gamma item (g) ║ +MENU07╚════════════════╝ +MENU08 abcdefghij +MENU09 abcdefghij +MENU10 abcdefghij +MENU11 abcdefghij +MENU12 abcdefghij diff --git a/regress/screen-redraw-results/menu-noborder.result b/regress/screen-redraw-results/menu-noborder.result new file mode 100644 index 000000000..d6479edba --- /dev/null +++ b/regress/screen-redraw-results/menu-noborder.result @@ -0,0 +1,13 @@ +MENU00 abcdefghij +MENU01 abcdefghij +MENU02 Menu  +MENU03  Alpha item (a)   +MENU04  Beta item (b)   +MENU05  +MENU06  Gamma item (g)   +MENU07  +MENU08 abcdefghij +MENU09 abcdefghij +MENU10 abcdefghij +MENU11 abcdefghij +MENU12 abcdefghij diff --git a/regress/screen-redraw-results/menu-over-split.result b/regress/screen-redraw-results/menu-over-split.result new file mode 100644 index 000000000..9d8714a67 --- /dev/null +++ b/regress/screen-redraw-results/menu-over-split.result @@ -0,0 +1,14 @@ +MENU00 abcdefghij │MENU00 abcdefghij +MENU01 abcdefghij │MENU01 abcdefghij +MENU02 abcdefghij │MENU02 abcdefghij +MENU03 a┌─Menu───────────┐3 abcdefghij +MENU04 a│ Alpha item (a) │4 abcdefghij +MENU05 a│ Beta item (b) │5 abcdefghij +MENU06 a├────────────────┤6 abcdefghij +MENU07 a│ Gamma item (g) │7 abcdefghij +MENU08 a└────────────────┘8 abcdefghij +MENU09 abcdefghij │MENU09 abcdefghij +MENU10 abcdefghij │MENU10 abcdefghij +MENU11 abcdefghij │MENU11 abcdefghij +MENU12 abcdefghij │MENU12 abcdefghij + │ diff --git a/regress/screen-redraw-results/menu-restyled.result b/regress/screen-redraw-results/menu-restyled.result new file mode 100644 index 000000000..04d144b72 --- /dev/null +++ b/regress/screen-redraw-results/menu-restyled.result @@ -0,0 +1,13 @@ +MENU00 abcdefghij +MENU01 abcdefghij +MENU02┌─Menu───────────┐ +MENU03│ Alpha item (a) │ +MENU04│ Beta item (b) │ +MENU05├────────────────┤ +MENU06│ Gamma item (g) │ +MENU07└────────────────┘ +MENU08 abcdefghij +MENU09 abcdefghij +MENU10 abcdefghij +MENU11 abcdefghij +MENU12 abcdefghij diff --git a/resize.c b/resize.c index c8257cec0..1d83e4b70 100644 --- a/resize.c +++ b/resize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: resize.c,v 1.56 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: resize.c,v 1.57 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -22,9 +22,27 @@ #include "tmux.h" +static void +resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_window(&fs, w, 0); + event_payload_set_target(ep, &fs); + event_payload_set_window(ep, "window", w); + event_payload_set_uint(ep, "width", w->sx); + event_payload_set_uint(ep, "height", w->sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("window-resized", ep); +} + void resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) { + u_int old_sx = w->sx, old_sy = w->sy; int zoomed; /* Check size limits. */ @@ -61,7 +79,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) tty_update_window_offset(w); server_redraw_window(w); events_fire_window("window-layout-changed", w); - events_fire_window("window-resized", w); + resize_fire_window_resized(w, old_sx, old_sy); w->flags &= ~WINDOW_RESIZE; } diff --git a/screen-redraw.c b/screen-redraw.c index fd67041f7..454c5df6c 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-redraw.c,v 1.150 2026/07/09 07:32:58 nicm Exp $ */ +/* $OpenBSD: screen-redraw.c,v 1.151 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -68,8 +68,9 @@ enum redraw_span_type { REDRAW_SPAN_STATUS, /* pane status line */ REDRAW_SPAN_BORDER, /* pane border */ REDRAW_SPAN_SCROLLBAR, /* pane scrollbar */ + REDRAW_SPAN_MENU, /* window menu */ }; -#define REDRAW_SPAN_TYPES 6 +#define REDRAW_SPAN_TYPES 7 /* Border connections to adjacent cells. */ #define REDRAW_BORDER_L 0x1 @@ -91,7 +92,8 @@ enum redraw_span_type { #define REDRAW_PANE_STATUS 0x10 #define REDRAW_PANE_SCROLLBAR 0x20 #define REDRAW_STATUS 0x40 -#define REDRAW_OVERLAY 0x80 +#define REDRAW_MENU 0x80 +#define REDRAW_OVERLAY 0x100 /* Draw everything. */ #define REDRAW_ALL 0x7fffffff @@ -163,6 +165,15 @@ struct redraw_span_data { /* Flags for this span. */ int flags; } sb; /* pane scrollbar */ + + struct { + /* Menu this span belongs to. */ + struct menu_data *md; + + /* Position of span inside the menu. */ + u_int px; + u_int py; + } m; /* menu */ }; }; @@ -251,6 +262,8 @@ redraw_flags_to_string(int flags) strlcat(s, "pane-status ", sizeof s); if (flags & REDRAW_PANE_SCROLLBAR) strlcat(s, "scrollbar ", sizeof s); + if (flags & REDRAW_MENU) + strlcat(s, "menu ", sizeof s); if (flags & REDRAW_OVERLAY) strlcat(s, "overlay ", sizeof s); if (REDRAW_IS_ALL(flags)) @@ -828,6 +841,34 @@ redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx) } } +/* Mark the window menu above all panes. */ +static void +redraw_mark_menu(struct redraw_build_ctx *bctx) +{ + struct menu_data *md = bctx->w->menu; + struct redraw_build_cell *bc; + u_int px, py, x, y, sx, sy; + + if (md == NULL) + return; + + sx = menu_width(md); + sy = menu_height(md); + for (py = 0; py < sy; py++) { + for (px = 0; px < sx; px++) { + if (!redraw_window_to_scene(bctx, menu_x(md) + px, + menu_y(md) + py, &x, &y)) + continue; + bc = redraw_get_build_cell(bctx, x, y); + memset(bc, 0, sizeof *bc); + bc->data.type = REDRAW_SPAN_MENU; + bc->data.m.md = md; + bc->data.m.px = px; + bc->data.m.py = py; + } + } +} + /* Return true if two adjacent build cells can be joined into one span. */ static int redraw_compare_data(struct redraw_build_cell *a, struct redraw_build_cell *b) @@ -874,6 +915,12 @@ redraw_compare_data(struct redraw_build_cell *a, struct redraw_build_cell *b) ad->sb.flags != bd->sb.flags) return (0); return (1); + case REDRAW_SPAN_MENU: + if (ad->m.md != bd->m.md || + ad->m.py != bd->m.py || + ad->m.px + 1 != bd->m.px) + return (0); + return (1); case REDRAW_SPAN_OUTSIDE: case REDRAW_SPAN_EMPTY: return (1); @@ -908,6 +955,7 @@ redraw_build_cells(struct redraw_build_ctx *bctx) TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) redraw_mark_pane(bctx, wp); redraw_mark_two_pane_colours(bctx); + redraw_mark_menu(bctx); } /* @@ -1303,6 +1351,20 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, } } +/* Draw a menu span. */ +static void +redraw_draw_menu_span(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int x, u_int y, u_int n) +{ + struct redraw_scene *scene = dctx->scene; + struct tty *tty = &scene->c->tty; + struct screen *s = menu_screen(span->data.m.md); + u_int px; + + px = span->data.m.px + (x - span->x); + tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL); +} + /* Draw a span. */ static void redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, @@ -1343,6 +1405,9 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, case REDRAW_SPAN_SCROLLBAR: redraw_draw_scrollbar_span(dctx, span, x, y, n); break; + case REDRAW_SPAN_MENU: + redraw_draw_menu_span(dctx, span, x, y, n); + break; } } } @@ -1434,6 +1499,10 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) if (~flags & REDRAW_PANE_SCROLLBAR) continue; break; + case REDRAW_SPAN_MENU: + if (~flags & REDRAW_MENU) + continue; + break; default: continue; } @@ -1445,6 +1514,26 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) } } +/* Draw menu spans. */ +static void +redraw_draw_menu_lines(struct redraw_draw_ctx *dctx) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_line *line; + struct redraw_span *span; + u_int y, cy; + + for (y = 0; y < scene->sy; y++) { + line = &scene->lines[y]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + y; + else + cy = y; + TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) + redraw_draw_span(dctx, span, cy); + } +} + /* Get line for pane status line. */ static int redraw_pane_status_line(struct redraw_draw_ctx *dctx, @@ -1620,6 +1709,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) if (scene == NULL) return; redraw_set_draw_context(&dctx, scene); + if (w->menu != NULL) + menu_update(w->menu); if (flags & (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS)) { TAILQ_FOREACH(loop, &scene->w->panes, entry) { @@ -1686,6 +1777,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) } } } + if (w->menu != NULL && (flags & REDRAW_MENU)) + redraw_draw_menu_lines(&dctx); if (flags & REDRAW_STATUS) { lines = dctx.status_lines; @@ -1774,6 +1867,10 @@ redraw_screen(struct client *c) flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWOVERLAY) flags |= REDRAW_OVERLAY; + if (c->flags & CLIENT_REDRAWMENU) + flags |= REDRAW_MENU; + if (c->session->curw->window->menu != NULL) + flags |= REDRAW_MENU; if (flags != 0) redraw_draw(c, NULL, flags); } @@ -1784,6 +1881,8 @@ void redraw_pane(struct client *c, struct window_pane *wp) { redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR); + if (c->session->curw->window->menu != NULL) + redraw_draw(c, NULL, REDRAW_MENU); } /* Draw a pane's scrollbar. */ diff --git a/server-client.c b/server-client.c index 2865133a7..c5457b14e 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.486 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.491 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -48,8 +48,6 @@ static void server_client_dispatch(struct imsg *, void *); static int server_client_dispatch_command(struct client *, struct imsg *); static int server_client_dispatch_identify(struct client *, struct imsg *); static int server_client_dispatch_shell(struct client *); -static void server_client_update_scrollbar_hover(struct client *, int, int, - int); static void server_client_report_theme(struct client *, enum client_theme); /* Compare client windows. */ @@ -403,6 +401,62 @@ server_client_attached_lost(struct client *c) } } +/* Fire client session changed. */ +static void +server_client_fire_session_changed(struct client *c, struct session *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) { + event_payload_set_session(ep, "session", fs.s); + event_payload_set_session(ep, "new_session", fs.s); + } + if (old != NULL) + event_payload_set_session(ep, "old_session", old); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + events_fire("client-session-changed", ep); +} + +/* Fire client resized. */ +static void +server_client_fire_resized(struct client *c, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) + event_payload_set_session(ep, "session", fs.s); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + event_payload_set_uint(ep, "width", c->tty.sx); + event_payload_set_uint(ep, "height", c->tty.sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("client-resized", ep); +} + /* Set client session. */ void server_client_set_session(struct client *c, struct session *s) @@ -429,7 +483,7 @@ server_client_set_session(struct client *c, struct session *s) alerts_check_session(s); tty_update_client_offset(c); status_timer_start(c); - events_fire_client("client-session-changed", c); + server_client_fire_session_changed(c, old); server_redraw_client(c); } @@ -466,6 +520,8 @@ server_client_lost(struct client *c) server_client_attached_lost(c); events_fire_client("client-detached", c); } + if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL))) + events_fire_client("client-closed", c); if (c->flags & CLIENT_CONTROL) control_stop(c); @@ -1111,6 +1167,10 @@ have_event: * the scrollbar, store the relative position in the slider * where the user grabbed. */ + if (c->tty.mouse_drag_flag == 0) { + c->tty.mouse_drag_x = px; + c->tty.mouse_drag_y = py; + } c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; /* Only change pane if not already dragging a pane border. */ @@ -1589,6 +1649,40 @@ out: return (CMD_RETURN_NORMAL); } +/* Handle a key event for the active window menu, if any. */ +static int +server_client_handle_menu_key(struct client *c, struct key_event *event) +{ + struct window *w = c->session->curw->window; + struct key_event new_event; + struct mouse_event *m; + u_int ox, oy, sx, sy; + + if (w->menu == NULL) + return (0); + + memcpy(&new_event, event, sizeof new_event); + if (KEYC_IS_MOUSE(event->key)) { + m = &new_event.m; + tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); + m->x += ox; + if (m->statusat == 0) { + if (m->y < m->statuslines) + m->x = m->y = UINT_MAX; + else + m->y = m->y - m->statuslines + oy; + } else if (m->statusat > 0 && + m->y >= (u_int)m->statusat) + m->x = m->y = UINT_MAX; + else + m->y += oy; + } + + if (menu_key(c, w->menu, &new_event) == 1) + menu_close(w); + return (1); +} + /* Handle a key event. */ static int server_client_handle_key0(struct client *c, struct key_event *event, @@ -1638,6 +1732,8 @@ server_client_handle_key0(struct client *c, struct key_event *event, } server_client_clear_overlay(c); + if (server_client_handle_menu_key(c, event)) + return (0); if (c->prompt != NULL) { switch (status_prompt_key(c, event->key, &event->m)) { case PROMPT_KEY_HANDLED: @@ -1660,7 +1756,8 @@ server_client_handle_key0(struct client *c, struct key_event *event, if (wp != NULL && window_pane_has_prompt(wp) && window_pane_is_visible(wp)) { - switch (window_pane_prompt_key(wp, c, event->key, &event->m)) { + switch (window_pane_prompt_key(wp, c, event->key, + &event->m)) { case PROMPT_KEY_HANDLED: case PROMPT_KEY_CLOSE: case PROMPT_KEY_MOVE: @@ -1750,7 +1847,8 @@ server_client_loop(void) server_client_check_pane_resize(wp); server_client_check_pane_buffer(wp); } - wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR); + wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR| + PANE_ACTIVITY); } check_window_name(w); } @@ -2011,6 +2109,9 @@ server_client_reset_state(struct client *c) if (c->overlay_draw != NULL) { if (c->overlay_mode != NULL) s = c->overlay_mode(c, c->overlay_data, &cx, &cy); + } else if (w->menu != NULL) { + menu_get_cursor(w->menu, &cx, &cy); + s = menu_screen(w->menu); } else if (wp != NULL && c->prompt == NULL) s = wp->screen; else @@ -2031,7 +2132,22 @@ server_client_reset_state(struct client *c) prompt = 1; status_prompt_cursor(c, &cx, &cy); } else if (wp != NULL && c->overlay_draw == NULL) { - prompt = server_client_prompt_cursor(c, wp, &mode, &cx, &cy); + if (w->menu != NULL) { + tty_window_offset(tty, &ox, &oy, &sx, &sy); + if (cx < ox || cx >= ox + sx || + cy < oy || cy >= oy + sy) + mode &= ~MODE_CURSOR; + else { + cx -= ox; + cy -= oy; + if (status_at_line(c) == 0) + cy += status_line_size(c); + } + prompt = 1; + } else { + prompt = server_client_prompt_cursor(c, wp, &mode, &cx, + &cy); + } if (!prompt) { cursor = 0; pane_mode = wp->base.mode; @@ -2083,7 +2199,7 @@ server_client_reset_state(struct client *c) * movement events. */ if (options_get_number(oo, "mouse")) { - if (c->overlay_draw == NULL) { + if (c->overlay_draw == NULL && w->menu == NULL) { mode &= ~ALL_MOUSE_MODES; TAILQ_FOREACH(loop, &w->panes, entry) { if (loop->screen->mode & MODE_MOUSE_ALL) @@ -2262,11 +2378,12 @@ server_client_check_redraw(struct client *c) if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) return; if (c->flags & CLIENT_ALLREDRAWFLAGS) { - log_debug("%s: redraw%s%s%s%s", c->name, + log_debug("%s: redraw%s%s%s%s%s", c->name, (c->flags & CLIENT_REDRAWWINDOW) ? " window" : "", (c->flags & CLIENT_REDRAWSTATUS) ? " status" : "", (c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "", - (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : ""); + (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "", + (c->flags & CLIENT_REDRAWMENU) ? " menu" : ""); } /* Work out if a redraw is actually needed. */ @@ -2421,6 +2538,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) struct client *c = arg; ssize_t datalen; struct session *s; + u_int old_sx, old_sy; if (c->flags & CLIENT_DEAD) return; @@ -2459,6 +2577,8 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (c->flags & CLIENT_CONTROL) break; server_client_update_latest(c); + old_sx = c->tty.sx; + old_sy = c->tty.sy; tty_resize(&c->tty); tty_repeat_requests(&c->tty, 0); recalculate_sizes(); @@ -2468,7 +2588,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) c->overlay_resize(c, c->overlay_data); server_redraw_client(c); if (c->session != NULL) - events_fire_client("client-resized", c); + server_client_fire_resized(c, old_sx, old_sy); break; case MSG_EXITING: if (datalen != 0) @@ -2750,6 +2870,8 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) close(c->out_fd); c->out_fd = -1; } + if (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL)) + events_fire_client("client-created", c); /* If pasting has taken too long, turn it off. */ if (c->flags & (CLIENT_BRACKETPASTING|CLIENT_ASSUMEPASTING) && @@ -3033,7 +3155,8 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb) wp = server_client_get_pane(c); wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) - window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL, + NULL); if (parse) { do { line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF); diff --git a/server-fn.c b/server-fn.c index 9bf48b162..862f4d836 100644 --- a/server-fn.c +++ b/server-fn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-fn.c,v 1.147 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: server-fn.c,v 1.148 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -128,6 +128,19 @@ server_redraw_window(struct window *w) } } +void +server_redraw_window_menu(struct window *w) +{ + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + if (c->session != NULL && + c->session->curw != NULL && + c->session->curw->window == w) + c->flags |= CLIENT_REDRAWMENU; + } +} + void server_redraw_window_borders(struct window *w) { diff --git a/session.c b/session.c index da30f586b..d52b70b88 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.105 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: session.c,v 1.106 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -471,6 +471,29 @@ session_last(struct session *s) return (session_set_current(s, wl)); } +/* Fire session window changed. */ +static void +session_fire_window_changed(struct session *s, struct winlink *wl, + struct winlink *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_winlink(&fs, wl, 0); + event_payload_set_target(ep, &fs); + event_payload_set_session(ep, "session", s); + event_payload_set_window(ep, "window", wl->window); + event_payload_set_window(ep, "new_window", wl->window); + event_payload_set_int(ep, "window_index", wl->idx); + event_payload_set_int(ep, "new_window_index", wl->idx); + if (old != NULL) { + event_payload_set_window(ep, "old_window", old->window); + event_payload_set_int(ep, "old_window_index", old->idx); + } + events_fire("session-window-changed", ep); +} + /* Set current winlink to wl .*/ int session_set_current(struct session *s, struct winlink *wl) @@ -493,7 +516,7 @@ session_set_current(struct session *s, struct winlink *wl) winlink_clear_flags(wl); window_update_activity(wl->window); tty_update_window_offset(wl->window); - events_fire_session("session-window-changed", s); + session_fire_window_changed(s, wl, old); return (0); } @@ -540,12 +563,33 @@ session_group_new(const char *name) return (sg); } +/* Fire session group changed. */ +static void +session_group_fire(const char *name, struct session_group *sg, + struct session *s) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + if (session_alive(s)) { + cmd_find_from_session(&fs, s, 0); + event_payload_set_target(ep, &fs); + } + event_payload_set_session(ep, "session", s); + event_payload_set_string(ep, "group", "%s", sg->name); + event_payload_set_uint(ep, "group_size", session_group_count(sg)); + events_fire(name, ep); +} + /* Add a session to a session group. */ void session_group_add(struct session_group *sg, struct session *s) { - if (session_group_contains(s) == NULL) + if (session_group_contains(s) == NULL) { TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); + session_group_fire("session-added-to-group", sg, s); + } } /* Remove a session from its group and destroy the group if empty. */ @@ -556,6 +600,7 @@ session_group_remove(struct session *s) if ((sg = session_group_contains(s)) == NULL) return; + session_group_fire("session-removed-from-group", sg, s); TAILQ_REMOVE(&sg->sessions, s, gentry); if (TAILQ_EMPTY(&sg->sessions)) { RB_REMOVE(session_groups, &session_groups, sg); diff --git a/spawn.c b/spawn.c index 37798f44f..f11e2e2a0 100644 --- a/spawn.c +++ b/spawn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spawn.c,v 1.47 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: spawn.c,v 1.48 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -229,8 +229,10 @@ spawn_window(struct spawn_context *sc, char **cause) session_select(s, sc->wl->idx); /* Fire notification if new window. */ - if (~sc->flags & SPAWN_RESPAWN) + if (~sc->flags & SPAWN_RESPAWN) { + events_fire_window("window-created", w); events_fire_winlink("window-linked", sc->wl); + } session_group_synchronize_from(s); return (sc->wl); diff --git a/tmux.1 b/tmux.1 index 9f921c91a..372c47c7e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1132 2026/07/10 15:20:06 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1140 2026/07/14 17:17:18 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 10 2026 $ +.Dd $Mdocdate: July 14 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -850,6 +850,7 @@ Each has a single-character alternative form. .It Li "{next}" Ta "+" Ta "The next window by number" .It Li "{previous}" Ta "\-" Ta "The previous window by number" .It Li "{current}" Ta "@" Ta "The current window" +.It Li "{mouse}" Ta "=" Ta "The window where the mouse event happened" .El .Pp .Ar target\-pane @@ -883,6 +884,8 @@ The following special tokens are available for the pane index: .It Li "{left\-of}" Ta "" Ta "The pane to the left of the active pane" .It Li "{right\-of}" Ta "" Ta "The pane to the right of the active pane" .It Li "{active}" Ta "@" Ta "The active pane" +.It Li "{marked}" Ta "~" Ta "The marked pane" +.It Li "{mouse}" Ta "=" Ta "The pane where the mouse event happened" .El .Pp The tokens @@ -2160,6 +2163,19 @@ before the target. .Xc Jump to the last mark. .It Xo +.Ic line\-numbers\-on +.Xc +Show line numbers in copy mode. +.It Xo +.Ic line\-numbers\-off +.Xc +Hide line numbers in copy mode. +.It Xo +.Ic line\-numbers\-toggle +(emacs: L) +.Xc +Toggle line numbers in copy mode. +.It Xo .Ic middle\-line (vi: M) (emacs: M\-r) @@ -3076,6 +3092,7 @@ The following keys may be used in customize mode: .It Li "w" Ta "Set window option value, if option is for pane and window" .It Li "d" Ta "Set an option or key to the default" .It Li "D" Ta "Set tagged options and tagged keys to the default" +.It Li "e" Ta "Open the value in an editor" .It Li "u" Ta "Unset an option (set to default value if global) or unbind a key" .It Li "U" Ta "Unset tagged options and unbind tagged keys" .It Li "C\-s" Ta "Search by name" @@ -3154,19 +3171,31 @@ option. .It Xo .Tg displayp .Ic display\-panes -.Op Fl bN +.Op Fl kNZ .Op Fl d Ar duration -.Op Fl t Ar target\-client +.Op Fl s Ar source\-window +.Op Fl t Ar target\-pane .Op Ar template .Xc .D1 Pq alias: Ic displayp -Display a visible indicator of each pane shown by -.Ar target\-client . +Put +.Ar target\-pane +into panes mode, which displays a preview of the window and a visible +indicator of each pane. +If +.Fl s +is given, the panes in +.Ar source\-window +are displayed instead of those in the window containing +.Ar target\-pane . See the -.Ic display\-panes\-colour +.Ic display\-panes\-colour , +.Ic display\-panes\-active\-colour , +.Ic display\-panes\-border\-style , +.Ic display\-panes\-format and -.Ic display\-panes\-active\-colour -session options. +.Ic display\-panes\-time +window options. The indicator is closed when a key is pressed (unless .Fl N is given) or @@ -3178,11 +3207,16 @@ is not given, .Ic display\-panes\-time is used. A duration of zero means the indicator stays until a key is pressed. +The mode is zoomed by default; +.Fl Z +starts it unzoomed. +.Fl k +kills the pane when the mode is exited. While the indicator is on screen, a pane may be chosen with the .Ql 0 to .Ql 9 -keys, which will cause +keys or by clicking it with the mouse, which will cause .Ar template to be executed as a command with .Ql %% @@ -3190,9 +3224,6 @@ substituted by the pane ID. The default .Ar template is "select\-pane \-t \[aq]%%\[aq]". -With -.Fl b , -other commands are not blocked from running until the indicator is closed. .Tg findw .It Xo Ic find\-window .Op Fl iCNrTZ @@ -3616,7 +3647,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl bdefhIkLPvWZ +.Op Fl bdefhIkLMPvWZ .Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment @@ -3662,6 +3693,11 @@ to specify a percentage of the window size. .Fl B sets the pane border lines for floating panes; see .Ic pane\-border\-lines . +.Fl M +may be used when bound to a mouse drag key; the new floating pane is resized to +follow the mouse drag. +It has no effect with +.Fl L . .Pp The .Fl L @@ -4018,6 +4054,7 @@ the command behaves like .Tg splitw .It Xo Ic split\-window .Op Fl bdefhIkPvZ +.Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -4065,6 +4102,9 @@ may be followed by to specify a percentage of the available space. .Fl p is a shorthand option for this. +.Fl B +sets the pane border lines for floating panes; see +.Ic pane\-border\-lines . The .Fl b option causes the new pane to be created to the left of or above @@ -4077,6 +4117,21 @@ or full window width (with .Fl v ) , instead of splitting the active pane. .Pp +If +.Ar target\-pane +is floating, a new floating pane is created adjacent to +.Ar target\-pane +with equal sizes. +The position is determined by the +.Fl h , +.Fl v , +and +.Fl b +flags. +If any of the new pane would not be visible on the screen, +.Ar target\-pane +and the new pane are resized to fit. +.Pp .Fl k keeps the pane open after the optional .Ar shell\-command @@ -5194,24 +5249,6 @@ If or .Ic next , the client is switched to the previous or next session in alphabetical order. -.It Ic display\-panes\-active\-colour Ar colour -Set the colour used by the -.Ic display\-panes -command to show the indicator for the active pane. -.It Ic display\-panes\-colour Ar colour -Set the colour used by the -.Ic display\-panes -command to show the indicators for inactive panes. -.It Ic display\-panes\-format Ar format -Set the -.Ar format -of the text shown by the -.Ic display\-panes -command, expanded for each pane. -.It Ic display\-panes\-time Ar time -Set the time in milliseconds for which the indicators shown by the -.Ic display\-panes -command appear. .It Ic display\-time Ar time Set the amount of time for which status line messages and other on-screen indicators are displayed. @@ -5643,6 +5680,38 @@ Set clock colour. .Xc Set clock hour format. .Pp +.It Ic display\-panes\-active\-colour Ar colour +Set the colour used by the +.Ic display\-panes +command to show the indicator for the active pane. +.Pp +.It Ic display\-panes\-border\-style Ar style +Set the style of borders in panes mode used by the +.Ic display\-panes +command. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic display\-panes\-colour Ar colour +Set the colour used by the +.Ic display\-panes +command to show the indicators for inactive panes. +.Pp +.It Ic display\-panes\-format Ar format +Set the +.Ar format +of the text shown by the +.Ic display\-panes +command, expanded for each pane. +.Pp +.It Ic display\-panes\-time Ar time +Set the time in milliseconds for which the indicators shown by the +.Ic display\-panes +command appear. +.Pp .It Ic fill\-character Ar character Set the character used to fill areas of the terminal unused by a window. .Pp @@ -6345,6 +6414,10 @@ See Run when a client becomes the latest active client of its session. .It client\-attached Run when a client is attached. +.It client\-closed +Run when a client is closed. +.It client\-created +Run when a client is created. .It client\-detached Run when a client is detached. .It client\-focus\-in @@ -6363,15 +6436,12 @@ Run when a client switches to a dark theme. Run when a command fails. .It marked\-pane\-changed Run when the marked pane is set or cleared. +.It pane\-activity +Run when there is new output in a pane. +.It pane\-bell +Run when a pane receives a bell. .It pane\-command\-finished Run when an OSC 133 command finishes in a pane. -The event payload includes -.Ql hook_command_start_time , -.Ql hook_command_end_time , -.Ql hook_command_duration -and -.Ql hook_command_status -if present. .It pane\-command\-started Run when an OSC 133 command starts in a pane. .It pane\-created @@ -6396,6 +6466,8 @@ Run when a pane changes mode. Run when a pane enters a mode. .It pane\-mode\-exited Run when a pane exits a mode. +.It pane\-moved +Run when a pane is moved to another window. .It pane\-prompt\-closed Run when a prompt in a pane is closed. .It pane\-prompt\-opened @@ -6414,10 +6486,18 @@ Run when a pane title is changed. Run when a session is closed. .It session\-created Run when a new session is created. +.It session\-added\-to\-group +Run when a session is added to a session group. +.It session\-removed\-from\-group +Run when a session is removed from a session group. .It session\-renamed Run when a session is renamed. .It session\-window\-changed Run when a session changes its active window. +.It window\-closed +Run when a window is closed. +.It window\-created +Run when a window is created. .It window\-layout\-changed Run when a window layout is changed. .It window\-linked @@ -7148,6 +7228,7 @@ The following variables are available, where appropriate: .It Li "copy_cursor_word" Ta "" Ta "Word under cursor in copy mode" .It Li "copy_cursor_x" Ta "" Ta "Cursor X position in copy mode" .It Li "copy_cursor_y" Ta "" Ta "Cursor Y position in copy mode" +.It Li "copy_line_numbers" Ta "" Ta "1 if line numbers are shown in copy mode" .It Li "copy_position" Ta "" Ta "Position shown in the copy mode indicator" .It Li "copy_position_limit" Ta "" Ta "Limit shown in the copy mode indicator" .It Li "current_file" Ta "" Ta "Current configuration file" @@ -7267,6 +7348,7 @@ The following variables are available, where appropriate: .It Li "prev_window_active" Ta "" Ta "1 if previous window in W: loop is active" .It Li "prev_window_index" Ta "" Ta "Index of previous window in W: loop" .It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated" +.It Li "refresh_active" Ta "" Ta "1 if copy mode refresh is active" .It Li "scroll_position" Ta "" Ta "Scroll position in copy mode" .It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane" .It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane" diff --git a/tmux.h b/tmux.h index 3dd80b724..d2e3010d7 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1396 2026/07/10 15:45:11 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1404 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1170,8 +1170,13 @@ struct window_mode { const char *name; const char *default_format; + int flags; +#define WINDOW_MODE_HIDE_PANE_STATUS 0x1 +#define WINDOW_MODE_NO_STACK 0x2 + struct screen *(*init)(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); void (*free)(struct window_mode_entry *); void (*resize)(struct window_mode_entry *, u_int, u_int); void (*update)(struct window_mode_entry *); @@ -1305,6 +1310,7 @@ struct window_pane { #define PANE_REDRAWSCROLLBAR 0x8000 #define PANE_DESTROYED 0x10000 #define PANE_CMDRUNNING 0x20000 +#define PANE_ACTIVITY 0x40000 bitstr_t *sync_dirty; u_int sync_dirty_size; @@ -1432,6 +1438,10 @@ struct window { uint64_t redraw_scene_generation; + struct menu_data *menu; + u_int menu_last_px; + u_int menu_last_py; + u_int last_new_pane_x; u_int last_new_pane_y; @@ -1782,6 +1792,8 @@ struct tty { u_int mouse_last_y; u_int mouse_last_b; int mouse_drag_flag; + u_int mouse_drag_x; + u_int mouse_drag_y; int mouse_scrolling_flag; int mouse_slider_mpos; int mouse_last_pane; @@ -2103,6 +2115,8 @@ struct prompt_create_data { struct grid_cell style; struct grid_cell command_style; + const char *style_str; + const char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; @@ -2225,7 +2239,7 @@ struct client { #define CLIENT_CONTROL_NOOUTPUT 0x4000000 #define CLIENT_DEFAULTSOCKET 0x8000000 #define CLIENT_STARTSERVER 0x10000000 -/* 0x20000000 unused */ +#define CLIENT_REDRAWMENU 0x20000000 #define CLIENT_NOFORK 0x40000000 #define CLIENT_ACTIVEPANE 0x80000000ULL #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL @@ -2241,7 +2255,8 @@ struct client { CLIENT_REDRAWSTATUS| \ CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ - CLIENT_REDRAWOVERLAY) + CLIENT_REDRAWOVERLAY| \ + CLIENT_REDRAWMENU) #define CLIENT_UNATTACHEDFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ @@ -2295,9 +2310,6 @@ struct client { void *overlay_data; struct event overlay_timer; - u_int menu_last_px; - u_int menu_last_py; - struct client_files files; u_int source_file_depth; @@ -2472,6 +2484,8 @@ struct spawn_context { #define SPAWN_EMPTY 0x40 #define SPAWN_ZOOM 0x80 #define SPAWN_FLOATING 0x100 +#define SPAWN_HORIZONTAL 0x200 +#define SPAWN_SPLIT 0x400 }; /* Paste buffer. */ @@ -2719,6 +2733,7 @@ char *format_trim_right(const char *, u_int); /* hooks.c */ void hooks_add_event(const char *); +int hooks_is_event(const char *); int hooks_valid_event_name(const char *); void hooks_build_events(void); void hooks_run(struct cmdq_item *, const char *); @@ -3094,6 +3109,7 @@ void cmdq_merge_formats(struct cmdq_item *, struct format_tree *); struct cmdq_list *cmdq_new(void); void cmdq_free(struct cmdq_list *); const char *cmdq_get_name(struct cmdq_item *); +struct cmd *cmdq_get_cmd(struct cmdq_item *); struct client *cmdq_get_client(struct cmdq_item *); struct client *cmdq_get_target_client(struct cmdq_item *); struct cmdq_state *cmdq_get_state(struct cmdq_item *); @@ -3254,6 +3270,7 @@ void server_redraw_session_group(struct session *); void server_status_session(struct session *); void server_status_session_group(struct session *); void server_redraw_window(struct window *); +void server_redraw_window_menu(struct window *); void server_redraw_window_borders(struct window *); void server_status_window(struct window *); void server_lock(void); @@ -3621,6 +3638,8 @@ int window_has_floating_panes(struct window *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); +void window_fire_pane_moved(struct window_pane *, struct window *, + int, struct window *, int); void window_update_focus(struct window *); void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, @@ -3652,7 +3671,7 @@ void window_pane_clear_resizes(struct window_pane *, struct window_pane_resize *); int window_pane_set_mode(struct window_pane *, struct window_pane *, const struct window_mode *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, struct args *); void window_pane_reset_mode(struct window_pane *); void window_pane_reset_mode_all(struct window_pane *); int window_pane_key(struct window_pane *, struct client *, @@ -3754,6 +3773,8 @@ void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); void layout_fix_zindexes(struct window *, struct layout_cell *); int layout_cell_is_tiled(struct layout_cell *); +int layout_add_horizontal_border(struct layout_cell *, + struct layout_cell *, int); void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, @@ -3791,10 +3812,13 @@ struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct window_pane *, - char **); + int, char **); int layout_floating_args_parse(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct layout_geometry *, char **); +int layout_split_floating_cell(struct layout_cell *, + struct window *, struct layout_geometry *, enum pane_lines, + int, char **); int layout_remove_tile(struct window *, struct layout_cell *); int layout_insert_tile(struct window *, struct layout_cell *); @@ -3874,6 +3898,9 @@ extern const struct window_mode window_switch_mode; extern const struct window_mode window_clock_mode; extern const char window_clock_table[14][5][5]; +/* window-panes.c */ +extern const struct window_mode window_panes_mode; + /* window-client.c */ extern const struct window_mode window_client_mode; @@ -4041,20 +4068,21 @@ void menu_add_item(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); void menu_free(struct menu *); -struct menu_data *menu_prepare(struct menu *, int, int, struct cmdq_item *, - u_int, u_int, struct client *, enum box_lines, const char *, - const char *, const char *, struct cmd_find_state *, - menu_choice_cb, void *); int menu_display(struct menu *, int, int, struct cmdq_item *, u_int, u_int, struct client *, enum box_lines, const char *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); -struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -struct visible_ranges *menu_check_cb(struct client *, void *, u_int, u_int, - u_int); -void menu_draw_cb(struct client *, void *); -void menu_free_cb(struct client *, void *); -int menu_key_cb(struct client *, void *, struct key_event *); +void menu_close(struct window *); +void menu_destroy(struct window *); +void menu_update(struct menu_data *); +struct screen *menu_screen(struct menu_data *); +u_int menu_width(struct menu_data *); +u_int menu_height(struct menu_data *); +u_int menu_x(struct menu_data *); +u_int menu_y(struct menu_data *); +void menu_get_cursor(struct menu_data *, u_int *, u_int *); +void menu_resize(struct menu_data *, struct window *); +int menu_key(struct client *, struct menu_data *, struct key_event *); /* popup.c */ #define POPUP_CLOSEEXIT 0x1 diff --git a/window-buffer.c b/window-buffer.c index 3e0d3df62..12c847944 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-buffer.c,v 1.49 2026/06/26 08:10:49 nicm Exp $ */ +/* $OpenBSD: window-buffer.c,v 1.50 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -28,7 +28,8 @@ #include "tmux.h" static struct screen *window_buffer_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_buffer_free(struct window_mode_entry *); static void window_buffer_resize(struct window_mode_entry *, u_int, u_int); @@ -362,7 +363,8 @@ window_buffer_help(u_int *width, const char **item) } static struct screen * -window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, +window_buffer_init(struct window_mode_entry *wme, + __unused struct cmdq_item *item, struct cmd_find_state *fs, struct args *args) { struct window_pane *wp = wme->wp; diff --git a/window-client.c b/window-client.c index 64e38cb81..b94c22119 100644 --- a/window-client.c +++ b/window-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-client.c,v 1.46 2026/07/04 22:09:06 nicm Exp $ */ +/* $OpenBSD: window-client.c,v 1.47 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -26,7 +26,8 @@ #include "tmux.h" static struct screen *window_client_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_client_free(struct window_mode_entry *); static void window_client_resize(struct window_mode_entry *, u_int, u_int); @@ -421,7 +422,8 @@ window_client_help(u_int *width, const char **item) static struct screen * window_client_init(struct window_mode_entry *wme, - __unused struct cmd_find_state *fs, struct args *args) + __unused struct cmdq_item *item, __unused struct cmd_find_state *fs, + struct args *args) { struct window_pane *wp = wme->wp; struct window_client_modedata *data; diff --git a/window-clock.c b/window-clock.c index 9853b2abb..b796d6d05 100644 --- a/window-clock.c +++ b/window-clock.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-clock.c,v 1.34 2026/06/25 16:32:42 nicm Exp $ */ +/* $OpenBSD: window-clock.c,v 1.35 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -25,7 +25,8 @@ #include "tmux.h" static struct screen *window_clock_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_clock_free(struct window_mode_entry *); static void window_clock_resize(struct window_mode_entry *, u_int, u_int); static void window_clock_key(struct window_mode_entry *, struct client *, @@ -169,7 +170,8 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) static struct screen * window_clock_init(struct window_mode_entry *wme, - __unused struct cmd_find_state *fs, __unused struct args *args) + __unused struct cmdq_item *item, __unused struct cmd_find_state *fs, + __unused struct args *args) { struct window_pane *wp = wme->wp; struct window_clock_mode_data *data; diff --git a/window-copy.c b/window-copy.c index 997cb8888..a6909a8d4 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-copy.c,v 1.413 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: window-copy.c,v 1.421 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -33,9 +33,9 @@ static void window_copy_command(struct window_mode_entry *, struct client *, struct session *, struct winlink *, struct args *, struct mouse_event *); static struct screen *window_copy_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, struct args *); static struct screen *window_copy_view_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, struct args *); static void window_copy_free(struct window_mode_entry *); static void window_copy_resize(struct window_mode_entry *, u_int, u_int); static void window_copy_formats(struct window_mode_entry *, @@ -49,14 +49,16 @@ static void window_copy_next_paragraph(struct window_mode_entry *); static void window_copy_previous_paragraph(struct window_mode_entry *); static void window_copy_redraw_selection(struct window_mode_entry *, u_int); static void window_copy_redraw_lines(struct window_mode_entry *, u_int, - u_int, int); -static void window_copy_redraw_screen(struct window_mode_entry *, int); + u_int); +static void window_copy_redraw_screen(struct window_mode_entry *); static void window_copy_do_refresh(struct window_mode_entry *, int); static void window_copy_refresh_timer(int, short, void *); static void window_copy_refresh_arm(struct window_mode_entry *); static void window_copy_refresh_start(struct window_mode_entry *); static void window_copy_refresh_stop(struct window_mode_entry *); static void window_copy_style_changed(struct window_mode_entry *); +static void window_copy_set_line_numbers1(struct window_mode_entry *, int, + int); static int window_copy_line_number_mode(struct window_mode_entry *); static int window_copy_line_number_is_absolute(struct window_mode_entry *); static int window_copy_line_numbers_active(struct window_mode_entry *); @@ -66,7 +68,7 @@ static u_int window_copy_cursor_offset(struct window_mode_entry *, u_int, static u_int window_copy_cursor_unoffset(struct window_mode_entry *, u_int, u_int); static void window_copy_write_line(struct window_mode_entry *, - struct screen_write_ctx *, u_int, int); + struct screen_write_ctx *, u_int); static void window_copy_write_lines(struct window_mode_entry *, struct screen_write_ctx *, u_int, u_int); static char *window_copy_match_at_cursor(struct window_copy_mode_data *); @@ -103,8 +105,12 @@ static void window_copy_goto_line(struct window_mode_entry *, const char *); static void window_copy_update_cursor(struct window_mode_entry *, u_int, u_int); static void window_copy_start_selection(struct window_mode_entry *); +static int window_copy_mouse_in_selection(struct window_mode_entry *, + u_int, u_int, int *, int *); static int window_copy_adjust_selection(struct window_mode_entry *, u_int *, u_int *); +static int window_copy_update_selection_view(struct window_mode_entry *, + int, int); static int window_copy_set_selection(struct window_mode_entry *, int, int); static int window_copy_update_selection(struct window_mode_entry *, int, int); @@ -292,7 +298,7 @@ struct window_copy_mode_data { int rectflag; /* in rectangle copy mode? */ int scroll_exit; /* exit on scroll to end? */ int hide_position; /* hide position marker */ - int line_numbers; + int line_numbers; /* 0 off, 1 from option, 2 default */ enum { SEL_CHAR, /* select one char at a time */ @@ -591,7 +597,8 @@ window_copy_common_init(struct window_mode_entry *wme) static struct screen * window_copy_init(struct window_mode_entry *wme, - __unused struct cmd_find_state *fs, struct args *args) + __unused struct cmdq_item *item, __unused struct cmd_find_state *fs, + struct args *args) { struct window_pane *wp = wme->swp; struct window_copy_mode_data *data; @@ -629,7 +636,7 @@ window_copy_init(struct window_mode_entry *wme, screen_write_start(&ctx, &data->screen); for (i = 0; i < screen_size_y(&data->screen); i++) - window_copy_write_line(wme, &ctx, i, 1); + window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(&data->screen)), data->cy, 0); screen_write_stop(&ctx); @@ -642,7 +649,8 @@ window_copy_init(struct window_mode_entry *wme, static struct screen * window_copy_view_init(struct window_mode_entry *wme, - __unused struct cmd_find_state *fs, __unused struct args *args) + __unused struct cmdq_item *item, __unused struct cmd_find_state *fs, + __unused struct args *args) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data; @@ -743,10 +751,10 @@ window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap) * (If there's any history at all, it has changed.) */ if (screen_hsize(data->backing)) - window_copy_redraw_lines(wme, 0, 1, 1); + window_copy_redraw_lines(wme, 0, 1); /* Write the new lines. */ - window_copy_redraw_lines(wme, old_cy, backing->cy - old_cy + 1, 1); + window_copy_redraw_lines(wme, old_cy, backing->cy - old_cy + 1); screen_write_stop(&ctx); } @@ -862,9 +870,9 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 1, 0); + window_copy_update_selection_view(wme, 1, 0); window_pane_scrollbar_show(wp, 1); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } void @@ -918,7 +926,7 @@ window_copy_pageup1(struct window_mode_entry *wme, int half_page) window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); window_pane_scrollbar_show(wme->wp, 1); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } void @@ -979,7 +987,7 @@ window_copy_pagedown1(struct window_mode_entry *wme, int half_page, window_copy_search_marks(wme, NULL, data->searchregex, 1); window_copy_update_selection(wme, 1, 0); window_pane_scrollbar_show(wme->wp, 1); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); return (0); } @@ -1113,6 +1121,9 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) } format_add(ft, "copy_position", "%u", position); format_add(ft, "copy_position_limit", "%u", limit); + format_add(ft, "copy_line_numbers", "%d", + window_copy_line_numbers_active(wme)); + format_add(ft, "refresh_active", "%d", data->refresh_active); format_add(ft, "rectangle_toggle", "%d", data->rectflag); format_add(ft, "copy_cursor_x", "%d", data->cx); @@ -1224,7 +1235,7 @@ window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy) } window_copy_size_changed(wme); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static const char * @@ -1648,7 +1659,7 @@ window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to) data->cy += delta; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -2350,6 +2361,7 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; /* * If at the bottom and scroll_exit is active with no selection, @@ -2361,6 +2373,17 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); return (WINDOW_COPY_CMD_NOTHING); } + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move down in the history. */ + window_copy_scroll_up(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } + for (; np != 0; np--) window_copy_cursor_down(wme, 1); if (data->scroll_exit && data->oy == 0 && data->screen.sel == NULL) @@ -2374,6 +2397,17 @@ window_copy_cmd_scroll_down_and_cancel(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move down in the history. */ + window_copy_scroll_up(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } for (; np != 0; np--) window_copy_cursor_down(wme, 1); @@ -2388,6 +2422,7 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; /* * If at the top, nothing can change, so return WINDOW_COPY_CMD_NOTHING @@ -2395,6 +2430,17 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) */ if (data->oy == screen_hsize(data->backing)) return (WINDOW_COPY_CMD_NOTHING); + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move up in the history. */ + window_copy_scroll_down(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } + for (; np != 0; np--) window_copy_cursor_up(wme, 1); return (WINDOW_COPY_CMD_MOVE); @@ -2499,7 +2545,7 @@ window_copy_cmd_select_word(struct window_copy_cmd_state *cs) else { window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 1)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } data->endselrx = data->cx; data->endselry = screen_hsize(data->backing) + data->cy - data->oy; @@ -3007,7 +3053,7 @@ window_copy_refresh_timer(__unused int fd, __unused short events, void *arg) follow = (data->oy == 0 && data->cy == screen_size_y(&data->screen) - 1); window_copy_do_refresh(wme, follow); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); /* The timer runs outside key handling, so force a repaint. */ wp->flags |= PANE_REDRAW; wp->flags &= ~PANE_UNSEENCHANGES; @@ -3117,11 +3163,33 @@ window_copy_cmd_recentre_top_bottom(struct window_copy_cmd_state *cs) data->cy = cy + (data->oy - oy); break; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } +static enum window_copy_cmd_action +window_copy_cmd_line_numbers_on(struct window_copy_cmd_state *cs) +{ + window_copy_set_line_numbers1(cs->wme, 1, 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_line_numbers_off(struct window_copy_cmd_state *cs) +{ + window_copy_set_line_numbers1(cs->wme, 0, 0); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_line_numbers_toggle(struct window_copy_cmd_state *cs) +{ + window_copy_set_line_numbers1(cs->wme, + !window_copy_line_numbers_active(cs->wme), 1); + return (WINDOW_COPY_CMD_NOTHING); +} + static const struct { const char *command; u_int minargs; @@ -3386,6 +3454,24 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_jump_to_mark }, + { .command = "line-numbers-on", + .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_line_numbers_on + }, + { .command = "line-numbers-off", + .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_line_numbers_off + }, + { .command = "line-numbers-toggle", + .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_line_numbers_toggle + }, { .command = "next-prompt", .args = { "o", 0, 0, NULL }, .flags = WINDOW_COPY_CMD_FLAG_READONLY, @@ -3781,13 +3867,13 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, if (action == WINDOW_COPY_CMD_CANCEL) window_pane_reset_mode(wp); else if (action == WINDOW_COPY_CMD_REDRAW) - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); else if (action == WINDOW_COPY_CMD_MOVE) { /* * If the cursor moves or a similar trivial change is made, * only redraw the first line to update the indicator. */ - window_copy_redraw_lines(wme, 0, 1, 0); + window_copy_redraw_lines(wme, 0, 1); } } @@ -3824,7 +3910,7 @@ window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py, if (data->oy != old_oy) window_pane_scrollbar_show(wme->wp, 1); if (!no_redraw) - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static int @@ -4612,7 +4698,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) } } } - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); screen_free(&ss); return (found); @@ -4865,7 +4951,7 @@ window_copy_goto_line(struct window_mode_entry *wme, const char *linestr) } window_copy_update_selection(wme, 1, 0); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static void @@ -5019,7 +5105,7 @@ window_copy_write_one(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; struct grid_cell gc; - u_int fx; + u_int fx, i, width; screen_write_cursormove(ctx, px, py, 0); for (fx = 0; fx < nx; fx++) { @@ -5027,8 +5113,22 @@ window_copy_write_one(struct window_mode_entry *wme, if (fx + gc.data.width <= nx) { window_copy_update_style(wme, fx, fy, &gc, mgc, cgc, mkgc); - screen_write_cell(ctx, &gc); - } + if (gc.flags & GRID_FLAG_PADDING) { + if (ctx->s->cy == py && ctx->s->cx <= px + fx) { + gc.flags &= ~GRID_FLAG_PADDING; + screen_write_cursormove(ctx, px + fx, py, + 0); + screen_write_putc(ctx, &gc, ' '); + } + } else if (gc.flags & GRID_FLAG_TAB) { + width = gc.data.width; + gc.flags &= ~GRID_FLAG_TAB; + for (i = 0; i < width; i++) + screen_write_putc(ctx, &gc, ' '); + } else + screen_write_cell(ctx, &gc); + } else + screen_write_putc(ctx, &grid_default_cell, ' '); } } @@ -5038,10 +5138,14 @@ window_copy_line_number_mode(struct window_mode_entry *wme) struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; struct options *oo = wp->window->options; + int mode; if (!data->line_numbers) return (WINDOW_COPY_LINE_NUMBERS_OFF); - return (options_get_number(oo, "copy-mode-line-numbers")); + mode = options_get_number(oo, "copy-mode-line-numbers"); + if (data->line_numbers == 2 && mode == WINDOW_COPY_LINE_NUMBERS_OFF) + return (WINDOW_COPY_LINE_NUMBERS_DEFAULT); + return (mode); } static int @@ -5127,15 +5231,34 @@ void window_copy_set_line_numbers(struct window_pane *wp, int enabled) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); - struct window_copy_mode_data *data; if (wme == NULL || wme->mode != &window_copy_mode) return; - data = wme->data; - if (data == NULL || data->line_numbers == enabled) + window_copy_set_line_numbers1(wme, enabled, 0); +} + +static void +window_copy_set_line_numbers1(struct window_mode_entry *wme, int enabled, + int force) +{ + struct window_copy_mode_data *data = wme->data; + struct options *oo = wme->wp->window->options; + int active, line_numbers; + + if (data == NULL) return; - data->line_numbers = enabled; - window_copy_redraw_screen(wme, 1); + active = window_copy_line_numbers_active(wme); + if (!enabled) + line_numbers = 0; + else if (force && options_get_number(oo, "copy-mode-line-numbers") == + WINDOW_COPY_LINE_NUMBERS_OFF) + line_numbers = 2; + else + line_numbers = 1; + if (data->line_numbers == line_numbers && active == enabled) + return; + data->line_numbers = line_numbers; + window_copy_redraw_screen(wme); } int @@ -5157,7 +5280,7 @@ window_copy_get_current_offset(struct window_pane *wp, u_int *offset, static void window_copy_write_line(struct window_mode_entry *wme, - struct screen_write_ctx *ctx, u_int py, int clear) + struct screen_write_ctx *ctx, u_int py) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; @@ -5182,8 +5305,6 @@ window_copy_write_line(struct window_mode_entry *wme, content_sx = sx; screen_write_cursormove(ctx, 0, py, 0); - if (clear) - screen_write_clearline(ctx, 8); ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); @@ -5254,7 +5375,7 @@ window_copy_write_lines(struct window_mode_entry *wme, u_int yy; for (yy = py; yy < py + ny; yy++) - window_copy_write_line(wme, ctx, yy, 1); + window_copy_write_line(wme, ctx, yy); } static void @@ -5282,12 +5403,11 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y) if (end < gd->sy + data->oy - 1) end++; } - window_copy_redraw_lines(wme, start, end - start + 1, 0); + window_copy_redraw_lines(wme, start, end - start + 1); } static void -window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny, - int clear) +window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; @@ -5298,7 +5418,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny, if (window_copy_line_number_width(wme) != 0) { screen_write_start(&ctx, &data->screen); for (i = py; i < py + ny; i++) - window_copy_write_line(wme, &ctx, i, clear); + window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); @@ -5312,7 +5432,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny, else screen_write_start_pane(&ctx, wp, NULL); for (i = py; i < py + ny; i++) - window_copy_write_line(wme, &ctx, i, clear); + window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); @@ -5322,11 +5442,11 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny, } static void -window_copy_redraw_screen(struct window_mode_entry *wme, int clear) +window_copy_redraw_screen(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; - window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen), clear); + window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen)); } static void @@ -5336,7 +5456,7 @@ window_copy_style_changed(struct window_mode_entry *wme) if (data->screen.sel != NULL) window_copy_set_selection(wme, 0, 1); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static void @@ -5455,9 +5575,10 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) if (window_copy_line_numbers_active(wme)) { width = window_copy_line_number_width(wme); - if (s->sel != NULL || data->lineflag != LINE_SEL_NONE || + if (s->sel != NULL || + data->lineflag != LINE_SEL_NONE || old_cy != data->cy) { - window_copy_redraw_screen(wme, 0); + window_copy_redraw_screen(wme); return; } if (width >= screen_size_x(s)) @@ -5465,7 +5586,7 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) else content_sx = screen_size_x(s) - width; if (old_cx >= content_sx || data->cx >= content_sx) { - window_copy_redraw_screen(wme, 0); + window_copy_redraw_screen(wme); return; } screen_write_start_pane(&ctx, wp, NULL); @@ -5476,9 +5597,9 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) return; } if (old_cx == screen_size_x(s)) - window_copy_redraw_lines(wme, old_cy, 1, 1); + window_copy_redraw_lines(wme, old_cy, 1); if (data->cx == screen_size_x(s)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); else { screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, @@ -5504,6 +5625,76 @@ window_copy_start_selection(struct window_mode_entry *wme) window_copy_set_selection(wme, 1, 0); } +/* + * Return whether x,y is inside the selection and optionally identify the + * endpoint. + */ +static int +window_copy_mouse_in_selection(struct window_mode_entry *wme, u_int x, u_int y, + int *on_start, int *on_end) +{ + struct window_copy_mode_data *data = wme->data; + u_int sx = screen_size_x(&data->screen); + u_int sy = screen_size_y(&data->screen); + u_int hsize, screeny; + u_int mx, my, selx, sely, endselx, endsely; + u_int cursorx, cursory; + long long mpos, spos, epos, dstart, dend; + + if (on_start != NULL) + *on_start = 0; + if (on_end != NULL) + *on_end = 0; + if (data->screen.sel == NULL) + return (0); + + hsize = screen_hsize(data->backing); + screeny = hsize - data->oy; + + selx = window_copy_cursor_offset(wme, data->selx, sx); + sely = data->sely - screeny; + if (data->sely >= screeny && sely < sy && x == selx && y == sely) { + if (on_start != NULL) + *on_start = 1; + return (1); + } + + endselx = window_copy_cursor_offset(wme, data->endselx, sx); + endsely = data->endsely - screeny; + if (data->endsely >= screeny && + endsely < sy && + x == endselx && y == endsely) { + if (on_end != NULL) + *on_end = 1; + return (1); + } + + cursorx = window_copy_cursor_offset(wme, data->cx, sx); + cursory = data->cy; + if (x != cursorx || y != cursory) { + if (!screen_check_selection(&data->screen, x, y)) + return (0); + } + + if (on_start != NULL || on_end != NULL) { + mx = window_copy_cursor_unoffset(wme, x, sx); + my = screeny + y; + mpos = (long long)my * (sx + 1) + mx; + spos = (long long)data->sely * (sx + 1) + data->selx; + epos = (long long)data->endsely * (sx + 1) + data->endselx; + dstart = llabs(mpos - spos); + dend = llabs(mpos - epos); + if (dstart <= dend) { + if (on_start != NULL) + *on_start = 1; + } else { + if (on_end != NULL) + *on_end = 1; + } + } + return (1); +} + static int window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx, u_int *sely) @@ -5549,6 +5740,35 @@ window_copy_update_selection(struct window_mode_entry *wme, int may_redraw, return (window_copy_set_selection(wme, may_redraw, no_reset)); } +/* + * Update the visible selection after the view has changed. If the mouse is + * not dragging, keep the selection endpoints anchored to the same text. + */ +static int +window_copy_update_selection_view(struct window_mode_entry *wme, int may_redraw, + int no_reset) +{ + struct window_copy_mode_data *data = wme->data; + u_int selx, sely, endselx, endsely; + int changed; + + if (data->cursordrag != CURSORDRAG_NONE) + return (window_copy_update_selection(wme, may_redraw, no_reset)); + + selx = data->selx; + sely = data->sely; + endselx = data->endselx; + endsely = data->endsely; + + changed = window_copy_update_selection(wme, may_redraw, 1); + + data->selx = selx; + data->sely = sely; + data->endselx = endselx; + data->endsely = endsely; + return (changed); +} + static int window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, int no_reset) @@ -5605,16 +5825,14 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, cy = data->cy; if (data->cursordrag == CURSORDRAG_ENDSEL) { if (sy < cy) - window_copy_redraw_lines(wme, sy, cy - sy + 1, 1); + window_copy_redraw_lines(wme, sy, cy - sy + 1); else - window_copy_redraw_lines(wme, cy, sy - cy + 1, 1); + window_copy_redraw_lines(wme, cy, sy - cy + 1); } else { if (endsy < cy) { - window_copy_redraw_lines(wme, endsy, - cy - endsy + 1, 1); + window_copy_redraw_lines(wme, endsy, cy - endsy + 1); } else { - window_copy_redraw_lines(wme, cy, - endsy - cy + 1, 1); + window_copy_redraw_lines(wme, cy, endsy - cy + 1); } } } @@ -6094,7 +6312,7 @@ window_copy_other_end(struct window_mode_entry *wme) data->cx = hsize; window_copy_update_selection(wme, 1, 1); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static void @@ -6171,9 +6389,9 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_scroll_down(wme, 1); if (scroll_only) { if (data->cy == screen_size_y(s) - 1) - window_copy_redraw_lines(wme, data->cy, 1, 0); + window_copy_redraw_lines(wme, data->cy, 1); else - window_copy_redraw_lines(wme, data->cy, 2, 0); + window_copy_redraw_lines(wme, data->cy, 2); } } else { if (norectsel) { @@ -6183,9 +6401,9 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_update_cursor(wme, data->cx, data->cy - 1); if (window_copy_update_selection(wme, 1, 0)) { if (data->cy == screen_size_y(s) - 1) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); else - window_copy_redraw_lines(wme, data->cy, 2, 1); + window_copy_redraw_lines(wme, data->cy, 2); } } @@ -6197,7 +6415,7 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) { window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } } @@ -6210,13 +6428,13 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) px = window_copy_find_length(wme, py); window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) { window_copy_update_cursor(wme, 0, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } } @@ -6250,7 +6468,7 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) data->cx = data->lastcx; window_copy_scroll_up(wme, 1); if (scroll_only && data->cy > 0) - window_copy_redraw_lines(wme, data->cy - 1, 2, 0); + window_copy_redraw_lines(wme, data->cy - 1, 2); } else { if (norectsel) { window_copy_update_cursor(wme, data->lastcx, @@ -6258,7 +6476,7 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) } else window_copy_update_cursor(wme, data->cx, data->cy + 1); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy - 1, 2, 1); + window_copy_redraw_lines(wme, data->cy - 1, 2); } if (norectsel) { @@ -6269,7 +6487,7 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) { window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } } @@ -6282,13 +6500,13 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) px = window_copy_find_length(wme, py); window_copy_update_cursor(wme, px, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) { window_copy_update_cursor(wme, 0, data->cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1, 1); + window_copy_redraw_lines(wme, data->cy, 1); } } @@ -6558,7 +6776,7 @@ window_copy_cursor_prompt(struct window_mode_entry *wme, int direction, } window_copy_update_selection(wme, 1, 0); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static void @@ -6578,27 +6796,24 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { - window_copy_redraw_screen(wme, 0); + window_copy_redraw_screen(wme); return; } screen_write_start(&ctx, &data->screen); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_deleteline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny); - window_copy_write_line(wme, &ctx, 0, 0); + window_copy_write_line(wme, &ctx, 0); if (screen_size_y(s) > 1) - window_copy_write_line(wme, &ctx, 1, 0); + window_copy_write_line(wme, &ctx, 1); if (screen_size_y(s) > 3) - window_copy_write_line(wme, &ctx, - screen_size_y(s) - 2, 0); - if (s->sel != NULL && screen_size_y(s) > ny) { - window_copy_write_line(wme, &ctx, - screen_size_y(s) - ny - 1, 0); - } + window_copy_write_line(wme, &ctx, screen_size_y(s) - 2); + if (s->sel != NULL && screen_size_y(s) > ny) + window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); @@ -6614,14 +6829,13 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) screen_write_cursormove(&ctx, 0, 0, 0); screen_write_deleteline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny); - window_copy_write_line(wme, &ctx, 0, 0); + window_copy_write_line(wme, &ctx, 0); if (screen_size_y(s) > 1) - window_copy_write_line(wme, &ctx, 1, 0); + window_copy_write_line(wme, &ctx, 1); if (screen_size_y(s) > 3) - window_copy_write_line(wme, &ctx, screen_size_y(s) - 2, 0); + window_copy_write_line(wme, &ctx, screen_size_y(s) - 2); if (s->sel != NULL && screen_size_y(s) > ny) - window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1, - 0); + window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); @@ -6649,11 +6863,11 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { - window_copy_redraw_screen(wme, 0); + window_copy_redraw_screen(wme); return; } screen_write_start(&ctx, &data->screen); @@ -6661,9 +6875,9 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) screen_write_insertline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, 0, ny); if (s->sel != NULL && screen_size_y(s) > ny) - window_copy_write_line(wme, &ctx, ny, 0); + window_copy_write_line(wme, &ctx, ny); else if (ny == 1) - window_copy_write_line(wme, &ctx, 1, 0); + window_copy_write_line(wme, &ctx, 1); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); @@ -6680,9 +6894,9 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) screen_write_insertline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, 0, ny); if (s->sel != NULL && screen_size_y(s) > ny) - window_copy_write_line(wme, &ctx, ny, 0); + window_copy_write_line(wme, &ctx, ny); else if (ny == 1) /* nuke position */ - window_copy_write_line(wme, &ctx, 1, 0); + window_copy_write_line(wme, &ctx, 1); screen_write_cursormove(&ctx, window_copy_cursor_offset(wme, data->cx, screen_size_x(s)), data->cy, 0); screen_write_stop(&ctx); @@ -6703,7 +6917,7 @@ window_copy_rectangle_set(struct window_mode_entry *wme, int rectflag) window_copy_update_cursor(wme, px, data->cy); window_copy_update_selection(wme, 1, 0); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } static void @@ -6738,6 +6952,7 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) struct window_mode_entry *wme; struct window_copy_mode_data *data; u_int x, y, yg; + int inside_selection, on_start, on_end; if (c == NULL) return; @@ -6758,10 +6973,16 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) c->tty.mouse_drag_release = window_copy_drag_release; data = wme->data; + on_start = on_end = 0; + inside_selection = window_copy_mouse_in_selection(wme, x, y, + &on_start, &on_end); x = window_copy_cursor_unoffset(wme, x, screen_size_x(&data->screen)); yg = screen_hsize(data->backing) + y - data->oy; - if (x < data->selrx || x > data->endselrx || yg != data->selry) + if (on_start || on_end || !inside_selection || + x < data->selrx || x > data->endselrx || yg != data->selry) { + data->lineflag = LINE_SEL_NONE; data->selflag = SEL_CHAR; + } switch (data->selflag) { case SEL_WORD: if (data->separators != NULL) { @@ -6777,11 +6998,19 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) break; case SEL_CHAR: window_copy_update_cursor(wme, x, y); - window_copy_start_selection(wme); + if (!inside_selection) + window_copy_start_selection(wme); + else { + if (on_start) + data->cursordrag = CURSORDRAG_SEL; + else if (on_end) + data->cursordrag = CURSORDRAG_ENDSEL; + window_copy_update_selection(wme, 1, 0); + } break; } - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); window_copy_drag_update(c, m); } @@ -6853,6 +7082,7 @@ window_copy_drag_release(struct client *c, struct mouse_event *m) data = wme->data; if (window_copy_line_numbers_active(wme)) window_copy_drag_update(c, m); + data->cursordrag = CURSORDRAG_NONE; evtimer_del(&data->dragtimer); } @@ -6876,7 +7106,7 @@ window_copy_jump_to_mark(struct window_mode_entry *wme) data->my = tmy; data->showmark = 1; window_copy_update_selection(wme, 0, 0); - window_copy_redraw_screen(wme, 1); + window_copy_redraw_screen(wme); } /* Scroll up if the cursor went off the visible screen. */ @@ -6902,7 +7132,7 @@ window_copy_acquire_cursor_up(struct window_mode_entry *wme, u_int hsize, } window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, cy, nd, 1); + window_copy_redraw_lines(wme, cy, nd); } /* Scroll down if the cursor went off the visible screen. */ @@ -6931,5 +7161,5 @@ window_copy_acquire_cursor_down(struct window_mode_entry *wme, u_int hsize, else window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, no_reset)) - window_copy_redraw_lines(wme, oldy, nd, 1); + window_copy_redraw_lines(wme, oldy, nd); } diff --git a/window-customize.c b/window-customize.c index aecc5ac46..94716e34c 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-customize.c,v 1.30 2026/07/06 14:40:57 nicm Exp $ */ +/* $OpenBSD: window-customize.c,v 1.34 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -25,10 +25,12 @@ #include "tmux.h" static struct screen *window_customize_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_customize_free(struct window_mode_entry *); static void window_customize_resize(struct window_mode_entry *, u_int, u_int); +static void window_customize_update(struct window_mode_entry *); static void window_customize_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -39,17 +41,24 @@ static void window_customize_key(struct window_mode_entry *, "#[fg=themelightgrey]#[ignore]#{option_value}" \ "#{?option_unit, #{option_unit},}" \ "," \ - "#{key}" \ + "#{?is_environment," \ + "#[fg=themelightgrey]#[ignore]#{environment_value}" \ + "," \ + "#{key}" \ + "}" \ "}" static const struct menu_item window_customize_menu_items[] = { { "Select", '\r', NULL }, + { "Edit", 'e', NULL }, { "Expand", KEYC_RIGHT, NULL }, { "", KEYC_NONE, NULL }, { "Tag", 't', NULL }, { "Tag All", '\024', NULL }, { "Tag None", 'T', NULL }, { "", KEYC_NONE, NULL }, + { "Changed Only", 'C', NULL }, + { "", KEYC_NONE, NULL }, { "Cancel", 'q', NULL }, { NULL, KEYC_NONE, NULL } @@ -62,6 +71,7 @@ const struct window_mode window_customize_mode = { .init = window_customize_init, .free = window_customize_free, .resize = window_customize_resize, + .update = window_customize_update, .key = window_customize_key, }; @@ -73,7 +83,9 @@ enum window_customize_scope { WINDOW_CUSTOMIZE_SESSION, WINDOW_CUSTOMIZE_GLOBAL_WINDOW, WINDOW_CUSTOMIZE_WINDOW, - WINDOW_CUSTOMIZE_PANE + WINDOW_CUSTOMIZE_PANE, + WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT, + WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT }; enum window_customize_change { @@ -81,14 +93,36 @@ enum window_customize_change { WINDOW_CUSTOMIZE_RESET, }; +enum window_customize_option_type { + WINDOW_CUSTOMIZE_OPTIONS, + WINDOW_CUSTOMIZE_HOOKS +}; + +enum window_customize_item_type { + WINDOW_CUSTOMIZE_ITEM_OPTION, + WINDOW_CUSTOMIZE_ITEM_KEY, + WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT +}; + +enum window_customize_edit_type { + WINDOW_CUSTOMIZE_EDIT_OPTION, + WINDOW_CUSTOMIZE_EDIT_KEY_COMMAND, + WINDOW_CUSTOMIZE_EDIT_KEY_NOTE, + WINDOW_CUSTOMIZE_EDIT_ENVIRONMENT +}; + struct window_customize_itemdata { struct window_customize_modedata *data; + enum window_customize_item_type type; + enum window_customize_option_type option_type; enum window_customize_scope scope; char *table; key_code key; struct options *oo; + struct environ *environ; + int environ_flags; char *name; char *array_key; }; @@ -99,8 +133,11 @@ struct window_customize_modedata { int references; struct mode_tree_data *data; + struct spawn_editor_state *editor; + struct window_customize_editdata *edit; char *format; int hide_global; + int hide_default; int prompt_flags; struct window_customize_itemdata **item_list; @@ -110,6 +147,13 @@ struct window_customize_modedata { enum window_customize_change change; }; +struct window_customize_editdata { + u_int wp_id; + enum window_customize_edit_type edit_type; + struct window_customize_itemdata *item; + struct spawn_editor_state *editor; +}; + static uint64_t window_customize_get_tag(struct options_entry *o, struct options_array_item *a, const struct options_table_entry *oe) @@ -144,10 +188,27 @@ window_customize_get_tree(enum window_customize_scope scope, return (fs->w->options); case WINDOW_CUSTOMIZE_PANE: return (fs->wp->options); + case WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT: + case WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT: + return (NULL); } return (NULL); } +static struct environ * +window_customize_get_environment(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + switch (scope) { + case WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT: + return (global_environ); + case WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT: + return (fs->s->environ); + default: + return (NULL); + } +} + static int window_customize_check_item(struct window_customize_modedata *data, struct window_customize_itemdata *item, struct cmd_find_state *fsp) @@ -161,6 +222,10 @@ window_customize_check_item(struct window_customize_modedata *data, cmd_find_copy_state(fsp, &data->fs); else cmd_find_from_pane(fsp, data->wp, 0); + if (item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) { + return (item->environ == + window_customize_get_environment(item->scope, fsp)); + } return (item->oo == window_customize_get_tree(item->scope, fsp)); } @@ -198,6 +263,7 @@ window_customize_scope_text(enum window_customize_scope scope, xasprintf(&s, "pane %u", idx); break; case WINDOW_CUSTOMIZE_SESSION: + case WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT: xasprintf(&s, "session %s", fs->s->name); break; case WINDOW_CUSTOMIZE_WINDOW: @@ -221,6 +287,38 @@ window_customize_add_item(struct window_customize_modedata *data) return (item); } +static int printflike(7, 8) +window_customize_write_value(struct screen_write_ctx *ctx, u_int cx, + u_int sx, u_int sy, int more, const char *label, const char *fmt, ...) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + va_list ap; + char *value; + u_int cy = s->cy; + int retval; + + if (sy == 0) + return (0); + if (!screen_write_text(ctx, cx, sx, sy, 1, &grid_default_cell, "%s", + label)) + return (0); + if (s->cy - cy >= sy) + return (0); + sy -= s->cy - cy; + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = COLOUR_THEME_LIGHT_GREY|COLOUR_FLAG_THEME; + + va_start(ap, fmt); + xvasprintf(&value, fmt, ap); + va_end(ap); + + retval = screen_write_text(ctx, cx, sx, sy, more, &gc, "%s", value); + free(value); + return (retval); +} + static void window_customize_free_item(struct window_customize_itemdata *item) { @@ -230,7 +328,304 @@ window_customize_free_item(struct window_customize_itemdata *item) free(item); } +static struct window_customize_itemdata * +window_customize_copy_item(struct window_customize_itemdata *item) +{ + struct window_customize_itemdata *new_item; + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = item->data; + new_item->type = item->type; + new_item->option_type = item->option_type; + new_item->scope = item->scope; + new_item->key = item->key; + new_item->oo = item->oo; + new_item->environ = item->environ; + new_item->environ_flags = item->environ_flags; + if (item->table != NULL) + new_item->table = xstrdup(item->table); + if (item->name != NULL) + new_item->name = xstrdup(item->name); + if (item->array_key != NULL) + new_item->array_key = xstrdup(item->array_key); + return (new_item); +} + static void +window_customize_finish_edit(struct window_customize_editdata *ed) +{ + window_customize_free_item(ed->item); + free(ed); +} + +static void +window_customize_draw_waiting(struct window_customize_modedata *data) +{ + struct screen_write_ctx ctx; + struct screen *s = data->wp->screen; + struct grid_cell gc; + char text[128]; + u_int sx, sy, box_w, box_h, x, y, text_x; + size_t textlen; + pid_t pid; + + if (data->editor == NULL) + return; + sx = screen_size_x(s); + sy = screen_size_y(s); + if (sx == 0 || sy == 0) + return; + + pid = spawn_get_editor_pid(data->editor); + if (pid == -1) + xsnprintf(text, sizeof text, "WAITING FOR EDITOR"); + else { + xsnprintf(text, sizeof text, "WAITING FOR EDITOR (PID %ld)", + (long)pid); + } + + textlen = strlen(text); + box_w = textlen + 4; + box_h = 3; + if (sx < box_w || sy < box_h) + return; + x = (sx - box_w) / 2; + y = (sy - box_h) / 2; + text_x = x + (box_w - textlen) / 2; + + memcpy(&gc, &grid_default_cell, sizeof gc); + screen_write_start(&ctx, s); + screen_write_cursormove(&ctx, x, y, 0); + screen_write_box(&ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL); + screen_write_cursormove(&ctx, text_x, y + 1, 0); + screen_write_nputs(&ctx, box_w - 2, &gc, "%s", text); + screen_write_stop(&ctx); +} + +static int +window_customize_set_option_value(struct window_customize_itemdata *item, + const char *s, char **cause) +{ + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo = item->oo; + const char *name = item->name; + const char *array_key = item->array_key; + u_int idx; + char keybuf[32]; + + o = options_get(oo, name); + if (o == NULL) + return (-1); + oe = options_table_entry(o); + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (array_key == NULL) { + for (idx = 0; idx < INT_MAX; idx++) { + if (options_array_getv(o, "%u", idx) == NULL) + break; + } + xsnprintf(keybuf, sizeof keybuf, "%u", idx); + array_key = keybuf; + } + if (options_array_set(o, array_key, s, 0, cause) != 0) + return (-1); + } else { + if (options_from_string(oo, oe, name, s, 0, cause) != 0) + return (-1); + } + if (item->option_type == WINDOW_CUSTOMIZE_HOOKS && *name == '@') + hooks_add_event(name); + + options_push_changes(item->name); + return (0); +} + +static int +window_customize_option_editable(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options_entry *o; + const struct options_table_entry *oe; + + if (item->type != WINDOW_CUSTOMIZE_ITEM_OPTION || + !window_customize_check_item(data, item, NULL)) + return (0); + + o = options_get(item->oo, item->name); + if (o == NULL) + return (0); + oe = options_table_entry(o); + if (oe == NULL) + return (1); + if (oe->type == OPTIONS_TABLE_FLAG || oe->type == OPTIONS_TABLE_CHOICE) + return (0); + return (1); +} + +static int +window_customize_check_command_node(struct cmd_parse_node *node, char **cause) +{ + struct cmd_parse_node *child, *first, *text; + char *error = NULL; + + if (cmd_parse_node_type(node) == CMD_PARSE_COMMAND) { + first = cmd_parse_node_first_child(node); + if (first != NULL && + cmd_parse_node_type(first) == CMD_PARSE_STRING) { + text = cmd_parse_node_first_child(first); + if (text != NULL && + cmd_parse_node_next(text) == NULL && + cmd_parse_node_type(text) == CMD_PARSE_TEXT && + cmd_find(cmd_parse_node_value(text), &error) == + NULL) { + *cause = error; + return (-1); + } + } + } + + child = cmd_parse_node_first_child(node); + while (child != NULL) { + if (window_customize_check_command_node(child, cause) != 0) + return (-1); + child = cmd_parse_node_next(child); + } + return (0); +} + +static int +window_customize_check_command(struct cmd_parse_tree *tree, char **cause) +{ + *cause = NULL; + return (window_customize_check_command_node(cmd_parse_root(tree), cause)); +} + +static int +window_customize_set_command_value(struct window_customize_itemdata *item, + const char *s, char **cause) +{ + struct key_binding *bd; + struct cmd_parse_tree *tree; + + if (!window_customize_get_key(item, NULL, &bd)) + return (-1); + + tree = cmd_parse_from_string(s, NULL, cause); + if (tree == NULL) + return (-1); + if (window_customize_check_command(tree, cause) != 0) { + cmd_parse_free(tree); + return (-1); + } + + cmd_parse_free(bd->cmd); + bd->cmd = tree; + return (0); +} + +static int +window_customize_set_note_value(struct window_customize_itemdata *item, + const char *s) +{ + struct key_binding *bd; + + if (!window_customize_get_key(item, NULL, &bd)) + return (-1); + + free((void *)bd->note); + if (*s == '\0') + bd->note = NULL; + else + bd->note = xstrdup(s); + return (0); +} + +static void +window_customize_set_environment_value(struct window_customize_itemdata *item, + const char *s) +{ + struct environ_entry *envent; + int flags; + + flags = item->environ_flags; + envent = environ_find(item->environ, item->name); + if (envent != NULL) + flags = envent->flags; + environ_set(item->environ, item->name, flags, "%s", s); +} + +static int +window_customize_option_is_changed(struct options_entry *o, + const char *array_key) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo; + struct options_entry *defaults; + union options_value *ov, *default_ov; + char *value, *default_value; + int changed; + + if (oe == NULL || options_get_monitor_data(o) != NULL) + return (1); + if (*options_name(o) == '@' && hooks_is_event(options_name(o))) + return (1); + + if (oe->flags & OPTIONS_TABLE_IS_ARRAY) { + oo = options_create(NULL); + defaults = options_default(oo, oe); + if (array_key != NULL) { + ov = options_array_get(o, array_key); + default_ov = options_array_get(defaults, array_key); + if (ov == NULL || default_ov == NULL) { + changed = (ov != default_ov); + options_free(oo); + return (changed); + } + } + value = options_to_string(o, array_key, 0); + default_value = options_to_string(defaults, array_key, 0); + changed = (strcmp(value, default_value) != 0); + free(value); + free(default_value); + options_free(oo); + return (changed); + } + + value = options_to_string(o, NULL, 0); + default_value = options_default_to_string(oe); + changed = (strcmp(value, default_value) != 0); + free(value); + free(default_value); + return (changed); +} + +static int +window_customize_key_is_changed(struct key_table *kt, struct key_binding *bd) +{ + struct key_binding *default_bd; + char *cmd, *default_cmd; + int changed; + + default_bd = key_bindings_get_default(kt, bd->key); + if (default_bd == NULL) + return (1); + if (bd->flags != default_bd->flags) + return (1); + if ((bd->note == NULL) != (default_bd->note == NULL)) + return (1); + if (bd->note != NULL && strcmp(bd->note, default_bd->note) != 0) + return (1); + + cmd = cmd_parse_print(bd->cmd, 0); + default_cmd = cmd_parse_print(default_bd->cmd, 0); + changed = (strcmp(cmd, default_cmd) != 0); + free(cmd); + free(default_cmd); + return (changed); +} + +static u_int window_customize_build_array(struct window_customize_modedata *data, struct mode_tree_item *top, enum window_customize_scope scope, struct options_entry *o, struct format_tree *ft) @@ -242,10 +637,16 @@ window_customize_build_array(struct window_customize_modedata *data, char *name, *value, *text; uint64_t tag; const char *array_key; + u_int count = 0; ai = options_array_first(o); while (ai != NULL) { array_key = options_array_item_key(ai); + if (data->hide_default && + !window_customize_option_is_changed(o, array_key)) { + ai = options_array_next(ai); + continue; + } xasprintf(&name, "%s[%s]", options_name(o), array_key); format_add(ft, "option_name", "%s", name); @@ -253,6 +654,9 @@ window_customize_build_array(struct window_customize_modedata *data, format_add(ft, "option_value", "%s", value); item = window_customize_add_item(data); + item->type = WINDOW_CUSTOMIZE_ITEM_OPTION; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)) + item->option_type = WINDOW_CUSTOMIZE_HOOKS; item->scope = scope; item->oo = oo; item->name = xstrdup(options_name(o)); @@ -265,16 +669,19 @@ window_customize_build_array(struct window_customize_modedata *data, free(name); free(value); + count++; ai = options_array_next(ai); } + return (count); } -static void +static u_int window_customize_build_option(struct window_customize_modedata *data, struct mode_tree_item *top, enum window_customize_scope scope, struct options_entry *o, struct format_tree *ft, - const char *filter, struct cmd_find_state *fs) + const char *filter, struct cmd_find_state *fs, + enum window_customize_option_type type) { const struct options_table_entry *oe = options_table_entry(o); struct options *oo = options_owner(o); @@ -282,10 +689,26 @@ window_customize_build_option(struct window_customize_modedata *data, struct window_customize_itemdata *item; char *text, *expanded, *value; int global = 0, array = 0; + int is_hook = 0, is_monitor = 0; + int is_user_hook = 0; uint64_t tag; if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)) - return; + is_hook = 1; + if (options_get_monitor_data(o) != NULL) + is_monitor = 1; + if (*name == '@' && hooks_is_event(name)) + is_user_hook = 1; + switch (type) { + case WINDOW_CUSTOMIZE_OPTIONS: + if (is_hook || is_monitor || is_user_hook) + return (0); + break; + case WINDOW_CUSTOMIZE_HOOKS: + if (!is_hook && !is_monitor && !is_user_hook) + return (0); + break; + } if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) array = 1; @@ -294,11 +717,15 @@ window_customize_build_option(struct window_customize_modedata *data, scope == WINDOW_CUSTOMIZE_GLOBAL_WINDOW) global = 1; if (data->hide_global && global) - return; + return (0); + if (data->hide_default && !window_customize_option_is_changed(o, NULL)) + return (0); format_add(ft, "option_name", "%s", name); format_add(ft, "option_is_global", "%d", global); format_add(ft, "option_is_array", "%d", array); + format_add(ft, "option_is_hook", "%d", is_hook); + format_add(ft, "option_is_monitor", "%d", is_monitor); text = window_customize_scope_text(scope, fs); format_add(ft, "option_scope", "%s", text); @@ -309,6 +736,16 @@ window_customize_build_option(struct window_customize_modedata *data, else format_add(ft, "option_unit", "%s", ""); + if (is_monitor) { + value = hooks_monitor_to_string(o); + if (value != NULL) { + format_add(ft, "option_monitor", "%s", value); + free(value); + } else + format_add(ft, "option_monitor", "%s", ""); + } else + format_add(ft, "option_monitor", "%s", ""); + if (!array) { value = options_to_string(o, NULL, 0); format_add(ft, "option_value", "%s", value); @@ -319,11 +756,13 @@ window_customize_build_option(struct window_customize_modedata *data, expanded = format_expand(ft, filter); if (!format_true(expanded)) { free(expanded); - return; + return (0); } free(expanded); } item = window_customize_add_item(data); + item->type = WINDOW_CUSTOMIZE_ITEM_OPTION; + item->option_type = type; item->oo = oo; item->scope = scope; item->name = xstrdup(name); @@ -336,8 +775,9 @@ window_customize_build_option(struct window_customize_modedata *data, top = mode_tree_add(data->data, top, item, tag, name, text, 0); free(text); - if (array) - window_customize_build_array(data, top, scope, o, ft); + if (!array) + return (1); + return (1 + window_customize_build_array(data, top, scope, o, ft)); } static void @@ -376,12 +816,13 @@ window_customize_build_options(struct window_customize_modedata *data, enum window_customize_scope scope0, struct options *oo0, enum window_customize_scope scope1, struct options *oo1, enum window_customize_scope scope2, struct options *oo2, - struct format_tree *ft, const char *filter, struct cmd_find_state *fs) + struct format_tree *ft, const char *filter, struct cmd_find_state *fs, + enum window_customize_option_type type) { struct mode_tree_item *top; struct options_entry *o = NULL, *loop; const char **list = NULL, *name; - u_int size = 0, i; + u_int size = 0, i, count = 0; enum window_customize_scope scope; top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); @@ -400,6 +841,7 @@ window_customize_build_options(struct window_customize_modedata *data, window_customize_find_user_options(oo2, &list, &size); for (i = 0; i < size; i++) { + o = NULL; if (oo2 != NULL) o = options_get(oo2, list[i]); if (o == NULL && oo1 != NULL) @@ -412,8 +854,8 @@ window_customize_build_options(struct window_customize_modedata *data, scope = scope1; else scope = scope0; - window_customize_build_option(data, top, scope, o, ft, filter, - fs); + count += window_customize_build_option(data, top, scope, o, ft, + filter, fs, type); } free(list); @@ -436,37 +878,51 @@ window_customize_build_options(struct window_customize_modedata *data, scope = scope1; else scope = scope0; - window_customize_build_option(data, top, scope, o, ft, filter, - fs); + count += window_customize_build_option(data, top, scope, o, ft, + filter, fs, type); loop = options_next(loop); } + if (data->hide_default && count == 0) + mode_tree_remove(data->data, top); +} + +static uint64_t +window_customize_key_tag(const void *ptr, u_int type) +{ + return ((uint64_t)(uintptr_t)ptr|type); } static void window_customize_build_keys(struct window_customize_modedata *data, struct key_table *kt, struct format_tree *ft, const char *filter, - struct cmd_find_state *fs, u_int number) + struct cmd_find_state *fs) { struct mode_tree_item *top, *child, *mti; struct window_customize_itemdata *item; struct key_binding *bd; char *title, *text, *tmp, *expanded; const char *flag; - uint64_t tag; - - tag = (1ULL << 62)|((uint64_t)number << 54)|1; + u_int count = 0; xasprintf(&title, "Key Table - %s", kt->name); - top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + top = mode_tree_add(data->data, NULL, NULL, + window_customize_key_tag(kt, 0), title, NULL, 0); mode_tree_no_tag(top); free(title); ft = format_create_from_state(NULL, NULL, fs); format_add(ft, "is_option", "0"); format_add(ft, "is_key", "1"); + format_add(ft, "is_environment", "0"); bd = key_bindings_first(kt); while (bd != NULL) { + if (data->hide_default && + !window_customize_key_is_changed(kt, bd)) { + bd = key_bindings_next(kt, bd); + continue; + } + format_add(ft, "key", "%s", key_string_lookup_key(bd->key, 0)); if (bd->note != NULL) format_add(ft, "key_note", "%s", bd->note); @@ -481,31 +937,33 @@ window_customize_build_keys(struct window_customize_modedata *data, } item = window_customize_add_item(data); + item->type = WINDOW_CUSTOMIZE_ITEM_KEY; item->scope = WINDOW_CUSTOMIZE_KEY; item->table = xstrdup(kt->name); item->key = bd->key; item->name = xstrdup(key_string_lookup_key(item->key, 0)); expanded = format_expand(ft, data->format); - child = mode_tree_add(data->data, top, item, (uint64_t)bd, - expanded, NULL, 0); + child = mode_tree_add(data->data, top, item, + window_customize_key_tag(bd, 0), expanded, NULL, 0); free(expanded); tmp = cmd_parse_print(bd->cmd, 0); - xasprintf(&text, "#[ignore]%s", tmp); + xasprintf(&text, "#[fg=themelightgrey]#[ignore]%s", tmp); free(tmp); mti = mode_tree_add(data->data, child, item, - tag|(bd->key << 3)|(0 << 1)|1, "Command", text, -1); + window_customize_key_tag(bd, 1), "Command", text, -1); mode_tree_draw_as_parent(mti); mode_tree_no_tag(mti); free(text); - if (bd->note != NULL) - xasprintf(&text, "#[ignore]%s", bd->note); - else + if (bd->note != NULL) { + xasprintf(&text, "#[fg=themelightgrey]#[ignore]%s", + bd->note); + } else text = xstrdup(""); mti = mode_tree_add(data->data, child, item, - tag|(bd->key << 3)|(1 << 1)|1, "Note", text, -1); + window_customize_key_tag(bd, 2), "Note", text, -1); mode_tree_draw_as_parent(mti); mode_tree_no_tag(mti); free(text); @@ -514,15 +972,97 @@ window_customize_build_keys(struct window_customize_modedata *data, flag = "on"; else flag = "off"; + xasprintf(&text, "#[fg=themelightgrey]#[ignore]%s", flag); mti = mode_tree_add(data->data, child, item, - tag|(bd->key << 3)|(2 << 1)|1, "Repeat", flag, -1); + window_customize_key_tag(bd, 3), "Repeat", text, -1); mode_tree_draw_as_parent(mti); mode_tree_no_tag(mti); + free(text); + count++; bd = key_bindings_next(kt, bd); } format_free(ft); + if (data->hide_default && count == 0) + mode_tree_remove(data->data, top); +} + +static void +window_customize_build_environment(struct window_customize_modedata *data, + const char *title, uint64_t tag, enum window_customize_scope scope, + struct environ *env, struct format_tree *ft, const char *filter, + struct cmd_find_state *fs) +{ + struct mode_tree_item *top; + struct window_customize_itemdata *item; + struct environ_entry *envent; + char *name, *text, *expanded, *value; + uint64_t item_tag; + int global; + + if (data->hide_default) + return; + + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + mode_tree_no_tag(top); + + global = (scope == WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT); + format_add(ft, "is_option", "0"); + format_add(ft, "is_key", "0"); + format_add(ft, "is_environment", "1"); + format_add(ft, "environment_is_global", "%d", global); + + text = window_customize_scope_text(scope, fs); + format_add(ft, "environment_scope", "%s", text); + free(text); + + envent = environ_first(env); + while (envent != NULL) { + format_add(ft, "environment_name", "%s", envent->name); + format_add(ft, "environment_hidden", "%d", + !!(envent->flags & ENVIRON_HIDDEN)); + format_add(ft, "environment_removed", "%d", + envent->value == NULL); + if (envent->value == NULL) + value = xstrdup(""); + else + value = xstrdup(envent->value); + format_add(ft, "environment_value", "%s", value); + + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + free(value); + envent = environ_next(envent); + continue; + } + free(expanded); + } + + item = window_customize_add_item(data); + item->type = WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT; + item->scope = scope; + item->environ = env; + item->environ_flags = envent->flags; + item->name = xstrdup(envent->name); + + if (envent->value == NULL) { + xasprintf(&name, "-%s", envent->name); + text = NULL; + } else { + name = xstrdup(envent->name); + text = format_expand(ft, data->format); + } + item_tag = (2ULL << 62)|((uint64_t)(uintptr_t)envent); + mode_tree_add(data->data, top, item, item_tag, name, text, 0); + free(name); + free(text); + free(value); + + envent = environ_next(envent); + } } static void @@ -550,38 +1090,55 @@ window_customize_build(void *modedata, ft = format_create_from_state(NULL, NULL, &fs); format_add(ft, "is_option", "1"); format_add(ft, "is_key", "0"); + format_add(ft, "is_environment", "0"); window_customize_build_options(data, "Server Options", (3ULL << 62)|(OPTIONS_TABLE_SERVER << 1)|1, WINDOW_CUSTOMIZE_SERVER, global_options, WINDOW_CUSTOMIZE_NONE, NULL, WINDOW_CUSTOMIZE_NONE, NULL, - ft, filter, &fs); + ft, filter, &fs, WINDOW_CUSTOMIZE_OPTIONS); window_customize_build_options(data, "Session Options", (3ULL << 62)|(OPTIONS_TABLE_SESSION << 1)|1, WINDOW_CUSTOMIZE_GLOBAL_SESSION, global_s_options, WINDOW_CUSTOMIZE_SESSION, fs.s->options, WINDOW_CUSTOMIZE_NONE, NULL, - ft, filter, &fs); + ft, filter, &fs, WINDOW_CUSTOMIZE_OPTIONS); window_customize_build_options(data, "Window & Pane Options", (3ULL << 62)|(OPTIONS_TABLE_WINDOW << 1)|1, WINDOW_CUSTOMIZE_GLOBAL_WINDOW, global_w_options, WINDOW_CUSTOMIZE_WINDOW, fs.w->options, WINDOW_CUSTOMIZE_PANE, fs.wp->options, - ft, filter, &fs); + ft, filter, &fs, WINDOW_CUSTOMIZE_OPTIONS); + window_customize_build_options(data, "Session Hooks", + (3ULL << 62)|(1ULL << 8)|(OPTIONS_TABLE_SESSION << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, global_s_options, + WINDOW_CUSTOMIZE_SESSION, fs.s->options, + WINDOW_CUSTOMIZE_NONE, NULL, + ft, filter, &fs, WINDOW_CUSTOMIZE_HOOKS); + window_customize_build_options(data, "Window & Pane Hooks", + (3ULL << 62)|(1ULL << 8)|(OPTIONS_TABLE_WINDOW << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, global_w_options, + WINDOW_CUSTOMIZE_WINDOW, fs.w->options, + WINDOW_CUSTOMIZE_PANE, fs.wp->options, + ft, filter, &fs, WINDOW_CUSTOMIZE_HOOKS); + window_customize_build_environment(data, "Global Environment", + (3ULL << 62)|(2ULL << 8)|1, + WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT, global_environ, ft, filter, + &fs); + window_customize_build_environment(data, "Session Environment", + (3ULL << 62)|(2ULL << 8)|3, + WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT, fs.s->environ, ft, filter, + &fs); format_free(ft); ft = format_create_from_state(NULL, NULL, &fs); + format_add(ft, "is_environment", "0"); - i = 0; kt = key_bindings_first_table(); while (kt != NULL) { - if (!RB_EMPTY(&kt->key_bindings)) { - window_customize_build_keys(data, kt, ft, filter, &fs, - i); - if (++i == 256) - break; - } + if (!RB_EMPTY(&kt->key_bindings)) + window_customize_build_keys(data, kt, ft, filter, &fs); kt = key_bindings_next_table(kt); } @@ -618,17 +1175,16 @@ window_customize_draw_key(__unused struct window_customize_modedata *data, if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "This key is in the %s table.", kt->name)) return; - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "This key %s repeat.", - (bd->flags & KEY_BINDING_REPEAT) ? "does" : "does not")) + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), 0, + "Repeat: ", "%s", (bd->flags & KEY_BINDING_REPEAT) ? "on" : "off")) return; screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ if (s->cy >= cy + sy - 1) return; cmd = cmd_parse_print(bd->cmd, CMD_PARSE_PRINT_MULTILINE); - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "Command: %s", cmd)) { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), 0, + "Command: ", "%s", cmd)) { free(cmd); return; } @@ -637,8 +1193,8 @@ window_customize_draw_key(__unused struct window_customize_modedata *data, default_cmd = cmd_parse_print(default_bd->cmd, CMD_PARSE_PRINT_MULTILINE); if (strcmp(cmd, default_cmd) != 0 && - !screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "The default is: %s", default_cmd)) { + !window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "The default is: ", "%s", default_cmd)) { free(default_cmd); free(cmd); return; @@ -663,10 +1219,12 @@ window_customize_draw_option(struct window_customize_modedata *data, const char *array_key; const char *space = "", *unit = ""; char *value = NULL, *expanded; + char *monitor, label[64]; char *default_value = NULL; char choices[256] = ""; struct cmd_find_state fs; struct format_tree *ft; + int is_hook, is_monitor, is_user_hook; if (!window_customize_check_item(data, item, &fs)) return; @@ -677,6 +1235,9 @@ window_customize_draw_option(struct window_customize_modedata *data, if (o == NULL) return; oe = options_table_entry(o); + is_hook = (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)); + is_monitor = (options_get_monitor_data(o) != NULL); + is_user_hook = (*name == '@' && hooks_is_event(name)); if (oe != NULL && oe->unit != NULL) { space = " "; @@ -684,9 +1245,14 @@ window_customize_draw_option(struct window_customize_modedata *data, } ft = format_create_from_state(NULL, NULL, &fs); - if (oe == NULL || oe->text == NULL) - text = "This option doesn't have a description."; - else + if (oe == NULL || oe->text == NULL) { + if (is_monitor) + text = "This hook runs when a monitor changes."; + else if (is_user_hook) + text = "This hook doesn't have a description."; + else + text = "This option doesn't have a description."; + } else text = oe->text; if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s", text)) @@ -695,6 +1261,12 @@ window_customize_draw_option(struct window_customize_modedata *data, if (s->cy >= cy + sy - 1) goto out; + if (is_monitor) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a monitor hook.")) + goto out; + goto monitor; + } if (oe == NULL) text = "user"; else if ((oe->scope & (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) == @@ -706,11 +1278,35 @@ window_customize_draw_option(struct window_customize_modedata *data, text = "session"; else text = "server"; - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "This is a %s option.", text)) - goto out; + if (is_user_hook) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a user hook.")) + goto out; + } else if (is_hook) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a %s hook.", text)) + goto out; + } else { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a %s option.", text)) + goto out; + } + +monitor: + monitor = hooks_monitor_to_string(o); + if (monitor != NULL) { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "Monitor: ", "%s", monitor)) { + free(monitor); + goto out; + } + free(monitor); + } if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { - if (array_key != NULL) { + if (is_hook) { + if (array_key == NULL) + goto out; + } else if (array_key != NULL) { if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &grid_default_cell, "This is an array option, key %s.", array_key)) @@ -735,14 +1331,20 @@ window_customize_draw_option(struct window_customize_modedata *data, default_value = NULL; } } - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "Option value: %s%s%s", value, space, unit)) - goto out; + if (is_hook || is_monitor || is_user_hook) { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "Hook command: ", "%s%s%s", value, space, unit)) + goto out; + } else { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "Option value: ", "%s%s%s", value, space, unit)) + goto out; + } if (oe == NULL || oe->type == OPTIONS_TABLE_STRING) { expanded = format_expand(ft, value); if (strcmp(expanded, value) != 0) { - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), - 0, &grid_default_cell, "This expands to: %s", + if (!window_customize_write_value(ctx, cx, sx, + sy - (s->cy - cy), 0, "This expands to: ", "%s", expanded)) { free(expanded); goto out; @@ -756,8 +1358,8 @@ window_customize_draw_option(struct window_customize_modedata *data, strlcat(choices, ", ", sizeof choices); } choices[strlen(choices) - 2] = '\0'; - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "Available values are: %s", + if (!window_customize_write_value(ctx, cx, sx, + sy - (s->cy - cy), 0, "Available values are: ", "%s", choices)) goto out; } @@ -790,9 +1392,8 @@ window_customize_draw_option(struct window_customize_modedata *data, goto out; } if (default_value != NULL) { - if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, - &grid_default_cell, "The default is: %s%s%s", default_value, - space, unit)) + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "The default is: ", "%s%s%s", default_value, space, unit)) goto out; } @@ -823,10 +1424,11 @@ window_customize_draw_option(struct window_customize_modedata *data, parent = options_get_only(wo, name); if (parent != NULL) { value = options_to_string(parent, NULL, 0); - if (!screen_write_text(ctx, s->cx, sx, - sy - (s->cy - cy), 0, &grid_default_cell, - "Window value (from window %u): %s%s%s", fs.wl->idx, - value, space, unit)) + xsnprintf(label, sizeof label, + "Window value (from window %u): ", fs.wl->idx); + if (!window_customize_write_value(ctx, s->cx, sx, + sy - (s->cy - cy), 0, label, "%s%s%s", value, space, + unit)) goto out; } } @@ -834,9 +1436,9 @@ window_customize_draw_option(struct window_customize_modedata *data, parent = options_get_only(go, name); if (parent != NULL) { value = options_to_string(parent, NULL, 0); - if (!screen_write_text(ctx, s->cx, sx, - sy - (s->cy - cy), 0, &grid_default_cell, - "Global value: %s%s%s", value, space, unit)) + if (!window_customize_write_value(ctx, s->cx, sx, + sy - (s->cy - cy), 0, "Global value: ", "%s%s%s", + value, space, unit)) goto out; } } @@ -847,6 +1449,67 @@ out: format_free(ft); } +static void +window_customize_draw_environment(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct screen_write_ctx *ctx, + u_int sx, u_int sy) +{ + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; + struct environ_entry *envent, *parent; + struct cmd_find_state fs; + const char *text; + + if (!window_customize_check_item(data, item, &fs)) + return; + envent = environ_find(item->environ, item->name); + if (envent == NULL) + return; + + if (item->scope == WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT) + text = "global"; + else + text = "session"; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, + "This is a %s environment variable.", text)) + return; + if (envent->flags & ENVIRON_HIDDEN) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This variable is hidden.")) + return; + } + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + if (envent->value == NULL) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Variable is removed.")) + return; + } else { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "Variable value: ", "%s", envent->value)) + return; + } + + if (item->scope != WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT) + return; + + parent = environ_find(global_environ, item->name); + if (parent == NULL) + return; + if (parent->value == NULL) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Global variable is removed.")) + return; + } else { + if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), + 0, "Global value: ", "%s", parent->value)) + return; + } +} + static void window_customize_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, u_int sx, u_int sy) @@ -857,8 +1520,10 @@ window_customize_draw(void *modedata, void *itemdata, if (item == NULL) return; - if (item->scope == WINDOW_CUSTOMIZE_KEY) + if (item->type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_draw_key(data, item, ctx, sx, sy); + else if (item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) + window_customize_draw_environment(data, item, ctx, sx, sy); else window_customize_draw_option(data, item, ctx, sx, sy); } @@ -900,8 +1565,12 @@ static const char* window_customize_help_lines[] = { "#[fg=themelightgrey]" " a #[#{E:tree-mode-border-style},acs]x#[default] Change array key", "#[fg=themelightgrey]" + " e #[#{E:tree-mode-border-style},acs]x#[default] Open %1 value in editor", + "#[fg=themelightgrey]" " f #[#{E:tree-mode-border-style},acs]x#[default] Enter a filter", "#[fg=themelightgrey]" + " C #[#{E:tree-mode-border-style},acs]x#[default] Toggle only changed items", + "#[fg=themelightgrey]" " v #[#{E:tree-mode-border-style},acs]x#[default] Toggle information", NULL }; @@ -910,12 +1579,13 @@ static const char** window_customize_help(u_int *width, const char **item) { *width = 52; - *item = "option"; + *item = "item"; return (window_customize_help_lines); } static struct screen * -window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs, +window_customize_init(struct window_mode_entry *wme, + __unused struct cmdq_item *item, struct cmd_find_state *fs, struct args *args) { struct window_pane *wp = wme->wp; @@ -973,6 +1643,10 @@ window_customize_free(struct window_mode_entry *wme) return; data->dead = 1; + if (data->editor != NULL) { + spawn_cancel_editor(data->editor); + window_customize_finish_edit(data->edit); + } mode_tree_free(data->data); window_customize_destroy(data); } @@ -985,6 +1659,14 @@ window_customize_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_customize_update(struct window_mode_entry *wme) +{ + struct window_customize_modedata *data = wme->data; + + window_customize_draw_waiting(data); +} + static void window_customize_free_callback(void *modedata) { @@ -1040,6 +1722,8 @@ window_customize_set_option_callback(struct client *c, void *itemdata, if (options_from_string(oo, oe, name, s, 0, &cause) != 0) goto fail; } + if (item->option_type == WINDOW_CUSTOMIZE_HOOKS && *name == '@') + hooks_add_event(name); options_push_changes(item->name); mode_tree_build(data->data); @@ -1055,6 +1739,375 @@ fail: return (PROMPT_CLOSE); } +static enum prompt_result +window_customize_set_environment_callback(__unused struct client *c, + void *itemdata, const char *s, __unused enum prompt_key_result key) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct environ_entry *envent; + int flags; + + if (s == NULL || data->dead) + return (PROMPT_CLOSE); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (PROMPT_CLOSE); + + flags = item->environ_flags; + envent = environ_find(item->environ, item->name); + if (envent != NULL) + flags = envent->flags; + environ_set(item->environ, item->name, flags, "%s", s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (PROMPT_CLOSE); +} + +static void +window_customize_set_environment(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item, int global) +{ + struct window_customize_itemdata *new_item; + struct environ_entry *envent; + struct environ *env; + enum window_customize_scope scope; + struct cmd_find_state fs; + const char *space = ""; + char *prompt, *text; + + if (item == NULL || !window_customize_check_item(data, item, &fs)) + return; + envent = environ_find(item->environ, item->name); + if (envent == NULL) + return; + + if (global) { + scope = WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT; + env = global_environ; + } else { + scope = item->scope; + env = item->environ; + } + + text = window_customize_scope_text(scope, &fs); + if (*text != '\0') + space = ", for "; + else if (scope == WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT) + space = ", global"; + xasprintf(&prompt, "(%s%s%s) ", item->name, space, text); + free(text); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT; + new_item->scope = scope; + new_item->environ = env; + new_item->environ_flags = envent->flags; + new_item->name = xstrdup(item->name); + + data->references++; + mode_tree_set_prompt(data->data, c, prompt, + envent->value == NULL ? "" : envent->value, PROMPT_TYPE_COMMAND, + PROMPT_NOFORMAT, window_customize_set_environment_callback, + window_customize_free_item_callback, new_item); + + free(prompt); +} + +static enum prompt_result +window_customize_add_option_callback(struct client *c, void *itemdata, + const char *s, __unused enum prompt_key_result key) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + char *copy, *name, *array_key; + const char *value; + const char *what; + int ambiguous; + size_t namelen; + + if (s == NULL || *s == '\0' || data->dead) + return (PROMPT_CLOSE); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (PROMPT_CLOSE); + + namelen = strcspn(s, " \t"); + if (namelen == 0 || s[namelen] == '\0') { + status_message_set(c, -1, 1, 0, 0, + "User option must be @name value"); + return (PROMPT_CLOSE); + } + + value = s + namelen; + while (*value == ' ' || *value == '\t') + value++; + if (*value == '\0') { + status_message_set(c, -1, 1, 0, 0, + "User option must be @name value"); + return (PROMPT_CLOSE); + } + + copy = xstrndup(s, namelen); + name = options_match(copy, &array_key, &ambiguous); + free(copy); + if (name == NULL || *name != '@' || array_key != NULL) { + what = (item->option_type == WINDOW_CUSTOMIZE_HOOKS) ? + "hook" : "option"; + status_message_set(c, -1, 1, 0, 0, + "User %s name must start with @", what); + free(name); + free(array_key); + return (PROMPT_CLOSE); + } + + options_set_string(item->oo, name, 0, "%s", value); + if (item->option_type == WINDOW_CUSTOMIZE_HOOKS) + hooks_add_event(name); + options_push_changes(name); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + free(name); + free(array_key); + return (PROMPT_CLOSE); +} + +static void +window_customize_add_option(struct client *c, + struct window_customize_modedata *data, enum window_customize_scope scope, + struct options *oo, enum window_customize_option_type type) +{ + struct window_customize_itemdata *new_item; + char *prompt; + const char *what; + + what = (type == WINDOW_CUSTOMIZE_HOOKS) ? "hook" : "option"; + xasprintf(&prompt, "New user %s: ", what); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_OPTION; + new_item->option_type = type; + new_item->scope = scope; + new_item->oo = oo; + + data->references++; + mode_tree_set_prompt(data->data, c, prompt, "@", PROMPT_TYPE_COMMAND, + PROMPT_NOFORMAT, window_customize_add_option_callback, + window_customize_free_item_callback, new_item); + + free(prompt); +} + +static enum prompt_result +window_customize_add_environment_callback(struct client *c, void *itemdata, + const char *s, __unused enum prompt_key_result key) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + char *name; + const char *value; + + if (s == NULL || *s == '\0' || data->dead) + return (PROMPT_CLOSE); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (PROMPT_CLOSE); + + if (*s == '-') { + if (s[1] == '\0' || strchr(s + 1, '=') != NULL) { + status_message_set(c, -1, 1, 0, 0, + "Bad environment variable: %s", s); + return (PROMPT_CLOSE); + } + environ_clear(item->environ, s + 1); + } else { + value = strchr(s, '='); + if (value == NULL || value == s) { + status_message_set(c, -1, 1, 0, 0, + "Environment variable must be NAME=value"); + return (PROMPT_CLOSE); + } + + name = xstrdup(s); + name[strcspn(name, "=")] = '\0'; + environ_set(item->environ, name, 0, "%s", value + 1); + free(name); + } + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (PROMPT_CLOSE); +} + +static void +window_customize_add_environment(struct client *c, + struct window_customize_modedata *data, enum window_customize_scope scope, + struct environ *env) +{ + struct window_customize_itemdata *new_item; + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT; + new_item->scope = scope; + new_item->environ = env; + + data->references++; + mode_tree_set_prompt(data->data, c, "New environment: ", "", + PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT, + window_customize_add_environment_callback, + window_customize_free_item_callback, new_item); +} + +static void +window_customize_edit_close_cb(char *buf, size_t len, void *arg) +{ + struct window_customize_editdata *ed = arg; + struct window_customize_itemdata *item = ed->item; + struct window_pane *wp; + struct window_mode_entry *wme; + struct window_customize_modedata *data = NULL; + char *value, *cause = NULL; + + wp = window_pane_find_by_id(ed->wp_id); + if (wp != NULL) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode == &window_customize_mode) { + data = wme->data; + if (data->editor == ed->editor) { + data->editor = NULL; + data->edit = NULL; + } + } + } + + if (buf == NULL || len == 0 || data == NULL || data->dead) { + free(buf); + window_customize_finish_edit(ed); + return; + } + if (buf[len - 1] == '\n') + len--; + value = xmalloc(len + 1); + memcpy(value, buf, len); + value[len] = '\0'; + free(buf); + + switch (ed->edit_type) { + case WINDOW_CUSTOMIZE_EDIT_OPTION: + if (window_customize_option_editable(data, item) && + window_customize_set_option_value(item, value, &cause) != 0) { + free(cause); + goto out; + } + break; + case WINDOW_CUSTOMIZE_EDIT_KEY_COMMAND: + if (window_customize_set_command_value(item, value, &cause) != 0) { + free(cause); + goto out; + } + break; + case WINDOW_CUSTOMIZE_EDIT_KEY_NOTE: + if (window_customize_set_note_value(item, value) != 0) + goto out; + break; + case WINDOW_CUSTOMIZE_EDIT_ENVIRONMENT: + if (!window_customize_check_item(data, item, NULL)) + goto out; + window_customize_set_environment_value(item, value); + break; + } + + mode_tree_build(data->data); + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + +out: + free(value); + window_customize_finish_edit(ed); +} + +static void +window_customize_start_edit(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct client *c) +{ + struct window_customize_editdata *ed; + struct options_entry *o; + struct environ_entry *envent; + struct key_binding *bd; + const char *name; + const char *buf; + char *value; + size_t len; + enum window_customize_edit_type edit_type; + + if (data->editor != NULL || item == NULL) + return; + + if (item->type == WINDOW_CUSTOMIZE_ITEM_OPTION) { + if (!window_customize_option_editable(data, item)) + return; + o = options_get(item->oo, item->name); + if (o == NULL) + return; + value = options_to_string(o, item->array_key, 0); + edit_type = WINDOW_CUSTOMIZE_EDIT_OPTION; + } else if (item->type == WINDOW_CUSTOMIZE_ITEM_KEY) { + name = mode_tree_get_current_name(data->data); + if (!window_customize_get_key(item, NULL, &bd)) + return; + if (strcmp(name, "Command") == 0) { + value = cmd_parse_print(bd->cmd, 0); + edit_type = WINDOW_CUSTOMIZE_EDIT_KEY_COMMAND; + } else if (strcmp(name, "Note") == 0) { + if (bd->note == NULL) + value = xstrdup(""); + else + value = xstrdup(bd->note); + edit_type = WINDOW_CUSTOMIZE_EDIT_KEY_NOTE; + } else + return; + } else if (item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) { + if (!window_customize_check_item(data, item, NULL)) + return; + envent = environ_find(item->environ, item->name); + if (envent == NULL || envent->value == NULL) + return; + value = xstrdup(envent->value); + edit_type = WINDOW_CUSTOMIZE_EDIT_ENVIRONMENT; + } else + return; + + ed = xcalloc(1, sizeof *ed); + ed->wp_id = data->wp->id; + ed->edit_type = edit_type; + ed->item = window_customize_copy_item(item); + + buf = value; + len = strlen(value); + if (len == 0) { + buf = "\n"; + len = 1; + } + ed->editor = spawn_editor(c, buf, len, window_customize_edit_close_cb, + ed); + free(value); + if (ed->editor == NULL) + window_customize_finish_edit(ed); + else { + data->editor = ed->editor; + data->edit = ed; + } +} + static void window_customize_set_option(struct client *c, struct window_customize_modedata *data, @@ -1092,6 +2145,8 @@ window_customize_set_option(struct client *c, case WINDOW_CUSTOMIZE_SERVER: case WINDOW_CUSTOMIZE_GLOBAL_SESSION: case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + case WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT: + case WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT: scope = item->scope; break; case WINDOW_CUSTOMIZE_SESSION: @@ -1126,6 +2181,10 @@ window_customize_set_option(struct client *c, else scope = WINDOW_CUSTOMIZE_WINDOW; break; + case WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT: + case WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT: + scope = item->scope; + break; } } if (scope == item->scope) @@ -1166,6 +2225,8 @@ window_customize_set_option(struct client *c, new_item = xcalloc(1, sizeof *new_item); new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_OPTION; + new_item->option_type = item->option_type; new_item->scope = scope; new_item->oo = oo; new_item->name = xstrdup(name); @@ -1247,6 +2308,8 @@ window_customize_set_array_key(struct client *c, new_item = xcalloc(1, sizeof *new_item); new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_OPTION; + new_item->option_type = item->option_type; new_item->scope = item->scope; new_item->oo = item->oo; new_item->name = xstrdup(item->name); @@ -1261,6 +2324,19 @@ window_customize_set_array_key(struct client *c, free(prompt); } +static void +window_customize_unset_environment(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + if (environ_find(item->environ, item->name) == NULL) + return; + if (item == mode_tree_get_current(data->data)) + mode_tree_up(data->data, 0); + environ_unset(item->environ, item->name); +} + static void window_customize_unset_option(struct window_customize_modedata *data, struct window_customize_itemdata *item) @@ -1293,7 +2369,7 @@ window_customize_reset_option(struct window_customize_modedata *data, oo = item->oo; while (oo != NULL) { - o = options_get_only(item->oo, item->name); + o = options_get_only(oo, item->name); if (o != NULL) options_remove_or_default(o, NULL, NULL); oo = options_get_parent(oo); @@ -1318,6 +2394,10 @@ window_customize_set_command_callback(struct client *c, void *itemdata, tree = cmd_parse_from_string(s, NULL, &error); if (tree == NULL) goto fail; + if (window_customize_check_command(tree, &error) != 0) { + cmd_parse_free(tree); + goto fail; + } cmd_parse_free(bd->cmd); bd->cmd = tree; @@ -1380,6 +2460,7 @@ window_customize_set_key(struct client *c, new_item = xcalloc(1, sizeof *new_item); new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_KEY; new_item->scope = item->scope; new_item->table = xstrdup(item->table); new_item->key = key; @@ -1396,6 +2477,7 @@ window_customize_set_key(struct client *c, new_item = xcalloc(1, sizeof *new_item); new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_KEY; new_item->scope = item->scope; new_item->table = xstrdup(item->table); new_item->key = key; @@ -1410,6 +2492,91 @@ window_customize_set_key(struct client *c, } } +static enum prompt_result +window_customize_add_key_callback(struct client *c, void *itemdata, + const char *s, __unused enum prompt_key_result key0) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + key_code key; + struct cmd_parse_tree *tree; + const char *command; + char *error, *keystr; + size_t keylen; + + if (s == NULL || *s == '\0' || data->dead) + return (PROMPT_CLOSE); + + keylen = strcspn(s, " \t"); + if (keylen == 0 || s[keylen] == '\0') { + status_message_set(c, -1, 1, 0, 0, + "Key binding must be key command"); + return (PROMPT_CLOSE); + } + + command = s + keylen; + while (*command == ' ' || *command == '\t') + command++; + if (*command == '\0') { + status_message_set(c, -1, 1, 0, 0, + "Key binding must be key command"); + return (PROMPT_CLOSE); + } + + keystr = xstrndup(s, keylen); + key = key_string_lookup_string(keystr); + if (key == KEYC_NONE || key == KEYC_UNKNOWN) { + status_message_set(c, -1, 1, 0, 0, "Unknown key: %s", keystr); + free(keystr); + return (PROMPT_CLOSE); + } + free(keystr); + + tree = cmd_parse_from_string(command, NULL, &error); + if (tree == NULL) + goto fail; + if (window_customize_check_command(tree, &error) != 0) { + cmd_parse_free(tree); + goto fail; + } + key_bindings_add(item->table, key, NULL, 0, tree); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (PROMPT_CLOSE); + +fail: + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, 0, "%s", error); + free(error); + return (PROMPT_CLOSE); +} + +static void +window_customize_add_key(struct client *c, + struct window_customize_modedata *data, const char *table) +{ + struct window_customize_itemdata *new_item; + char *prompt; + + xasprintf(&prompt, "New key in %s: ", table); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->type = WINDOW_CUSTOMIZE_ITEM_KEY; + new_item->scope = WINDOW_CUSTOMIZE_KEY; + new_item->table = xstrdup(table); + + data->references++; + mode_tree_set_prompt(data->data, c, prompt, "", PROMPT_TYPE_COMMAND, + PROMPT_NOFORMAT, window_customize_add_key_callback, + window_customize_free_item_callback, new_item); + + free(prompt); +} + static void window_customize_unset_key(struct window_customize_modedata *data, struct window_customize_itemdata *item) @@ -1420,10 +2587,8 @@ window_customize_unset_key(struct window_customize_modedata *data, if (item == NULL || !window_customize_get_key(item, &kt, &bd)) return; - if (item == mode_tree_get_current(data->data)) { - mode_tree_collapse_current(data->data); + if (item == mode_tree_get_current(data->data)) mode_tree_up(data->data, 0); - } key_bindings_remove(kt->name, bd->key); } @@ -1440,10 +2605,8 @@ window_customize_reset_key(struct window_customize_modedata *data, dd = key_bindings_get_default(kt, bd->key); if (dd != NULL && bd->cmd == dd->cmd) return; - if (dd == NULL && item == mode_tree_get_current(data->data)) { - mode_tree_collapse_current(data->data); + if (dd == NULL && item == mode_tree_get_current(data->data)) mode_tree_up(data->data, 0); - } key_bindings_reset(kt->name, bd->key); } @@ -1453,23 +2616,30 @@ window_customize_change_each(void *modedata, void *itemdata, { struct window_customize_modedata *data = modedata; struct window_customize_itemdata *item = itemdata; + enum window_customize_item_type type = item->type; + char *name = NULL; + if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) + name = xstrdup(item->name); switch (data->change) { case WINDOW_CUSTOMIZE_UNSET: - if (item->scope == WINDOW_CUSTOMIZE_KEY) + if (type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_unset_key(data, item); + else if (type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) + window_customize_unset_environment(data, item); else window_customize_unset_option(data, item); break; case WINDOW_CUSTOMIZE_RESET: - if (item->scope == WINDOW_CUSTOMIZE_KEY) + if (type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_reset_key(data, item); - else + else if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) window_customize_reset_option(data, item); break; } - if (item->scope != WINDOW_CUSTOMIZE_KEY) - options_push_changes(item->name); + if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) + options_push_changes(name); + free(name); } static enum prompt_result @@ -1478,6 +2648,8 @@ window_customize_change_current_callback(__unused struct client *c, { struct window_customize_modedata *data = modedata; struct window_customize_itemdata *item; + enum window_customize_item_type type; + char *name = NULL; if (s == NULL || *s == '\0' || data->dead) return (PROMPT_CLOSE); @@ -1485,22 +2657,30 @@ window_customize_change_current_callback(__unused struct client *c, return (PROMPT_CLOSE); item = mode_tree_get_current(data->data); + if (item == NULL) + return (PROMPT_CLOSE); + type = item->type; + if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) + name = xstrdup(item->name); switch (data->change) { case WINDOW_CUSTOMIZE_UNSET: - if (item->scope == WINDOW_CUSTOMIZE_KEY) + if (type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_unset_key(data, item); + else if (type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) + window_customize_unset_environment(data, item); else window_customize_unset_option(data, item); break; case WINDOW_CUSTOMIZE_RESET: - if (item->scope == WINDOW_CUSTOMIZE_KEY) + if (type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_reset_key(data, item); - else + else if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) window_customize_reset_option(data, item); break; } - if (item->scope != WINDOW_CUSTOMIZE_KEY) - options_push_changes(item->name); + if (type == WINDOW_CUSTOMIZE_ITEM_OPTION) + options_push_changes(name); + free(name); mode_tree_build(data->data); mode_tree_draw(data->data); data->wp->flags |= PANE_REDRAW; @@ -1528,6 +2708,63 @@ window_customize_change_tagged_callback(struct client *c, void *modedata, return (PROMPT_CLOSE); } +static int +window_customize_add_current(struct client *c, + struct window_customize_modedata *data) +{ + struct cmd_find_state fs; + const char *name, *table; + + name = mode_tree_get_current_name(data->data); + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(&fs, &data->fs); + else + cmd_find_from_pane(&fs, data->wp, 0); + + if (strcmp(name, "Server Options") == 0) { + window_customize_add_option(c, data, WINDOW_CUSTOMIZE_SERVER, + global_options, WINDOW_CUSTOMIZE_OPTIONS); + return (1); + } + if (strcmp(name, "Session Options") == 0) { + window_customize_add_option(c, data, WINDOW_CUSTOMIZE_SESSION, + fs.s->options, WINDOW_CUSTOMIZE_OPTIONS); + return (1); + } + if (strcmp(name, "Window & Pane Options") == 0) { + window_customize_add_option(c, data, WINDOW_CUSTOMIZE_PANE, + fs.wp->options, WINDOW_CUSTOMIZE_OPTIONS); + return (1); + } + if (strcmp(name, "Session Hooks") == 0) { + window_customize_add_option(c, data, WINDOW_CUSTOMIZE_SESSION, + fs.s->options, WINDOW_CUSTOMIZE_HOOKS); + return (1); + } + if (strcmp(name, "Window & Pane Hooks") == 0) { + window_customize_add_option(c, data, WINDOW_CUSTOMIZE_PANE, + fs.wp->options, WINDOW_CUSTOMIZE_HOOKS); + return (1); + } + if (strcmp(name, "Global Environment") == 0) { + window_customize_add_environment(c, data, + WINDOW_CUSTOMIZE_GLOBAL_ENVIRONMENT, global_environ); + return (1); + } + if (strcmp(name, "Session Environment") == 0) { + window_customize_add_environment(c, data, + WINDOW_CUSTOMIZE_SESSION_ENVIRONMENT, fs.s->environ); + return (1); + } + + if (strncmp(name, "Key Table - ", 12) == 0) { + table = name + 12; + window_customize_add_key(c, data, table); + return (1); + } + return (0); +} + static void window_customize_key(struct window_mode_entry *wme, struct client *c, __unused struct session *s, __unused struct winlink *wl, key_code key, @@ -1541,22 +2778,38 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, u_int tagged; item = mode_tree_get_current(data->data); + if (data->editor != NULL) { + if (key == 'q' || key == '\033' || key == '\003') + finished = 1; + else + finished = 0; + goto out; + } + finished = mode_tree_key(data->data, c, &key, m, NULL, NULL); if (item != (new_item = mode_tree_get_current(data->data))) item = new_item; switch (key) { + case 'e': + window_customize_start_edit(data, item, c); + break; case 'a': - if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + if (item == NULL || item->type != WINDOW_CUSTOMIZE_ITEM_OPTION) break; window_customize_set_array_key(c, data, item); break; case '\r': case 's': - if (item == NULL) + if (item == NULL) { + if (window_customize_add_current(c, data)) + mode_tree_build(data->data); break; - if (item->scope == WINDOW_CUSTOMIZE_KEY) + } + if (item->type == WINDOW_CUSTOMIZE_ITEM_KEY) window_customize_set_key(c, data, item); + else if (item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) + window_customize_set_environment(c, data, item, 0); else { window_customize_set_option(c, data, item, 0, 1); options_push_changes(item->name); @@ -1564,7 +2817,7 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, mode_tree_build(data->data); break; case 'w': - if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + if (item == NULL || item->type != WINDOW_CUSTOMIZE_ITEM_OPTION) break; window_customize_set_option(c, data, item, 0, 0); options_push_changes(item->name); @@ -1572,14 +2825,21 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, break; case 'S': case 'W': - if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + if (item == NULL || item->type == WINDOW_CUSTOMIZE_ITEM_KEY) break; - window_customize_set_option(c, data, item, 1, 0); - options_push_changes(item->name); + if (item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) + window_customize_set_environment(c, data, item, 1); + else { + window_customize_set_option(c, data, item, 1, 0); + options_push_changes(item->name); + } mode_tree_build(data->data); break; case 'd': - if (item == NULL || item->array_key != NULL) + if (item == NULL || + (item->type == WINDOW_CUSTOMIZE_ITEM_OPTION && + item->array_key != NULL) || + item->type == WINDOW_CUSTOMIZE_ITEM_ENVIRONMENT) break; xasprintf(&prompt, "Reset %s to default? ", item->name); data->references++; @@ -1640,11 +2900,18 @@ window_customize_key(struct window_mode_entry *wme, struct client *c, data->hide_global = !data->hide_global; mode_tree_build(data->data); break; + case 'C': + data->hide_default = !data->hide_default; + mode_tree_build(data->data); + break; } + +out: if (finished) window_pane_reset_mode(wp); else { mode_tree_draw(data->data); + window_customize_draw_waiting(data); wp->flags |= PANE_REDRAW; } } diff --git a/window-panes.c b/window-panes.c new file mode 100644 index 000000000..67886b2ef --- /dev/null +++ b/window-panes.c @@ -0,0 +1,1071 @@ +/* $OpenBSD: window-panes.c,v 1.1 2026/07/14 17:17:18 nicm Exp $ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +static struct screen *window_panes_init(struct window_mode_entry *, + struct cmdq_item *, struct cmd_find_state *, + struct args *); +static void window_panes_free(struct window_mode_entry *); +static void window_panes_resize(struct window_mode_entry *, u_int, + u_int); +static void window_panes_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); + +const struct window_mode window_panes_mode = { + .name = "panes-mode", + .flags = WINDOW_MODE_HIDE_PANE_STATUS|WINDOW_MODE_NO_STACK, + + .init = window_panes_init, + .free = window_panes_free, + .resize = window_panes_resize, + .key = window_panes_key, +}; + +struct window_panes_area { + u_int id; + u_int x; + u_int y; + u_int sx; + u_int sy; +}; + +struct window_panes_modedata { + struct window_pane *wp; + struct session *session; + u_int source_session; + u_int source_window; + struct screen screen; + + struct event timer; + + struct cmd_parse_tree *tree; + u_int delay; + int ignore_keys; + int zoomed; + + struct window_panes_area *areas; + u_int areas_size; +}; + +#define WINDOW_PANES_BORDER_L 0x1 +#define WINDOW_PANES_BORDER_R 0x2 +#define WINDOW_PANES_BORDER_U 0x4 +#define WINDOW_PANES_BORDER_D 0x8 + +static int +window_panes_get_source(struct window_panes_modedata *data, struct session **sp, + struct winlink **wlp, struct window **wp) +{ + struct session *s; + struct window *w; + struct winlink *wl = NULL; + + w = window_find_by_id(data->source_window); + if (w == NULL) + return (0); + + s = session_find_by_id(data->source_session); + if (s != NULL) + wl = winlink_find_by_window(&s->windows, w); + if (wl == NULL) + s = data->session; + if (s != NULL) + wl = winlink_find_by_window(&s->windows, w); + + if (sp != NULL) + *sp = s; + if (wlp != NULL) + *wlp = wl; + if (wp != NULL) + *wp = w; + return (1); +} + +static void +window_panes_free_areas(struct window_panes_modedata *data) +{ + free(data->areas); + data->areas = NULL; + data->areas_size = 0; +} + +static void +window_panes_add_area(struct window_panes_modedata *data, + struct window_pane *wp, u_int x, u_int y, u_int sx, u_int sy) +{ + struct window_panes_area *area; + + data->areas = xreallocarray(data->areas, data->areas_size + 1, + sizeof *data->areas); + area = &data->areas[data->areas_size++]; + area->id = wp->id; + area->x = x; + area->y = y; + area->sx = sx; + area->sy = sy; +} + +static int +window_panes_pane_floating(struct window_pane *wp) +{ + struct layout_cell *lc = wp->saved_layout_cell; + + if (lc == NULL) + lc = wp->layout_cell; + if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING)) + return (0); + return (1); +} + +static int +window_panes_pane_visible(struct window_pane *wp) +{ + if (wp->saved_layout_cell != NULL) + return (1); + return (window_pane_is_visible(wp)); +} + +static int +window_panes_get_geometry(struct window_pane *wp, struct layout_cell *root, + u_int osx, u_int osy, u_int dsx, u_int dsy, u_int *xp, u_int *yp, + u_int *sxp, u_int *syp) +{ + struct layout_cell *lc = wp->saved_layout_cell; + int status; + u_int x, y, sx, sy, x2, y2; + + if (lc == NULL) + lc = wp->layout_cell; + if (lc == NULL || osx == 0 || osy == 0 || dsx == 0 || dsy == 0) + return (0); + + if (osx <= dsx && osy <= dsy) { + x = lc->g.xoff; + y = lc->g.yoff; + x2 = x + lc->g.sx; + y2 = y + lc->g.sy; + } else { + x = (u_int)lc->g.xoff * dsx / osx; + y = (u_int)lc->g.yoff * dsy / osy; + x2 = ((u_int)lc->g.xoff + lc->g.sx) * dsx / osx; + y2 = ((u_int)lc->g.yoff + lc->g.sy) * dsy / osy; + } + + if (x >= dsx || y >= dsy) + return (0); + if (x2 <= x) + x2 = x + 1; + if (y2 <= y) + y2 = y + 1; + if (x2 > dsx) + x2 = dsx; + if (y2 > dsy) + y2 = dsy; + + sx = x2 - x; + sy = y2 - y; + if (sx == 0 || sy == 0) + return (0); + + status = window_get_pane_status(wp->window); + if (layout_add_horizontal_border(root, lc, status) && sy > 1) { + if (status == PANE_STATUS_TOP) + y++; + sy--; + } + + *xp = x; + *yp = y; + *sxp = sx; + *syp = sy; + return (1); +} + +static void +window_panes_get_border_cell(struct window_panes_modedata *data, + struct grid_cell *gc) +{ + struct format_tree *ft; + struct window_pane *wp = data->wp; + struct session *s = data->session; + + memcpy(gc, &grid_default_cell, sizeof *gc); + ft = format_create_defaults(NULL, NULL, s, s->curw, wp); + style_apply(gc, wp->window->options, "display-panes-border-style", ft); + format_free(ft); +} + +static int +window_panes_map_x(u_int x, u_int osx, u_int dsx) +{ + if (osx <= dsx) + return (x); + return (x * dsx / osx); +} + +static int +window_panes_map_y(u_int y, u_int osy, u_int dsy) +{ + if (osy <= dsy) + return (y); + return (y * dsy / osy); +} + +static struct layout_cell * +window_panes_next_tiled_cell(struct layout_cell *lc) +{ + struct layout_cell *next; + + next = TAILQ_NEXT(lc, entry); + while (next != NULL) { + if (~next->flags & LAYOUT_CELL_FLOATING) + return (next); + next = TAILQ_NEXT(next, entry); + } + return (NULL); +} + +static void +window_panes_mark_border(u_char *map, u_int dsx, u_int dsy, u_int x, u_int y, + u_char mask) +{ + if (x < dsx && y < dsy) + map[y * dsx + x] |= mask; +} + +static void +window_panes_mark_vline(u_char *map, u_int dsx, u_int dsy, int x, int y, int y2) +{ + u_char mask; + int yy; + + if (x < 0 || (u_int)x >= dsx || y2 <= y) + return; + if (y < 0) + y = 0; + if ((u_int)y2 > dsy) + y2 = dsy; + + for (yy = y; yy < y2; yy++) { + mask = 0; + if (yy > y) + mask |= WINDOW_PANES_BORDER_U; + if (yy + 1 < y2) + mask |= WINDOW_PANES_BORDER_D; + if (mask == 0) + mask = WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D; + window_panes_mark_border(map, dsx, dsy, x, yy, mask); + } +} + +static void +window_panes_mark_hline(u_char *map, u_int dsx, u_int dsy, int x, int x2, int y) +{ + u_char mask; + int xx; + + if (y < 0 || (u_int)y >= dsy || x2 <= x) + return; + if (x < 0) + x = 0; + if ((u_int)x2 > dsx) + x2 = dsx; + + for (xx = x; xx < x2; xx++) { + mask = 0; + if (xx > x) + mask |= WINDOW_PANES_BORDER_L; + if (xx + 1 < x2) + mask |= WINDOW_PANES_BORDER_R; + if (mask == 0) + mask = WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R; + window_panes_mark_border(map, dsx, dsy, xx, y, mask); + } +} + +static void +window_panes_mark_borders_cell(u_char *map, struct layout_cell *lc, u_int osx, + u_int osy, u_int dsx, u_int dsy) +{ + struct layout_cell *lcchild, *lcnext; + int x, y, x2, y2; + + if (lc->type == LAYOUT_WINDOWPANE) + return; + + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + window_panes_mark_borders_cell(map, lcchild, osx, osy, dsx, + dsy); + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; + lcnext = window_panes_next_tiled_cell(lcchild); + if (lcnext == NULL) + continue; + + if (lc->type == LAYOUT_LEFTRIGHT) { + x = window_panes_map_x(lcchild->g.xoff + lcchild->g.sx, + osx, dsx); + y = window_panes_map_y(lc->g.yoff, osy, dsy); + y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy, + dsy); + window_panes_mark_vline(map, dsx, dsy, x, y, y2); + } else { + x = window_panes_map_x(lc->g.xoff, osx, dsx); + x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, + dsx); + y = window_panes_map_y(lcchild->g.yoff + lcchild->g.sy, + osy, dsy); + window_panes_mark_hline(map, dsx, dsy, x, x2, y); + } + } +} + +static void +window_panes_mark_pane_status_borders(u_char *map, struct window *w, + struct layout_cell *root, u_int osx, u_int osy, u_int dsx, u_int dsy) +{ + struct window_pane *wp; + struct layout_cell *lc; + int status, x, y, x2, y2; + + status = window_get_pane_status(w); + if (status != PANE_STATUS_TOP && status != PANE_STATUS_BOTTOM) + return; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (!window_panes_pane_visible(wp)) + continue; + + lc = wp->saved_layout_cell; + if (lc == NULL) + lc = wp->layout_cell; + if (lc == NULL || + !layout_add_horizontal_border(root, lc, status)) + continue; + + x = window_panes_map_x(lc->g.xoff, osx, dsx); + x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, dsx); + if (status == PANE_STATUS_TOP) + y = window_panes_map_y(lc->g.yoff, osy, dsy); + else { + y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy, + dsy); + y = y2 - 1; + } + window_panes_mark_hline(map, dsx, dsy, x, x2, y); + } +} + +static int +window_panes_get_floating_frame(struct window_pane *wp, u_int osx, u_int osy, + u_int dsx, u_int dsy, int *xp, int *yp, int *x2p, int *y2p) +{ + struct layout_cell *lc; + + lc = wp->saved_layout_cell; + if (lc == NULL) + lc = wp->layout_cell; + if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING)) + return (0); + + if (lc->g.xoff == 0) + *xp = -1; + else + *xp = window_panes_map_x(lc->g.xoff - 1, osx, dsx); + if (lc->g.yoff == 0) + *yp = -1; + else + *yp = window_panes_map_y(lc->g.yoff - 1, osy, dsy); + *x2p = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, dsx); + *y2p = window_panes_map_y(lc->g.yoff + lc->g.sy, osy, dsy); + return (1); +} + +static int +window_panes_clip_floating_body(struct window_pane *wp, u_int osx, u_int osy, + u_int dsx, u_int dsy, u_int *xp, u_int *yp, u_int *sxp, u_int *syp) +{ + int x, y, x2, y2, bx, by, bx2, by2; + + if (!window_panes_get_floating_frame(wp, osx, osy, dsx, dsy, &x, &y, + &x2, &y2)) + return (1); + + bx = *xp; + by = *yp; + bx2 = bx + *sxp - 1; + by2 = by + *syp - 1; + + if (x >= 0 && bx <= x) + bx = x + 1; + if (y >= 0 && by <= y) + by = y + 1; + if ((u_int)x2 < dsx && bx2 >= x2) + bx2 = x2 - 1; + if ((u_int)y2 < dsy && by2 >= y2) + by2 = y2 - 1; + if (bx2 < bx || by2 < by) + return (0); + + *xp = bx; + *yp = by; + *sxp = bx2 - bx + 1; + *syp = by2 - by + 1; + return (1); +} + +static int +window_panes_border_cell_type(u_char mask) +{ + switch (mask) { + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R| + WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D: + return (CELL_LRUD); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U: + return (CELL_LRU); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_D: + return (CELL_LRD); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R: + case WINDOW_PANES_BORDER_L: + case WINDOW_PANES_BORDER_R: + return (CELL_LR); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D: + return (CELL_ULD); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_U: + return (CELL_LU); + case WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_D: + return (CELL_LD); + case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D: + return (CELL_URD); + case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_U: + return (CELL_RU); + case WINDOW_PANES_BORDER_R|WINDOW_PANES_BORDER_D: + return (CELL_RD); + case WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D: + case WINDOW_PANES_BORDER_U: + case WINDOW_PANES_BORDER_D: + return (CELL_UD); + } + return (CELL_NONE); +} + +static int +window_panes_border_has_horizontal(u_char mask) +{ + return ((mask & (WINDOW_PANES_BORDER_L|WINDOW_PANES_BORDER_R)) != 0); +} + +static int +window_panes_border_has_vertical(u_char mask) +{ + return ((mask & (WINDOW_PANES_BORDER_U|WINDOW_PANES_BORDER_D)) != 0); +} + +static void +window_panes_mark_border_joins_cell(u_char *map, struct layout_cell *lc, + u_int osx, u_int osy, u_int dsx, u_int dsy) +{ + struct layout_cell *lcchild, *lcnext; + int x, y, x2, y2; + + if (lc->type == LAYOUT_WINDOWPANE) + return; + + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + window_panes_mark_border_joins_cell(map, lcchild, osx, osy, + dsx, dsy); + if (lcchild->flags & LAYOUT_CELL_FLOATING) + continue; + lcnext = window_panes_next_tiled_cell(lcchild); + if (lcnext == NULL) + continue; + + if (lc->type == LAYOUT_LEFTRIGHT) { + x = window_panes_map_x(lcchild->g.xoff + lcchild->g.sx, + osx, dsx); + y = window_panes_map_y(lc->g.yoff, osy, dsy); + y2 = window_panes_map_y(lc->g.yoff + lc->g.sy, osy, + dsy); + if (x < 0 || (u_int)x >= dsx) + continue; + if (y > 0 && + window_panes_border_has_horizontal( + map[(y - 1) * dsx + x])) { + window_panes_mark_border(map, dsx, dsy, x, + y - 1, WINDOW_PANES_BORDER_D); + window_panes_mark_border(map, dsx, dsy, x, y, + WINDOW_PANES_BORDER_U); + } + if ((u_int)y2 < dsy && + window_panes_border_has_horizontal( + map[y2 * dsx + x])) { + window_panes_mark_border(map, dsx, dsy, x, y2, + WINDOW_PANES_BORDER_U); + window_panes_mark_border(map, dsx, dsy, x, + y2 - 1, WINDOW_PANES_BORDER_D); + } + } else { + x = window_panes_map_x(lc->g.xoff, osx, dsx); + x2 = window_panes_map_x(lc->g.xoff + lc->g.sx, osx, + dsx); + y = window_panes_map_y(lcchild->g.yoff + lcchild->g.sy, + osy, dsy); + if (y < 0 || (u_int)y >= dsy) + continue; + if (x > 0 && + window_panes_border_has_vertical( + map[y * dsx + x - 1])) { + window_panes_mark_border(map, dsx, dsy, x - 1, + y, WINDOW_PANES_BORDER_R); + window_panes_mark_border(map, dsx, dsy, x, y, + WINDOW_PANES_BORDER_L); + } + if ((u_int)x2 < dsx && + window_panes_border_has_vertical( + map[y * dsx + x2])) { + window_panes_mark_border(map, dsx, dsy, x2, y, + WINDOW_PANES_BORDER_L); + window_panes_mark_border(map, dsx, dsy, x2 - 1, + y, WINDOW_PANES_BORDER_R); + } + } + } +} + +static void +window_panes_draw_borders(struct screen_write_ctx *ctx, struct window *w, + struct layout_cell *lc, const struct grid_cell *gc, u_int osx, u_int osy, + u_int dsx, u_int dsy) +{ + struct grid_cell border_gc; + u_char *map; + u_int xx, yy; + int cell_type; + + if (dsx == 0 || dsy == 0) + return; + + map = xcalloc(dsx, dsy); + window_panes_mark_borders_cell(map, lc, osx, osy, dsx, dsy); + window_panes_mark_pane_status_borders(map, w, lc, osx, osy, dsx, + dsy); + window_panes_mark_border_joins_cell(map, lc, osx, osy, dsx, dsy); + + for (yy = 0; yy < dsy; yy++) { + for (xx = 0; xx < dsx; xx++) { + if (map[yy * dsx + xx] == 0) + continue; + cell_type = window_panes_border_cell_type( + map[yy * dsx + xx]); + memcpy(&border_gc, gc, sizeof border_gc); + border_gc.attr |= GRID_ATTR_CHARSET; + utf8_set(&border_gc.data, CELL_BORDERS[cell_type]); + screen_write_cursormove(ctx, xx, yy, 0); + screen_write_cell(ctx, &border_gc); + } + } + free(map); +} + +static void +window_panes_draw_floating_border(struct screen_write_ctx *ctx, + struct window_pane *wp, const struct grid_cell *gc, u_int osx, u_int osy, + u_int dsx, u_int dsy) +{ + struct grid_cell border_gc; + u_char *map; + u_int xx, yy; + int x, y, x2, y2, cell_type; + + if (dsx == 0 || dsy == 0) + return; + if (!window_panes_get_floating_frame(wp, osx, osy, dsx, dsy, &x, &y, + &x2, &y2)) + return; + + map = xcalloc(dsx, dsy); + window_panes_mark_hline(map, dsx, dsy, x, x2 + 1, y); + window_panes_mark_hline(map, dsx, dsy, x, x2 + 1, y2); + window_panes_mark_vline(map, dsx, dsy, x, y, y2 + 1); + window_panes_mark_vline(map, dsx, dsy, x2, y, y2 + 1); + + for (yy = 0; yy < dsy; yy++) { + for (xx = 0; xx < dsx; xx++) { + if (map[yy * dsx + xx] == 0) + continue; + cell_type = window_panes_border_cell_type( + map[yy * dsx + xx]); + memcpy(&border_gc, gc, sizeof border_gc); + border_gc.attr |= GRID_ATTR_CHARSET; + utf8_set(&border_gc.data, CELL_BORDERS[cell_type]); + screen_write_cursormove(ctx, xx, yy, 0); + screen_write_cell(ctx, &border_gc); + } + } + free(map); +} + +static void +window_panes_clear_floating_area(struct screen_write_ctx *ctx, + struct window_pane *wp, u_int osx, u_int osy, u_int dsx, u_int dsy) +{ + struct grid_cell gc; + int x, y, x2, y2, xx, yy; + + if (!window_panes_get_floating_frame(wp, osx, osy, dsx, dsy, &x, &y, + &x2, &y2)) + return; + + memcpy(&gc, &grid_default_cell, sizeof gc); + if (x < 0) + x = 0; + if (y < 0) + y = 0; + if ((u_int)x2 >= dsx) + x2 = dsx - 1; + if ((u_int)y2 >= dsy) + y2 = dsy - 1; + if (x2 < x || y2 < y) + return; + + for (yy = y; yy <= y2; yy++) { + screen_write_cursormove(ctx, x, yy, 0); + for (xx = x; xx <= x2; xx++) + screen_write_putc(ctx, &gc, ' '); + } +} + +static void +window_panes_draw_format(struct window_panes_modedata *data, + struct screen_write_ctx *ctx, struct window_pane *wp, u_int x, u_int y, + u_int sx, const struct grid_cell *gc) +{ + struct session *s = data->session; + struct winlink *wl = s->curw; + const char *format; + char *expanded; + + if (sx == 0) + return; + format = options_get_string(data->wp->window->options, + "display-panes-format"); + if (*format == '\0') + return; + + window_panes_get_source(data, &s, &wl, NULL); + if (s == NULL) + return; + + expanded = format_single(NULL, format, NULL, s, wl, wp); + if (*expanded != '\0') { + screen_write_cursormove(ctx, x, y, 0); + format_draw(ctx, gc, sx, expanded, NULL, 0); + } + free(expanded); +} + +static void +window_panes_draw_number(struct window_panes_modedata *data, + struct screen_write_ctx *ctx, struct window_pane *wp, u_int pane, u_int x, + u_int y, u_int sx, u_int sy) +{ + struct session *s = data->session; + struct window *w = wp->window; + struct winlink *wl = s->curw; + struct options *oo = data->wp->window->options; + struct grid_cell fgc, bgc; + struct format_tree *ft; + const char *name; + char buf[16], lbuf[16] = { 0 }, *ptr; + size_t len, llen = 0, width; + u_int cx, cy, px, py, idx, i, j, format; + + len = xsnprintf(buf, sizeof buf, "%u", pane); + if (pane > 9 && pane < 35) + llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); + if (sx < len) + return; + + window_panes_get_source(data, &s, &wl, NULL); + if (s != NULL) { + if (wl == NULL) + wl = s->curw; + } + if (w->active == wp) + name = "display-panes-active-colour"; + else + name = "display-panes-colour"; + ft = format_create_defaults(NULL, NULL, s, wl, wp); + style_apply(&fgc, oo, name, ft); + format_free(ft); + + memcpy(&bgc, &grid_default_cell, sizeof bgc); + bgc.bg = fgc.fg; + + format = 0; + if (options_get_string(oo, "display-panes-format")[0] != '\0') + format = 1; + width = (len * 6) - 1; + if (sx < width || sy < (format ? 7 : 5)) { + width = len; + if (llen != 0 && sx >= len + llen + 1) + width += llen + 1; + cx = x + (sx - width) / 2; + cy = y + sy / 2; + screen_write_cursormove(ctx, cx, cy, 0); + screen_write_puts(ctx, &fgc, "%s", buf); + if (width > len) + screen_write_puts(ctx, &fgc, " %s", lbuf); + if (format && sy > 1) + window_panes_draw_format(data, ctx, wp, x, y, sx, &fgc); + return; + } + + px = (sx - width) / 2; + py = (sy - 5) / 2; + for (ptr = buf; *ptr != '\0'; ptr++) { + if (*ptr < '0' || *ptr > '9') + continue; + idx = *ptr - '0'; + + for (j = 0; j < 5; j++) { + for (i = 0; i < 5; i++) { + if (!window_clock_table[idx][j][i]) + continue; + screen_write_cursormove(ctx, x + px + i, + y + py + j, 0); + screen_write_putc(ctx, &bgc, ' '); + } + } + px += 6; + } + + if (sy <= 6) + return; + window_panes_draw_format(data, ctx, wp, x, y, sx, &fgc); + if (llen != 0) { + cx = x + px - llen - 1; + cy = y + py + 5; + screen_write_cursormove(ctx, cx, cy, 0); + screen_write_puts(ctx, &fgc, "%s", lbuf); + } +} + +static void +window_panes_draw_pane(struct window_panes_modedata *data, + struct screen_write_ctx *ctx, struct window_pane *wp, + struct layout_cell *root, u_int osx, u_int osy, u_int dsx, u_int dsy) +{ + u_int pane, x, y, sx, sy; + + if (!window_panes_pane_visible(wp)) + return; + if (!window_panes_get_geometry(wp, root, osx, osy, dsx, dsy, &x, &y, + &sx, &sy)) + return; + if (!window_panes_clip_floating_body(wp, osx, osy, dsx, dsy, &x, &y, + &sx, &sy)) + return; + if (window_pane_index(wp, &pane) != 0) + return; + window_panes_add_area(data, wp, x, y, sx, sy); + + screen_write_cursormove(ctx, x, y, 0); + if (osx <= dsx && osy <= dsy) + screen_write_fast_copy(ctx, &wp->base, 0, wp->base.grid->hsize, + sx, sy); + else + screen_write_preview(ctx, &wp->base, sx, sy); + window_panes_draw_number(data, ctx, wp, pane, x, y, sx, sy); +} + +static void +window_panes_draw_screen(struct window_mode_entry *wme) +{ + struct window_panes_modedata *data = wme->data; + struct window *w; + struct window_pane *wp; + struct screen_write_ctx ctx; + struct layout_cell *root; + struct grid_cell border_gc; + u_int osx, osy, sx, sy; + + if (!window_panes_get_source(data, NULL, NULL, &w)) + return; + root = w->saved_layout_root; + if (root == NULL) + root = w->layout_root; + if (root == NULL) + return; + + osx = root->g.sx; + osy = root->g.sy; + sx = screen_size_x(&data->screen); + sy = screen_size_y(&data->screen); + + window_panes_free_areas(data); + screen_write_start(&ctx, &data->screen); + screen_write_clearscreen(&ctx, 8); + TAILQ_FOREACH(wp, &w->panes, entry) { + if (window_panes_pane_floating(wp)) + continue; + window_panes_draw_pane(data, &ctx, wp, root, osx, osy, sx, sy); + } + window_panes_get_border_cell(data, &border_gc); + window_panes_draw_borders(&ctx, w, root, &border_gc, osx, osy, sx, sy); + TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) { + if (!window_panes_pane_floating(wp)) + continue; + window_panes_clear_floating_area(&ctx, wp, osx, osy, sx, sy); + window_panes_draw_pane(data, &ctx, wp, root, osx, osy, sx, sy); + window_panes_draw_floating_border(&ctx, wp, &border_gc, osx, + osy, sx, sy); + } + screen_write_stop(&ctx); + + data->wp->flags |= PANE_REDRAW; +} + +static void +window_panes_timer_callback(__unused int fd, __unused short events, void *arg) +{ + struct window_mode_entry *wme = arg; + + window_pane_reset_mode(wme->wp); +} + +static struct screen * +window_panes_init(struct window_mode_entry *wme, struct cmdq_item *item, + __unused struct cmd_find_state *fs, struct args *args) +{ + struct window_pane *wp = wme->wp; + struct window *w = wp->window; + struct window_panes_modedata *data; + struct cmd *self; + struct cmd_find_state *source, *target; + struct session *s; + struct timeval tv; + char *cause; + u_int sx = screen_size_x(&wp->base); + u_int sy = screen_size_y(&wp->base); + u_int delay; + + if (item == NULL) + return (NULL); + self = cmdq_get_cmd(item); + if (self == NULL) + return (NULL); + + source = cmdq_get_source(item); + target = cmdq_get_target(item); + s = target->s; + + if (!args_has(args, 'd')) + delay = options_get_number(w->options, "display-panes-time"); + else { + delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause); + if (cause != NULL) { + cmdq_error(item, "delay %s", cause); + free(cause); + return (NULL); + } + } + + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + data->session = s; + + screen_init(&data->screen, sx, sy, 0); + data->screen.mode &= ~MODE_CURSOR; + + data->tree = args_to_commands(self, item, 0, "select-pane -t \"%%%\"", + 0, &cause); + if (data->tree == NULL) { + cmdq_error(item, "%s", cause); + free(cause); + screen_free(&data->screen); + free(data); + wme->data = NULL; + return (NULL); + } + if (args_has(args, 's')) { + data->source_session = source->s->id; + data->source_window = source->w->id; + } else { + data->source_session = target->s->id; + data->source_window = target->w->id; + } + + data->delay = delay; + data->ignore_keys = args_has(args, 'N'); + + if (args_has(args, 'Z')) + data->zoomed = -1; + else { + data->zoomed = (w->flags & WINDOW_ZOOMED); + if (!data->zoomed && window_zoom(wp) == 0) + server_redraw_window(w); + } + + evtimer_set(&data->timer, window_panes_timer_callback, wme); + if (data->delay != 0) { + tv.tv_sec = data->delay / 1000; + tv.tv_usec = (data->delay % 1000) * 1000; + evtimer_add(&data->timer, &tv); + } + + window_panes_draw_screen(wme); + return (&data->screen); +} + +static void +window_panes_free(struct window_mode_entry *wme) +{ + struct window_panes_modedata *data = wme->data; + struct window *w = wme->wp->window; + + evtimer_del(&data->timer); + + if (data->zoomed == 0) + server_unzoom_window(w); + server_redraw_window(w); + server_redraw_window_borders(w); + server_status_window(w); + + cmd_parse_free(data->tree); + window_panes_free_areas(data); + screen_free(&data->screen); + free(data); +} + +static void +window_panes_resize(struct window_mode_entry *wme, u_int sx, u_int sy) +{ + struct window_panes_modedata *data = wme->data; + + screen_resize(&data->screen, sx, sy, 0); + window_panes_draw_screen(wme); +} + +static void +window_panes_run_command(struct window_panes_modedata *data, struct client *c, + struct window_pane *wp) +{ + struct cmdq_item *new_item; + struct cmd_invoke_input ci = { 0 }; + char *expanded; + + xasprintf(&expanded, "%%%u", wp->id); + ci.argc = 1; + ci.argv = &expanded; + + new_item = cmd_invoke_get(data->tree, NULL, &ci); + cmdq_append(c, new_item); + free(expanded); +} + +static struct window_pane * +window_panes_find_pane(struct window_panes_modedata *data, u_int x, u_int y) +{ + struct window_panes_area *area; + u_int i; + + for (i = data->areas_size; i > 0; i--) { + area = &data->areas[i - 1]; + if (x < area->x || x >= area->x + area->sx) + continue; + if (y < area->y || y >= area->y + area->sy) + continue; + return (window_pane_find_by_id(area->id)); + } + return (NULL); +} + +static struct window_pane * +window_panes_key_pane(struct window_panes_modedata *data, key_code key) +{ + struct window *w; + u_int index; + + if (key >= '0' && key <= '9') + index = key - '0'; + else if ((key & KEYC_MASK_MODIFIERS) == 0) { + key &= KEYC_MASK_KEY; + if (key < 'a' || key > 'z') + return (NULL); + index = 10 + (key - 'a'); + } else + return (NULL); + if (!window_panes_get_source(data, NULL, NULL, &w)) + return (NULL); + return (window_pane_at_index(w, index)); +} + +static struct window_pane * +window_panes_get_target(struct window_mode_entry *wme, key_code key, + struct mouse_event *m) +{ + struct window_panes_modedata *data = wme->data; + u_int x, y; + + if (data->ignore_keys) + return (NULL); + + if (KEYC_IS_MOUSE(key)) { + if (key != KEYC_MOUSEDOWN1_PANE || + m == NULL || + cmd_mouse_at(wme->wp, m, &x, &y, 0) != 0) + return (NULL); + return (window_panes_find_pane(data, x, y)); + } + + return (window_panes_key_pane(data, key)); +} + +static void +window_panes_key(struct window_mode_entry *wme, struct client *c, + __unused struct session *s, __unused struct winlink *wl, key_code key, + struct mouse_event *m) +{ + struct window_pane *wp = wme->wp, *target = NULL; + struct window_panes_modedata *data = wme->data; + + if (key == '\033' || key == 'q') { + window_pane_reset_mode(wp); + return; + } + + target = window_panes_get_target(wme, key, m); + if (target == NULL) { + if (!data->ignore_keys && !KEYC_IS_MOUSE(key)) + window_pane_reset_mode(wp); + return; + } + + if (wp->window->flags & WINDOW_ZOOMED) + window_unzoom(wp->window, 1); + window_panes_run_command(data, c, target); + window_pane_reset_mode(wp); +} diff --git a/window-switch.c b/window-switch.c index f3de15411..ac6156c4f 100644 --- a/window-switch.c +++ b/window-switch.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-switch.c,v 1.1 2026/06/26 14:40:30 nicm Exp $ */ +/* $OpenBSD: window-switch.c,v 1.2 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -25,7 +25,8 @@ #include "tmux.h" static struct screen *window_switch_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_switch_free(struct window_mode_entry *); static void window_switch_resize(struct window_mode_entry *, u_int, u_int); @@ -333,7 +334,8 @@ window_switch_draw_screen(struct window_mode_entry *wme) static struct screen * window_switch_init(struct window_mode_entry *wme, - struct cmd_find_state *fs, struct args *args) + __unused struct cmdq_item *item, struct cmd_find_state *fs, + struct args *args) { struct window_pane *wp = wme->wp; struct window_switch_modedata *data; diff --git a/window-tree.c b/window-tree.c index 35b7e68e0..342a1c3bc 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-tree.c,v 1.93 2026/07/04 22:09:06 nicm Exp $ */ +/* $OpenBSD: window-tree.c,v 1.94 2026/07/14 17:17:18 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -25,7 +25,8 @@ #include "tmux.h" static struct screen *window_tree_init(struct window_mode_entry *, - struct cmd_find_state *, struct args *); + struct cmdq_item *, struct cmd_find_state *, + struct args *); static void window_tree_free(struct window_mode_entry *); static void window_tree_resize(struct window_mode_entry *, u_int, u_int); @@ -1097,7 +1098,8 @@ window_tree_help(u_int *width, const char **item) } static struct screen * -window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, +window_tree_init(struct window_mode_entry *wme, + __unused struct cmdq_item *item, struct cmd_find_state *fs, struct args *args) { struct window_pane *wp = wme->wp; diff --git a/window.c b/window.c index 8b26c7d20..abeb87a1c 100644 --- a/window.c +++ b/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.360 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.363 2026/07/14 19:07:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -128,6 +128,29 @@ window_fire_pane_changed(struct window *w, struct window_pane *wp, events_fire("window-pane-changed", ep); } +void +window_fire_pane_moved(struct window_pane *wp, struct window *old_w, + int old_idx, struct window *new_w, int new_idx) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", new_w); + event_payload_set_window(ep, "old_window", old_w); + event_payload_set_window(ep, "new_window", new_w); + if (old_idx != -1) + event_payload_set_int(ep, "old_window_index", old_idx); + if (new_idx != -1) { + event_payload_set_int(ep, "window_index", new_idx); + event_payload_set_int(ep, "new_window_index", new_idx); + } + events_fire("pane-moved", ep); +} + static void window_fire_pane_mode_changed(const char *name, struct window_pane *wp, const char *previous, const char *current, int entered) @@ -437,6 +460,7 @@ window_destroy(struct window *w) layout_free_cell(w->saved_layout_root, 0); free(w->old_layout); + menu_destroy(w); window_destroy_panes(w); if (event_initialized(&w->name_event)) @@ -488,6 +512,9 @@ window_add_ref(struct window *w, const char *from) void window_remove_ref(struct window *w, const char *from) { + if (w->references == 1) + events_fire_window("window-closed", w); + w->references--; log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references); @@ -542,6 +569,11 @@ window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) ypixel == -1 ? w->ypixel : (u_int)ypixel); w->sx = sx; w->sy = sy; + if (w->menu != NULL) { + menu_resize(w->menu, w); + redraw_invalidate_scene(w); + server_redraw_window(w); + } if (xpixel != -1) w->xpixel = xpixel; if (ypixel != -1) @@ -625,7 +657,8 @@ window_pane_update_focus(struct window_pane *wp) c->session->attached != 0 && (c->flags & CLIENT_FOCUSED) && c->session->curw->window == wp->window && - c->overlay_draw == NULL) { + c->overlay_draw == NULL && + wp->window->menu == NULL) { focused = 1; break; } @@ -979,7 +1012,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (w->active != NULL) { window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; - events_fire_window("window-pane-changed", w); + window_fire_pane_changed(w, w->active, wp); window_update_focus(w); } } @@ -1481,18 +1514,21 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) int window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, - const struct window_mode *mode, struct cmd_find_state *fs, - struct args *args) + const struct window_mode *mode, struct cmdq_item *item, + struct cmd_find_state *fs, struct args *args) { struct window_mode_entry *wme; struct window *w = wp->window; - const char *name = mode->name, *p = NULL; + const char *name = mode->name, *oname = NULL; if (!TAILQ_EMPTY(&wp->modes)) { if (TAILQ_FIRST(&wp->modes)->mode == mode) return (1); - p = TAILQ_FIRST(&wp->modes)->mode->name; + if (TAILQ_FIRST(&wp->modes)->mode->flags & WINDOW_MODE_NO_STACK) + window_pane_reset_mode(wp); } + if (!TAILQ_EMPTY(&wp->modes)) + oname = TAILQ_FIRST(&wp->modes)->mode->name; TAILQ_FOREACH(wme, &wp->modes, entry) { if (wme->mode == mode) @@ -1508,7 +1544,12 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, wme->mode = mode; wme->prefix = 1; TAILQ_INSERT_HEAD(&wp->modes, wme, entry); - wme->screen = wme->mode->init(wme, fs, args); + wme->screen = wme->mode->init(wme, item, fs, args); + if (wme->screen == NULL) { + TAILQ_REMOVE(&wp->modes, wme, entry); + free(wme); + return (1); + } } wme->kill = args != NULL ? args_has(args, 'k') : 0; wp->screen = wme->screen; @@ -1519,8 +1560,8 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, server_redraw_window_borders(wp->window); server_status_window(wp->window); - window_fire_pane_mode_changed("pane-mode-entered", wp, p, name, 1); - window_fire_pane_mode_changed("pane-mode-changed", wp, p, name, 1); + window_fire_pane_mode_changed("pane-mode-entered", wp, oname, name, 1); + window_fire_pane_mode_changed("pane-mode-changed", wp, oname, name, 1); return (0); } @@ -2669,8 +2710,15 @@ window_get_pane_status(struct window *w) int window_pane_get_pane_status(struct window_pane *wp) { + struct window_mode_entry *wme; int status; + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && + (wme->mode->flags & WINDOW_MODE_HIDE_PANE_STATUS) && + (wp->flags & PANE_ZOOMED)) + return (PANE_STATUS_OFF); + if (!window_pane_is_floating(wp)) return (window_get_pane_status(wp->window)); if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)